188 lines · 6.4 KB · TypeScript
RawDownload
import * as NodeCrypto from "node:crypto"; import { describe, expect, it } from "@effect/vitest";import { signRelayJwt } from "@t3tools/shared/relayJwt";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Redacted from "effect/Redacted"; import * as RelayConfiguration from "../Config.ts";import * as RelayTokens from "./RelayTokens.ts"; const keyPair = NodeCrypto.generateKeyPairSync("ed25519", {  privateKeyEncoding: { format: "pem", type: "pkcs8" },  publicKeyEncoding: { format: "pem", type: "spki" },}); const config = RelayConfiguration.RelayConfiguration.of({  relayIssuer: "https://relay.example.test/",  apns: {    environment: "sandbox",    teamId: "team-id",    keyId: "key-id",    privateKey: Redacted.make("private-key"),    bundleId: "com.t3tools.t3code.dev",  },  apnsDeliveryJobSigningSecret: Redacted.make("job-secret"),  clerkSecretKey: Redacted.make("clerk-secret"),  clerkPublishableKey: "pk_test_test",  clerkJwtAudience: "t3-code-relay",  cloudMintPrivateKey: Redacted.make(keyPair.privateKey),  cloudMintPublicKey: keyPair.publicKey,  managedEndpointBaseDomain: undefined,  managedEndpointNamespace: undefined,}); const layer = RelayTokens.layer.pipe(Layer.provide(RelayConfiguration.layer(config))); describe("RelayTokens", () => {  it.effect("issues a user-bound environment link challenge", () =>    Effect.gen(function* () {      const relayTokens = yield* RelayTokens.RelayTokens;      const token = yield* relayTokens.issueLinkChallenge({        userId: "user_123",        request: {          notificationsEnabled: true,          liveActivitiesEnabled: true,          managedTunnelsEnabled: true,        },        jti: "challenge-1",        issuedAtEpochSeconds: 100,        expiresAtEpochSeconds: 200,      });       expect(        yield* relayTokens.verifyLinkChallenge({          token,          userId: "user_123",          request: {            notificationsEnabled: true,            liveActivitiesEnabled: true,            managedTunnelsEnabled: true,          },          nowEpochSeconds: 150,        }),      ).toMatchObject({ sub: "user_123", jti: "challenge-1" });      expect(        yield* relayTokens.verifyLinkChallenge({          token,          userId: "attacker",          request: {            notificationsEnabled: true,            liveActivitiesEnabled: true,            managedTunnelsEnabled: true,          },          nowEpochSeconds: 150,        }),      ).toBeNull();    }).pipe(Effect.provide(layer)),  );   it.effect("issues and verifies DPoP access tokens bound to one proof-key thumbprint", () =>    Effect.gen(function* () {      const relayTokens = yield* RelayTokens.RelayTokens;      const token = yield* relayTokens.issueDpopAccessToken({        userId: "user_123",        proofKeyThumbprint: "proof-key-thumbprint",        jti: "access-token-1",        issuedAtEpochSeconds: 100,        expiresAtEpochSeconds: 1_900,        clientId: "t3-mobile",        scopes: ["environment:connect", "environment:status", "mobile:registration"],      });       expect(        yield* relayTokens.verifyDpopAccessToken({ token, nowEpochSeconds: 700 }),      ).toMatchObject({        sub: "user_123",        cnf: { jkt: "proof-key-thumbprint" },        client_id: "t3-mobile",        scope: ["environment:connect", "environment:status", "mobile:registration"],      });      expect(        yield* relayTokens.verifyDpopAccessToken({ token, nowEpochSeconds: 1_961 }),      ).toBeNull();    }).pipe(Effect.provide(layer)),  );   it.effect("issues tunnel-only DPoP access tokens to web public clients", () =>    Effect.gen(function* () {      const relayTokens = yield* RelayTokens.RelayTokens;      const token = yield* relayTokens.issueDpopAccessToken({        userId: "user_123",        proofKeyThumbprint: "web-proof-key-thumbprint",        jti: "web-access-token-1",        issuedAtEpochSeconds: 100,        expiresAtEpochSeconds: 200,        clientId: "t3-web",        scopes: ["environment:connect", "environment:status"],      });       expect(        yield* relayTokens.verifyDpopAccessToken({ token, nowEpochSeconds: 150 }),      ).toMatchObject({        client_id: "t3-web",        scope: ["environment:connect", "environment:status"],        cnf: { jkt: "web-proof-key-thumbprint" },      });    }).pipe(Effect.provide(layer)),  );   it.effect("treats requested scope as an order-independent set", () =>    Effect.gen(function* () {      const relayTokens = yield* RelayTokens.RelayTokens;      expect(        relayTokens.resolveDpopAccessTokenScopes({          clientId: "t3-mobile",          scope: "environment:status environment:connect environment:status",        }),      ).toEqual(["environment:status", "environment:connect"]);    }).pipe(Effect.provide(layer)),  );   it.effect("rejects signed DPoP tokens whose scope is outside the relay policy", () =>    Effect.gen(function* () {      const relayTokens = yield* RelayTokens.RelayTokens;      const token = yield* signRelayJwt({        privateKey: keyPair.privateKey,        typ: "t3-relay-dpop-access+jwt",        payload: {          iss: "https://relay.example.test",          aud: "https://relay.example.test",          sub: "user_123",          jti: "access-token-invalid-scope",          iat: 100,          exp: 200,          client_id: "t3-mobile",          scope: "environment:admin",          cnf: { jkt: "proof-key-thumbprint" },        },      });       expect(yield* relayTokens.verifyDpopAccessToken({ token, nowEpochSeconds: 150 })).toBeNull();    }).pipe(Effect.provide(layer)),  );   it.effect("rejects mobile registration scope on a web public client token", () =>    Effect.gen(function* () {      const relayTokens = yield* RelayTokens.RelayTokens;      const token = yield* signRelayJwt({        privateKey: keyPair.privateKey,        typ: "t3-relay-dpop-access+jwt",        payload: {          iss: "https://relay.example.test",          aud: "https://relay.example.test",          sub: "user_123",          jti: "web-token-invalid-mobile-scope",          iat: 100,          exp: 200,          client_id: "t3-web",          scope: "environment:connect mobile:registration",          cnf: { jkt: "proof-key-thumbprint" },        },      });       expect(yield* relayTokens.verifyDpopAccessToken({ token, nowEpochSeconds: 150 })).toBeNull();    }).pipe(Effect.provide(layer)),  );});