Skip to content

Commit

Permalink
feat!: implement auth and navigation guard
Browse files Browse the repository at this point in the history
  • Loading branch information
PleahMaCaka committed May 28, 2024
1 parent 62aa634 commit 847a4fc
Show file tree
Hide file tree
Showing 12 changed files with 212 additions and 8 deletions.
File renamed without changes.
10 changes: 10 additions & 0 deletions src/routes/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { PageServerLoad } from './$types'

export const load: PageServerLoad = async ({ locals: { supabase } }) => {
const { data: countries } = await supabase
.from('countries')
.select('name')
.limit(5)
.order('name')
return { countries: countries ?? [] }
}
30 changes: 22 additions & 8 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
<h1 class="text-center text-3xl font-bold underline">
Hello world!
</h1>
<button class="btn btn-primary size-full h-full
text-3xl text-white"
>
BIG HELLO
</button>
<script>
export let data
$: ({countries} = data)
</script>
<div class="size-full flex flex-col justify-center items-center gap-4">
<h1 class="text-center text-3xl font-bold underline">
Supavelte!
</h1>
<div class="flex flex-col gap-4">
<a class="btn btn-primary w-full" href="/auth">
Authenticate
</a>
<a class="btn btn-outline w-full" href="/private">
Private
</a>
</div>
<ul>
{#each countries as country}
<li>{country.name}</li>
{/each}
</ul>
</div>
30 changes: 30 additions & 0 deletions src/routes/api/auth/confirm/+server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { EmailOtpType } from "@supabase/supabase-js"
import { redirect } from "@sveltejs/kit"
import type { RequestHandler } from "./$types"

export const GET: RequestHandler = async ({ url, locals: { supabase } }) => {
const token_hash = url.searchParams.get("token_hash")
const type = url.searchParams.get("type") as EmailOtpType | null
const next = url.searchParams.get("next") ?? "/"

/**
* Clean up the redirect URL by deleting the Auth flow parameters.
*
* `next` is preserved for now, because it"s needed in the error case.
*/
const redirectTo = new URL(url)
redirectTo.pathname = next
redirectTo.searchParams.delete("token_hash")
redirectTo.searchParams.delete("type")

if (token_hash && type) {
const { error } = await supabase.auth.verifyOtp({ type, token_hash })
if (!error) {
redirectTo.searchParams.delete("next")
return redirect(303, redirectTo)
}
}

redirectTo.pathname = "/auth/error"
return redirect(303, redirectTo)
}
6 changes: 6 additions & 0 deletions src/routes/auth/+layout.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<header>
<nav>
<a href="/">Home</a>
</nav>
</header>
<slot/>
32 changes: 32 additions & 0 deletions src/routes/auth/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { redirect } from "@sveltejs/kit"

import type { Actions } from './$types'

export const actions: Actions = {
signup: async ({ request, locals: { supabase } }) => {
const formData = await request.formData()
const email = formData.get("email") as string
const password = formData.get("password") as string

const { error } = await supabase.auth.signUp({ email, password })
if (error) {
console.error(error)
return redirect(303, "/auth/error")
} else {
return redirect(303, "/")
}
},
login: async ({ request, locals: { supabase } }) => {
const formData = await request.formData()
const email = formData.get("email") as string
const password = formData.get("password") as string

const { error } = await supabase.auth.signInWithPassword({ email, password })
if (error) {
console.error(error)
return redirect(303, "/auth/error")
} else {
return redirect(303, "/private")
}
}
}
35 changes: 35 additions & 0 deletions src/routes/auth/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<div class="size-full flex flex-col justify-center items-center gap-8">
<form action="?/login" class="flex flex-col gap-2 bg-base-300 rounded-box px-10 py-8 shadow-2xl"
method="POST"
>
<label class="input input-bordered flex items-center gap-2">
<svg class="w-4 h-4 opacity-70" fill="currentColor" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path
d="M2.5 3A1.5 1.5 0 0 0 1 4.5v.793c.026.009.051.02.076.032L7.674 8.51c.206.1.446.1.652 0l6.598-3.185A.755.755 0 0 1 15 5.293V4.5A1.5 1.5 0 0 0 13.5 3h-11Z"/>
<path
d="M15 6.954 8.978 9.86a2.25 2.25 0 0 1-1.956 0L1 6.954V11.5A1.5 1.5 0 0 0 2.5 13h11a1.5 1.5 0 0 0 1.5-1.5V6.954Z"/>
</svg>
<input class="grow"
name="email"
placeholder="example@gmail.com"
type="email"
/>
</label>
<label class="input input-bordered flex items-center gap-2">
<svg class="w-4 h-4 opacity-70" fill="currentColor" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd"
d="M14 6a4 4 0 0 1-4.899 3.899l-1.955 1.955a.5.5 0 0 1-.353.146H5v1.5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2.293a.5.5 0 0 1 .146-.353l3.955-3.955A4 4 0 1 1 14 6Zm-4-2a.75.75 0 0 0 0 1.5.5.5 0 0 1 .5.5.75.75 0 0 0 1.5 0 2 2 0 0 0-2-2Z"
fill-rule="evenodd"/>
</svg>
<input class="grow"
name="password"
type="password"
/>
</label>
<div class="divider my-0"></div>
<button class="btn btn-primary text-white" formaction="?/signup">
Continue
</button>
</form>
</div>

1 change: 1 addition & 0 deletions src/routes/auth/error/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>Login error</p>
5 changes: 5 additions & 0 deletions src/routes/private/+layout.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* This file is necessary to ensure protection of all routes in the `private`
* directory. It makes the routes in this directory _dynamic_ routes, which
* send a server request, and thus trigger `hooks.server.ts`.
**/
21 changes: 21 additions & 0 deletions src/routes/private/+layout.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script>
export let data
$: ({ supabase } = data)
$: logout = async () => {
const { error } = await supabase.auth.signOut()
if (error) {
console.error(error)
}
}
</script>

<header>
<nav>
<a href="/">Home</a>
</nav>
<button on:click={logout}>Logout</button>
</header>
<main>
<slot />
</main>
10 changes: 10 additions & 0 deletions src/routes/private/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { PageServerLoad } from "./$types"

export const load: PageServerLoad = async ({ depends, locals: { supabase } }) => {
depends("supabase:db:notes")
const { data: notes } = await supabase
.from("notes")
.select("id,note")
.order("id")
return { notes: notes ?? [] }
}
40 changes: 40 additions & 0 deletions src/routes/private/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script lang="ts">
import { invalidate } from "$app/navigation"
import type { EventHandler } from "svelte/elements"
import type { PageData } from "./$types"
export let data: PageData
$: ({ notes, supabase, user } = data)
let handleSubmit: EventHandler<SubmitEvent, HTMLFormElement>
$: handleSubmit = async (evt) => {
evt.preventDefault()
if (!evt.target) return
const form = evt.target as HTMLFormElement
const note = (new FormData(form).get("note") ?? "") as string
if (!note) return
const { error } = await supabase.from("notes").insert({ note })
if (error) console.error(error)
await invalidate("supabase:db:notes")
form.reset()
}
</script>

<h1>Private page for user: {user?.email}</h1>
<h2>Notes</h2>
<ul>
{#each notes as note}
<li>{note.note}</li>
{/each}
</ul>
<form on:submit={handleSubmit}>
<label>
Add a note
<input name="note" type="text" />
</label>
</form>

0 comments on commit 847a4fc

Please sign in to comment.