1// @effect-diagnostics-next-line nodeBuiltinImport:off -- Effect's Crypto has no generateKeyPairSync or sign.
2import * as NodeCrypto from "node:crypto";
3import type {
4 RelayEnvironmentLinkProofPayload,
5 RelayEnvironmentLinkRequest,
6} from "@t3tools/contracts/relay";
7import { RELAY_LINK_PROOF_TYP } from "@t3tools/shared/relayJwt";
8import { describe, expect, it } from "@effect/vitest";
9import * as DateTime from "effect/DateTime";
10import * as Effect from "effect/Effect";
11import * as Layer from "effect/Layer";
12import * as Redacted from "effect/Redacted";
13import * as Result from "effect/Result";
14import * as Schema from "effect/Schema";
15
16import * as DpopProofs from "../auth/DpopProofs.ts";
17import * as RelayTokens from "../auth/RelayTokens.ts";
18import * as EnvironmentCredentials from "./EnvironmentCredentials.ts";
19import * as EnvironmentLinks from "./EnvironmentLinks.ts";
20import * as RelayConfiguration from "../Config.ts";
21import * as EnvironmentLinker from "./EnvironmentLinker.ts";
22import * as ManagedEndpointProvider from "./ManagedEndpointProvider.ts";
23
24const relayKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
25 privateKeyEncoding: { format: "pem", type: "pkcs8" },
26 publicKeyEncoding: { format: "pem", type: "spki" },
27});
28const environmentKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
29 privateKeyEncoding: { format: "pem", type: "pkcs8" },
30 publicKeyEncoding: { format: "pem", type: "spki" },
31});
32const config = RelayConfiguration.RelayConfiguration.of({
33 relayIssuer: "https://relay.example.test",
34 apns: {
35 environment: "sandbox",
36 teamId: "team-id",
37 keyId: "key-id",
38 privateKey: Redacted.make("private-key"),
39 bundleId: "com.t3tools.t3code.dev",
40 },
41 apnsDeliveryJobSigningSecret: Redacted.make("job-secret"),
42 clerkSecretKey: Redacted.make("clerk-secret"),
43 clerkPublishableKey: "pk_test_test",
44 clerkJwtAudience: "t3-code-relay",
45 cloudMintPrivateKey: Redacted.make(relayKeyPair.privateKey),
46 cloudMintPublicKey: relayKeyPair.publicKey,
47 managedEndpointBaseDomain: undefined,
48 managedEndpointNamespace: undefined,
49});
50const isEnvironmentLinkProofInvalid = Schema.is(EnvironmentLinker.EnvironmentLinkProofInvalid);
51
52function signTestJwt(payload: object, typ: string, privateKey: string): string {
53 const header = Buffer.from(JSON.stringify({ alg: "EdDSA", typ })).toString("base64url");
54 const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
55 const signingInput = `${header}.${encodedPayload}`;
56 return `${signingInput}.${NodeCrypto.sign(null, Buffer.from(signingInput), privateKey).toString("base64url")}`;
57}
58
59const makeRequest = Effect.gen(function* () {
60 const now = yield* DateTime.now;
61 const expiresAt = DateTime.add(now, { minutes: 5 });
62 const relayTokens = yield* RelayTokens.RelayTokens;
63 const challenge = yield* relayTokens.issueLinkChallenge({
64 userId: "user_123",
65 request: {
66 notificationsEnabled: true,
67 liveActivitiesEnabled: true,
68 managedTunnelsEnabled: true,
69 },
70 jti: "challenge-jti",
71 issuedAtEpochSeconds: Math.floor(now.epochMilliseconds / 1_000),
72 expiresAtEpochSeconds: Math.floor(expiresAt.epochMilliseconds / 1_000),
73 });
74 const payload = {
75 iss: "t3-env:env-link-test",
76 aud: "https://relay.example.test",
77 sub: "env-link-test",
78 jti: "link-proof-jti",
79 iat: Math.floor(now.epochMilliseconds / 1_000),
80 exp: Math.floor(expiresAt.epochMilliseconds / 1_000),
81 challenge,
82 environmentId: "env-link-test" as RelayEnvironmentLinkProofPayload["environmentId"],
83 descriptor: {
84 environmentId: "env-link-test" as RelayEnvironmentLinkProofPayload["environmentId"],
85 label: "Link Test Environment",
86 platform: { os: "darwin", arch: "arm64" },
87 serverVersion: "0.0.0-test",
88 capabilities: { repositoryIdentity: true },
89 },
90 environmentPublicKey: environmentKeyPair.publicKey.trim(),
91 endpoint: {
92 httpBaseUrl: "https://env.example.test/",
93 wsBaseUrl: "wss://env.example.test/",
94 providerKind: "manual",
95 },
96 origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 },
97 scopes: ["agent_activity_notifications", "managed_tunnels"],
98 } satisfies RelayEnvironmentLinkProofPayload;
99 return {
100 request: {
101 proof: signTestJwt(payload, RELAY_LINK_PROOF_TYP, environmentKeyPair.privateKey),
102 notificationsEnabled: true,
103 liveActivitiesEnabled: true,
104 managedTunnelsEnabled: false,
105 } satisfies RelayEnvironmentLinkRequest,
106 payload,
107 };
108});
109
110function layerTest(input?: {
111 readonly upsert?: EnvironmentLinks.EnvironmentLinks["Service"]["upsert"];
112 readonly consume?: DpopProofs.DpopProofReplay["Service"]["consume"];
113 readonly deprovision?: ManagedEndpointProvider.ManagedEndpointProvider["Service"]["deprovision"];
114}) {
115 return EnvironmentLinker.layer.pipe(
116 Layer.provideMerge(RelayTokens.layer),
117 Layer.provide(
118 Layer.mergeAll(
119 RelayConfiguration.layer(config),
120 Layer.succeed(DpopProofs.DpopProofReplay, {
121 verifyAndConsume: () => Effect.die("unexpected DPoP proof verification"),
122 consume: input?.consume ?? (() => Effect.succeed(true)),
123 pruneExpired: Effect.void,
124 }),
125 Layer.succeed(EnvironmentLinks.EnvironmentLinks, {
126 upsert: input?.upsert ?? (() => Effect.void),
127 listDeliveryUsersForEnvironment: () => Effect.succeed([]),
128 listForUser: () => Effect.succeed([]),
129 getForUser: () => Effect.succeed(null),
130 findActiveManagedForEnvironment: () => Effect.succeed([]),
131 setHoldWebhooksWhileOffline: () => Effect.void,
132 revokeForUser: () => Effect.succeed(false),
133 }),
134 Layer.succeed(EnvironmentCredentials.EnvironmentCredentials, {
135 create: () => Effect.succeed("t3env_credential_secret"),
136 authenticate: () => Effect.succeedNone,
137 revokeForEnvironmentPublicKey: () => Effect.succeed(false),
138 }),
139 Layer.succeed(ManagedEndpointProvider.ManagedEndpointProvider, {
140 reconcileOrigin: () => Effect.succeed("ready"),
141 prepareDeprovision: () => Effect.succeed(null),
142 deprovision: input?.deprovision ?? (() => Effect.succeed(true)),
143 release: () => Effect.succeed(true),
144 provision: () =>
145 Effect.succeed({
146 endpoint: {
147 httpBaseUrl: "https://managed.example.test/",
148 wsBaseUrl: "wss://managed.example.test/ws",
149 providerKind: "cloudflare_tunnel",
150 },
151 runtime: { providerKind: "cloudflare_tunnel", connectorToken: "connector-token" },
152 }),
153 }),
154 ),
155 ),
156 );
157}
158
159describe("EnvironmentLinker", () => {
160 it.effect("uses verified JWT claims when linking an environment", () => {
161 let persistedEnvironmentId: string | null = null;
162 return Effect.gen(function* () {
163 const { request, payload } = yield* makeRequest;
164 const linker = yield* EnvironmentLinker.EnvironmentLinker;
165 const result = yield* linker.link({ userId: "user_123", request });
166 expect(result.environmentId).toBe(payload.environmentId);
167 expect(result.environmentCredential).toBe("t3env_credential_secret");
168 expect(persistedEnvironmentId).toBe(payload.environmentId);
169 }).pipe(
170 Effect.provide(
171 layerTest({
172 upsert: (input) =>
173 Effect.sync(() => {
174 persistedEnvironmentId = input.proof.environmentId;
175 }),
176 }),
177 ),
178 );
179 });
180
181 it.effect("links a publish-only environment with a non-secure nominal endpoint", () => {
182 let persistedEndpoint: string | null = null;
183 let deprovisionedEnvironmentId: string | null = null;
184 return Effect.gen(function* () {
185 const now = yield* DateTime.now;
186 const expiresAt = DateTime.add(now, { minutes: 5 });
187 const relayTokens = yield* RelayTokens.RelayTokens;
188 const challenge = yield* relayTokens.issueLinkChallenge({
189 userId: "user_123",
190 request: {
191 notificationsEnabled: true,
192 liveActivitiesEnabled: true,
193 managedTunnelsEnabled: false,
194 },
195 jti: "publish-only-challenge-jti",
196 issuedAtEpochSeconds: Math.floor(now.epochMilliseconds / 1_000),
197 expiresAtEpochSeconds: Math.floor(expiresAt.epochMilliseconds / 1_000),
198 });
199 const payload = {
200 iss: "t3-env:env-link-test",
201 aud: "https://relay.example.test",
202 sub: "env-link-test",
203 jti: "publish-only-proof-jti",
204 iat: Math.floor(now.epochMilliseconds / 1_000),
205 exp: Math.floor(expiresAt.epochMilliseconds / 1_000),
206 challenge,
207 environmentId: "env-link-test" as RelayEnvironmentLinkProofPayload["environmentId"],
208 descriptor: {
209 environmentId: "env-link-test" as RelayEnvironmentLinkProofPayload["environmentId"],
210 label: "Link Test Environment",
211 platform: { os: "darwin", arch: "arm64" },
212 serverVersion: "0.0.0-test",
213 capabilities: { repositoryIdentity: true },
214 },
215 environmentPublicKey: environmentKeyPair.publicKey.trim(),
216 endpoint: {
217 httpBaseUrl: "http://127.0.0.1:3773/",
218 wsBaseUrl: "ws://127.0.0.1:3773/",
219 providerKind: "manual",
220 },
221 origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 },
222 scopes: ["agent_activity_notifications"],
223 } satisfies RelayEnvironmentLinkProofPayload;
224 const request = {
225 proof: signTestJwt(payload, RELAY_LINK_PROOF_TYP, environmentKeyPair.privateKey),
226 notificationsEnabled: true,
227 liveActivitiesEnabled: true,
228 managedTunnelsEnabled: false,
229 } satisfies RelayEnvironmentLinkRequest;
230 const linker = yield* EnvironmentLinker.EnvironmentLinker;
231 const result = yield* linker.link({ userId: "user_123", request });
232 expect(result.environmentCredential).toBe("t3env_credential_secret");
233 expect(result.endpointRuntime).toBeNull();
234 expect(persistedEndpoint).toBe("http://127.0.0.1:3773/");
235 // Downgrading from a managed link must release the previously provisioned
236 // tunnel; nothing else cleans it up before a full unlink.
237 expect(deprovisionedEnvironmentId).toBe("env-link-test");
238 }).pipe(
239 Effect.provide(
240 layerTest({
241 upsert: (input) =>
242 Effect.sync(() => {
243 persistedEndpoint = input.endpoint.httpBaseUrl;
244 }),
245 deprovision: (input) =>
246 Effect.sync(() => {
247 deprovisionedEnvironmentId = input.environmentId;
248 return true;
249 }),
250 }),
251 ),
252 );
253 });
254
255 it.effect("rejects a tampered compact proof before persistence", () => {
256 let persisted = false;
257 return Effect.gen(function* () {
258 const { request } = yield* makeRequest;
259 const segments = request.proof.split(".");
260 const signature = segments[2]!;
261 segments[2] = `${signature.startsWith("A") ? "B" : "A"}${signature.slice(1)}`;
262 const tampered = { ...request, proof: segments.join(".") };
263 const linker = yield* EnvironmentLinker.EnvironmentLinker;
264 const result = yield* Effect.result(linker.link({ userId: "user_123", request: tampered }));
265 expect(Result.isFailure(result)).toBe(true);
266 if (Result.isFailure(result)) {
267 expect(isEnvironmentLinkProofInvalid(result.failure)).toBe(true);
268 if (isEnvironmentLinkProofInvalid(result.failure)) {
269 expect(result.failure).toMatchObject({
270 userId: "user_123",
271 environmentId: "env-link-test",
272 reason: "invalid_signature_or_scope",
273 stage: "verify_proof",
274 cause: { _tag: "RelayJwtError" },
275 });
276 }
277 }
278 expect(persisted).toBe(false);
279 }).pipe(
280 Effect.provide(
281 layerTest({
282 upsert: () =>
283 Effect.sync(() => {
284 persisted = true;
285 }),
286 }),
287 ),
288 );
289 });
290
291 it.effect("rejects replayed JWT ids", () =>
292 Effect.gen(function* () {
293 const { request } = yield* makeRequest;
294 const linker = yield* EnvironmentLinker.EnvironmentLinker;
295 const result = yield* Effect.result(linker.link({ userId: "user_123", request }));
296 expect(Result.isFailure(result)).toBe(true);
297 if (Result.isFailure(result)) {
298 expect(isEnvironmentLinkProofInvalid(result.failure)).toBe(true);
299 if (isEnvironmentLinkProofInvalid(result.failure)) {
300 expect(result.failure).toMatchObject({
301 userId: "user_123",
302 environmentId: "env-link-test",
303 reason: "replayed_nonce",
304 stage: "consume_proof_nonce",
305 });
306 }
307 }
308 }).pipe(Effect.provide(layerTest({ consume: () => Effect.succeed(false) }))),
309 );
310});