60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
import { error } from '@sveltejs/kit';
|
|
|
|
// The site has exactly one owner: Stan's account (prod auth.user id tfn6OZDOpiQvMARIH65HQ2XJuaVvmGEe).
|
|
// Owner-only data, such as the health tracker, is gated on that account's email, not on
|
|
// a role, so other users (now or later, e.g. on a public tracking project) never see it.
|
|
// Override with OWNER_EMAIL if the address ever changes.
|
|
const OWNER_EMAIL = (process.env.OWNER_EMAIL || 'stan@stanrunge.dev').trim().toLowerCase();
|
|
|
|
type OwnerEvent = { locals: App.Locals };
|
|
|
|
export function isOwner(event: OwnerEvent): boolean {
|
|
return event.locals.user?.email?.trim().toLowerCase() === OWNER_EMAIL;
|
|
}
|
|
|
|
/** Owner-only routes answer 404 to everyone else, so they don't reveal that they exist. */
|
|
export function requireOwner(event: OwnerEvent) {
|
|
if (!isOwner(event)) error(404, 'Not Found');
|
|
return event.locals.user!;
|
|
}
|
|
|
|
/** Paths that only the owner may see (checked in hooks.server.ts before any route runs). */
|
|
export const ownerOnlyPrefixes = [
|
|
'/trackers/health',
|
|
'/trackers/tasks',
|
|
'/trackers/books/files',
|
|
'/lists/db'
|
|
] as const;
|
|
|
|
/**
|
|
* Owner-only paths inside otherwise public areas: the book reader and its progress/notes
|
|
* APIs. /trackers/books and /trackers/books/<id> themselves are public catalogue pages.
|
|
*/
|
|
export const ownerOnlyPatterns = [/^\/trackers\/books\/\d+\/(read|progress|notes)(\/|$)/] as const;
|
|
|
|
/**
|
|
* Areas where only the owner may write: any non-GET request from anyone else is refused
|
|
* (404) in hooks.server.ts, which also covers form actions on the public book pages.
|
|
*/
|
|
export const ownerWritePrefixes = [
|
|
'/trackers/health',
|
|
'/trackers/tasks',
|
|
'/trackers/books'
|
|
] as const;
|
|
|
|
const normalise = (pathname: string) => pathname.toLowerCase().replace(/\/+$/, '');
|
|
const under = (path: string, prefix: string) => path === prefix || path.startsWith(`${prefix}/`);
|
|
|
|
export function isOwnerOnlyPath(pathname: string): boolean {
|
|
const path = normalise(pathname);
|
|
return (
|
|
ownerOnlyPrefixes.some((prefix) => under(path, prefix)) ||
|
|
ownerOnlyPatterns.some((pattern) => pattern.test(path))
|
|
);
|
|
}
|
|
|
|
export function isOwnerWritePath(pathname: string): boolean {
|
|
const path = normalise(pathname);
|
|
return ownerWritePrefixes.some((prefix) => under(path, prefix));
|
|
}
|