import { prisma } from "@/lib/db"; import { z } from "@/lib/validierung"; import type { Aufrufer } from "@/server/auth/zugang"; import { darf } from "@/server/auth/rechte"; import { AnwendungsFehler, keinRecht, nichtGefunden } from "@/server/fehler"; import { protokollieren } from "@/server/services/protokoll"; import { lokaleQuelle } from "@/server/schluessel/lokal"; import { fingerabdruck, istOeffentlicherSchluessel, type GastSchluessel, type SchluesselQuelle, } from "@/server/schluessel/quelle"; /** Alle angebundenen Quellen. Eine externe Verwaltung kommt hier dazu. */ export function schluesselQuellen(): SchluesselQuelle[] { return [lokaleQuelle]; } export async function schluesselFuer(benutzer: { id: string; benutzername: string; }): Promise { const listen = await Promise.all(schluesselQuellen().map((q) => q.schluesselFuer(benutzer))); // Derselbe Schlüssel aus zwei Quellen nur einmal. const gesehen = new Set(); return listen.flat().filter((s) => !gesehen.has(s.fingerabdruck) && gesehen.add(s.fingerabdruck)); } const Eingabe = z.object({ name: z.string().trim().min(1, "Name fehlt.").max(80), oeffentlich: z .string() .trim() .refine( istOeffentlicherSchluessel, "Kein öffentlicher SSH-Schlüssel (ssh-ed25519 …, ssh-rsa …, ecdsa-sha2-… ). Den .pub-Inhalt einfügen, nie den privaten Schlüssel.", ), team: z.boolean().default(false), }); export async function schluesselAnlegen( roh: unknown, aufrufer: Aufrufer, ip: string | null, ): Promise { const eingabe = Eingabe.parse(roh); if (eingabe.team && !darf(aufrufer.benutzer.rolle, "benutzerVerwalten")) throw keinRecht(); const abdruck = fingerabdruck(eingabe.oeffentlich); const benutzerId = eingabe.team ? null : aufrufer.benutzer.id; if (await prisma.sshSchluessel.findFirst({ where: { benutzerId, fingerabdruck: abdruck } })) { throw new AnwendungsFehler(409, "vergeben", "Diesen Schlüssel gibt es hier schon."); } const zeile = await prisma.$transaction(async (tx) => { const neu = await tx.sshSchluessel.create({ data: { benutzerId, name: eingabe.name, oeffentlich: eingabe.oeffentlich, fingerabdruck: abdruck }, }); await protokollieren( { benutzerId: aufrufer.benutzer.id, quelle: aufrufer.quelle, aktion: eingabe.team ? "Team-Schlüssel hinzugefügt" : "SSH-Schlüssel hinzugefügt", ziel: `schluessel:${abdruck}`, details: { name: eingabe.name }, ip, }, tx, ); return neu; }); return { id: zeile.id, name: zeile.name, oeffentlich: zeile.oeffentlich, fingerabdruck: abdruck, bereich: benutzerId ? "persoenlich" : "team", quelle: "lokal", }; } export async function schluesselLoeschen(id: string, aufrufer: Aufrufer, ip: string | null): Promise { const zeile = await prisma.sshSchluessel.findUnique({ where: { id } }); if (!zeile) throw nichtGefunden("Den Schlüssel"); const eigener = zeile.benutzerId === aufrufer.benutzer.id; if (!eigener && !(zeile.benutzerId === null && darf(aufrufer.benutzer.rolle, "benutzerVerwalten"))) throw keinRecht(); await prisma.$transaction(async (tx) => { await tx.sshSchluessel.delete({ where: { id } }); await protokollieren( { benutzerId: aufrufer.benutzer.id, quelle: aufrufer.quelle, aktion: zeile.benutzerId ? "SSH-Schlüssel entfernt" : "Team-Schlüssel entfernt", ziel: `schluessel:${zeile.fingerabdruck}`, ip, }, tx, ); }); }