import type { Handle } from '@sveltejs/kit'; import { createPbClient } from '$lib/server/pocketbase'; import { SESSION_COOKIE, setSessionCookie, clearSessionCookie, PLATFORM_SESSION_COOKIE, clearPlatformSession } from '$lib/server/session'; import type { SessionUser } from '$lib/server/types'; import { handleOf } from '@shared/slugify'; import { migrateOnBoot } from '$lib/server/migrate-boot'; // Run the PB schema migration once at server boot (idempotent). void migrateOnBoot(); export const handle: Handle = async ({ event, resolve }) => { event.locals.user = null; event.locals.pbToken = null; event.locals.platformAdmin = false; // Platform-admin routes authenticate via the superuser JWT in // platform_session, verified against PB (authRefresh) — the cookie value // is a real signed token, so forging it gains nothing. if (event.url.pathname.startsWith('/admin')) { const suToken = event.cookies.get(PLATFORM_SESSION_COOKIE); if (suToken) { try { await createPbClient(suToken).collection('_superusers').authRefresh(); event.locals.platformAdmin = true; } catch { // Expired/revoked/forged token — drop it and treat as logged out. clearPlatformSession(event.cookies); } } return resolve(event); } // Fam-user session: pb_token JWT → authRefresh → locals.user. const token = event.cookies.get(SESSION_COOKIE); if (token) { const pb = createPbClient(token); try { // authRefresh() does two jobs in one call: // 1. Verifies the token (PB JWTs can't be checked offline — the // signing secret is per-record and never leaves PB), so this // round trip IS the verification step. // 2. Returns the current record — the only way to get // name/role/famId, since PB doesn't embed custom fields in the // token itself. const { record, token: freshToken } = await pb.collection('users').authRefresh(); event.locals.user = { id: record.id, name: record.name || record.username || '', username: handleOf(record.username || ''), role: record.role || 'parent', famId: record.famId, color: record.color || '', pattern: record.pattern || '', themeSize: record.themeSize || '', themeOpacity: record.themeOpacity || '' } satisfies SessionUser; event.locals.pbToken = freshToken; if (freshToken !== token) { setSessionCookie(event.cookies, freshToken); } } catch { // Expired, malformed, or revoked — drop it and treat as logged out. clearSessionCookie(event.cookies); } } return resolve(event); };