feat(auth): Anmeldung mit Passwort und Passkey
Kern der Benutzerverwaltung. Passwoerter: - scrypt aus der Node-Standardbibliothek (N=32768, r=8, p=1), speicherhart und ohne zusaetzliches natives Modul - Format scrypt$N$r$p$salt$hash, damit sich die Parameter spaeter anheben lassen - fakeVerifyDelay verbrennt bei unbekanntem Benutzernamen dieselbe Rechenzeit, sonst liesse sich an der Antwortzeit ablesen, welche Konten existieren Passkeys ueber SimpleWebAuthn 13: - Anmeldung ohne Benutzernamen ueber auffindbare Credentials - Challenges liegen in der Datenbank und werden beim Einloesen geloescht, koennen also nicht erneut eingespielt werden - rpID und Origin kommen aus APP_URL Sitzungen: - Zufallstoken im HttpOnly-Cookie, in der Datenbank nur dessen Hash - Abmelden und Sperren wirken sofort, weil bei jeder Anfrage nachgeschlagen wird - anders als bei einem selbsttragenden Token Middleware: CSRF-Pruefung ueber Sec-Fetch-Site bzw. Origin, Sicherheitskopfzeilen inklusive CSP mit Per-Request-Nonce und strict-dynamic, dazu eine grobe Zugangsschranke. Letztere ist ausdruecklich keine Sicherheitsgrenze - im Edge-Runtime gibt es keine Datenbank. Verbindlich entscheiden requireUserPage und requireUserApi, serverseitig, in jeder Seite und jedem Endpunkt. config.ts liest APP_NAME, APP_TAGLINE und APP_URL zur Laufzeit statt ueber NEXT_PUBLIC_-Variablen. Die waeren beim Build fest ins Bundle geschrieben; so laesst sich dasselbe Docker-Image ohne Neubau umbenennen und unter einer anderen Adresse betreiben. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1e9f7bd45a
commit
37573962a8
@@ -0,0 +1,43 @@
|
||||
import type { AppSettings } from "@prisma/client";
|
||||
import { APP_NAME } from "@/lib/config";
|
||||
import { prisma } from "@/lib/db";
|
||||
|
||||
/**
|
||||
* Einstellungen der Instanz. Es gibt genau eine Zeile mit der id "singleton";
|
||||
* sie wird beim ersten Zugriff angelegt.
|
||||
*/
|
||||
export async function getAppSettings(): Promise<AppSettings> {
|
||||
return prisma.appSettings.upsert({
|
||||
where: { id: "singleton" },
|
||||
create: { id: "singleton", rpName: APP_NAME },
|
||||
update: {},
|
||||
});
|
||||
}
|
||||
|
||||
export interface InstanceState {
|
||||
settings: AppSettings;
|
||||
userCount: number;
|
||||
/** Kein Konto vorhanden – die Einrichtung steht noch aus. */
|
||||
needsSetup: boolean;
|
||||
/** Darf sich jemand selbst registrieren? */
|
||||
registrationOpen: boolean;
|
||||
multiUser: boolean;
|
||||
}
|
||||
|
||||
export async function getInstanceState(): Promise<InstanceState> {
|
||||
const [settings, userCount] = await Promise.all([
|
||||
getAppSettings(),
|
||||
prisma.user.count(),
|
||||
]);
|
||||
|
||||
const multiUser = settings.mode === "MULTI_USER";
|
||||
|
||||
return {
|
||||
settings,
|
||||
userCount,
|
||||
needsSetup: userCount === 0,
|
||||
// Im Einzelbetrieb gibt es grundsaetzlich keine Selbstregistrierung.
|
||||
registrationOpen: multiUser && settings.allowRegistration,
|
||||
multiUser,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user