Phase B1: Anmeldung, Benutzer, Gruppen und Rollen
Umschaltbar ueber AUTH_ENABLED in der .env. Steht sie auf false, verhaelt
sich alles wie bisher - geprueft mit den 200 bestehenden Pruefungen.
Kern ist eine Actor- und Policy-Schicht: getActor() liefert bei
ausgeschalteter Anmeldung einen system-Actor mit Vollzugriff, sonst den
angemeldeten Benutzer. Dadurch braucht keine einzige Route ein
'if (anmeldungAktiv)' - sie fragen alle nur can() bzw. requirePermission().
- Datenmodell: User, Session, Group, Membership; Codes bekommen Gruppe und
Ersteller. Die Migration legt eine Standardgruppe an und ordnet ihr
bestehende Codes zu, bevor die Spalte verpflichtend wird - sonst
scheiterte sie auf jeder bereits benutzten Installation.
- Passwoerter mit scrypt aus der Node-Standardbibliothek. Bewusst kein
Argon2: das braeuchte ein natives Modul passend zur Container-Plattform.
- Sitzungen in der Datenbank, im Cookie nur ein Zufallstoken; gespeichert
wird dessen SHA-256. Deaktivieren oder ein neues Passwort beendet alle
offenen Sitzungen sofort.
- Aussperrschutz: der letzte aktive Administrator kann sich nicht selbst
entmachten, deaktivieren oder loeschen. Der Schalter liegt in der .env,
nicht in der Oberflaeche - so bleibt der Weg zurueck immer offen.
- Ersteinrichtung legt den ersten Administrator an und schliesst sich danach.
- Die Weiterleitung /r/{slug} bleibt oeffentlich, sonst liesse sich kein
gedruckter Code mehr scannen.
Zwei Fehler dabei gefunden und behoben:
- 'return handleLogin(...)' innerhalb eines try-Blocks gibt das Promise
zurueck, bevor es sich aufloest. Die Ablehnung kam nie im catch an und
wurde zu einem nackten 500er statt einer 401. Jetzt mit await.
- Die Fehlerklassen wurden ueber instanceof erkannt. type-module.ts landet
aber sowohl im Server- als auch im Client-Buendel, es gibt also zwei
Klassenidentitaeten - saubere 400er wurden dadurch still zu 500ern. Die
Erkennung laeuft jetzt ueber eine Eigenschaft.
Geprueft: 51 neue Pruefungen fuer Anmeldung und die vollstaendige
Rollenmatrix, dazu die 200 bestehenden bei ausgeschalteter Anmeldung.
This commit is contained in:
+30
-9
@@ -1,23 +1,44 @@
|
||||
import { QrRenderError } from "@/lib/qr/render";
|
||||
import { QrRequestError } from "@/lib/qr/request";
|
||||
import { QrTypeError } from "@/lib/qr/type-module";
|
||||
|
||||
export function jsonError(message: string, status = 400): Response {
|
||||
return Response.json({ error: message }, { status });
|
||||
}
|
||||
|
||||
/**
|
||||
* Erkennt Fehler, deren Meldung an den Aufrufer gehen darf.
|
||||
*
|
||||
* Bewusst über eine Eigenschaft statt über `instanceof`: die Fehlerklassen
|
||||
* stehen in einem Modul, das sowohl im Server- als auch im Client-Bündel
|
||||
* landet. Dann existieren zwei Klassenidentitäten, und ein
|
||||
* `instanceof`-Vergleich würde eine saubere 400er-Antwort stillschweigend zu
|
||||
* einem 500er machen.
|
||||
*/
|
||||
function asUserFacing(
|
||||
error: unknown,
|
||||
): { message: string; status: number } | null {
|
||||
if (
|
||||
typeof error !== "object" ||
|
||||
error === null ||
|
||||
(error as { userFacing?: unknown }).userFacing !== true
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const message = (error as { message?: unknown }).message;
|
||||
if (typeof message !== "string") return null;
|
||||
|
||||
const status = (error as { status?: unknown }).status;
|
||||
return { message, status: typeof status === "number" ? status : 400 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Übersetzt eine Ausnahme in eine Antwort mit deutscher Fehlermeldung.
|
||||
* Unerwartete Fehler werden protokolliert, nach außen aber generisch
|
||||
* gemeldet.
|
||||
*/
|
||||
export function errorResponse(error: unknown): Response {
|
||||
if (error instanceof QrRequestError) {
|
||||
return jsonError(error.message, error.status);
|
||||
}
|
||||
if (error instanceof QrRenderError || error instanceof QrTypeError) {
|
||||
return jsonError(error.message, 400);
|
||||
const userFacing = asUserFacing(error);
|
||||
if (userFacing) {
|
||||
return jsonError(userFacing.message, userFacing.status);
|
||||
}
|
||||
|
||||
console.error("Unerwarteter Fehler in einer API-Route:", error);
|
||||
return jsonError(
|
||||
"Es ist ein unerwarteter Fehler aufgetreten. Bitte versuche es erneut.",
|
||||
|
||||
Reference in New Issue
Block a user