The Atlas Donna's documentation, bound to its code
147 documents

The BFF trust boundary

How auth actually works, prose bound to the server code: the global guard, the httpOnly cookie session, and the authed client that refreshes on 401.

src/lib/server/lqClient.ts61 lines · lqFetch L20–47
Outline 3 symbols
1import type { RequestEvent } from '@sveltejs/kit';
2import { LQ_API } from './env';
3import { AT_COOKIE, RT_COOKIE, setSessionCookies, clearSessionCookies } from './session';
4
5async function raw(path: string, init: RequestInit, token?: string): Promise<Response> {
6 const headers = new Headers(init.headers);
7 if (token) headers.set('authorization', `Bearer ${token}`);
8 // Default to JSON for non-empty bodies, but never override FormData — Node's
9 // fetch needs to set its own multipart/form-data boundary, and an explicit
10 // application/json header would clobber it (the backend then rejects the
11 // multipart body with 422 "Field required: body.file"). First seen when
12 // P4-3a's uploadFile action started forwarding multipart through lqFetch.
13 if (init.body && !(init.body instanceof FormData) && !headers.has('content-type')) {
14 headers.set('content-type', 'application/json');
15 }
16 return fetch(`${LQ_API()}${path}`, { ...init, headers });
17}
18
19/** Authed fetch through the BFF: attaches Bearer, refreshes once on 401, retries. */
20export async function lqFetch(
21 event: RequestEvent,
22 path: string,
23 init: RequestInit = {}
24): Promise<Response> {
25 const at = event.cookies.get(AT_COOKIE);
26 const res = await raw(path, init, at);
27 if (res.status !== 401) return res;
28
29 const rt = event.cookies.get(RT_COOKIE);
30 if (!rt) return res;
31
32 const refreshed = await raw('/api/v1/auth/refresh', {
33 method: 'POST',
34 body: JSON.stringify({ refresh_token: rt })
35 });
36 if (!refreshed.ok) {
37 clearSessionCookies(event);
38 return res;
39 }
40 const tok = (await refreshed.json()) as {
41 access_token: string;
42 refresh_token: string;
43 expires_in: number;
44 };
45 setSessionCookies(event, tok.access_token, tok.refresh_token, tok.expires_in);
46 return raw(path, init, tok.access_token);
47}
48
49/**
50 * Streaming pass-through for SSE (consumed in a later phase). Single attempt with
51 * the current access token; refresh is handled by the page `load` that precedes
52 * the stream request.
53 */
54export async function lqStream(
55 event: RequestEvent,
56 path: string,
57 init: RequestInit = {}
58): Promise<Response> {
59 return raw(path, init, event.cookies.get(AT_COOKIE));
60}
61