Merge branch 'phase/009-host-key-pruefung'

This commit is contained in:
2026-10-12 09:51:59 +02:00
10 changed files with 800 additions and 3 deletions
+68
View File
@@ -0,0 +1,68 @@
"use server";
import { revalidatePath } from "next/cache";
import { z } from "@/lib/validierung";
import { bestaetigungFrisch, rechtErforderlich, RechteFehler } from "@/server/auth/sitzungspflicht";
import { HostKeyEntscheidungFehler, hostKeyEntscheiden } from "@/server/services/hostkeys";
import { vorgangEinreihen } from "@/server/vorgaenge/vorgaenge";
/** Aktionen rund um Host-Keys und Erreichbarkeit (#009). */
export type VorgangsAntwort = { readonly vorgangId?: string; readonly fehler?: string };
export type EntscheidungsAntwort = {
readonly fehler?: string;
readonly bestaetigen?: string;
readonly erledigt?: boolean;
};
const hostIdSchema = z.string().min(1).max(64);
async function einreihen(
art: "HOSTKEY_ABRUFEN" | "ERREICHBARKEIT",
hostId: string,
): Promise<VorgangsAntwort> {
try {
const { benutzer } = await rechtErforderlich("hostsVerwalten");
const id = hostIdSchema.parse(hostId);
return { vorgangId: await vorgangEinreihen({ art, angefordertVonId: benutzer.id, hostId: id }) };
} catch (fehler) {
if (fehler instanceof RechteFehler) return { fehler: fehler.message };
throw fehler;
}
}
export async function hostKeyAbrufenAktion(hostId: string): Promise<VorgangsAntwort> {
return einreihen("HOSTKEY_ABRUFEN", hostId);
}
export async function erreichbarkeitPruefenAktion(hostId: string): Promise<VorgangsAntwort> {
return einreihen("ERREICHBARKEIT", hostId);
}
const entscheidungSchema = z.object({
hostId: hostIdSchema,
hostSchluesselId: z.string().min(1).max(64),
vertrauen: z.boolean(),
ersetzen: z.boolean().default(false),
});
export async function hostKeyEntscheidenAktion(
eingabe: z.input<typeof entscheidungSchema>,
): Promise<EntscheidungsAntwort> {
try {
const { benutzer } = await rechtErforderlich("hostKeyEntscheiden");
const daten = entscheidungSchema.parse(eingabe);
// Einen vertrauten Key zu ersetzen ist der Moment eines moeglichen Angriffs – nur mit
// frischer Bestaetigung der eigenen Anmeldung.
if (daten.ersetzen && !bestaetigungFrisch(benutzer.bestaetigtAm)) {
return { bestaetigen: `/bestaetigen?ziel=${encodeURIComponent(`/hosts/${daten.hostId}`)}` };
}
await hostKeyEntscheiden(daten, { id: benutzer.id, name: benutzer.name });
revalidatePath(`/hosts/${daten.hostId}`);
return { erledigt: true };
} catch (fehler) {
if (fehler instanceof RechteFehler || fehler instanceof HostKeyEntscheidungFehler)
return { fehler: fehler.message };
throw fehler;
}
}