feat(einstellungen): Betriebswerte im Dashboard einstellbar (#025)
Vier Bereiche unter Einstellungen -> System: Adresse und Agent, Kaufmaennisches, Sicherheit sowie Firma und Darstellung. Alles liegt als eine JSON-Zeile in der Tabelle Einstellung; fehlende Felder bekommen ihren Vorgabewert, eine kaputte Zeile legt den Betrieb nicht lahm. Die Werte wirken auch wirklich: Die Dashboard-Adresse und der Melderhythmus landen im ausgelieferten Einzeiler und im systemd-Timer (live geprueft), der Steuersatz in neuen Rechnungspositionen, die Sitzungsdauer im Token, die Firmenangaben im Kopf der PDF-Dateien. Nebenbei geschlossen: die OIDC-Luecke aus #024. Erlaubte Domaenen und ein Schalter gegen automatisch angelegte Konten. Dazu raeumt der Worker nachts nach den eingestellten Fristen auf - der juengste Report je Host bleibt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,9 +3,26 @@ import type { BenutzerModel } from "@/generated/prisma/models";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { RateLimit } from "@/lib/ratelimit";
|
||||
import { protokolliere } from "@/server/audit";
|
||||
import { domaeneErlaubt, einstellungen } from "@/server/einstellungen";
|
||||
|
||||
/** Anmeldeversuche: fünf pro Viertelstunde je Kennung, damit Raten teuer wird. */
|
||||
export const anmeldeRateLimit = new RateLimit(5, 5 / 900);
|
||||
/**
|
||||
* Anmeldeversuche je Kennung und Viertelstunde. Die Zahl steht in den Einstellungen;
|
||||
* für jede Einstellung wird eine eigene Bremse gehalten, damit der Zustand beim Ändern
|
||||
* nicht verlorengeht.
|
||||
*/
|
||||
const bremsen = new Map<number, RateLimit>();
|
||||
|
||||
export function anmeldeBremse(versuche: number): RateLimit {
|
||||
const vorhanden = bremsen.get(versuche);
|
||||
if (vorhanden) return vorhanden;
|
||||
|
||||
const neu = new RateLimit(versuche, versuche / 900);
|
||||
bremsen.set(versuche, neu);
|
||||
return neu;
|
||||
}
|
||||
|
||||
/** Vorgabe für Aufrufer ohne Einstellungen (Tests). */
|
||||
export const anmeldeRateLimit = anmeldeBremse(5);
|
||||
|
||||
export class AnmeldeFehler extends Error {}
|
||||
|
||||
@@ -15,8 +32,9 @@ export async function mitPasswortAnmelden(
|
||||
passwort: string,
|
||||
quelle: string,
|
||||
): Promise<BenutzerModel> {
|
||||
const { anmeldeversuche } = await einstellungen();
|
||||
const kennung = `${quelle}:${email.toLowerCase()}`;
|
||||
if (!anmeldeRateLimit.pruefe(kennung).erlaubt) {
|
||||
if (!anmeldeBremse(anmeldeversuche).pruefe(kennung).erlaubt) {
|
||||
throw new AnmeldeFehler("Zu viele Versuche. Bitte einige Minuten warten.");
|
||||
}
|
||||
|
||||
@@ -41,10 +59,17 @@ export async function mitPasswortAnmelden(
|
||||
|
||||
/**
|
||||
* Ordnet eine OIDC-Anmeldung einem Benutzer zu: erst über die Subject-ID, sonst über die E-Mail.
|
||||
* Ein unbekanntes Konto wird angelegt – wer sich bei authentik anmelden darf, darf auch hier arbeiten.
|
||||
* Ob ein unbekanntes Konto angelegt werden darf und welche E-Mail-Domänen zugelassen sind,
|
||||
* steht in den Einstellungen (#025). Vorgabe bleibt: anlegen, alle Domänen.
|
||||
*/
|
||||
export async function mitOidcAnmelden(subject: string, email: string, name: string): Promise<BenutzerModel> {
|
||||
const normalisiert = email.toLowerCase().trim();
|
||||
const { oidcDomaenen, oidcKontenAnlegen } = await einstellungen();
|
||||
|
||||
if (!domaeneErlaubt(normalisiert, oidcDomaenen)) {
|
||||
await protokolliere("anmeldung.abgelehnt", "Benutzer", null, { email: normalisiert, grund: "domaene" });
|
||||
throw new AnmeldeFehler("Diese E-Mail-Domäne ist für die Anmeldung nicht zugelassen.");
|
||||
}
|
||||
|
||||
const vorhanden =
|
||||
(await prisma.benutzer.findUnique({ where: { oidcSubject: subject } })) ??
|
||||
@@ -54,6 +79,11 @@ export async function mitOidcAnmelden(subject: string, email: string, name: stri
|
||||
throw new AnmeldeFehler("Dieses Konto ist deaktiviert.");
|
||||
}
|
||||
|
||||
if (!vorhanden && !oidcKontenAnlegen) {
|
||||
await protokolliere("anmeldung.abgelehnt", "Benutzer", null, { email: normalisiert, grund: "kein-konto" });
|
||||
throw new AnmeldeFehler("Für diese Adresse gibt es hier kein Konto.");
|
||||
}
|
||||
|
||||
const benutzer = vorhanden
|
||||
? await prisma.benutzer.update({
|
||||
where: { id: vorhanden.id },
|
||||
|
||||
Reference in New Issue
Block a user