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/hooks.server.ts48 lines · handle L5–47
Outline 1 symbols
1import { redirect, type Handle } from '@sveltejs/kit';
2import { lqFetch } from '$lib/server/lqClient';
3import { AT_COOKIE } from '$lib/server/session';
4
5export const handle: Handle = async ({ event, resolve }) => {
6 event.locals.user = null;
7 event.locals.mustChangePassword = false;
8
9 if (event.cookies.get(AT_COOKIE)) {
10 const res = await lqFetch(event, '/api/v1/users/me');
11 if (res.status === 200) {
12 const user = await res.json();
13 event.locals.user = user;
14 event.locals.mustChangePassword = !!user.must_change_password;
15 } else if (res.status === 403) {
16 const body = await res.json().catch(() => ({}) as Record<string, unknown>);
17 const code = body?.error?.code ?? body?.detail;
18 if (code === 'password_change_required') event.locals.mustChangePassword = true;
19 }
20 }
21
22 const id = event.route.id ?? '';
23 const isApp = id.startsWith('/(app)');
24 const isAuth = id.startsWith('/(auth)');
25 const path = event.url.pathname;
26
27 // Forced first-run password rotation takes precedence.
28 if (event.locals.mustChangePassword && path !== '/change-password') {
29 throw redirect(303, '/change-password');
30 }
31 // Protect app routes.
32 if (isApp && !event.locals.user) {
33 throw redirect(303, `/login?next=${encodeURIComponent(path)}`);
34 }
35 // Authed users skip the auth screens — except /change-password, which they may
36 // visit voluntarily (e.g. from Settings) to rotate their password.
37 if (
38 isAuth &&
39 event.locals.user &&
40 !event.locals.mustChangePassword &&
41 path !== '/change-password'
42 ) {
43 throw redirect(303, '/');
44 }
45
46 return resolve(event);
47};
48