Fundament: db/migrations/0001_init.sql (users/sessions/passkey_credentials/ webauthn_challenges + Inhaltstabellen ohne RLS), db/migrate.mjs (idempotenter Migrationsrunner), lib/db (pg-Pool + query-Wrapper), lib/auth/password.ts (Node-crypto.scrypt, keine neue Abhängigkeit), lib/auth/session.ts (opake DB-Session-Tokens, Sliding-Refresh in proxy.ts). Passkeys: @simplewebauthn/server + /browser ersetzen Supabase's experimentelles WebAuthn-API (dafür gab es bisher kein eigenes Credential-Schema); RP-Konfiguration kommt jetzt direkt aus APP_URL statt über scripts/configure-passkeys.mjs und die Supabase-Management-API. scripts/bootstrap-admin.mjs legt bei leerer users-Tabelle automatisch einen Admin mit zufälligem Passwort an und hängt die Zugangsdaten an ZUGANGSDATEN.md an (idempotent, via predev/prestart-Hooks aufgerufen). Bugfix im Zuge des Umbaus: "mind. 1 Passkey nötig, um Passwort-Login zu deaktivieren" wurde bisher nur clientseitig geprüft – jetzt zusätzlich serverseitig in PATCH /api/account. E-Mail-Änderung im eigenen Konto läuft jetzt direkt nach Passwort-Bestätigung statt über eine Supabase-Bestätigungsmail (kein SMTP-Server vorhanden). Rest der Anwendung (Gerichte/Essenszeiten/Kennzeichnung/Vorschläge/ Benutzerverwaltung/Branding) nutzt noch den alten Supabase-Client – folgt in weiteren Commits.
23 lines
1017 B
TypeScript
23 lines
1017 B
TypeScript
// CSRF-Schutz (Defense-in-Depth) für schreibende Route-Handler:
|
||
// Browser senden bei Cross-Site-POSTs immer einen Origin-Header –
|
||
// stimmt er nicht mit dem Host überein, wird die Anfrage abgelehnt.
|
||
// (Erste Verteidigungslinie ist bereits das SameSite=Lax-Session-Cookie,
|
||
// siehe lib/auth/session.ts, sowie der JSON-Content-Type.)
|
||
export function isSameOrigin(request: Request): boolean {
|
||
const origin = request.headers.get("origin");
|
||
// Kein Origin-Header: kein Browser-Cross-Site-Kontext (z. B. curl);
|
||
// die Authentifizierung greift trotzdem.
|
||
if (!origin) return true;
|
||
const host = request.headers.get("host");
|
||
try {
|
||
const originUrl = new URL(origin);
|
||
if (originUrl.host === host) return true;
|
||
// Hinter einem Reverse-Proxy, der den Host-Header umschreibt, gilt
|
||
// zusätzlich die öffentlich konfigurierte App-URL als eigene Origin.
|
||
const appUrl = process.env.APP_URL;
|
||
return !!appUrl && originUrl.origin === new URL(appUrl).origin;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|