Merge branch 'phase/023-024' (Rotation, Widerruf)
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "@/lib/validierung";
|
||||
import { akteurVon } from "@/server/actions/formular";
|
||||
import { darf } from "@/server/auth/rechte";
|
||||
import { BestaetigungFehler, RechteFehler, rechtErforderlich } from "@/server/auth/sitzungspflicht";
|
||||
import { FachFehler } from "@/server/services/fachfehler";
|
||||
import { rotationAltWiderrufen, rotationStarten, rotationUebernehmen } from "@/server/services/rotation";
|
||||
import { VerwendungsFehler } from "@/server/services/verwendungen";
|
||||
|
||||
/**
|
||||
* Gefuehrte Rotation (#023): je Schritt eine duenne Aktion, die Oberflaeche verkettet sie.
|
||||
* Rotieren heisst einen Schluessel erzeugen und ausrollen: `schluesselVerwalten` und
|
||||
* `publicKeyAusrollen`; fuer Private-Key-Zuweisungen zusaetzlich `privateKeyAusrollen` (Service).
|
||||
*/
|
||||
export type RotationsAntwort = { readonly vorgangId?: string; readonly fehler?: string };
|
||||
|
||||
function alsAntwort(fehler: unknown): RotationsAntwort {
|
||||
if (
|
||||
fehler instanceof RechteFehler ||
|
||||
fehler instanceof BestaetigungFehler ||
|
||||
fehler instanceof VerwendungsFehler ||
|
||||
fehler instanceof FachFehler
|
||||
) {
|
||||
return { fehler: fehler.message };
|
||||
}
|
||||
throw fehler;
|
||||
}
|
||||
|
||||
const id = z.string().min(1).max(64);
|
||||
|
||||
async function berechtigt() {
|
||||
await rechtErforderlich("schluesselVerwalten");
|
||||
return rechtErforderlich("publicKeyAusrollen");
|
||||
}
|
||||
|
||||
export async function rotationStartenAktion(schluesselId: string): Promise<RotationsAntwort> {
|
||||
try {
|
||||
const { benutzer } = await berechtigt();
|
||||
return { vorgangId: await rotationStarten(id.parse(schluesselId), akteurVon(benutzer)) };
|
||||
} catch (fehler) {
|
||||
return alsAntwort(fehler);
|
||||
}
|
||||
}
|
||||
|
||||
export async function rotationUebernehmenAktion(schluesselId: string): Promise<RotationsAntwort> {
|
||||
try {
|
||||
const { benutzer } = await berechtigt();
|
||||
await rotationUebernehmen(id.parse(schluesselId), akteurVon(benutzer), {
|
||||
privatErlaubt: darf(benutzer.rolle, "privateKeyAusrollen"),
|
||||
});
|
||||
revalidatePath(`/schluessel/${schluesselId}`);
|
||||
return {};
|
||||
} catch (fehler) {
|
||||
return alsAntwort(fehler);
|
||||
}
|
||||
}
|
||||
|
||||
export async function rotationAltWiderrufenAktion(schluesselId: string): Promise<RotationsAntwort> {
|
||||
try {
|
||||
const { benutzer } = await berechtigt();
|
||||
await rotationAltWiderrufen(id.parse(schluesselId), akteurVon(benutzer), {
|
||||
privatErlaubt: darf(benutzer.rolle, "privateKeyAusrollen"),
|
||||
});
|
||||
revalidatePath(`/schluessel/${schluesselId}`);
|
||||
return {};
|
||||
} catch (fehler) {
|
||||
return alsAntwort(fehler);
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,11 @@ export async function publicEntfernenAktion(
|
||||
}
|
||||
}
|
||||
|
||||
export type AusrollAuswahl = { readonly schluesselId: string } | { readonly hostId: string };
|
||||
export type AusrollAuswahl =
|
||||
| { readonly schluesselId: string }
|
||||
| { readonly hostId: string }
|
||||
/** Ein gemeinsamer Dry-Run fuer mehrere Schluessel (Massenwiderruf, #024). */
|
||||
| { readonly schluesselIds: readonly string[] };
|
||||
|
||||
export async function trockenlaufAktion(auswahl: AusrollAuswahl): Promise<VorgangsAntwort> {
|
||||
try {
|
||||
@@ -84,7 +88,9 @@ export async function trockenlaufAktion(auswahl: AusrollAuswahl): Promise<Vorgan
|
||||
vorgangId: await trockenlaufStarten(
|
||||
"schluesselId" in auswahl
|
||||
? { schluesselId: id.parse(auswahl.schluesselId) }
|
||||
: { hostId: id.parse(auswahl.hostId) },
|
||||
: "schluesselIds" in auswahl
|
||||
? { schluesselIds: z.array(id).min(1).max(200).parse(auswahl.schluesselIds) }
|
||||
: { hostId: id.parse(auswahl.hostId) },
|
||||
akteurVon(benutzer),
|
||||
// Private-Key-Ziele nur fuer Admins – ein Operator rollt so nie einen Private Key aus.
|
||||
darf(benutzer.rolle, "privateKeyAusrollen"),
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "@/lib/validierung";
|
||||
import { akteurVon } from "@/server/actions/formular";
|
||||
import { darf } from "@/server/auth/rechte";
|
||||
import { BestaetigungFehler, RechteFehler, rechtErforderlich } from "@/server/auth/sitzungspflicht";
|
||||
import { VerwendungsFehler } from "@/server/services/verwendungen";
|
||||
import {
|
||||
widerrufAbschliessen,
|
||||
widerrufEinleiten,
|
||||
widerrufZielkontenEntfernen,
|
||||
type AbschlussErgebnis,
|
||||
} from "@/server/services/widerruf";
|
||||
|
||||
/**
|
||||
* Widerruf und Massenwiderruf (#024). Duenn: Recht, Eingabe, Service.
|
||||
*
|
||||
* Rechte: einen Public Key entfernen darf, wer ausrollen darf; sobald Private Keys betroffen
|
||||
* sind, nur ein Admin (prueft der Service mit `privatErlaubt`). Mehrere Schluessel auf einmal
|
||||
* (Massenwiderruf) und das Entfernen von Zielkonten verlangen Admin mit frischer Bestaetigung.
|
||||
*/
|
||||
export type WiderrufAntwort = {
|
||||
readonly fehler?: string;
|
||||
/** Frische Bestaetigung noetig: die Oberflaeche verlinkt /bestaetigen. */
|
||||
readonly bestaetigen?: boolean;
|
||||
};
|
||||
|
||||
function alsAntwort(fehler: unknown): WiderrufAntwort {
|
||||
if (fehler instanceof BestaetigungFehler) return { bestaetigen: true, fehler: fehler.message };
|
||||
if (fehler instanceof RechteFehler || fehler instanceof VerwendungsFehler)
|
||||
return { fehler: fehler.message };
|
||||
throw fehler;
|
||||
}
|
||||
|
||||
const idListe = z.array(z.string().min(1).max(64)).min(1).max(200);
|
||||
|
||||
function pfadeNeuLaden(ids: readonly string[]) {
|
||||
revalidatePath("/schluessel");
|
||||
for (const id of ids) revalidatePath(`/schluessel/${id}`);
|
||||
}
|
||||
|
||||
export async function widerrufEinleitenAktion(schluesselIds: string[]): Promise<WiderrufAntwort> {
|
||||
try {
|
||||
const ids = idListe.parse(schluesselIds);
|
||||
const { benutzer } = await rechtErforderlich(ids.length > 1 ? "massenwiderruf" : "publicKeyAusrollen");
|
||||
await widerrufEinleiten(ids, akteurVon(benutzer), {
|
||||
privatErlaubt: darf(benutzer.rolle, "privateKeyAusrollen"),
|
||||
});
|
||||
pfadeNeuLaden(ids);
|
||||
return {};
|
||||
} catch (fehler) {
|
||||
return alsAntwort(fehler);
|
||||
}
|
||||
}
|
||||
|
||||
export async function widerrufZielkontenAktion(
|
||||
schluesselIds: string[],
|
||||
zielkontoIds: string[],
|
||||
): Promise<WiderrufAntwort & { vorgaenge?: { id: string; beschriftung: string }[] }> {
|
||||
try {
|
||||
const ids = idListe.parse(schluesselIds);
|
||||
const konten = idListe.parse(zielkontoIds);
|
||||
// Ein Benutzer samt Home verschwindet: Admin mit frischer Bestaetigung (#018).
|
||||
const { benutzer } = await rechtErforderlich("zielkontenVerwalten");
|
||||
return { vorgaenge: await widerrufZielkontenEntfernen(ids, konten, akteurVon(benutzer)) };
|
||||
} catch (fehler) {
|
||||
return alsAntwort(fehler);
|
||||
}
|
||||
}
|
||||
|
||||
export async function widerrufAbschliessenAktion(
|
||||
schluesselIds: string[],
|
||||
): Promise<WiderrufAntwort & { ergebnisse?: AbschlussErgebnis[] }> {
|
||||
try {
|
||||
const ids = idListe.parse(schluesselIds);
|
||||
// Abschliessen setzt nur den Status nach dem, was die Datenbank belegt – kein Host-Zugriff.
|
||||
const { benutzer } = await rechtErforderlich("publicKeyAusrollen");
|
||||
const ergebnisse = await widerrufAbschliessen(ids, akteurVon(benutzer));
|
||||
pfadeNeuLaden(ids);
|
||||
return { ergebnisse };
|
||||
} catch (fehler) {
|
||||
return alsAntwort(fehler);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user