All files / src/routes/auth/callback +server.ts

100% Statements 12/12
100% Branches 13/13
100% Functions 1/1
100% Lines 12/12

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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      1x 9x 9x       9x   9x 1x       1x           8x   8x 2x       2x           6x          
import type { RequestHandler } from './$types';
import { CALLBACK_ERROR } from '$lib/auth/errors';
 
export const GET: RequestHandler = async ({ url, locals: { supabase } }) => {
	const code = url.searchParams.get('code');
	const next = url.searchParams.get('next') ?? '/dashboard';
 
	// Validate next to prevent open redirect — only allow relative paths
	const safeNext =
		next.startsWith('/') && !next.startsWith('//') && !next.includes('\\') ? next : '/dashboard';
 
	if (!code) {
		console.error('Auth callback: no code parameter', {
			url: url.pathname,
			params: Object.fromEntries(url.searchParams),
		});
		return new Response(null, {
			status: 303,
			headers: { location: `/login?error=${CALLBACK_ERROR.MISSING_CODE}` },
		});
	}
 
	const { data, error } = await supabase.auth.exchangeCodeForSession(code);
 
	if (error || !data.session) {
		console.error('Auth callback: code exchange failed', {
			error: error?.message,
			code: code.substring(0, 8) + '…',
		});
		return new Response(null, {
			status: 303,
			headers: { location: `/login?error=${CALLBACK_ERROR.LINK_EXPIRED}` },
		});
	}
 
	return new Response(null, {
		status: 303,
		headers: { location: safeNext },
	});
};