109 lines
3.2 KiB
TypeScript
109 lines
3.2 KiB
TypeScript
import { createServerClient } from '@supabase/ssr'
|
|
import { type Handle, redirect } from '@sveltejs/kit'
|
|
import { sequence } from '@sveltejs/kit/hooks'
|
|
|
|
import { env } from '$env/dynamic/public'
|
|
|
|
const supabase: Handle = async ({ event, resolve }) => {
|
|
/**
|
|
* Creates a Supabase client specific to this server request.
|
|
*
|
|
* The Supabase client gets the Auth token from the request cookies.
|
|
*/
|
|
event.locals.supabase = createServerClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY, {
|
|
cookies: {
|
|
getAll: () => event.cookies.getAll(),
|
|
/**
|
|
* SvelteKit's cookies API requires `path` to be explicitly set in
|
|
* the cookie options. Setting `path` to `/` replicates previous/
|
|
* standard behavior.
|
|
*/
|
|
setAll: (cookiesToSet) => {
|
|
cookiesToSet.forEach(({ name, value, options }) => {
|
|
event.cookies.set(name, value, { ...options, path: '/' })
|
|
})
|
|
},
|
|
},
|
|
})
|
|
|
|
/**
|
|
* Unlike `supabase.auth.getSession()`, which returns the session _without_
|
|
* validating the JWT, this function also calls `getUser()` to validate the
|
|
* JWT before returning the session.
|
|
*/
|
|
event.locals.safeGetSession = async () => {
|
|
const {
|
|
data: { session },
|
|
} = await event.locals.supabase.auth.getSession()
|
|
if (!session) {
|
|
return { session: null, user: null }
|
|
}
|
|
|
|
const {
|
|
data: { user },
|
|
error,
|
|
} = await event.locals.supabase.auth.getUser()
|
|
if (error) {
|
|
// JWT validation has failed
|
|
return { session: null, user: null }
|
|
}
|
|
|
|
return { session, user }
|
|
}
|
|
|
|
/**
|
|
* Fetch user profile data including display name, section position, and section name
|
|
*/
|
|
event.locals.getUserProfile = async (userId) => {
|
|
if (!userId) return null
|
|
|
|
const { data: profile, error } = await event.locals.supabase
|
|
.from('profiles')
|
|
.select('display_name, section_position, section:sections (name)')
|
|
.eq('id', userId)
|
|
.single()
|
|
|
|
if (error) return null
|
|
return profile
|
|
}
|
|
|
|
return resolve(event, {
|
|
filterSerializedResponseHeaders(name) {
|
|
/**
|
|
* Supabase libraries use the `content-range` and `x-supabase-api-version`
|
|
* headers, so we need to tell SvelteKit to pass it through.
|
|
*/
|
|
return name === 'content-range' || name === 'x-supabase-api-version'
|
|
},
|
|
})
|
|
}
|
|
|
|
const authGuard: Handle = async ({ event, resolve }) => {
|
|
const { session, user } = await event.locals.safeGetSession()
|
|
event.locals.session = session
|
|
event.locals.user = user
|
|
|
|
// Fetch the user's profile if they're authenticated
|
|
if (user) {
|
|
event.locals.profile = await event.locals.getUserProfile(user.id)
|
|
}
|
|
|
|
if (!event.locals.session && event.url.pathname.startsWith('/private')) {
|
|
redirect(303, '/auth')
|
|
}
|
|
|
|
if (event.locals.session && event.url.pathname === '/auth') {
|
|
redirect(303, '/private/home')
|
|
}
|
|
|
|
// Role-based access control for events routes
|
|
if (event.url.pathname.startsWith('/private/events')) {
|
|
if (!event.locals.profile || event.locals.profile.section_position !== 'events_manager') {
|
|
redirect(303, '/private/errors/events/denied')
|
|
}
|
|
}
|
|
|
|
return resolve(event)
|
|
}
|
|
|
|
export const handle: Handle = sequence(supabase, authGuard) |