1import {
2 EnvironmentAuthInvalidError,
3 type AuthBrowserSessionResult,
4 type AuthCreatePairingCredentialInput,
5 type AuthSessionState,
6 type DesktopBridge,
7} from "@t3tools/contracts";
8import { createBrowserHistory } from "@tanstack/react-router";
9import * as DateTime from "effect/DateTime";
10import * as Effect from "effect/Effect";
11import { HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/http";
12import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
13
14import { installEnvironmentHttpTest } from "../test/environmentHttpTest";
15import { __setPrimaryHttpRunnerForTests, type PrimaryHttpEffectRunner } from "./lib/runtime";
16
17type TestWindow = {
18 location: URL;
19 history: {
20 state: unknown;
21 replaceState: (_data: unknown, _unused: string, url?: string) => void;
22 };
23 desktopBridge?: DesktopBridge;
24};
25
26const LOOPBACK_AUTH = {
27 policy: "loopback-browser",
28 bootstrapMethods: ["one-time-token"],
29 sessionMethods: ["browser-session-cookie"],
30 sessionCookieName: "t3_session",
31} as const;
32
33const DESKTOP_AUTH = {
34 policy: "desktop-managed-local",
35 bootstrapMethods: ["desktop-bootstrap"],
36 sessionMethods: ["browser-session-cookie"],
37 sessionCookieName: "t3_session",
38} as const;
39
40const SESSION_EXPIRES_AT = DateTime.makeUnsafe("2026-04-05T00:00:00.000Z");
41const unauthenticatedSession = (auth: AuthSessionState["auth"]): AuthSessionState => ({
42 authenticated: false,
43 auth,
44});
45
46const authenticatedSession = (auth: AuthSessionState["auth"]): AuthSessionState => ({
47 authenticated: true,
48 auth,
49 sessionMethod: "browser-session-cookie",
50 expiresAt: SESSION_EXPIRES_AT,
51});
52
53const browserSession = (scopes: AuthBrowserSessionResult["scopes"]): AuthBrowserSessionResult => ({
54 authenticated: true,
55 scopes,
56 sessionMethod: "browser-session-cookie",
57 expiresAt: SESSION_EXPIRES_AT,
58});
59
60function installTestBrowser(url: string) {
61 const testWindow: TestWindow = {
62 location: new URL(url),
63 history: {
64 state: null,
65 replaceState: (data, _unused, nextUrl) => {
66 testWindow.history.state = data;
67 if (nextUrl !== undefined) {
68 testWindow.location = new URL(nextUrl, testWindow.location.href);
69 }
70 },
71 },
72 };
73
74 vi.stubGlobal("window", testWindow);
75 vi.stubGlobal("document", { title: "T3 Code" });
76
77 return testWindow;
78}
79
80function installDesktopBootstrap() {
81 const testWindow = installTestBrowser("http://localhost/");
82 testWindow.desktopBridge = {
83 getLocalEnvironmentBootstraps: () => [
84 {
85 id: "primary",
86 label: "Local environment",
87 httpBaseUrl: "http://localhost:3773",
88 wsBaseUrl: "ws://localhost:3773",
89 bootstrapToken: "desktop-bootstrap-token",
90 },
91 ],
92 } as unknown as DesktopBridge;
93}
94
95function sequence<A>(...values: ReadonlyArray<A>) {
96 let index = 0;
97 return () => values[Math.min(index++, values.length - 1)]!;
98}
99
100function createSignal() {
101 let resolve = () => {};
102 const promise = new Promise<void>((resolvePromise) => {
103 resolve = resolvePromise;
104 });
105 return { promise, resolve };
106}
107
108let disposeHttpTest: (() => Promise<void>) | undefined;
109
110async function installAuthApi(input: {
111 readonly session?: () => AuthSessionState;
112 readonly browserSession?: (
113 credential: string,
114 ) => Effect.Effect<AuthBrowserSessionResult, EnvironmentAuthInvalidError>;
115 readonly pairingCredential?: (payload: AuthCreatePairingCredentialInput) => Effect.Effect<{
116 readonly id: string;
117 readonly credential: string;
118 readonly label?: string;
119 readonly expiresAt: DateTime.Utc;
120 }>;
121}) {
122 const testApi = await installEnvironmentHttpTest({
123 ...(input.session ? { session: () => Effect.succeed(input.session!()) } : {}),
124 ...(input.browserSession
125 ? { browserSession: (payload) => input.browserSession!(payload.credential) }
126 : {}),
127 ...(input.pairingCredential
128 ? { pairingCredential: (payload) => input.pairingCredential!(payload) }
129 : {}),
130 });
131 disposeHttpTest = testApi.dispose;
132 return testApi;
133}
134
135describe("resolveInitialServerAuthGateState", () => {
136 beforeEach(() => {
137 vi.restoreAllMocks();
138 vi.useRealTimers();
139 installTestBrowser("http://localhost/");
140 });
141
142 afterEach(async () => {
143 await disposeHttpTest?.();
144 disposeHttpTest = undefined;
145 const { __resetServerAuthBootstrapForTests } = await import("./environments/primary");
146 __resetServerAuthBootstrapForTests();
147 __setPrimaryHttpRunnerForTests();
148 vi.unstubAllEnvs();
149 vi.useRealTimers();
150 vi.restoreAllMocks();
151 });
152
153 it("reuses an in-flight silent bootstrap attempt", async () => {
154 const nextSession = sequence(
155 unauthenticatedSession(DESKTOP_AUTH),
156 authenticatedSession(DESKTOP_AUTH),
157 );
158 const testApi = await installAuthApi({
159 session: nextSession,
160 browserSession: () => Effect.succeed(browserSession(["orchestration:read", "access:write"])),
161 });
162
163 const testWindow = installTestBrowser("http://localhost/");
164 testWindow.desktopBridge = {
165 getLocalEnvironmentBootstraps: () => [
166 {
167 id: "primary",
168 label: "Windows",
169 httpBaseUrl: "http://localhost:3773",
170 wsBaseUrl: "ws://localhost:3773",
171 bootstrapToken: "desktop-bootstrap-token",
172 },
173 ],
174 } as unknown as DesktopBridge;
175
176 const { resolveInitialServerAuthGateState } = await import("./environments/primary");
177
178 await Promise.all([resolveInitialServerAuthGateState(), resolveInitialServerAuthGateState()]);
179
180 expect(testApi.calls.session).toBe(2);
181 expect(testApi.calls.browserSession).toEqual([{ credential: "desktop-bootstrap-token" }]);
182 });
183
184 it("uses https urls when the primary environment uses wss", async () => {
185 await installAuthApi({ session: () => unauthenticatedSession(LOOPBACK_AUTH) });
186 vi.stubEnv("VITE_HTTP_URL", "https://remote.example.com");
187 vi.stubEnv("VITE_WS_URL", "wss://remote.example.com");
188
189 const { resolveInitialServerAuthGateState, resolvePrimaryEnvironmentHttpUrl } =
190 await import("./environments/primary");
191
192 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
193 status: "requires-auth",
194 auth: LOOPBACK_AUTH,
195 });
196 expect(resolvePrimaryEnvironmentHttpUrl("/api/auth/session")).toBe(
197 "https://remote.example.com/api/auth/session",
198 );
199 });
200
201 it("uses the current origin as an auth proxy base for local dev environments", async () => {
202 await installAuthApi({ session: () => unauthenticatedSession(LOOPBACK_AUTH) });
203 installTestBrowser("http://localhost:5735/");
204
205 const { resolveInitialServerAuthGateState, resolvePrimaryEnvironmentHttpUrl } =
206 await import("./environments/primary");
207
208 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
209 status: "requires-auth",
210 auth: LOOPBACK_AUTH,
211 });
212 expect(resolvePrimaryEnvironmentHttpUrl("/api/auth/session")).toBe(
213 "http://localhost:5735/api/auth/session",
214 );
215 });
216
217 it("uses the vite proxy for desktop-managed loopback auth requests during local dev", async () => {
218 await installAuthApi({ session: () => unauthenticatedSession(DESKTOP_AUTH) });
219 vi.stubEnv("VITE_DEV_SERVER_URL", "http://127.0.0.1:5733");
220
221 const testWindow = installTestBrowser("http://127.0.0.1:5733/");
222 testWindow.desktopBridge = {
223 getLocalEnvironmentBootstraps: () => [
224 {
225 id: "primary",
226 label: "Windows",
227 httpBaseUrl: "http://127.0.0.1:3773",
228 wsBaseUrl: "ws://127.0.0.1:3773",
229 },
230 ],
231 } as unknown as DesktopBridge;
232
233 const { resolveInitialServerAuthGateState, resolvePrimaryEnvironmentHttpUrl } =
234 await import("./environments/primary");
235
236 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
237 status: "requires-auth",
238 auth: DESKTOP_AUTH,
239 });
240 expect(resolvePrimaryEnvironmentHttpUrl("/api/auth/session")).toBe(
241 "http://127.0.0.1:5733/api/auth/session",
242 );
243 });
244
245 it("returns a requires-auth state instead of throwing when no bootstrap credential exists", async () => {
246 await installAuthApi({ session: () => unauthenticatedSession(LOOPBACK_AUTH) });
247 const { resolveInitialServerAuthGateState } = await import("./environments/primary");
248
249 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
250 status: "requires-auth",
251 auth: LOOPBACK_AUTH,
252 });
253 });
254
255 it("retries transient auth session bootstrap failures after restart", async () => {
256 vi.useFakeTimers();
257 let attempts = 0;
258 const request = HttpClientRequest.get("http://localhost/api/auth/session");
259 const response = HttpClientResponse.fromWeb(
260 request,
261 new Response("Bad Gateway", { status: 502 }),
262 );
263 const runner: PrimaryHttpEffectRunner = async <A>() => {
264 attempts += 1;
265 if (attempts < 4) {
266 throw new HttpClientError.HttpClientError({
267 reason: new HttpClientError.StatusCodeError({ request, response }),
268 });
269 }
270 return unauthenticatedSession(LOOPBACK_AUTH) as A;
271 };
272 __setPrimaryHttpRunnerForTests(runner);
273
274 const { resolveInitialServerAuthGateState } = await import("./environments/primary");
275
276 const gateStatePromise = resolveInitialServerAuthGateState();
277 await vi.advanceTimersByTimeAsync(2_000);
278
279 await expect(gateStatePromise).resolves.toEqual({
280 status: "requires-auth",
281 auth: LOOPBACK_AUTH,
282 });
283 expect(attempts).toBe(4);
284 });
285
286 it("takes a pairing token from the location hash and strips it immediately", async () => {
287 const testWindow = installTestBrowser("http://localhost/#token=pairing-token");
288 const { takePairingTokenFromUrl } = await import("./environments/primary");
289
290 expect(takePairingTokenFromUrl()).toBe("pairing-token");
291 expect(testWindow.location.hash).toBe("");
292 expect(testWindow.location.searchParams.get("token")).toBeNull();
293 });
294
295 it("accepts query-string pairing tokens as a backward-compatible fallback", async () => {
296 const testWindow = installTestBrowser("http://localhost/?token=pairing-token");
297 const { takePairingTokenFromUrl } = await import("./environments/primary");
298
299 expect(takePairingTokenFromUrl()).toBe("pairing-token");
300 expect(testWindow.location.searchParams.get("token")).toBeNull();
301 });
302
303 it.each([
304 { suffix: "#token=replacement-token", cached: false },
305 { suffix: "#token=replacement-token", cached: true },
306 { suffix: "?token=replacement-token", cached: true },
307 { suffix: "/#token=replacement-token", cached: true },
308 ])(
309 "re-pairs an authenticated browser with $suffix when cached=$cached",
310 async ({ suffix, cached }) => {
311 let scopes: AuthBrowserSessionResult["scopes"] = ["orchestration:read"];
312 const testApi = await installAuthApi({
313 session: () => ({ ...authenticatedSession(LOOPBACK_AUTH), scopes }),
314 browserSession: () =>
315 Effect.sync(() => {
316 scopes = ["orchestration:read", "orchestration:operate"];
317 return browserSession(scopes);
318 }),
319 });
320 const testWindow = installTestBrowser("http://localhost/");
321 const {
322 resolveInitialServerAuthGateState,
323 submitServerAuthCredential,
324 takePairingTokenFromUrl,
325 } = await import("./environments/primary");
326 const { fetchSessionState } = await import("./environments/primary/auth");
327
328 if (cached) {
329 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
330 status: "authenticated",
331 });
332 }
333
334 testWindow.location = new URL(`http://localhost/pair${suffix}`);
335 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
336 status: "requires-auth",
337 auth: LOOPBACK_AUTH,
338 });
339 expect(testApi.calls.browserSession).toEqual([]);
340
341 const token = takePairingTokenFromUrl();
342 expect(token).toBe("replacement-token");
343 await submitServerAuthCredential(token!);
344
345 expect(testApi.calls.browserSession).toEqual([{ credential: "replacement-token" }]);
346 await expect(fetchSessionState()).resolves.toMatchObject({
347 authenticated: true,
348 scopes: ["orchestration:read", "orchestration:operate"],
349 });
350 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
351 status: "authenticated",
352 });
353 },
354 );
355
356 it.each([false, true])(
357 "keeps rejected replacement pairing open when cached=%s",
358 async (cached) => {
359 const existingSession = {
360 ...authenticatedSession(LOOPBACK_AUTH),
361 scopes: ["orchestration:read"] as const,
362 };
363 const exchangeStarted = createSignal();
364 const finishExchange = createSignal();
365 const testApi = await installAuthApi({
366 session: () => existingSession,
367 browserSession: () =>
368 Effect.gen(function* () {
369 exchangeStarted.resolve();
370 yield* Effect.promise(() => finishExchange.promise);
371 return yield* new EnvironmentAuthInvalidError({
372 code: "auth_invalid",
373 reason: "invalid_credential",
374 traceId: "trace-invalid-replacement",
375 });
376 }),
377 });
378 const testWindow = installTestBrowser("http://localhost/");
379 const {
380 resolveInitialServerAuthGateState,
381 submitServerAuthCredential,
382 takePairingTokenFromUrl,
383 } = await import("./environments/primary");
384 const { fetchSessionState } = await import("./environments/primary/auth");
385 if (cached) {
386 await resolveInitialServerAuthGateState();
387 }
388 testWindow.location = new URL("http://localhost/pair#token=invalid-replacement");
389 const history = createBrowserHistory({
390 window: {
391 get location() {
392 return testWindow.location;
393 },
394 history: testWindow.history,
395 addEventListener() {},
396 removeEventListener() {},
397 },
398 });
399 const gateLoads: Array<ReturnType<typeof resolveInitialServerAuthGateState>> = [];
400 const unsubscribe = history.subscribe(() => {
401 gateLoads.push(resolveInitialServerAuthGateState());
402 });
403 try {
404 const requiresAuth = { status: "requires-auth", auth: LOOPBACK_AUTH };
405 await expect(resolveInitialServerAuthGateState()).resolves.toEqual(requiresAuth);
406
407 // The pairing form strips the token before submitting. TanStack history
408 // reloads the auth gate synchronously when replaceState runs.
409 const token = takePairingTokenFromUrl();
410 expect(gateLoads).toHaveLength(1);
411 await expect(gateLoads[0]).resolves.toEqual(requiresAuth);
412 const rejected = expect(submitServerAuthCredential(token!)).rejects.toMatchObject({
413 _tag: "PrimaryEnvironmentPairingCredentialRejectedError",
414 message: "Invalid pairing token. Check the token and try again.",
415 });
416 await exchangeStarted.promise;
417 await expect(resolveInitialServerAuthGateState()).resolves.toEqual(requiresAuth);
418 finishExchange.resolve();
419 await rejected;
420 await expect(resolveInitialServerAuthGateState()).resolves.toEqual(requiresAuth);
421 expect(testApi.calls.browserSession).toEqual([{ credential: "invalid-replacement" }]);
422 await expect(fetchSessionState()).resolves.toEqual(existingSession);
423
424 testWindow.history.replaceState({}, "", "/");
425 await expect(gateLoads[1]).resolves.toEqual({ status: "authenticated" });
426 testWindow.history.replaceState({}, "", "/pair");
427 await expect(gateLoads[2]).resolves.toEqual({ status: "authenticated" });
428 } finally {
429 finishExchange.resolve();
430 unsubscribe();
431 history.destroy();
432 }
433 },
434 );
435
436 it("allows manual token submission after the initial auth check requires pairing", async () => {
437 const nextSession = sequence(
438 unauthenticatedSession(LOOPBACK_AUTH),
439 authenticatedSession(LOOPBACK_AUTH),
440 );
441 const testApi = await installAuthApi({
442 session: nextSession,
443 browserSession: () => Effect.succeed(browserSession(["orchestration:read"])),
444 });
445 const { resolveInitialServerAuthGateState, submitServerAuthCredential } =
446 await import("./environments/primary");
447
448 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
449 status: "requires-auth",
450 auth: LOOPBACK_AUTH,
451 });
452 await expect(submitServerAuthCredential("retry-token")).resolves.toBeUndefined();
453 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
454 status: "authenticated",
455 });
456 expect(testApi.calls.browserSession).toEqual([{ credential: "retry-token" }]);
457 expect(testApi.calls.session).toBe(2);
458 });
459
460 it("keeps manual token submission pending until the session is authenticated", async () => {
461 vi.useFakeTimers();
462 let authenticated = false;
463 let settled = false;
464 try {
465 const testApi = await installAuthApi({
466 session: () =>
467 authenticated
468 ? authenticatedSession(LOOPBACK_AUTH)
469 : unauthenticatedSession(LOOPBACK_AUTH),
470 browserSession: () => Effect.succeed(browserSession(["orchestration:read"])),
471 });
472 const { submitServerAuthCredential } = await import("./environments/primary");
473
474 const submission = submitServerAuthCredential("retry-token").finally(() => {
475 settled = true;
476 });
477 await vi.advanceTimersByTimeAsync(0);
478
479 expect(testApi.calls.browserSession).toEqual([{ credential: "retry-token" }]);
480 expect(testApi.calls.session).toBe(1);
481 expect(settled).toBe(false);
482
483 authenticated = true;
484 await vi.advanceTimersByTimeAsync(100);
485 await expect(submission).resolves.toBeUndefined();
486 expect(testApi.calls.session).toBe(2);
487 } finally {
488 vi.useRealTimers();
489 }
490 });
491
492 it("fails manual token submission when the session is not established", async () => {
493 vi.useFakeTimers();
494 try {
495 const testApi = await installAuthApi({
496 session: () => unauthenticatedSession(LOOPBACK_AUTH),
497 browserSession: () => Effect.succeed(browserSession(["orchestration:read"])),
498 });
499 const { PrimaryEnvironmentAuthSessionTimeoutError, submitServerAuthCredential } =
500 await import("./environments/primary/auth");
501
502 const submission = submitServerAuthCredential("retry-token");
503 const failure = submission.then(
504 () => null,
505 (error: unknown) => error,
506 );
507 await vi.advanceTimersByTimeAsync(2_000);
508
509 await expect(failure).resolves.toBeInstanceOf(PrimaryEnvironmentAuthSessionTimeoutError);
510 expect(testApi.calls.browserSession).toEqual([{ credential: "retry-token" }]);
511 expect(testApi.calls.session).toBeGreaterThan(1);
512 } finally {
513 vi.useRealTimers();
514 }
515 });
516
517 it("rejects a blank pairing token with a structured validation error", async () => {
518 const { PrimaryEnvironmentPairingCredentialRequiredError, submitServerAuthCredential } =
519 await import("./environments/primary/auth");
520
521 const error = await submitServerAuthCredential(" ").then(
522 () => null,
523 (failure: unknown) => failure,
524 );
525
526 expect(error).toBeInstanceOf(PrimaryEnvironmentPairingCredentialRequiredError);
527 expect(error).toMatchObject({
528 _tag: "PrimaryEnvironmentPairingCredentialRequiredError",
529 providedLength: 3,
530 message: "Enter a pairing token to continue.",
531 });
532 });
533
534 it("surfaces a friendly error message when an invalid pairing token is submitted", async () => {
535 const cause = new EnvironmentAuthInvalidError({
536 code: "auth_invalid",
537 reason: "invalid_credential",
538 traceId: "trace-invalid-credential",
539 });
540 const testApi = await installAuthApi({
541 browserSession: () => Effect.fail(cause),
542 });
543
544 const { isPrimaryEnvironmentPairingCredentialRejectedError, submitServerAuthCredential } =
545 await import("./environments/primary");
546
547 const error = await submitServerAuthCredential("bad-token").then(
548 () => null,
549 (failure: unknown) => failure,
550 );
551 expect(error).toMatchObject({
552 _tag: "PrimaryEnvironmentPairingCredentialRejectedError",
553 providedLength: 9,
554 message: "Invalid pairing token. Check the token and try again.",
555 });
556 expect(isPrimaryEnvironmentPairingCredentialRejectedError(error)).toBe(true);
557 if (!isPrimaryEnvironmentPairingCredentialRejectedError(error)) {
558 throw new Error("Expected a structured rejected pairing credential error.");
559 }
560 expect(error.cause).toMatchObject({
561 _tag: "EnvironmentAuthInvalidError",
562 code: "auth_invalid",
563 reason: "invalid_credential",
564 traceId: "trace-invalid-credential",
565 });
566 expect(testApi.calls.browserSession).toEqual([{ credential: "bad-token" }]);
567 });
568
569 it("derives primary request messages from structural request context", async () => {
570 const cause = new Error("private transport detail");
571 const { PrimaryEnvironmentRequestError } = await import("./environments/primary");
572 const error = PrimaryEnvironmentRequestError.fromCause({
573 operation: "list-pairing-links",
574 cause,
575 });
576
577 expect(error.status).toBe(500);
578 expect(error.cause).toBe(cause);
579 expect(error.message).toBe(
580 "Primary environment request failed during list-pairing-links (HTTP 500).",
581 );
582 expect(error.message).not.toContain(cause.message);
583 });
584
585 it("waits for the authenticated session to become observable after silent desktop bootstrap", async () => {
586 vi.useFakeTimers();
587 const nextSession = sequence(
588 unauthenticatedSession(DESKTOP_AUTH),
589 unauthenticatedSession(DESKTOP_AUTH),
590 authenticatedSession(DESKTOP_AUTH),
591 );
592 const testApi = await installAuthApi({
593 session: nextSession,
594 browserSession: () => Effect.succeed(browserSession(["orchestration:read", "access:write"])),
595 });
596
597 const testWindow = installTestBrowser("http://localhost/");
598 testWindow.desktopBridge = {
599 getLocalEnvironmentBootstraps: () => [
600 {
601 id: "primary",
602 label: "Windows",
603 httpBaseUrl: "http://localhost:3773",
604 wsBaseUrl: "ws://localhost:3773",
605 bootstrapToken: "desktop-bootstrap-token",
606 },
607 ],
608 } as unknown as DesktopBridge;
609
610 const { resolveInitialServerAuthGateState } = await import("./environments/primary");
611
612 const gateStatePromise = resolveInitialServerAuthGateState();
613 await vi.advanceTimersByTimeAsync(100);
614
615 await expect(gateStatePromise).resolves.toEqual({ status: "authenticated" });
616 expect(testApi.calls.session).toBe(3);
617 });
618
619 it("preserves the timeout message when a bootstrapped session never becomes observable", async () => {
620 vi.useFakeTimers();
621 const testApi = await installAuthApi({
622 session: () => unauthenticatedSession(DESKTOP_AUTH),
623 browserSession: () => Effect.succeed(browserSession(["orchestration:read", "access:write"])),
624 });
625
626 installDesktopBootstrap();
627
628 const { resolveInitialServerAuthGateState } = await import("./environments/primary");
629
630 const gateStatePromise = resolveInitialServerAuthGateState();
631 await vi.advanceTimersByTimeAsync(2_000);
632
633 await expect(gateStatePromise).resolves.toEqual({
634 status: "requires-auth",
635 auth: DESKTOP_AUTH,
636 errorMessage: "Timed out waiting for authenticated session after bootstrap.",
637 });
638 expect(testApi.calls.browserSession).toEqual([{ credential: "desktop-bootstrap-token" }]);
639 });
640
641 it("memoizes the authenticated gate state after the first successful read", async () => {
642 const testApi = await installAuthApi({
643 session: sequence(authenticatedSession(LOOPBACK_AUTH), unauthenticatedSession(LOOPBACK_AUTH)),
644 });
645 const { resolveInitialServerAuthGateState } = await import("./environments/primary");
646
647 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
648 status: "authenticated",
649 });
650 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
651 status: "authenticated",
652 });
653 expect(testApi.calls.session).toBe(1);
654 });
655
656 it("exchanges a URL token when the browser already has a session", async () => {
657 const testApi = await installAuthApi({
658 session: () => authenticatedSession(LOOPBACK_AUTH),
659 browserSession: () => Effect.succeed(browserSession(["orchestration:read", "access:write"])),
660 });
661 const testWindow = installTestBrowser("http://localhost/#token=reusable-token");
662 const { resolveInitialServerAuthGateState } = await import("./environments/primary");
663
664 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
665 status: "authenticated",
666 });
667
668 expect(testApi.calls.browserSession).toEqual([{ credential: "reusable-token" }]);
669 expect(testWindow.location.hash).toBe("");
670 });
671
672 it("exchanges a root URL token after caching an authenticated state", async () => {
673 const testApi = await installAuthApi({
674 session: () => authenticatedSession(LOOPBACK_AUTH),
675 browserSession: () => Effect.succeed(browserSession(["orchestration:read", "access:write"])),
676 });
677 const testWindow = installTestBrowser("http://localhost/");
678 const { resolveInitialServerAuthGateState } = await import("./environments/primary");
679
680 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
681 status: "authenticated",
682 });
683 testWindow.location = new URL("http://localhost/#token=reusable-token");
684
685 await Promise.all([resolveInitialServerAuthGateState(), resolveInitialServerAuthGateState()]);
686
687 expect(testApi.calls.browserSession).toEqual([{ credential: "reusable-token" }]);
688 expect(testApi.calls.session).toBe(3);
689 });
690
691 it("makes later callers wait for a URL token that arrives during bootstrap", async () => {
692 let releaseExchange!: () => void;
693 const exchangeRelease = new Promise<void>((resolve) => {
694 releaseExchange = resolve;
695 });
696 let markExchangeStarted!: () => void;
697 const exchangeStarted = new Promise<void>((resolve) => {
698 markExchangeStarted = resolve;
699 });
700 const nextSession = sequence(
701 authenticatedSession(LOOPBACK_AUTH),
702 authenticatedSession(LOOPBACK_AUTH),
703 );
704 const testApi = await installAuthApi({
705 session: nextSession,
706 browserSession: () => {
707 markExchangeStarted();
708 return Effect.promise(() => exchangeRelease).pipe(
709 Effect.andThen(
710 Effect.fail(
711 new EnvironmentAuthInvalidError({
712 code: "auth_invalid",
713 reason: "invalid_credential",
714 traceId: "trace-rejected-queued-credential",
715 }),
716 ),
717 ),
718 );
719 },
720 });
721 const testWindow = installTestBrowser("http://localhost/");
722 const { resolveInitialServerAuthGateState } = await import("./environments/primary");
723
724 const initialBootstrap = resolveInitialServerAuthGateState();
725 testWindow.location = new URL("http://localhost/#token=reusable-token");
726 const explicitPairing = resolveInitialServerAuthGateState();
727 const laterCaller = resolveInitialServerAuthGateState();
728 let laterCallerSettled = false;
729 void laterCaller.then(() => {
730 laterCallerSettled = true;
731 });
732
733 try {
734 await expect(initialBootstrap).resolves.toEqual({ status: "authenticated" });
735 await exchangeStarted;
736 expect(laterCallerSettled).toBe(false);
737 } finally {
738 releaseExchange();
739 }
740
741 const rejectedState = {
742 status: "requires-auth",
743 auth: LOOPBACK_AUTH,
744 errorMessage: "Invalid pairing token. Check the token and try again.",
745 } as const;
746 await expect(explicitPairing).resolves.toEqual(rejectedState);
747 await expect(laterCaller).resolves.toEqual(rejectedState);
748 expect(testApi.calls.browserSession).toEqual([{ credential: "reusable-token" }]);
749 expect(testApi.calls.session).toBe(2);
750 });
751
752 it("does not exchange a token during an ordinary authenticated load", async () => {
753 const testApi = await installAuthApi({
754 session: () => authenticatedSession(LOOPBACK_AUTH),
755 browserSession: () => Effect.succeed(browserSession(["orchestration:read"])),
756 });
757 const { resolveInitialServerAuthGateState } = await import("./environments/primary");
758
759 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
760 status: "authenticated",
761 });
762
763 expect(testApi.calls.browserSession).toEqual([]);
764 });
765
766 it("reports a rejected URL token without caching false success", async () => {
767 const cause = new EnvironmentAuthInvalidError({
768 code: "auth_invalid",
769 reason: "invalid_credential",
770 traceId: "trace-invalid-url-credential",
771 });
772 const nextSession = sequence(
773 authenticatedSession(LOOPBACK_AUTH),
774 unauthenticatedSession(LOOPBACK_AUTH),
775 );
776 const testApi = await installAuthApi({
777 session: nextSession,
778 browserSession: () => Effect.fail(cause),
779 });
780 installTestBrowser("http://localhost/#token=rejected-token");
781 const { resolveInitialServerAuthGateState } = await import("./environments/primary");
782
783 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
784 status: "requires-auth",
785 auth: LOOPBACK_AUTH,
786 errorMessage: "Invalid pairing token. Check the token and try again.",
787 });
788 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
789 status: "requires-auth",
790 auth: LOOPBACK_AUTH,
791 });
792 expect(testApi.calls.browserSession).toEqual([{ credential: "rejected-token" }]);
793 });
794
795 it("creates a pairing credential from the authenticated auth endpoint", async () => {
796 const testApi = await installAuthApi({
797 pairingCredential: (payload) =>
798 Effect.succeed({
799 id: "pairing-link-1",
800 credential: "pairing-token",
801 ...(payload.label === undefined ? {} : { label: payload.label }),
802 expiresAt: SESSION_EXPIRES_AT,
803 }),
804 });
805 const { createServerPairingCredential } = await import("./environments/primary");
806
807 const credential = await createServerPairingCredential({
808 label: "Julius iPhone",
809 scopes: ["orchestration:read"],
810 });
811 expect(credential).toMatchObject({
812 id: "pairing-link-1",
813 credential: "pairing-token",
814 label: "Julius iPhone",
815 });
816 expect(DateTime.formatIso(credential.expiresAt)).toBe("2026-04-05T00:00:00.000Z");
817 expect(testApi.calls.pairingCredential).toEqual([
818 { label: "Julius iPhone", scopes: ["orchestration:read"] },
819 ]);
820 });
821});