File size: 2,333 Bytes
4bb4f90
857fbc2
b11153a
4bb4f90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
857fbc2
 
 
 
 
 
 
 
4bb4f90
 
 
6d928e9
b11153a
 
6d928e9
 
4bb4f90
 
 
6d928e9
4bb4f90
6d928e9
4bb4f90
 
 
 
 
 
e830ad4
 
4bb4f90
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import { dev } from '$app/environment';
import { decodePathname } from '$lib/server/auth/decode-path';
import { SAFE_NEXT } from '$lib/server/auth/safe-next';
import { SESSION_COOKIE, verifySessionToken } from '$lib/server/auth/session';
import { getApp } from '$lib/server/app';
import { error, redirect, type Handle } from '@sveltejs/kit';

const PUBLIC_PATHS = new Set(['/login']);
const ALLOWED_WHILE_PASSWORD_CHANGE = new Set(['/me/password', '/logout']);

export const handle: Handle = async ({ event, resolve }) => {
	const app = await getApp();
	event.locals.app = app;
	event.locals.user = null;

	const token = event.cookies.get(SESSION_COOKIE);
	if (token) {
		const payload = verifySessionToken(token, app.config.sessionSecret);
		const user = payload ? app.users.byId(payload.uid) : undefined;
		if (payload && user && user.active && user.sessionVersion === payload.sv) {
			event.locals.user = user;
		} else {
			event.cookies.delete(SESSION_COOKIE, { path: '/' });
		}
	}

	// SvelteKit matches routes on the decoded pathname, so every check below must use it too:
	// gating on the raw pathname lets `/%61dmin` slip past the admin check and still hit /admin.
	let path: string;
	try {
		path = decodePathname(event.url.pathname);
	} catch {
		error(400, 'Malformed URL');
	}
	const user = event.locals.user;

	if (!user) {
		if (!PUBLIC_PATHS.has(path)) {
			const fullPath = event.url.pathname + event.url.search;
			const next = SAFE_NEXT.test(path) ? `?next=${encodeURIComponent(fullPath)}` : '';
			redirect(303, `/login${next}`);
		}
	} else {
		if (path === '/login') redirect(303, '/');
		if (user.mustChangePassword && !ALLOWED_WHILE_PASSWORD_CHANGE.has(path)) redirect(303, '/me/password');
		if ((path === '/admin' || path.startsWith('/admin/')) && user.role !== 'admin') error(403, 'Admins only');
		if (event.request.method !== 'GET' && event.request.method !== 'HEAD' && path !== '/logout') {
			app.syncReadOnly();
			if (!app.guard.canWrite)
				error(503, 'Read-only: another instance of the app is currently active. Try again in a couple of minutes.');
		}
	}

	const response = await resolve(event);
	response.headers.set('X-Content-Type-Options', 'nosniff');
	response.headers.set('Referrer-Policy', 'same-origin');
	if (!dev) response.headers.set('X-Frame-Options', 'DENY');
	return response;
};