Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More idiomatic supabase auth #139

Merged
merged 2 commits into from
Aug 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ declare global {
user: User | null
amr: AMREntry[] | null
}>
session: Session | null
user: User | null
scosman marked this conversation as resolved.
Show resolved Hide resolved
}
interface PageData {
session: Session | null
Expand Down
22 changes: 21 additions & 1 deletion src/hooks.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import {
import { createServerClient } from "@supabase/ssr"
import { createClient } from "@supabase/supabase-js"
import type { Handle } from "@sveltejs/kit"
import { sequence } from "@sveltejs/kit/hooks"
import { redirect } from "@sveltejs/kit"

export const handle: Handle = async ({ event, resolve }) => {
export const supabase: Handle = async ({ event, resolve }) => {
event.locals.supabase = createServerClient(
PUBLIC_SUPABASE_URL,
PUBLIC_SUPABASE_ANON_KEY,
Expand Down Expand Up @@ -82,3 +84,21 @@ export const handle: Handle = async ({ event, resolve }) => {
},
})
}

const authGuard: Handle = async ({ event, resolve }) => {
const { session, user } = await event.locals.safeGetSession()
event.locals.session = session
event.locals.user = user

if (!event.locals.session && event.url.pathname.startsWith("/account")) {
redirect(303, "/login")
}

if (event.locals.session && event.url.pathname === "/login") {
redirect(303, "/account")
}

return resolve(event)
}

export const handle: Handle = sequence(supabase, authGuard)
19 changes: 5 additions & 14 deletions src/routes/(admin)/account/+layout.server.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
import { redirect } from "@sveltejs/kit"
import type { LayoutServerLoad } from "./$types"

export const load: LayoutServerLoad = async ({
locals: { supabase, safeGetSession },
locals: { session },
cookies,
}) => {
const { session, user } = await safeGetSession()

if (!session || !user?.id) {
redirect(303, "/login")
// Session here is from authGuard hook
return {
session,
cookies: cookies.getAll(),
}

const { data: profile } = await supabase
.from("profiles")
.select(`*`)
.eq("id", user.id)
.single()

return { session, user, profile, cookies: cookies.getAll() }
}
26 changes: 16 additions & 10 deletions src/routes/(admin)/account/+layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,16 @@ export const load = async ({ fetch, data, depends, url }) => {
},
})

/**
* Not always safe on server, but calling getUser next to verify JWT token
*/
const {
data: { session },
} = await supabase.auth.getSession()
// on server populated on server by LayoutData, using authGuard hook
let session = data.session
if (isBrowser()) {
// Only call getSession in browser where it's safe.
const getSessionResponse = await supabase.auth.getSession()
session = getSessionResponse.data.session
}
if (!session) {
redirect(303, "/login")
}
Comment on lines +34 to +43
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve session management changes with a suggestion for improvement.

The conditional session management logic is a significant improvement, enhancing security by ensuring that session retrieval only occurs in the browser context. This approach aligns well with best practices.

However, consider adding error handling for the getSession call to manage potential failures gracefully.

Add error handling around the getSession call:

-    const getSessionResponse = await supabase.auth.getSession()
+    let getSessionResponse;
+    try {
+      getSessionResponse = await supabase.auth.getSession()
+    } catch (error) {
+      console.error('Failed to retrieve session:', error);
+      redirect(303, '/login');
+      return;
+    }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// on server populated on server by LayoutData, using authGuard hook
let session = data.session
if (isBrowser()) {
// Only call getSession in browser where it's safe.
const getSessionResponse = await supabase.auth.getSession()
session = getSessionResponse.data.session
}
if (!session) {
redirect(303, "/login")
}
// on server populated on server by LayoutData, using authGuard hook
let session = data.session
if (isBrowser()) {
// Only call getSession in browser where it's safe.
let getSessionResponse;
try {
getSessionResponse = await supabase.auth.getSession()
} catch (error) {
console.error('Failed to retrieve session:', error);
redirect(303, '/login');
return;
}
session = getSessionResponse.data.session
}
if (!session) {
redirect(303, "/login")
}


// https://github.com/supabase/auth-js/issues/888#issuecomment-2189298518
if ("suppressGetSessionWarning" in supabase.auth) {
Expand All @@ -53,14 +57,16 @@ export const load = async ({ fetch, data, depends, url }) => {
} = await supabase.auth.getUser()
if (userError || !user) {
// JWT validation has failed
console.log("User error", userError)
redirect(303, "/login")
}

const { data: aal } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel()
const { data: profile } = await supabase
.from("profiles")
.select(`*`)
.eq("id", user.id)
.single()

const profile: Database["public"]["Tables"]["profiles"]["Row"] | null =
data.profile
const { data: aal } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel()

const createProfilePath = "/account/create_profile"
const signOutPath = "/account/sign_out"
Expand Down
7 changes: 2 additions & 5 deletions src/routes/(marketing)/login/+layout.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,16 @@ import { redirect } from "@sveltejs/kit"
import type { LayoutServerLoad } from "./$types"

export const load: LayoutServerLoad = async ({
url,
locals: { safeGetSession },
locals: { session },
cookies,
url,
}) => {
const { session } = await safeGetSession()

// if the user is already logged in return them to the account page
if (session) {
redirect(303, "/account")
}

return {
session: session,
url: url.origin,
cookies: cookies.getAll(),
}
Expand Down
Loading