Implement auth as source of truth on server and protect routes. Integrate with portal

This commit is contained in:
Dominic Ferrando
2026-06-30 23:46:35 -04:00
parent 235c072421
commit f5fb64e467
9 changed files with 83 additions and 42 deletions
+10 -9
View File
@@ -1,15 +1,16 @@
import { env } from '$env/dynamic/private';
import { redirect, type Handle } from '@sveltejs/kit';
import { createHmac } from 'node:crypto';
import { jwtVerify } from 'jose';
export const handle: Handle = async ({ event, resolve }) => {
const cookieHash = event.cookies.get("session");
const computedHash = createHmac("sha256", env.PORTAL_SECRET)
.update("authenticated")
.digest("hex");
if (event.url.pathname !== "/login" && cookieHash !== computedHash)
redirect(307, "/login");
if (event.url.pathname !== "/login") {
const token = event.cookies.get("auth");
const secret = new TextEncoder().encode(env.AUTH_SECRET);
const authenticated = token != null && await jwtVerify(token, secret)
.then(() => true)
.catch(() => false);
if (!authenticated) redirect(307, "/login");
}
const response = await resolve(event);
return response;
return await resolve(event);
};
+5 -1
View File
@@ -2,4 +2,8 @@ import { treaty } from "@elysia/eden"
import type { API } from "@blade-and-brawn/api"
import { PUBLIC_API_URL } from "$env/static/public"
export const api = treaty<API>(PUBLIC_API_URL)
export const api = treaty<API>(PUBLIC_API_URL, {
fetch: {
credentials: "include",
},
})
@@ -1,21 +0,0 @@
import { fail, redirect } from "@sveltejs/kit";
import type { Actions } from "./$types";
import { env } from "$env/dynamic/private";
import { createHmac } from "node:crypto";
export const actions: Actions = {
default: async ({ request, cookies }) => {
const data = await request.formData();
const password = data.get("password");
if (password !== env.PORTAL_PASSWORD)
return fail(401, { error: "Invalid password" });
const hash = createHmac("sha256", env.PORTAL_SECRET)
.update("authenticated")
.digest("hex");
cookies.set("session", hash, { path: "/", maxAge: 60 * 60 * 24 * 7 });
redirect(303, "/");
},
};
+30 -6
View File
@@ -1,20 +1,40 @@
<script lang="ts">
import "../app.css";
import type { PageProps } from "./$types";
import { goto } from "$app/navigation";
import { api } from "$lib/api";
let { form }: PageProps = $props();
let error = $state("");
let loading = $state(false);
async function handleSubmit(event: SubmitEvent) {
event.preventDefault();
const form = event.currentTarget as HTMLFormElement;
const password = new FormData(form).get("password")?.toString() ?? "";
loading = true;
const { status } = await api.auth.login.post({ password });
loading = false;
if (status === 401) {
error = "Invalid password";
return;
}
await goto("/");
}
</script>
<div class="min-h-screen flex items-center justify-center bg-base-200">
<div class="card bg-base-100 w-full max-w-sm shadow-xl">
<div class="card-body gap-4">
{#if form?.error}
{#if error}
<div role="alert" class="alert alert-error">
<span>{form.error}</span>
<span>{error}</span>
</div>
{/if}
<form method="POST" class="flex flex-col gap-4">
<form onsubmit={handleSubmit} class="flex flex-col gap-4">
<label class="floating-label">
<input
type="password"
@@ -26,7 +46,11 @@
<span>Password</span>
</label>
<button type="submit" class="btn btn-primary w-full">
<button
type="submit"
class="btn btn-primary w-full"
disabled={loading}
>
Sign in
</button>
</form>