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 { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
12
13import { installEnvironmentHttpTest } from "../test/environmentHttpTest";
14import { __setPrimaryHttpRunnerForTests } from "./lib/runtime";
15
16type TestWindow = {
17 location: URL;
18 history: {
19 state: unknown;
20 replaceState: (_data: unknown, _unused: string, url?: string) => void;
21 };
22 desktopBridge?: DesktopBridge;
23};
24
25const LOOPBACK_AUTH = {
26 policy: "loopback-browser",
27 bootstrapMethods: ["one-time-token"],
28 sessionMethods: ["browser-session-cookie"],
29 sessionCookieName: "t3_session",
30} as const;
31
32const DESKTOP_AUTH = {
33 policy: "desktop-managed-local",
34 bootstrapMethods: ["desktop-bootstrap"],
35 sessionMethods: ["browser-session-cookie"],
36 sessionCookieName: "t3_session",
37} as const;
38
39const SESSION_EXPIRES_AT = DateTime.makeUnsafe("2026-04-05T00:00:00.000Z");
40const unauthenticatedSession = (auth: AuthSessionState["auth"]): AuthSessionState => ({
41 authenticated: false,
42 auth,
43});
44
45const authenticatedSession = (auth: AuthSessionState["auth"]): AuthSessionState => ({
46 authenticated: true,
47 auth,
48 sessionMethod: "browser-session-cookie",
49 expiresAt: SESSION_EXPIRES_AT,
50});
51
52const browserSession = (scopes: AuthBrowserSessionResult["scopes"]): AuthBrowserSessionResult => ({
53 authenticated: true,
54 scopes,
55 sessionMethod: "browser-session-cookie",
56 expiresAt: SESSION_EXPIRES_AT,
57});
58
59function installTestBrowser(url: string) {
60 const testWindow: TestWindow = {
61 location: new URL(url),
62 history: {
63 state: null,
64 replaceState: (data, _unused, nextUrl) => {
65 testWindow.history.state = data;
66 if (nextUrl !== undefined) {
67 testWindow.location = new URL(nextUrl, testWindow.location.href);
68 }
69 },
70 },
71 };
72
73 vi.stubGlobal("window", testWindow);
74 vi.stubGlobal("document", { title: "T3 Code" });
75
76 return testWindow;
77}
78
79function sequence<A>(...values: ReadonlyArray<A>) {
80 let index = 0;
81 return () => values[Math.min(index++, values.length - 1)]!;
82}
83
84function createSignal() {
85 let resolve = () => {};
86 const promise = new Promise<void>((resolvePromise) => {
87 resolve = resolvePromise;
88 });
89 return { promise, resolve };
90}
91
92let disposeHttpTest: (() => Promise<void>) | undefined;
93
94async function installAuthApi(input: {
95 readonly session?: () => AuthSessionState;
96 readonly browserSession?: (
97 credential: string,
98 ) => Effect.Effect<AuthBrowserSessionResult, EnvironmentAuthInvalidError>;
99 readonly pairingCredential?: (payload: AuthCreatePairingCredentialInput) => Effect.Effect<{
100 readonly id: string;
101 readonly credential: string;
102 readonly label?: string;
103 readonly expiresAt: DateTime.Utc;
104 }>;
105}) {
106 const testApi = await installEnvironmentHttpTest({
107 ...(input.session ? { session: () => Effect.succeed(input.session!()) } : {}),
108 ...(input.browserSession
109 ? { browserSession: (payload) => input.browserSession!(payload.credential) }
110 : {}),
111 ...(input.pairingCredential
112 ? { pairingCredential: (payload) => input.pairingCredential!(payload) }
113 : {}),
114 });
115 disposeHttpTest = testApi.dispose;
116 return testApi;
117}
118
119describe("resolveInitialServerAuthGateState", () => {
120 beforeEach(() => {
121 vi.restoreAllMocks();
122 vi.useRealTimers();
123 installTestBrowser("http://localhost/");
124 });
125
126 afterEach(async () => {
127 await disposeHttpTest?.();
128 disposeHttpTest = undefined;
129 const { __resetServerAuthBootstrapForTests } = await import("./environments/primary");
130 __resetServerAuthBootstrapForTests();
131 __setPrimaryHttpRunnerForTests();
132 vi.unstubAllEnvs();
133 vi.useRealTimers();
134 vi.restoreAllMocks();
135 });
136
137 it("reuses an in-flight silent bootstrap attempt", async () => {
138 const nextSession = sequence(
139 unauthenticatedSession(DESKTOP_AUTH),
140 authenticatedSession(DESKTOP_AUTH),
141 );
142 const testApi = await installAuthApi({
143 session: nextSession,
144 browserSession: () => Effect.succeed(browserSession(["orchestration:read", "access:write"])),
145 });
146
147 const testWindow = installTestBrowser("http://localhost/");
148 testWindow.desktopBridge = {
149 getLocalEnvironmentBootstraps: () => [
150 {
151 id: "primary",
152 label: "Windows",
153 httpBaseUrl: "http://localhost:3773",
154 wsBaseUrl: "ws://localhost:3773",
155 bootstrapToken: "desktop-bootstrap-token",
156 },
157 ],
158 } as unknown as DesktopBridge;
159
160 const { resolveInitialServerAuthGateState } = await import("./environments/primary");
161
162 await Promise.all([resolveInitialServerAuthGateState(), resolveInitialServerAuthGateState()]);
163
164 expect(testApi.calls.session).toBe(2);
165 expect(testApi.calls.browserSession).toEqual([{ credential: "desktop-bootstrap-token" }]);
166 });
167
168 it("takes a pairing token from the location hash and strips it immediately", async () => {
169 const testWindow = installTestBrowser("http://localhost/#token=pairing-token");
170 const { takePairingTokenFromUrl } = await import("./environments/primary");
171
172 expect(takePairingTokenFromUrl()).toBe("pairing-token");
173 expect(testWindow.location.hash).toBe("");
174 expect(testWindow.location.searchParams.get("token")).toBeNull();
175 });
176
177 it.each([
178 { suffix: "#token=replacement-token", cached: false },
179 { suffix: "#token=replacement-token", cached: true },
180 { suffix: "?token=replacement-token", cached: true },
181 { suffix: "/#token=replacement-token", cached: true },
182 ])(
183 "re-pairs an authenticated browser with $suffix when cached=$cached",
184 async ({ suffix, cached }) => {
185 let scopes: AuthBrowserSessionResult["scopes"] = ["orchestration:read"];
186 const testApi = await installAuthApi({
187 session: () => ({ ...authenticatedSession(LOOPBACK_AUTH), scopes }),
188 browserSession: () =>
189 Effect.sync(() => {
190 scopes = ["orchestration:read", "orchestration:operate"];
191 return browserSession(scopes);
192 }),
193 });
194 const testWindow = installTestBrowser("http://localhost/");
195 const {
196 resolveInitialServerAuthGateState,
197 submitServerAuthCredential,
198 takePairingTokenFromUrl,
199 } = await import("./environments/primary");
200 const { fetchSessionState } = await import("./environments/primary/auth");
201
202 if (cached) {
203 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
204 status: "authenticated",
205 });
206 }
207
208 testWindow.location = new URL(`http://localhost/pair${suffix}`);
209 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
210 status: "requires-auth",
211 auth: LOOPBACK_AUTH,
212 });
213 expect(testApi.calls.browserSession).toEqual([]);
214
215 const token = takePairingTokenFromUrl();
216 expect(token).toBe("replacement-token");
217 await submitServerAuthCredential(token!);
218
219 expect(testApi.calls.browserSession).toEqual([{ credential: "replacement-token" }]);
220 await expect(fetchSessionState()).resolves.toMatchObject({
221 authenticated: true,
222 scopes: ["orchestration:read", "orchestration:operate"],
223 });
224 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
225 status: "authenticated",
226 });
227 },
228 );
229
230 it.each([false, true])(
231 "keeps rejected replacement pairing open when cached=%s",
232 async (cached) => {
233 const existingSession = {
234 ...authenticatedSession(LOOPBACK_AUTH),
235 scopes: ["orchestration:read"] as const,
236 };
237 const exchangeStarted = createSignal();
238 const finishExchange = createSignal();
239 const testApi = await installAuthApi({
240 session: () => existingSession,
241 browserSession: () =>
242 Effect.gen(function* () {
243 exchangeStarted.resolve();
244 yield* Effect.promise(() => finishExchange.promise);
245 return yield* new EnvironmentAuthInvalidError({
246 code: "auth_invalid",
247 reason: "invalid_credential",
248 traceId: "trace-invalid-replacement",
249 });
250 }),
251 });
252 const testWindow = installTestBrowser("http://localhost/");
253 const {
254 resolveInitialServerAuthGateState,
255 submitServerAuthCredential,
256 takePairingTokenFromUrl,
257 } = await import("./environments/primary");
258 const { fetchSessionState } = await import("./environments/primary/auth");
259 if (cached) {
260 await resolveInitialServerAuthGateState();
261 }
262 testWindow.location = new URL("http://localhost/pair#token=invalid-replacement");
263 const history = createBrowserHistory({
264 window: {
265 get location() {
266 return testWindow.location;
267 },
268 history: testWindow.history,
269 addEventListener() {},
270 removeEventListener() {},
271 },
272 });
273 const gateLoads: Array<ReturnType<typeof resolveInitialServerAuthGateState>> = [];
274 const unsubscribe = history.subscribe(() => {
275 gateLoads.push(resolveInitialServerAuthGateState());
276 });
277 try {
278 const requiresAuth = { status: "requires-auth", auth: LOOPBACK_AUTH };
279 await expect(resolveInitialServerAuthGateState()).resolves.toEqual(requiresAuth);
280
281 // Token removal synchronously reloads the TanStack auth gate via replaceState.
282 const token = takePairingTokenFromUrl();
283 expect(gateLoads).toHaveLength(1);
284 await expect(gateLoads[0]).resolves.toEqual(requiresAuth);
285 const rejected = expect(submitServerAuthCredential(token!)).rejects.toMatchObject({
286 _tag: "PrimaryEnvironmentPairingCredentialRejectedError",
287 message: "Invalid pairing token. Check the token and try again.",
288 });
289 await exchangeStarted.promise;
290 await expect(resolveInitialServerAuthGateState()).resolves.toEqual(requiresAuth);
291 finishExchange.resolve();
292 await rejected;
293 await expect(resolveInitialServerAuthGateState()).resolves.toEqual(requiresAuth);
294 expect(testApi.calls.browserSession).toEqual([{ credential: "invalid-replacement" }]);
295 await expect(fetchSessionState()).resolves.toEqual(existingSession);
296
297 testWindow.history.replaceState({}, "", "/");
298 await expect(gateLoads[1]).resolves.toEqual({ status: "authenticated" });
299 testWindow.history.replaceState({}, "", "/pair");
300 await expect(gateLoads[2]).resolves.toEqual({ status: "authenticated" });
301 } finally {
302 finishExchange.resolve();
303 unsubscribe();
304 history.destroy();
305 }
306 },
307 );
308
309 it("makes later callers wait for a URL token that arrives during bootstrap", async () => {
310 let releaseExchange!: () => void;
311 const exchangeRelease = new Promise<void>((resolve) => {
312 releaseExchange = resolve;
313 });
314 let markExchangeStarted!: () => void;
315 const exchangeStarted = new Promise<void>((resolve) => {
316 markExchangeStarted = resolve;
317 });
318 const nextSession = sequence(
319 authenticatedSession(LOOPBACK_AUTH),
320 authenticatedSession(LOOPBACK_AUTH),
321 );
322 const testApi = await installAuthApi({
323 session: nextSession,
324 browserSession: () => {
325 markExchangeStarted();
326 return Effect.promise(() => exchangeRelease).pipe(
327 Effect.andThen(
328 Effect.fail(
329 new EnvironmentAuthInvalidError({
330 code: "auth_invalid",
331 reason: "invalid_credential",
332 traceId: "trace-rejected-queued-credential",
333 }),
334 ),
335 ),
336 );
337 },
338 });
339 const testWindow = installTestBrowser("http://localhost/");
340 const { resolveInitialServerAuthGateState } = await import("./environments/primary");
341
342 const initialBootstrap = resolveInitialServerAuthGateState();
343 testWindow.location = new URL("http://localhost/#token=reusable-token");
344 const explicitPairing = resolveInitialServerAuthGateState();
345 const laterCaller = resolveInitialServerAuthGateState();
346 let laterCallerSettled = false;
347 void laterCaller.then(() => {
348 laterCallerSettled = true;
349 });
350
351 try {
352 await expect(initialBootstrap).resolves.toEqual({ status: "authenticated" });
353 await exchangeStarted;
354 expect(laterCallerSettled).toBe(false);
355 } finally {
356 releaseExchange();
357 }
358
359 const rejectedState = {
360 status: "requires-auth",
361 auth: LOOPBACK_AUTH,
362 errorMessage: "Invalid pairing token. Check the token and try again.",
363 } as const;
364 await expect(explicitPairing).resolves.toEqual(rejectedState);
365 await expect(laterCaller).resolves.toEqual(rejectedState);
366 expect(testApi.calls.browserSession).toEqual([{ credential: "reusable-token" }]);
367 expect(testApi.calls.session).toBe(2);
368 });
369
370 it("reports a rejected URL token without caching false success", async () => {
371 const cause = new EnvironmentAuthInvalidError({
372 code: "auth_invalid",
373 reason: "invalid_credential",
374 traceId: "trace-invalid-url-credential",
375 });
376 const nextSession = sequence(
377 authenticatedSession(LOOPBACK_AUTH),
378 unauthenticatedSession(LOOPBACK_AUTH),
379 );
380 const testApi = await installAuthApi({
381 session: nextSession,
382 browserSession: () => Effect.fail(cause),
383 });
384 installTestBrowser("http://localhost/#token=rejected-token");
385 const { resolveInitialServerAuthGateState } = await import("./environments/primary");
386
387 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
388 status: "requires-auth",
389 auth: LOOPBACK_AUTH,
390 errorMessage: "Invalid pairing token. Check the token and try again.",
391 });
392 await expect(resolveInitialServerAuthGateState()).resolves.toEqual({
393 status: "requires-auth",
394 auth: LOOPBACK_AUTH,
395 });
396 expect(testApi.calls.browserSession).toEqual([{ credential: "rejected-token" }]);
397 });
398});