v1.5: Selbst gehostete Auth (Passwort + Passkeys), Postgres statt Supabase
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.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env node
|
||||
// Wendet alle noch nicht angewendeten Dateien aus db/migrations/*.sql auf
|
||||
// die per DATABASE_URL erreichbare Postgres-Datenbank an. Idempotent –
|
||||
// kann bei jedem App-Start gefahrlos erneut laufen (Docker-Entrypoint,
|
||||
// npm predev/prestart).
|
||||
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import pg from "pg";
|
||||
|
||||
const migrationsDir = join(dirname(fileURLToPath(import.meta.url)), "migrations");
|
||||
|
||||
async function main() {
|
||||
const client = new pg.Client({ connectionString: process.env.DATABASE_URL });
|
||||
await client.connect();
|
||||
|
||||
try {
|
||||
await client.query(`
|
||||
create table if not exists schema_migrations (
|
||||
filename text primary key,
|
||||
applied_at timestamptz not null default now()
|
||||
)
|
||||
`);
|
||||
|
||||
const { rows } = await client.query("select filename from schema_migrations");
|
||||
const applied = new Set(rows.map((r) => r.filename));
|
||||
|
||||
const files = readdirSync(migrationsDir)
|
||||
.filter((f) => f.endsWith(".sql"))
|
||||
.sort();
|
||||
|
||||
for (const file of files) {
|
||||
if (applied.has(file)) continue;
|
||||
const sql = readFileSync(join(migrationsDir, file), "utf8");
|
||||
console.log(`db:migrate – wende ${file} an…`);
|
||||
await client.query("begin");
|
||||
try {
|
||||
await client.query(sql);
|
||||
await client.query("insert into schema_migrations (filename) values ($1)", [file]);
|
||||
await client.query("commit");
|
||||
} catch (err) {
|
||||
await client.query("rollback");
|
||||
throw new Error(`Migration ${file} fehlgeschlagen: ${err.message}`, { cause: err });
|
||||
}
|
||||
}
|
||||
|
||||
console.log("db:migrate – Datenbank ist aktuell.");
|
||||
} finally {
|
||||
await client.end();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user