Simplify auth and search client, drop local JWT expiry tracking
Auth: - Remove jwt.ts — stop decoding JWT exp claims locally - Simplify OTP login: direct token extraction from verify response, remove CookieJar, BFS token extraction, session fallback, CF bypass - Storage: drop expires field, store token-only; clear on 401/403 - Single auth path: try stored token → macOS app → OTP fallback Search client: - Remove Cloudflare subprocess fallback (fetchViaBunRuntime) - Remove streamFromText, isCloudflareChallenge, BunFetchResult - Simplify SSE fetch to single fetch() call with abort signal - Let server validate tokens; clear cache on auth errors Render: - Extract shared utilities (asString, truncate) to render/util.ts - Simplify call.ts and result.ts to import from shared module Tests: - Remove jwt.test.ts (module deleted) - Add otp-flow.test.ts for email OTP authentication - Simplify login and client tests for reduced code paths Add debug scripts and plan documents.
This commit is contained in:
+1
-130
@@ -119,12 +119,11 @@ describe("auth/login", () => {
|
||||
expect(token).toBe(desktopToken);
|
||||
});
|
||||
|
||||
test("authenticate returns non-expired cached token without desktop or OTP calls", async () => {
|
||||
test("authenticate returns cached token without desktop or OTP calls", async () => {
|
||||
const cachedToken = createJwt(Date.now() + 2 * 60 * 60 * 1000);
|
||||
const loadTokenMock = mock(async () => ({
|
||||
type: "oauth",
|
||||
access: cachedToken,
|
||||
expires: Date.now() + 60 * 60 * 1000,
|
||||
}) satisfies StoredToken);
|
||||
const saveTokenMock = mock(async (_token: StoredToken) => undefined);
|
||||
const clearTokenMock = mock(async () => undefined);
|
||||
@@ -234,134 +233,6 @@ describe("auth/login", () => {
|
||||
|
||||
expect(clearTokenMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("authenticate accepts OTP token from session cookie when body has no token", async () => {
|
||||
process.env.PI_AUTH_NO_BORROW = "1";
|
||||
|
||||
const otpToken = createOpaqueToken();
|
||||
const loadTokenMock = mock(async () => null);
|
||||
const saveTokenMock = mock(async (_token: StoredToken) => undefined);
|
||||
const clearTokenMock = mock(async () => undefined);
|
||||
|
||||
mock.module("../../src/auth/storage.js", () => ({
|
||||
loadToken: loadTokenMock,
|
||||
saveToken: saveTokenMock,
|
||||
clearToken: clearTokenMock,
|
||||
}));
|
||||
|
||||
const fetchMock = mock(async (input: RequestInfo | URL) => {
|
||||
const url = String(input);
|
||||
|
||||
if (url.endsWith("/csrf")) {
|
||||
return new Response(JSON.stringify({ csrfToken: "csrf-token" }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
if (url.endsWith("/signin-email")) {
|
||||
return new Response(JSON.stringify({ ok: true }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
if (url.endsWith("/signin-otp")) {
|
||||
return new Response(JSON.stringify({ status: "ok" }), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"set-cookie": `__Secure-next-auth.session-token=${encodeURIComponent(otpToken)}; Path=/; HttpOnly`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return new Response("not found", { status: 404 });
|
||||
});
|
||||
|
||||
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
||||
|
||||
const { authenticate } = await importLoginModule();
|
||||
|
||||
const token = await authenticate({
|
||||
promptForEmail: async () => "user@example.com",
|
||||
promptForOtp: async () => "123456",
|
||||
});
|
||||
|
||||
expect(token).toBe(otpToken);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(3);
|
||||
expect(saveTokenMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("authenticate falls back to /session when OTP body has no token", async () => {
|
||||
process.env.PI_AUTH_NO_BORROW = "1";
|
||||
|
||||
const otpToken = createOpaqueToken();
|
||||
const loadTokenMock = mock(async () => null);
|
||||
const saveTokenMock = mock(async (_token: StoredToken) => undefined);
|
||||
const clearTokenMock = mock(async () => undefined);
|
||||
|
||||
mock.module("../../src/auth/storage.js", () => ({
|
||||
loadToken: loadTokenMock,
|
||||
saveToken: saveTokenMock,
|
||||
clearToken: clearTokenMock,
|
||||
}));
|
||||
|
||||
const fetchMock = mock(async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const url = String(input);
|
||||
|
||||
if (url.endsWith("/csrf")) {
|
||||
return new Response(JSON.stringify({ csrfToken: "csrf-token" }), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"set-cookie": "next-auth.csrf-token=csrf-cookie; Path=/",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (url.endsWith("/signin-email")) {
|
||||
return new Response(JSON.stringify({ ok: true }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
if (url.endsWith("/signin-otp")) {
|
||||
return new Response(JSON.stringify({ status: "ok" }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
if (url.endsWith("/session")) {
|
||||
const cookieHeader = new Headers(init?.headers).get("Cookie") ?? "";
|
||||
expect(cookieHeader).toContain("next-auth.csrf-token=csrf-cookie");
|
||||
|
||||
return new Response(JSON.stringify({ token: otpToken }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
return new Response("not found", { status: 404 });
|
||||
});
|
||||
|
||||
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
||||
|
||||
const { authenticate } = await importLoginModule();
|
||||
|
||||
const token = await authenticate({
|
||||
promptForEmail: async () => "user@example.com",
|
||||
promptForOtp: async () => "123456",
|
||||
});
|
||||
|
||||
expect(token).toBe(otpToken);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(4);
|
||||
expect(saveTokenMock).toHaveBeenCalledTimes(1);
|
||||
expect(clearTokenMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("authenticate throws NO_TOKEN when no cached token and no OTP email input", async () => {
|
||||
process.env.PI_AUTH_NO_BORROW = "1";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user