Implement auth as source of truth on server and protect routes. Integrate with portal
This commit is contained in:
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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, "/");
|
||||
},
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user