Skip to content

Commit

Permalink
migrate to new supabase ssr auth package
Browse files Browse the repository at this point in the history
  • Loading branch information
kizivat committed May 9, 2024
1 parent cae14a3 commit fc4ead6
Show file tree
Hide file tree
Showing 17 changed files with 176 additions and 98 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
},
"type": "module",
"dependencies": {
"@supabase/auth-helpers-sveltekit": "^0.10.2",
"@supabase/auth-ui-svelte": "^0.2.2",
"@supabase/supabase-js": "^2.33.0",
"@supabase/ssr": "^0.3.0",
"@supabase/supabase-js": "^2.43.1",
"stripe": "^13.3.0"
}
}
7 changes: 5 additions & 2 deletions src/app.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SupabaseClient, Session } from "@supabase/supabase-js"
import { Session, SupabaseClient } from "@supabase/supabase-js"
import { Database } from "./DatabaseDefinitions"

// See https://kit.svelte.dev/docs/types#app
Expand All @@ -8,7 +8,10 @@ declare global {
interface Locals {
supabase: SupabaseClient<Database>
supabaseServiceRole: SupabaseClient<Database>
getSession(): Promise<Session | null>
safeGetSession: () => Promise<{
session: Session | null
user: User | null
}>
}
interface PageData {
session: Session | null
Expand Down
55 changes: 43 additions & 12 deletions src/hooks.server.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
// src/hooks.server.ts
import { PRIVATE_SUPABASE_SERVICE_ROLE } from "$env/static/private"
import {
PUBLIC_SUPABASE_URL,
PUBLIC_SUPABASE_ANON_KEY,
PUBLIC_SUPABASE_URL,
} from "$env/static/public"
import { PRIVATE_SUPABASE_SERVICE_ROLE } from "$env/static/private"
import { createSupabaseServerClient } from "@supabase/auth-helpers-sveltekit"
import { createServerClient } from "@supabase/ssr"
import { createClient } from "@supabase/supabase-js"
import type { Handle } from "@sveltejs/kit"

export const handle: Handle = async ({ event, resolve }) => {
event.locals.supabase = createSupabaseServerClient({
supabaseUrl: PUBLIC_SUPABASE_URL,
supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
event,
})
event.locals.supabase = createServerClient(
PUBLIC_SUPABASE_URL,
PUBLIC_SUPABASE_ANON_KEY,
{
cookies: {
get: (key) => event.cookies.get(key),
/**
* Note: You have to add the `path` variable to the
* set and remove method due to sveltekit's cookie API
* requiring this to be set, setting the path to an empty string
* will replicate previous/standard behaviour (https://kit.svelte.dev/docs/types#public-types-cookies)
*/
set: (key, value, options) => {
event.cookies.set(key, value, { ...options, path: "/" })
},
remove: (key, options) => {
event.cookies.delete(key, { ...options, path: "/" })
},
},
},
)

event.locals.supabaseServiceRole = createClient(
PUBLIC_SUPABASE_URL,
Expand All @@ -22,18 +38,33 @@ export const handle: Handle = async ({ event, resolve }) => {
)

/**
* A convenience helper so we can just call await getSession() instead const { data: { session } } = await supabase.auth.getSession()
* 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.getSession = async () => {
event.locals.safeGetSession = async () => {
const {
data: { session },
} = await event.locals.supabase.auth.getSession()
return session
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 }
}

return resolve(event, {
filterSerializedResponseHeaders(name) {
return name === "content-range"
return name === "content-range" || name === "x-supabase-api-version"
},
})
}
4 changes: 2 additions & 2 deletions src/routes/(admin)/account/(menu)/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { redirect } from "@sveltejs/kit"

export const actions = {
signout: async ({ locals: { supabase, getSession } }) => {
const session = await getSession()
signout: async ({ locals: { supabase, safeGetSession } }) => {
const { session } = await safeGetSession()
if (session) {
await supabase.auth.signOut()
throw redirect(303, "/")
Expand Down
10 changes: 5 additions & 5 deletions src/routes/(admin)/account/(menu)/billing/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { redirect, error } from "@sveltejs/kit"
import { error, redirect } from "@sveltejs/kit"
import {
getOrCreateCustomerId,
fetchSubscription,
getOrCreateCustomerId,
} from "../../subscription_helpers.server"
import type { PageServerLoad } from "./$types"

export const load: PageServerLoad = async ({
locals: { getSession, supabaseServiceRole },
locals: { safeGetSession, supabaseServiceRole },
}) => {
const session = await getSession()
const { session, user } = await safeGetSession()
if (!session) {
throw redirect(303, "/login")
}

const { error: idError, customerId } = await getOrCreateCustomerId({
supabaseServiceRole,
session,
user,
})
if (idError || !customerId) {
throw error(500, {
Expand Down
12 changes: 6 additions & 6 deletions src/routes/(admin)/account/(menu)/billing/manage/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { redirect, error } from "@sveltejs/kit"
import { getOrCreateCustomerId } from "../../../subscription_helpers.server"
import type { PageServerLoad } from "./$types"
import { PRIVATE_STRIPE_API_KEY } from "$env/static/private"
import { error, redirect } from "@sveltejs/kit"
import Stripe from "stripe"
import { getOrCreateCustomerId } from "../../../subscription_helpers.server"
import type { PageServerLoad } from "./$types"
const stripe = new Stripe(PRIVATE_STRIPE_API_KEY, { apiVersion: "2023-08-16" })

export const load: PageServerLoad = async ({
url,
locals: { getSession, supabaseServiceRole },
locals: { safeGetSession, supabaseServiceRole },
}) => {
const session = await getSession()
const { session, user } = await safeGetSession()
if (!session) {
throw redirect(303, "/login")
}

const { error: idError, customerId } = await getOrCreateCustomerId({
supabaseServiceRole,
session,
user,
})
if (idError || !customerId) {
throw error(500, {
Expand Down
4 changes: 2 additions & 2 deletions src/routes/(admin)/account/(menu)/settings/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
adminSection.set("settings")
export let data
let { session, profile } = data
let { profile, user } = data
</script>

<svelte:head>
Expand Down Expand Up @@ -39,7 +39,7 @@
<SettingsModule
title="Email"
editable={false}
fields={[{ id: "email", initialValue: session?.user?.email || "" }]}
fields={[{ id: "email", initialValue: user?.email || "" }]}
editButtonTitle="Change Email"
editLink="/account/settings/change_email"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<script lang="ts">
import SettingsModule from "../settings_module.svelte"
import { getContext } from "svelte"
import type { Writable } from "svelte/store"
import SettingsModule from "../settings_module.svelte"
let adminSection: Writable<string> = getContext("adminSection")
adminSection.set("settings")
export let data
let { session } = data
let { user } = data
</script>

<svelte:head>
Expand All @@ -27,7 +27,7 @@
{
id: "email",
label: "Email",
initialValue: session?.user?.email ?? "",
initialValue: user?.email ?? "",
placeholder: "Email address",
},
]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,25 @@
adminSection.set("settings")
export let data
let { session, supabase } = data
let { user, supabase } = data
// True if definitely has a password, but can be false if they
// logged in with oAuth or email link
// @ts-expect-error: we ignore because Supabase does not maintain an AMR typedef
let hasPassword = session?.user?.amr?.find((x) => x.method === "password")
let hasPassword = user?.amr?.find((x) => x.method === "password")
? true
: false
// @ts-expect-error: we ignore because Supabase does not maintain an AMR typedef
let usingOAuth = session?.user?.amr?.find((x) => x.method === "oauth")
? true
: false
let usingOAuth = user?.amr?.find((x) => x.method === "oauth") ? true : false
let sendBtn: HTMLButtonElement
let sentEmail = false
let sendForgotPassword = () => {
sendBtn.disabled = true
sendBtn.textContent = "Sending..."
let email = session?.user.email
let email = user?.email
if (email) {
supabase.auth
.resetPasswordForEmail(email, {
Expand Down Expand Up @@ -91,8 +89,8 @@
<div class="font-bold">Change Password By Email</div>
{/if}
<div>
The button below will send you an email at {session?.user?.email} which will
allow you to set your password.
The button below will send you an email at {user?.email} which will allow
you to set your password.
</div>
<button
class="btn btn-outline btn-wide {sentEmail ? 'hidden' : ''}"
Expand Down
6 changes: 3 additions & 3 deletions src/routes/(admin)/account/+layout.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { redirect } from "@sveltejs/kit"
import type { LayoutServerLoad } from "./$types"

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

if (!session) {
throw redirect(303, "/login")
Expand All @@ -13,7 +13,7 @@ export const load: LayoutServerLoad = async ({
const { data: profile } = await supabase
.from("profiles")
.select(`*`)
.eq("id", session.user.id)
.eq("id", user.id)
.single()

return { session, profile }
Expand Down
43 changes: 34 additions & 9 deletions src/routes/(admin)/account/+layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,49 @@ import {
PUBLIC_SUPABASE_ANON_KEY,
PUBLIC_SUPABASE_URL,
} from "$env/static/public"
import { createSupabaseLoadClient } from "@supabase/auth-helpers-sveltekit"
import type { Database } from "../../../DatabaseDefinitions.js"
import {
createBrowserClient,
createServerClient,
isBrowser,
parse,
} from "@supabase/ssr"
import { redirect } from "@sveltejs/kit"
import type { Database } from "../../../DatabaseDefinitions.js"

export const load = async ({ fetch, data, depends, url }) => {
depends("supabase:auth")

const supabase = createSupabaseLoadClient({
supabaseUrl: PUBLIC_SUPABASE_URL,
supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
event: { fetch },
serverSession: data.session,
})
const supabase = isBrowser()
? createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
global: {
fetch,
},
cookies: {
get(key) {
const cookie = parse(document.cookie)
return cookie[key]
},
},
})
: createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
global: {
fetch,
},
cookies: {
get() {
return JSON.stringify(data.session)
},
},
})

const {
data: { session },
} = await supabase.auth.getSession()

const {
data: { user },
} = await supabase.auth.getUser()

const profile: Database["public"]["Tables"]["profiles"]["Row"] | null =
data.profile

Expand All @@ -32,7 +57,7 @@ export const load = async ({ fetch, data, depends, url }) => {
throw redirect(303, createProfilePath)
}

return { supabase, session, profile }
return { supabase, session, profile, user }
}

export const _hasFullProfile = (
Expand Down
Loading

0 comments on commit fc4ead6

Please sign in to comment.