diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-13 09:38:16 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-13 09:38:16 +0200 |
| commit | 007384be67559e1848ee0a8a1726c6b0fa39a221 (patch) | |
| tree | 6b11cc43bd00967564c5bc74aed60168331f225b /src/routes/login | |
| parent | b30150a8feb960c0667eaff6cb105dcc55b65744 (diff) | |
feat(auth): add persistent family login
Diffstat (limited to 'src/routes/login')
| -rw-r--r-- | src/routes/login/+page.server.ts | 30 | ||||
| -rw-r--r-- | src/routes/login/+page.svelte | 83 |
2 files changed, 113 insertions, 0 deletions
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> |
