532 lines · 18.6 KB · TypeScript
RawDownload
import { createClerkClient, verifyToken } from "@clerk/backend";import { describe, expect, it } from "@effect/vitest";import { vi } from "vite-plus/test";import * as Context from "effect/Context";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as Fiber from "effect/Fiber";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Predicate from "effect/Predicate";import * as Redacted from "effect/Redacted";import * as TestClock from "effect/testing/TestClock";import * as Tracer from "effect/Tracer";import * as HttpRouter from "effect/unstable/http/HttpRouter";import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";import { EnvironmentId } from "@t3tools/contracts";import { RelayEnvironmentAuth } from "@t3tools/contracts/relay"; import {  RELAY_REQUEST_DEADLINE_MS,  relayCors,  relayDocsRedirectRoute,  relayEnvironmentAuthLayer,  relayNotFoundRoute,  relayDpopFailureReason,  revokeEnvironmentLinkRecord,  traceRelayHttpRequestWith,  unlinkEnvironmentRecord,  verifyRelayClientBearerToken,  withoutCapturedParentSpan,} from "./Api.ts";import * as RelayConfiguration from "../Config.ts";import * as RelayDb from "../db.ts";import * as EnvironmentCredentials from "../environments/EnvironmentCredentials.ts";import * as EnvironmentLinks from "../environments/EnvironmentLinks.ts";import * as ManagedEndpointProvider from "../environments/ManagedEndpointProvider.ts"; vi.mock("@clerk/backend", () => ({  createClerkClient: vi.fn(),  verifyToken: vi.fn(),})); const relaySettings: RelayConfiguration.RelayConfiguration["Service"] = {  relayIssuer: "https://relay.example.test",  apns: {    teamId: "apns-team",    keyId: "apns-key",    privateKey: Redacted.make("apns-private-key"),    bundleId: "com.example.t3",    environment: "sandbox",  },  clerkSecretKey: Redacted.make("clerk-secret-key"),  clerkPublishableKey: "pk_test_test",  clerkJwtAudience: "t3-code-relay",  apnsDeliveryJobSigningSecret: Redacted.make("apns-delivery-secret"),  cloudMintPrivateKey: Redacted.make("cloud-mint-private-key"),  cloudMintPublicKey: "cloud-mint-public-key",  managedEndpointBaseDomain: undefined,  managedEndpointNamespace: undefined,}; describe("relay client authentication", () => {  it.effect("preserves the existing Clerk session JWT path", () =>    Effect.gen(function* () {      vi.mocked(verifyToken).mockResolvedValue({        sub: "user_session",        aud: relaySettings.clerkJwtAudience,      } as never);       expect(yield* verifyRelayClientBearerToken(relaySettings, "session-token")).toEqual({        sub: "user_session",        mode: "clerk_session_bearer",      });      expect(verifyToken).toHaveBeenCalledWith("session-token", {        secretKey: "clerk-secret-key",        audience: relaySettings.clerkJwtAudience,      });      expect(createClerkClient).not.toHaveBeenCalled();    }).pipe(      Effect.ensuring(        Effect.sync(() => {          vi.mocked(verifyToken).mockReset();          vi.mocked(createClerkClient).mockReset();        }),      ),    ),  );   it.effect("falls back to Clerk OAuth token verification for the headless CLI", () =>    Effect.gen(function* () {      vi.mocked(verifyToken).mockRejectedValue(new Error("not a session JWT"));      vi.mocked(createClerkClient).mockReturnValue({        authenticateRequest: vi.fn().mockResolvedValue({          isAuthenticated: true,          toAuth: () => ({ userId: "user_oauth" }),        }),      } as never);       expect(yield* verifyRelayClientBearerToken(relaySettings, "oauth-token")).toEqual({        sub: "user_oauth",        mode: "clerk_oauth_bearer",      });      expect(createClerkClient).toHaveBeenCalledWith({        secretKey: "clerk-secret-key",        publishableKey: "pk_test_test",      });    }).pipe(      Effect.ensuring(        Effect.sync(() => {          vi.mocked(verifyToken).mockReset();          vi.mocked(createClerkClient).mockReset();        }),      ),    ),  );}); describe("relay DPoP failure mapping", () => {  it("maps verifier failures to safe client-facing categories", () => {    const mappings = [      ["time_window", "time_window"],      ["key_mismatch", "key_mismatch"],      ["method_mismatch", "request_mismatch"],      ["url_mismatch", "request_mismatch"],      ["access_token_hash_mismatch", "token_mismatch"],      ["replayed", "replay"],      ["missing_proof", "invalid_proof"],      ["malformed_proof", "invalid_proof"],      ["invalid_signature", "invalid_proof"],      ["invalid_proof", "invalid_proof"],    ] as const;     for (const [code, expected] of mappings) {      expect(relayDpopFailureReason(code)).toBe(expected);    }  });}); describe("relay environment authentication", () => {  it.effect("preserves credential lookup persistence failures as internal errors", () => {    const failure = new EnvironmentCredentials.EnvironmentCredentialAuthenticatePersistenceError({      stage: "lookup-credential",      cause: "database unavailable",    });    const credentials: EnvironmentCredentials.EnvironmentCredentials["Service"] = {      create: () => Effect.die("unused create"),      authenticate: () => Effect.fail(failure),      revokeForEnvironmentPublicKey: () => Effect.die("unused revoke"),    };     return Effect.gen(function* () {      const auth = yield* RelayEnvironmentAuth;      const error = yield* Effect.flip(        auth.environmentBearer(Effect.succeed(HttpServerResponse.empty()), {          credential: Redacted.make("environment-credential"),          endpoint: {} as never,          group: {} as never,        }),      );       expect(Predicate.isTagged(error, "RelayInternalError")).toBe(true);      if (Predicate.isTagged(error, "RelayInternalError")) {        expect(error.reason).toBe("persistence_failed");      }    }).pipe(      Effect.provideService(        HttpServerRequest.HttpServerRequest,        HttpServerRequest.fromWeb(new Request("https://relay.test/v1/server/link")),      ),      Effect.provideService(HttpServerRequest.ParsedSearchParams, {}),      Effect.provideService(HttpRouter.RouteContext, {        params: {},        route: {} as never,      }),      Effect.provide(        relayEnvironmentAuthLayer.pipe(          Layer.provide(Layer.succeed(EnvironmentCredentials.EnvironmentCredentials, credentials)),        ),      ),      Effect.scoped,    );  });}); function relayUnlinkTestLayer(input?: {  readonly withTransaction?: RelayDb.RelayTransactions["Service"]["withTransaction"];  readonly getForUser?: EnvironmentLinks.EnvironmentLinks["Service"]["getForUser"];  readonly revokeForUser?: EnvironmentLinks.EnvironmentLinks["Service"]["revokeForUser"];  readonly revokeCredential?: EnvironmentCredentials.EnvironmentCredentials["Service"]["revokeForEnvironmentPublicKey"];  readonly prepareDeprovision?: ManagedEndpointProvider.ManagedEndpointProvider["Service"]["prepareDeprovision"];  readonly deprovision?: ManagedEndpointProvider.ManagedEndpointProvider["Service"]["deprovision"];}) {  return Layer.mergeAll(    Layer.succeed(      RelayDb.RelayTransactions,      RelayDb.RelayTransactions.of({        withTransaction: input?.withTransaction ?? ((effect) => effect),      }),    ),    Layer.succeed(      EnvironmentLinks.EnvironmentLinks,      EnvironmentLinks.EnvironmentLinks.of({        upsert: () => Effect.die("unused upsert"),        listUsersForEnvironment: () => Effect.die("unused listUsersForEnvironment"),        listDeliveryUsersForEnvironment: () => Effect.die("unused listDeliveryUsersForEnvironment"),        listPublicKeysForEnvironment: () => Effect.die("unused listPublicKeysForEnvironment"),        listForUser: () => Effect.die("unused listForUser"),        getForUser: input?.getForUser ?? (() => Effect.succeed(null)),        revokeForUser: input?.revokeForUser ?? (() => Effect.succeed(false)),      }),    ),    Layer.succeed(      EnvironmentCredentials.EnvironmentCredentials,      EnvironmentCredentials.EnvironmentCredentials.of({        create: () => Effect.die("unused create"),        authenticate: () => Effect.die("unused authenticate"),        revokeForEnvironmentPublicKey: input?.revokeCredential ?? (() => Effect.succeed(false)),      }),    ),    Layer.succeed(      ManagedEndpointProvider.ManagedEndpointProvider,      ManagedEndpointProvider.ManagedEndpointProvider.of({        provision: () => Effect.die("unused provision"),        prepareDeprovision: input?.prepareDeprovision ?? (() => Effect.succeed(null)),        deprovision: input?.deprovision ?? (() => Effect.void),        release: () => Effect.die("unused release"),      }),    ),  );} const linkedEnvironmentRecord = {  environmentId: EnvironmentId.make("environment-1"),  label: "Environment 1",  endpoint: {    httpBaseUrl: "https://environment-1.example.test/",    wsBaseUrl: "wss://environment-1.example.test/ws",    providerKind: "cloudflare_tunnel",  },  environmentPublicKey: "public-key",  linkedAt: "2026-07-28T00:00:00.000Z",} as const; describe("relay environment unlink", () => {  it.effect("revokes the link and its credentials in one database transaction", () => {    const calls: Array<string> = [];    return Effect.gen(function* () {      expect(        yield* revokeEnvironmentLinkRecord({          userId: "user-1",          environmentId: "environment-1",          environmentPublicKey: "public-key",        }),      ).toBe(true);      expect(calls).toEqual(["transaction", "link", "credential"]);    }).pipe(      Effect.provide(        relayUnlinkTestLayer({          withTransaction: (effect) => {            calls.push("transaction");            return effect;          },          revokeForUser: () =>            Effect.sync(() => {              calls.push("link");              return true;            }),          revokeCredential: () =>            Effect.sync(() => {              calls.push("credential");              return true;            }),        }),      ),    );  });   it.effect("commits database revocation before deprovisioning the managed endpoint", () => {    const calls: Array<string> = [];    const deprovisionTarget = {      userId: "user-1",      environmentId: "environment-1",      hostname: "environment-1.example.test",      tunnelId: "tunnel-1",      tunnelName: "environment-1-tunnel",      dnsRecordId: "dns-1",      readyAt: "2026-07-28T00:00:00.000Z",      updatedAt: "generation-before-unlink",    } satisfies ManagedEndpointProvider.ManagedEndpointDeprovisionTarget;     return Effect.gen(function* () {      expect(        yield* unlinkEnvironmentRecord({          userId: "user-1",          environmentId: "environment-1",        }),      ).toBe(true);      expect(calls).toEqual([        "prepare",        "lookup",        "transaction",        "link",        "credential",        "deprovision",      ]);    }).pipe(      Effect.provide(        relayUnlinkTestLayer({          withTransaction: (effect) => {            calls.push("transaction");            return effect;          },          getForUser: () =>            Effect.sync(() => {              calls.push("lookup");              return linkedEnvironmentRecord;            }),          revokeForUser: () =>            Effect.sync(() => {              calls.push("link");              return true;            }),          revokeCredential: () =>            Effect.sync(() => {              calls.push("credential");              return true;            }),          prepareDeprovision: () =>            Effect.sync(() => {              calls.push("prepare");              return deprovisionTarget;            }),          deprovision: (request) =>            Effect.sync(() => {              expect(request.target).toBe(deprovisionTarget);              calls.push("deprovision");            }),        }),      ),    );  });   it.effect("does not deprovision when database revocation fails", () => {    const calls: Array<string> = [];    const failure = new EnvironmentCredentials.EnvironmentCredentialRevokePersistenceError({      environmentId: "environment-1",      cause: "database unavailable",    });     return Effect.gen(function* () {      expect(        yield* Effect.flip(          unlinkEnvironmentRecord({            userId: "user-1",            environmentId: "environment-1",          }),        ),      ).toBe(failure);      expect(calls).toEqual(["prepare", "transaction", "link", "credential"]);    }).pipe(      Effect.provide(        relayUnlinkTestLayer({          withTransaction: (effect) => {            calls.push("transaction");            return effect;          },          getForUser: () => Effect.succeed(linkedEnvironmentRecord),          revokeForUser: () =>            Effect.sync(() => {              calls.push("link");              return true;            }),          revokeCredential: () =>            Effect.sync(() => {              calls.push("credential");            }).pipe(Effect.andThen(Effect.fail(failure))),          prepareDeprovision: () =>            Effect.sync(() => {              calls.push("prepare");              return null;            }),          deprovision: () =>            Effect.sync(() => {              calls.push("deprovision");            }),        }),      ),    );  });   it.effect("retries deprovisioning after the link is already revoked", () => {    const calls: Array<string> = [];    return Effect.gen(function* () {      expect(        yield* unlinkEnvironmentRecord({          userId: "user-1",          environmentId: "environment-1",        }),      ).toBe(false);      expect(calls).toEqual(["prepare", "deprovision"]);    }).pipe(      Effect.provide(        relayUnlinkTestLayer({          prepareDeprovision: () =>            Effect.sync(() => {              calls.push("prepare");              return null;            }),          deprovision: () =>            Effect.sync(() => {              calls.push("deprovision");            }),        }),      ),    );  });}); describe("relay request tracing", () => {  it.effect(    "does not parent endpoint spans to an ambient parent captured while building handlers",    () =>      Effect.gen(function* () {        const spans: Array<Tracer.NativeSpan> = [];        const tracer = Tracer.make({          span: (options) => {            const span = new Tracer.NativeSpan(options);            spans.push(span);            return span;          },        });        const ambientParent = Tracer.externalSpan({          traceId: "00000000000000000000000000000001",          spanId: "0000000000000001",          sampled: true,        });        const endpoint = yield* withoutCapturedParentSpan(          Effect.context<never>().pipe(            Effect.map((capturedContext: Context.Context<never>) =>              Effect.succeed(HttpServerResponse.empty({ status: 204 })).pipe(                Effect.withSpan("relay.test.endpoint"),                Effect.provideContext(capturedContext),              ),            ),          ),        ).pipe(Effect.provideService(Tracer.ParentSpan, ambientParent));        const request = HttpServerRequest.fromWeb(          new Request("https://relay.test/v1/mobile/devices?client=mobile", {            method: "POST",            headers: {              authorization: "Bearer secret",              dpop: "signed-proof",            },          }),        );         yield* traceRelayHttpRequestWith(endpoint, Layer.succeed(Tracer.Tracer, tracer)).pipe(          Effect.provideService(HttpServerRequest.HttpServerRequest, request),        );         expect(spans.map((span) => span.name)).toEqual(["http.server POST", "relay.test.endpoint"]);        expect(spans[0]?.kind).toBe("server");        expect(spans[0]?.attributes.get("url.path")).toBe("/v1/mobile/devices");        expect(spans[0]?.attributes.get("http.response.status_code")).toBe(204);        expect(spans[0]?.attributes.get("http.request.header.authorization")).toBe("<redacted>");        expect(spans[0]?.attributes.get("http.request.header.dpop")).toBe("<redacted>");        expect(Option.isNone(spans[0]!.parent)).toBe(true);        expect(Option.getOrUndefined(spans[1]!.parent)?.spanId).toBe(spans[0]?.spanId);      }),  );   it.effect("fails hung requests with a 504 before the client's 10s abort", () =>    Effect.gen(function* () {      const spans: Array<Tracer.NativeSpan> = [];      const tracer = Tracer.make({        span: (options) => {          const span = new Tracer.NativeSpan(options);          spans.push(span);          return span;        },      });      const request = HttpServerRequest.fromWeb(        new Request("https://relay.test/v1/mobile/devices", { method: "POST" }),      );       const fiber = yield* traceRelayHttpRequestWith(        Effect.never,        Layer.succeed(Tracer.Tracer, tracer),      ).pipe(Effect.provideService(HttpServerRequest.HttpServerRequest, request), Effect.forkChild);      yield* TestClock.adjust(Duration.millis(RELAY_REQUEST_DEADLINE_MS));      const response = yield* Fiber.join(fiber);       expect(response.status).toBe(504);      expect(spans[0]?.attributes.get("relay.request.deadline_exceeded")).toBe(true);      expect(spans[0]?.attributes.get("http.response.status_code")).toBe(504);    }),  );}); 

Lines 1–500 of 532.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t