1// @effect-diagnostics-next-line nodeBuiltinImport:off -- Effect's Crypto has no generateKeyPairSync or sign.
2import * as NodeCrypto from "node:crypto";
3import * as NodeServices from "@effect/platform-node/NodeServices";
4import type {
5 RelayAgentActivityPublishProofPayload,
6 RelayAgentActivityPublishRequest,
7 RelayAgentActivityState,
8} from "@t3tools/contracts/relay";
9import { RELAY_ACTIVITY_PUBLISH_TYP } from "@t3tools/shared/relayJwt";
10import { stableStringify } from "@t3tools/shared/relaySigning";
11import { describe, expect, it } from "@effect/vitest";
12import * as DateTime from "effect/DateTime";
13import * as Effect from "effect/Effect";
14import * as Layer from "effect/Layer";
15import * as Redacted from "effect/Redacted";
16import * as Result from "effect/Result";
17import * as Schema from "effect/Schema";
18
19import * as DpopProofs from "../auth/DpopProofs.ts";
20import * as RelayConfiguration from "../Config.ts";
21import * as EnvironmentPublishSignatures from "./EnvironmentPublishSignatures.ts";
22
23const keyPair = NodeCrypto.generateKeyPairSync("ed25519", {
24 privateKeyEncoding: { format: "pem", type: "pkcs8" },
25 publicKeyEncoding: { format: "pem", type: "spki" },
26});
27const config = RelayConfiguration.RelayConfiguration.of({
28 relayIssuer: "https://relay.example.test",
29 apns: {
30 environment: "sandbox",
31 teamId: "team-id",
32 keyId: "key-id",
33 privateKey: Redacted.make("private-key"),
34 bundleId: "com.t3tools.t3code.dev",
35 },
36 apnsDeliveryJobSigningSecret: Redacted.make("job-secret"),
37 clerkSecretKey: Redacted.make("clerk-secret"),
38 clerkPublishableKey: "pk_test_test",
39 clerkJwtAudience: "t3-code-relay",
40 cloudMintPrivateKey: Redacted.make(keyPair.privateKey),
41 cloudMintPublicKey: keyPair.publicKey,
42 managedEndpointBaseDomain: undefined,
43 managedEndpointNamespace: undefined,
44});
45const state: RelayAgentActivityState = {
46 environmentId: "env" as RelayAgentActivityState["environmentId"],
47 threadId: "thread" as RelayAgentActivityState["threadId"],
48 projectTitle: "Project",
49 threadTitle: "Thread",
50 modelTitle: "gpt-5.4",
51 phase: "running",
52 headline: "Running",
53 updatedAt: "2026-05-25T00:00:00.000Z",
54 deepLink: "/threads/env/thread",
55};
56const isEnvironmentPublishSignatureInvalid = Schema.is(
57 EnvironmentPublishSignatures.EnvironmentPublishSignatureInvalid,
58);
59
60function signTestJwt(payload: object, privateKey: string): string {
61 const header = Buffer.from(
62 JSON.stringify({ alg: "EdDSA", typ: RELAY_ACTIVITY_PUBLISH_TYP }),
63 ).toString("base64url");
64 const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
65 const signingInput = `${header}.${encodedPayload}`;
66 return `${signingInput}.${NodeCrypto.sign(null, Buffer.from(signingInput), privateKey).toString("base64url")}`;
67}
68
69const freshRequest = Effect.gen(function* () {
70 const now = yield* DateTime.now;
71 const payload = {
72 iss: "t3-env:env",
73 aud: "https://relay.example.test",
74 sub: "env",
75 jti: "publish-jti",
76 iat: Math.floor(now.epochMilliseconds / 1_000),
77 exp: Math.floor(DateTime.add(now, { minutes: 5 }).epochMilliseconds / 1_000),
78 environmentId: state.environmentId,
79 threadId: state.threadId,
80 state,
81 } satisfies RelayAgentActivityPublishProofPayload;
82 return {
83 state,
84 proof: signTestJwt(payload, keyPair.privateKey),
85 } satisfies RelayAgentActivityPublishRequest;
86});
87
88function layer(replay?: Partial<DpopProofs.DpopProofReplay["Service"]>) {
89 return EnvironmentPublishSignatures.layer.pipe(
90 Layer.provide(
91 Layer.merge(
92 RelayConfiguration.layer(config),
93 Layer.succeed(DpopProofs.DpopProofReplay, {
94 verifyAndConsume:
95 replay?.verifyAndConsume ?? (() => Effect.die("unexpected DPoP proof verification")),
96 consume: replay?.consume ?? (() => Effect.succeed(true)),
97 pruneExpired: replay?.pruneExpired ?? Effect.void,
98 }),
99 ),
100 ),
101 Layer.provideMerge(NodeServices.layer),
102 );
103}
104
105describe("EnvironmentPublishSignatures", () => {
106 it.effect("verifies activity JWTs and scopes replay storage to the environment key", () => {
107 let replayThumbprint: string | null = null;
108 return Effect.gen(function* () {
109 const request = yield* freshRequest;
110 const signatures = yield* EnvironmentPublishSignatures.EnvironmentPublishSignatures;
111 yield* signatures.verify({
112 environmentId: state.environmentId,
113 environmentPublicKey: keyPair.publicKey,
114 threadId: state.threadId,
115 request,
116 });
117 expect(replayThumbprint).toBe(
118 `env-publish:${NodeCrypto.createHash("sha256")
119 .update(
120 stableStringify({
121 environmentId: state.environmentId,
122 environmentPublicKey: keyPair.publicKey,
123 }),
124 )
125 .digest("base64url")}`,
126 );
127 }).pipe(
128 Effect.provide(
129 layer({
130 consume: (input) =>
131 Effect.sync(() => {
132 replayThumbprint = input.thumbprint;
133 return true;
134 }),
135 }),
136 ),
137 );
138 });
139
140 it.effect("rejects top-level state tampering", () =>
141 Effect.gen(function* () {
142 const request = yield* freshRequest;
143 const signatures = yield* EnvironmentPublishSignatures.EnvironmentPublishSignatures;
144 const result = yield* Effect.result(
145 signatures.verify({
146 environmentId: state.environmentId,
147 environmentPublicKey: keyPair.publicKey,
148 threadId: state.threadId,
149 request: { ...request, state: { ...state, headline: "Tampered" } },
150 }),
151 );
152 expect(Result.isFailure(result)).toBe(true);
153 if (Result.isFailure(result)) {
154 expect(isEnvironmentPublishSignatureInvalid(result.failure)).toBe(true);
155 if (isEnvironmentPublishSignatureInvalid(result.failure)) {
156 expect(result.failure).toMatchObject({
157 environmentId: state.environmentId,
158 threadId: state.threadId,
159 reason: "invalid_signature_or_payload",
160 stage: "validate_claims",
161 });
162 }
163 }
164 }).pipe(Effect.provide(layer())),
165 );
166
167 it.effect("preserves the JWT verification failure", () =>
168 Effect.gen(function* () {
169 const request = yield* freshRequest;
170 const segments = request.proof.split(".");
171 const signature = segments[2]!;
172 segments[2] = `${signature.startsWith("A") ? "B" : "A"}${signature.slice(1)}`;
173 const signatures = yield* EnvironmentPublishSignatures.EnvironmentPublishSignatures;
174 const result = yield* Effect.result(
175 signatures.verify({
176 environmentId: state.environmentId,
177 environmentPublicKey: keyPair.publicKey,
178 threadId: state.threadId,
179 request: { ...request, proof: segments.join(".") },
180 }),
181 );
182
183 expect(Result.isFailure(result)).toBe(true);
184 if (Result.isFailure(result)) {
185 expect(isEnvironmentPublishSignatureInvalid(result.failure)).toBe(true);
186 if (isEnvironmentPublishSignatureInvalid(result.failure)) {
187 expect(result.failure).toMatchObject({
188 environmentId: state.environmentId,
189 threadId: state.threadId,
190 reason: "invalid_signature_or_payload",
191 stage: "verify_proof",
192 cause: { _tag: "RelayJwtError" },
193 });
194 }
195 }
196 }).pipe(Effect.provide(layer())),
197 );
198
199 it.effect("rejects replayed activity JWT ids", () =>
200 Effect.gen(function* () {
201 const request = yield* freshRequest;
202 const signatures = yield* EnvironmentPublishSignatures.EnvironmentPublishSignatures;
203 const result = yield* Effect.result(
204 signatures.verify({
205 environmentId: state.environmentId,
206 environmentPublicKey: keyPair.publicKey,
207 threadId: state.threadId,
208 request,
209 }),
210 );
211 expect(Result.isFailure(result)).toBe(true);
212 if (Result.isFailure(result)) {
213 expect(isEnvironmentPublishSignatureInvalid(result.failure)).toBe(true);
214 if (isEnvironmentPublishSignatureInvalid(result.failure)) {
215 expect(result.failure).toMatchObject({
216 environmentId: state.environmentId,
217 threadId: state.threadId,
218 reason: "replayed_nonce",
219 stage: "consume_nonce",
220 });
221 }
222 }
223 }).pipe(Effect.provide(layer({ consume: () => Effect.succeed(false) }))),
224 );
225});