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>
44 lines
1.2 KiB
TypeScript
44 lines
1.2 KiB
TypeScript
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,
|
||
};
|
||
}
|