Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add effect/Redacted #993

Merged
merged 12 commits into from
Oct 24, 2024
14 changes: 9 additions & 5 deletions packages/shared/src/crypto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as Encoding from "effect/Encoding";
import * as Hash from "effect/Hash";
import * as Micro from "effect/Micro";
import * as Redacted from "effect/Redacted";
import SQIds, { defaultOptions } from "sqids";

import { UploadThingError } from "./error";
Expand All @@ -27,13 +28,16 @@ function shuffle(str: string, seed: string) {
return chars.join("");
}

export const signPayload = (payload: string, secret: string) =>
export const signPayload = (
payload: string,
secret: Redacted.Redacted<string>,
) =>
Micro.gen(function* () {
const signingKey = yield* Micro.tryPromise({
try: () =>
crypto.subtle.importKey(
"raw",
encoder.encode(secret),
encoder.encode(Redacted.value(secret)),
algorithm,
false,
["sign"],
Expand Down Expand Up @@ -61,13 +65,13 @@ export const signPayload = (payload: string, secret: string) =>
export const verifySignature = (
payload: string,
signature: string | null,
secret: string,
secret: Redacted.Redacted<string>,
) =>
Micro.gen(function* () {
const sig = signature?.slice(signaturePrefix.length);
if (!sig) return false;

const secretBytes = encoder.encode(secret);
const secretBytes = encoder.encode(Redacted.value(secret));
const signingKey = yield* Micro.promise(() =>
crypto.subtle.importKey("raw", secretBytes, algorithm, false, ["verify"]),
);
Expand Down Expand Up @@ -131,7 +135,7 @@ export const verifyKey = (key: string, appId: string) =>

export const generateSignedURL = (
url: string | URL,
secretKey: string,
secretKey: Redacted.Redacted<string>,
opts: {
ttlInSeconds?: Time | undefined;
data?: Record<string, string | number | boolean | null | undefined>;
Expand Down
18 changes: 11 additions & 7 deletions packages/shared/test/crypto.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as Exit from "effect/Exit";
import * as Redacted from "effect/Redacted";
import { describe, expect } from "vitest";

import {
Expand All @@ -14,7 +14,7 @@ import {
describe("crypto sign / verify", () => {
it.effect("signs and verifies a payload", () =>
Effect.gen(function* () {
const secret = "foo-123";
const secret = Redacted.make("foo-123");
const payload = "hello world";

const sig = yield* signPayload(payload, secret);
Expand All @@ -26,7 +26,7 @@ describe("crypto sign / verify", () => {

it.effect("doesn't verify a payload with a bad signature", () =>
Effect.gen(function* () {
const secret = "foo-123";
const secret = Redacted.make("foo-123");
const payload = "hello world";

const sig = yield* signPayload(payload, secret);
Expand All @@ -38,11 +38,15 @@ describe("crypto sign / verify", () => {

it.effect("doesn't verify a payload with a bad secret", () =>
Effect.gen(function* () {
const secret = "foo-123";
const secret = Redacted.make("foo-123");
const payload = "hello world";

const sig = yield* signPayload(payload, secret);
const verified = yield* verifySignature(payload, sig, "bad");
const verified = yield* verifySignature(
payload,
sig,
Redacted.make("bad"),
);

expect(verified).toBe(false);
}),
Expand All @@ -51,7 +55,7 @@ describe("crypto sign / verify", () => {
it.effect("generates a signed URL", () =>
Effect.gen(function* () {
const url = "https://example.com";
const secret = "foo-123";
const secret = Redacted.make("foo-123");

const signedURL = yield* generateSignedURL(url, secret, {
ttlInSeconds: 60 * 60,
Expand All @@ -67,7 +71,7 @@ describe("crypto sign / verify", () => {
it.effect("generates and verifies a signed URL", () =>
Effect.gen(function* () {
const url = "https://example.com";
const secret = "foo-123";
const secret = Redacted.make("foo-123");

const signedURL = yield* generateSignedURL(url, secret, {
ttlInSeconds: 60 * 60,
Expand Down
5 changes: 3 additions & 2 deletions packages/uploadthing/src/internal/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as Exit from "effect/Exit";
import * as Layer from "effect/Layer";
import * as Redacted from "effect/Redacted";
import { afterEach, beforeEach, describe, expect } from "vitest";

import { UploadThingError } from "@uploadthing/shared";
Expand All @@ -12,12 +13,12 @@ import { configProvider, IngestUrl, IsDevelopment, UTToken } from "./config";
import { ParsedToken, UploadThingToken } from "./shared-schemas";

const app1TokenData = {
apiKey: "sk_foo",
apiKey: Redacted.make("sk_foo"),
juliusmarminge marked this conversation as resolved.
Show resolved Hide resolved
appId: "app-1",
regions: ["fra1"] as const,
};
const app2TokenData = {
apiKey: "sk_bar",
apiKey: Redacted.make("sk_bar"),
appId: "app-2",
regions: ["dub1"] as const,
};
Expand Down
5 changes: 3 additions & 2 deletions packages/uploadthing/src/internal/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as ManagedRuntime from "effect/ManagedRuntime";
import * as Match from "effect/Match";
import * as Redacted from "effect/Redacted";

import {
fillInputRouteConfig,
Expand Down Expand Up @@ -373,7 +374,7 @@ const handleCallbackRequest = (opts: {
yield* HttpClientRequest.post(`/callback-result`).pipe(
HttpClientRequest.prependUrl(baseUrl),
HttpClientRequest.setHeaders({
"x-uploadthing-api-key": apiKey,
"x-uploadthing-api-key": Redacted.value(apiKey),
juliusmarminge marked this conversation as resolved.
Show resolved Hide resolved
"x-uploadthing-version": pkgJson.version,
"x-uploadthing-be-adapter": beAdapter,
"x-uploadthing-fe-package": fePackage,
Expand Down Expand Up @@ -602,7 +603,7 @@ const handleUploadAction = (opts: {
const metadataRequest = HttpClientRequest.post("/route-metadata").pipe(
HttpClientRequest.prependUrl(ingestUrl),
HttpClientRequest.setHeaders({
"x-uploadthing-api-key": apiKey,
"x-uploadthing-api-key": Redacted.value(apiKey),
"x-uploadthing-version": pkgJson.version,
"x-uploadthing-be-adapter": beAdapter,
"x-uploadthing-fe-package": fePackage,
Expand Down
4 changes: 3 additions & 1 deletion packages/uploadthing/src/internal/jsonl.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as Exit from "effect/Exit";
import * as Redacted from "effect/Redacted";
import * as Stream from "effect/Stream";
import { beforeEach, describe, expect, vi } from "vitest";

Expand All @@ -13,7 +14,8 @@ const te = new TextEncoder();

const createChunk = (_payload: unknown) => {
const payload = JSON.stringify(_payload);
return Effect.map(signPayload(payload, "sk_123"), (signature) =>
const secret = Redacted.make("sk_123");
return Effect.map(signPayload(payload, secret), (signature) =>
JSON.stringify(
MetadataFetchStreamPart.make({ payload, signature, hook: "callback" }),
),
Expand Down
2 changes: 1 addition & 1 deletion packages/uploadthing/src/internal/shared-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const DecodeString = S.transform(S.Uint8ArrayFromSelf, S.String, {
});

export const ParsedToken = S.Struct({
apiKey: S.String.pipe(S.startsWith("sk_")),
apiKey: S.Redacted(S.String.pipe(S.startsWith("sk_"))),
appId: S.String,
regions: S.NonEmptyArray(S.String),
ingestHost: S.String.pipe(
Expand Down
3 changes: 2 additions & 1 deletion packages/uploadthing/src/sdk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as Arr from "effect/Array";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Predicate from "effect/Predicate";
import * as Redacted from "effect/Redacted";

import type {
ACL,
Expand Down Expand Up @@ -75,7 +76,7 @@ export class UTApi {
HttpClientRequest.setHeaders({
"x-uploadthing-version": UPLOADTHING_VERSION,
"x-uploadthing-be-adapter": "server-sdk",
"x-uploadthing-api-key": apiKey,
"x-uploadthing-api-key": Redacted.value(apiKey),
}),
HttpClient.filterStatusOk(httpClient),
Effect.tapBoth({
Expand Down
3 changes: 2 additions & 1 deletion packages/uploadthing/test/__test-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as S from "@effect/schema/Schema";
import * as Redacted from "effect/Redacted";
import type { StrictRequest } from "msw";
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
Expand All @@ -17,7 +18,7 @@ export const uploadCompleteMock = vi.fn();
export const onErrorMock = vi.fn();

const tokenData = {
apiKey: "sk_foo",
apiKey: Redacted.make("sk_foo"),
juliusmarminge marked this conversation as resolved.
Show resolved Hide resolved
appId: "app-1",
regions: ["fra1"] as const,
};
Expand Down
3 changes: 2 additions & 1 deletion packages/uploadthing/test/request-handler.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as Effect from "effect/Effect";
import * as Redacted from "effect/Redacted";
import { describe, expect } from "vitest";
import { z } from "zod";

Expand Down Expand Up @@ -418,7 +419,7 @@ describe(".onUploadComplete()", () => {
}),
});
const signature = await Effect.runPromise(
signPayload(payload, "sk_live_badkey"),
signPayload(payload, Redacted.make("sk_live_badkey")),
);

const res = await handler(
Expand Down
Loading