Add access PIN authentication and manage access cookies

- Introduced ACCESS_PIN in env.example for site access control.
- Updated Locals interface to include hasAccess boolean for access management.
- Implemented access check in hooks.server.ts to redirect users without access to the /access page.
- Cleared access cookie on logout to ensure proper session management.
This commit is contained in:
taogaetz 2025-09-14 15:01:03 -04:00
parent ec24b39d1a
commit fe3f532a25
7 changed files with 123 additions and 0 deletions

4
cookies.txt Normal file
View File

@ -0,0 +1,4 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

View File

@ -8,5 +8,8 @@ ORIGIN=https://your-domain.com
# Authentication token (required - generate a secure random token)
MAGIC_LINK_TOKEN=your-secure-token-here
# Access PIN code (required - set a PIN for site access)
ACCESS_PIN=1234
# Cloudinary URL for photo uploads (optional)
CLOUDINARY_URL=cloudinary://api_key:api_secret@cloud_name

1
src/app.d.ts vendored
View File

@ -8,6 +8,7 @@ declare global {
// interface Platform {}
interface Locals {
authenticated: boolean;
hasAccess: boolean;
}
}
}

View File

@ -1,12 +1,24 @@
import type { Handle } from '@sveltejs/kit';
import { redirect } from '@sveltejs/kit';
import { dev } from '$app/environment';
export const handle: Handle = async ({ event, resolve }) => {
// Check if user has access (PIN code authentication)
const accessCookie = event.cookies.get('access_granted');
const hasAccess = !!accessCookie;
// If user doesn't have access and is not on the access page, redirect to /access
if (!hasAccess && event.url.pathname !== '/access') {
throw redirect(302, '/access');
}
// Check if user is authenticated using the chef token cookie
const chefToken = event.cookies.get('chef_token');
const authenticated = !!chefToken;
// Add authentication status to locals for use in load functions
event.locals.authenticated = authenticated;
event.locals.hasAccess = hasAccess;
// Continue with the request
const response = await resolve(event);

View File

@ -0,0 +1,38 @@
import type { PageServerLoad, Actions } from './$types';
import { fail, redirect } from '@sveltejs/kit';
import { dev } from '$app/environment';
export const load: PageServerLoad = async ({ locals }) => {
// If user already has access, redirect to home
if (locals.hasAccess) {
throw redirect(302, '/');
}
return {};
};
export const actions: Actions = {
default: async ({ request, cookies }) => {
const data = await request.formData();
const pin = data.get('pin') as string;
if (!pin) {
return fail(400, { error: 'PIN is required' });
}
if (pin !== process.env.ACCESS_PIN) {
return fail(400, { error: 'Invalid PIN' });
}
// Set access cookie
cookies.set('access_granted', 'true', {
path: '/',
httpOnly: true,
sameSite: 'lax',
secure: !dev,
maxAge: 60 * 60 * 24 * 30 // 30 days
});
throw redirect(302, '/');
}
};

View File

@ -0,0 +1,47 @@
<script lang="ts">
import { enhance } from '$app/forms';
import type { ActionData } from './$types';
let { form, data }: { form: ActionData; data: any } = $props();
</script>
<svelte:head>
<title>Access Bible</title>
</svelte:head>
<div class="flex min-h-screen items-center justify-center bg-base-200">
<div class="card w-full max-w-md bg-base-100 shadow-xl">
<div class="card-body">
<div class="mb-6 text-center">
<h1 class="text-2xl font-bold text-base-content">Access</h1>
<p class="mt-2 text-base-content/70">Enter your access code to continue</p>
</div>
<form method="POST" use:enhance>
<div class="form-control">
<label class="label" for="pin">
<span class="label-text">Access Code</span>
</label>
<input
type="password"
id="pin"
name="pin"
class="input-bordered input w-full"
placeholder="Enter access code"
required
autocomplete="off"
/>
{#if form?.error}
<div class="label">
<span class="label-text-alt text-error">{form.error}</span>
</div>
{/if}
</div>
<div class="form-control mt-6">
<button type="submit" class="btn btn-primary"> Access Site </button>
</div>
</form>
</div>
</div>
</div>

View File

@ -20,6 +20,15 @@ export const POST: RequestHandler = async ({ cookies }) => {
maxAge: 0 // Expire immediately
});
// Clear the access cookie
cookies.set('access_granted', '', {
path: '/',
httpOnly: true,
sameSite: 'lax',
secure: !dev,
maxAge: 0 // Expire immediately
});
// Redirect to home page
throw redirect(302, '/');
};
@ -42,6 +51,15 @@ export const GET: RequestHandler = async ({ cookies }) => {
maxAge: 0 // Expire immediately
});
// Clear the access cookie
cookies.set('access_granted', '', {
path: '/',
httpOnly: true,
sameSite: 'lax',
secure: !dev,
maxAge: 0 // Expire immediately
});
// Redirect to home page
throw redirect(302, '/');
};