supakit
Version:
A Supabase auth helper for SvelteKit.
183 lines (182 loc) • 8.95 kB
JavaScript
import { getSupabaseServerClientOptions } from '../config/index.js';
import { json } from "@sveltejs/kit";
import { createClient } from "@supabase/supabase-js";
import { csrfCheck, getCookieOptions, isAuthToken, stringToBoolean, testRegEx } from '../utils.js';
import { base } from '$app/paths';
import { env } from '$env/dynamic/public';
import { CookieStorage } from "./storage.js";
export const endpoints = (async ({ event, resolve }) => {
const { url, request, cookies } = event;
const { cookie_options } = getSupabaseServerClientOptions();
const { expire_cookie_options, session_cookie_options, remember_me_cookie_options } = getCookieOptions('all', cookie_options);
const supabase = createClient(env.PUBLIC_SUPABASE_URL || '', env.PUBLIC_SUPABASE_ANON_KEY || '', {
auth: {
autoRefreshToken: false,
detectSessionInUrl: false,
storage: new CookieStorage({ cookies, cookie_options }),
flowType: 'pkce',
...(cookie_options?.name ? { storageKey: cookie_options.name } : {})
}
});
const setCookie = (response, cookie, options = cookie_options) => {
response.headers.append('set-cookie', cookies.serialize(cookie.name, cookie.value, options));
};
const setReturnCookies = (response, cookies, session) => {
const existing_cookies = cookies.getAll();
for (const cookie of existing_cookies) {
if (testRegEx(cookie.name, 'code_verifier') || testRegEx(cookie.name, 'csrf')) {
/* expire any existing csrf or code-verifier cookies */
/**
* exchangeCodeForSession() won't expire code-verifier cookies correctly because
* of the custom response and nature of the cookies function.
*/
setCookie(response, { name: cookie.name, value: '' }, expire_cookie_options);
}
else if (testRegEx(cookie.name, 'remember_me')) {
/* add remember me cookie */
setCookie(response, cookie, remember_me_cookie_options);
}
else {
/* add all other cookies */
setCookie(response, cookie);
}
}
if (session) {
/* set provider cookies, if exist */
const provider_token = session?.provider_token ?? null;
const provider_refresh_token = session?.provider_refresh_token ?? null;
if (provider_token !== null || provider_refresh_token !== null) {
const remember_me_cookie = cookies.get('supakit-rememberme') ?? 'true';
const remember_me = stringToBoolean(remember_me_cookie);
if (provider_token)
setCookie(response, { name: 'sb-provider-token', value: JSON.stringify(provider_token) }, remember_me ? cookie_options : session_cookie_options);
if (provider_refresh_token)
setCookie(response, { name: 'sb-provider-refresh-token', value: JSON.stringify(provider_refresh_token) }, remember_me ? cookie_options : session_cookie_options);
}
}
};
/* Handle request to Supakit's auth code callback route */
if (url.pathname === `${base}/supakit/callback` && request.method === 'GET') {
const code = url.searchParams.get('code');
/* define post-auth redirect */
const next = url.searchParams.get('next') ?? '/';
/* setup redirect, with cookies */
const response = new Response(null, {
status: 303,
headers: {
Location: `${url.origin}${base}${next}`
}
});
if (code) {
const { data: { session }, error } = await supabase.auth.exchangeCodeForSession(code);
if (error) {
console.error(error);
throw error;
}
setReturnCookies(response, cookies, session);
}
return response;
}
/* Handle request to Supakit's auth token confirm route */
if (url.pathname === `${base}/supakit/confirm` && request.method === 'GET') {
const token_hash = url.searchParams.get('token_hash') ?? '';
const type = (url.searchParams.get('type') ?? 'email');
/* define post-auth redirect */
const next = url.searchParams.get('next') ?? '/';
if (token_hash && type) {
const { error } = await supabase.auth.verifyOtp({ token_hash, type });
if (error) {
console.error(error);
throw error;
}
}
/* setup redirect, with cookies */
const response = new Response(null, {
status: 303,
headers: {
Location: `${url.origin}${base}${next}`
}
});
setReturnCookies(response, cookies);
return response;
}
/* Handle request to Supakit's CSRF route */
if (url.pathname === `${base}/supakit/csrf`) {
const forbidden = csrfCheck(event);
if (forbidden)
return forbidden;
if (request.method === 'POST') {
const data = request.body ? await request.json() : {};
if (!data.token && !data.name)
return new Response('Invalid body.', { status: 400 });
const token = data.token;
const cookie_name = data.name;
const response = new Response(null);
setCookie(response, { name: `sb-${cookie_name}-csrf`, value: token }, session_cookie_options);
return response;
}
return new Response(null, { status: 401 });
}
/* Handle request to Supakit's cookie route */
if (url.pathname === `${base}/supakit/cookie`) {
const forbidden = csrfCheck(event);
if (forbidden)
return forbidden;
const cookie_name = request.headers.get('x-csrf-name') ?? false;
const cookie = cookies.get(`sb-${cookie_name}-csrf`) ?? false;
const token = request.headers.get('x-csrf-token') ?? false;
if (!cookie || !token)
return new Response('No CSRF cookie or token', { status: 401 });
if (cookie != token)
return new Response('CSRF cookie and token do not match', { status: 401 });
if (request.method === 'GET') {
const key = request.headers.get('x-storage-key') ?? '';
const response = json({
cookie: cookies.get(key) ?? null
});
return response;
}
if (request.method === 'POST') {
const cookie = request.body ? await request.json() : null;
if (cookie) {
const response = new Response(null);
const data = JSON.parse(cookie.value) ?? cookie.value;
const remember_me_cookie = cookies.get('supakit-rememberme') ?? 'true';
const remember_me = stringToBoolean(remember_me_cookie);
if (isAuthToken(cookie.name)) {
setCookie(response, cookie, remember_me ? cookie_options : session_cookie_options);
if (data.provider_token && data.provider_token !== '')
setCookie(response, { name: 'sb-provider-token', value: JSON.stringify(data.provider_token) }, remember_me ? cookie_options : session_cookie_options);
if (data.provider_refresh_token && data.provider_refresh_token !== '')
setCookie(response, { name: 'sb-provider-refresh-token', value: JSON.stringify(data.provider_refresh_token) }, remember_me ? cookie_options : session_cookie_options);
}
else if (testRegEx(cookie.name, 'remember_me')) {
/* add remember me cookie */
setCookie(response, cookie, remember_me_cookie_options);
}
else {
/* add all other cookies */
setCookie(response, cookie);
}
return response;
}
else {
return new Response('Invalid body.', { status: 400 });
}
}
if (request.method === 'DELETE') {
const cookie = request.body ? await request.json() : null;
const response = new Response(null, { status: 204 });
setCookie(response, cookie, expire_cookie_options);
if (isAuthToken(cookie.name)) {
if (cookies.get('sb-provider-token'))
setCookie(response, { name: 'sb-provider-token', value: '' }, expire_cookie_options);
if (cookies.get('sb-provider-refresh-token'))
setCookie(response, { name: 'sb-provider-refresh-token', value: '' }, expire_cookie_options);
}
return response;
}
return new Response(null, { status: 401 });
}
return await resolve(event);
});