feat(auth): wire AuthProvider + RequireAuth into app shell
This commit is contained in:
@@ -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" });
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user