76 lines
2.5 KiB
TypeScript
76 lines
2.5 KiB
TypeScript
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);
|
|
};
|