aboutsummaryrefslogtreecommitdiff
path: root/src/routes
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-08-13 09:38:16 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-08-13 09:38:16 +0200
commit007384be67559e1848ee0a8a1726c6b0fa39a221 (patch)
tree6b11cc43bd00967564c5bc74aed60168331f225b /src/routes
parentb30150a8feb960c0667eaff6cb105dcc55b65744 (diff)
feat(auth): add persistent family login
Diffstat (limited to 'src/routes')
-rw-r--r--src/routes/+layout.svelte5
-rw-r--r--src/routes/login/+page.server.ts30
-rw-r--r--src/routes/login/+page.svelte83
3 files changed, 117 insertions, 1 deletions
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index a168c65..b2803cc 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -1,5 +1,6 @@
<script>
import '../app.css';
+ import { page } from '$app/stores';
import Nav from '$lib/components/Nav.svelte';
let { children } = $props();
@@ -10,7 +11,9 @@
</svelte:head>
<div class="app">
- <Nav />
+ {#if $page.url.pathname !== '/login'}
+ <Nav />
+ {/if}
{@render children()}
</div>
diff --git a/src/routes/login/+page.server.ts b/src/routes/login/+page.server.ts
new file mode 100644
index 0000000..88c5f2d
--- /dev/null
+++ b/src/routes/login/+page.server.ts
@@ -0,0 +1,30 @@
+import { fail, redirect } from '@sveltejs/kit';
+import { setSession, verifyPassword } from '$lib/server/auth';
+import type { Actions, PageServerLoad } from './$types';
+
+function safeNext(value: string | null): string {
+ return value?.startsWith('/') && !value.startsWith('//') ? value : '/';
+}
+
+export const load: PageServerLoad = ({ url }) => ({
+ next: safeNext(url.searchParams.get('next')),
+});
+
+export const actions: Actions = {
+ default: async ({ request, cookies, url }) => {
+ const data = await request.formData();
+ const password = data.get('password')?.toString() ?? '';
+ const next = safeNext(
+ data.get('next')?.toString() ?? url.searchParams.get('next'),
+ );
+
+ if (!verifyPassword(password)) {
+ console.warn('[auth] failed login attempt');
+ return fail(401, { error: 'Mot de passe incorrect', next });
+ }
+
+ console.info('[auth] login succeeded');
+ setSession(cookies, url.protocol === 'https:');
+ throw redirect(303, next);
+ },
+};
diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte
new file mode 100644
index 0000000..303043f
--- /dev/null
+++ b/src/routes/login/+page.svelte
@@ -0,0 +1,83 @@
+<script lang="ts">
+ let { data, form } = $props();
+</script>
+
+<svelte:head>
+ <title>Connexion - Courses</title>
+</svelte:head>
+
+<main class="login">
+ <div class="login-card">
+ <h1>Courses</h1>
+ <p>Entrez le mot de passe familial pour continuer.</p>
+
+ <form method="POST">
+ <label>
+ <span class="form-label">Mot de passe</span>
+ <input
+ class="form-input"
+ type="password"
+ name="password"
+ autocomplete="current-password"
+ required
+ />
+ </label>
+ <input type="hidden" name="next" value={data.next} />
+ <button class="btn-primary" type="submit">Entrer</button>
+ </form>
+
+ {#if form?.error}
+ <p class="error">{form.error}</p>
+ {/if}
+ </div>
+</main>
+
+<style>
+ .login {
+ display: grid;
+ min-height: 100dvh;
+ place-items: center;
+ padding: 1rem;
+ }
+
+ .login-card {
+ width: min(100%, 24rem);
+ padding: 1.5rem;
+ background: var(--surface);
+ border-radius: 0.75rem;
+ box-shadow: 0 2px 8px var(--shadow);
+ }
+
+ .login-card h1 {
+ margin-bottom: 0.25rem;
+ }
+
+ .login-card > p {
+ margin-bottom: 1.5rem;
+ color: var(--text-secondary);
+ }
+
+ .login-card label {
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+ }
+
+ .login-card .form-input {
+ width: 100%;
+ }
+
+ form {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ }
+
+ .btn-primary {
+ align-self: stretch;
+ }
+
+ .error {
+ margin-top: 1rem;
+ }
+</style>