language-learning-app/frontend/src/hooks.server.ts

43 lines
1.3 KiB
TypeScript
Raw Normal View History

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
};