feat(auth): wire AuthProvider + RequireAuth into app shell

This commit is contained in:
Nora
2026-06-22 15:16:52 -06:00
parent 3d0c7766f0
commit cb87456575
5 changed files with 174 additions and 170 deletions
+3 -3
View File
@@ -23,7 +23,7 @@ describe("auth/api fetch wrapper", () => {
statusText: "Unauthorized",
headers: new Headers(),
json: async () => ({ error: "session_expired" }),
} as Response);
} as unknown as Response) as unknown as typeof globalThis.fetch;
// Stub window.location.href setter to capture navigation without
// actually navigating (jsdom does not implement location.href assignment).
@@ -54,7 +54,7 @@ describe("auth/api fetch wrapper", () => {
statusText: "Forbidden",
headers: new Headers(),
json: async () => ({ error: "forbidden" }),
} as Response);
} as unknown as Response) as unknown as typeof globalThis.fetch;
Object.defineProperty(window, "location", {
configurable: true,
get: () => ({
@@ -75,7 +75,7 @@ describe("auth/api fetch wrapper", () => {
status: 200,
headers: new Headers(),
json: async () => ({ hello: "world" }),
} as Response);
} as unknown as Response) as unknown as typeof globalThis.fetch;
const { authedFetch } = await import("./api");
const data = await authedFetch("/api/anything");
expect(data).toEqual({ hello: "world" });
+48
View File
@@ -88,6 +88,54 @@ export async function authedFetch<T = unknown>(
return (await res.json()) as T;
}
/**
* Like `authedFetch` but returns the response body as text instead of
* parsed JSON. Used by `serializeClaim837` (the SP8 endpoint returns
* `text/x12`, not JSON). Same 401-redirect + error-shape behavior as
* the JSON variant.
*/
export async function authedFetchText(path: string, init?: RequestInit): Promise<string> {
const res = await fetch(joinUrl(path), {
credentials: "include",
...init,
});
if (res.status === 401 && !path.startsWith("/api/auth/")) {
redirectToLogin();
throw new ApiError(401, "session_expired");
}
if (!res.ok) {
let body: any = null;
try {
body = await res.json();
} catch {
/* no body */
}
throw new ApiError(
res.status,
body?.error ?? "error",
body?.detail ?? res.statusText
);
}
return res.text();
}
/**
* Like `authedFetch` but returns the raw `Response` so the caller can
* stream the body (NDJSON). Used by `parse837` / `parse835`. 401 still
* redirects; the caller is responsible for reading the body.
*/
export async function authedFetchResponse(path: string, init?: RequestInit): Promise<Response> {
const res = await fetch(joinUrl(path), {
credentials: "include",
...init,
});
if (res.status === 401 && !path.startsWith("/api/auth/")) {
redirectToLogin();
throw new ApiError(401, "session_expired");
}
return res;
}
// ---------------------------------------------------------------------------
// Auth-specific endpoints. `login` and `logout` use raw `fetch` because
// they bypass the 401-redirect behavior on purpose — a 401 from