2026-03-26 20:46:15 +00:00
|
|
|
import { createHmac, timingSafeEqual } from 'crypto';
|
|
|
|
|
import { redirect, type Handle } from '@sveltejs/kit';
|
|
|
|
|
import { PRIVATE_JWT_SECRET } from '$env/static/private';
|
2026-03-25 21:09:38 +00:00
|
|
|
import { client } from './lib/apiClient.ts';
|
|
|
|
|
|
2026-03-26 20:46:15 +00:00
|
|
|
function verifyJwt(token: string, secret: string): boolean {
|
|
|
|
|
try {
|
|
|
|
|
const parts = token.split('.');
|
|
|
|
|
if (parts.length !== 3) return false;
|
|
|
|
|
const [header, payload, sig] = parts;
|
|
|
|
|
|
|
|
|
|
const expected = createHmac('sha256', secret)
|
|
|
|
|
.update(`${header}.${payload}`)
|
|
|
|
|
.digest('base64url');
|
|
|
|
|
|
|
|
|
|
const expectedBuf = Buffer.from(expected);
|
|
|
|
|
const sigBuf = Buffer.from(sig);
|
|
|
|
|
if (expectedBuf.length !== sigBuf.length) return false;
|
|
|
|
|
if (!timingSafeEqual(expectedBuf, sigBuf)) return false;
|
|
|
|
|
|
|
|
|
|
const claims = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
|
|
|
|
|
if (claims.exp && claims.exp < Date.now() / 1000) return false;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
} catch {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 21:09:38 +00:00
|
|
|
export const handle: Handle = async ({ event, resolve }) => {
|
|
|
|
|
event.locals.apiClient = client;
|
|
|
|
|
|
2026-03-26 20:46:15 +00:00
|
|
|
const rawToken = event.cookies.get('auth_token');
|
|
|
|
|
const isValid = rawToken ? verifyJwt(rawToken, PRIVATE_JWT_SECRET) : false;
|
|
|
|
|
event.locals.authToken = isValid ? rawToken! : null;
|
2026-03-25 21:09:38 +00:00
|
|
|
|
2026-03-26 20:46:15 +00:00
|
|
|
if (event.url.pathname.startsWith('/app') && !isValid) {
|
|
|
|
|
return redirect(307, '/login');
|
|
|
|
|
}
|
2026-03-25 21:09:38 +00:00
|
|
|
|
2026-03-26 20:46:15 +00:00
|
|
|
return resolve(event);
|
2026-03-25 21:09:38 +00:00
|
|
|
};
|