feat(schluessel): erzeugen, importieren, exportieren (#012)
Schluessel werden jetzt verwaltet statt nur im Prototyp gezeigt: Liste mit Filter, Anlegen (erzeugen oder importieren), Detail nach dem Prototyp aus #002, Bearbeiten der Metadaten und Download <slug>.pub. Warum so: - Erzeugen und Import eines Paars laufen als Vorgang im Worker, weil nur er den Master-Key hat. Passphrase und importierter Private Key reisen wie das Bootstrap-Passwort mit SSHKV_APP_SECRET verschluesselt im Job, nie in Vorgang.eingabe; der Worker loescht den Job danach. - Ein reiner Public Key ist kein Geheimnis und wird direkt im Web angelegt, mit verstaendlicher Meldung bei einem Duplikat. - Das Web liest nie das Chiffrat: ob ein Private Key gespeichert ist, verraet masterKeyVersion (per CHECK an das Chiffrat gekoppelt). Ein struktureller Test verlangt fuer jede zeilenliefernde Abfrage auf Schluessel ausserhalb des Workers ein festes select. - Migration: Vorgangsarten fuer Erzeugen, Import und (fuer #014) das Loeschen des Private Keys; CHECKs fuer den Slug (landet in Dateinamen) und "geloescht nur ohne Chiffrat". - Platzhalter PublicAusrollenAktion/PrivateAusrollenAktion fuer #016/#017. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,431 @@
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { entschluesseln } from "@/lib/geheimnisse";
|
||||
import {
|
||||
ablaufZustand,
|
||||
fingerprintKurz,
|
||||
freierSlug,
|
||||
slugAus,
|
||||
typAnzeige,
|
||||
erzeugenSchema,
|
||||
} from "@/lib/schluessel";
|
||||
import { privateKeyLesen, schluesselpaarErzeugen } from "@/lib/ssh/openssh";
|
||||
import { schluesselDetail, schluesselListe } from "@/server/queries/schluessel";
|
||||
import { FachFehler } from "@/server/services/fachfehler";
|
||||
import {
|
||||
erzeugenAnfordern,
|
||||
paarImportAnfordern,
|
||||
publicKeyImportieren,
|
||||
schluesselBearbeiten,
|
||||
} from "@/server/services/schluessel";
|
||||
import { masterKeyVergessen, privateKeyLaden } from "@/worker/geheimnisse";
|
||||
import { datenbankLeeren } from "./hilfen/db";
|
||||
import { datenbankAlsText, vorgangImWorker, warteschlangeStoppen } from "./hilfen/vorgang";
|
||||
import { ed25519Blob, rsaBlob, zeile } from "./ssh/hilfen";
|
||||
|
||||
/** Schluessel erzeugen, importieren, bearbeiten (#012) – Service, Worker-Ablauf, Lesemodell. */
|
||||
|
||||
let verzeichnis: string;
|
||||
let akteur: { id: string; name: string };
|
||||
|
||||
beforeAll(() => {
|
||||
verzeichnis = mkdtempSync(path.join(tmpdir(), "sshkv-master-"));
|
||||
const datei = path.join(verzeichnis, "master.key");
|
||||
writeFileSync(datei, randomBytes(32), { mode: 0o600 });
|
||||
process.env["SSHKV_MASTER_KEY_FILE"] = datei;
|
||||
masterKeyVergessen();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
rmSync(verzeichnis, { recursive: true, force: true });
|
||||
await warteschlangeStoppen();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await datenbankLeeren();
|
||||
const benutzer = await prisma.benutzer.create({ data: { name: "Olga Operator", rolle: "OPERATOR" } });
|
||||
akteur = { id: benutzer.id, name: benutzer.name };
|
||||
});
|
||||
|
||||
async function fachfehlerVon(versuch: Promise<unknown>): Promise<FachFehler> {
|
||||
const fehler = await versuch.then(
|
||||
() => undefined,
|
||||
(f: unknown) => f,
|
||||
);
|
||||
expect(fehler).toBeInstanceOf(FachFehler);
|
||||
return fehler as FachFehler;
|
||||
}
|
||||
|
||||
/** Kern eines Private Keys (ohne Kopf- und Fusszeile) – das eigentliche Geheimnis. */
|
||||
function kern(privateKey: string): string {
|
||||
return privateKey.split("\n").slice(1, -2).join("").slice(0, 60);
|
||||
}
|
||||
|
||||
describe("Regeln (rein)", () => {
|
||||
it.each([
|
||||
["Backup srv-backup01 → srv-pve01", "backup-srv-backup01-srv-pve01"],
|
||||
["Größter Schlüssel für Ärzte", "groesster-schluessel-fuer-aerzte"],
|
||||
[" --- ", "schluessel"],
|
||||
["Café", "cafe"],
|
||||
["x".repeat(80), "x".repeat(64)],
|
||||
])("slugAus(%j) = %j", (name, slug) => {
|
||||
expect(slugAus(name)).toBe(slug);
|
||||
});
|
||||
|
||||
it("haengt Nummern an, bis der Slug frei ist", async () => {
|
||||
const belegt = new Set(["backup", "backup-2"]);
|
||||
expect(await freierSlug("backup", async (s) => !belegt.has(s))).toBe("backup-3");
|
||||
const lang = "a".repeat(64);
|
||||
const ergebnis = await freierSlug(lang, async (s) => s !== lang);
|
||||
expect(ergebnis).toHaveLength(64);
|
||||
expect(ergebnis.endsWith("-2")).toBe(true);
|
||||
});
|
||||
|
||||
it("kuerzt Fingerprints und zeigt Typen", () => {
|
||||
expect(fingerprintKurz("SHA256:q3vN8xP0b4Tz7KfL2mWcR9yHdE1sJ6uAoGi5VnBQe0k")).toBe(
|
||||
"SHA256:q3vN8xP0…Qe0k",
|
||||
);
|
||||
expect(typAnzeige("ED25519", "ssh-ed25519 AAAA")).toBe("ed25519");
|
||||
expect(typAnzeige("RSA4096", "ssh-rsa AAAA")).toBe("rsa-4096");
|
||||
expect(typAnzeige("ANDERER", "ecdsa-sha2-nistp256 AAAA")).toBe("ecdsa-sha2-nistp256");
|
||||
expect(typAnzeige("ANDERER", "sk-ssh-ed25519@openssh.com AAAA")).toBe("sk-ssh-ed25519");
|
||||
});
|
||||
|
||||
it("bewertet das Ablaufdatum", () => {
|
||||
const jetzt = new Date("2026-10-12T08:00:00Z");
|
||||
expect(ablaufZustand(null, jetzt).art).toBe("keiner");
|
||||
expect(ablaufZustand(new Date("2026-10-30T00:00:00Z"), jetzt)).toEqual({ art: "bald", tage: 18 });
|
||||
expect(ablaufZustand(new Date("2026-11-12T00:00:00Z"), jetzt).art).toBe("spaeter");
|
||||
expect(ablaufZustand(new Date("2026-10-11T00:00:00Z"), jetzt).art).toBe("abgelaufen");
|
||||
});
|
||||
|
||||
it("prueft Eingaben", () => {
|
||||
expect(erzeugenSchema.safeParse({ name: "" }).success).toBe(false);
|
||||
expect(erzeugenSchema.safeParse({ name: "x", slug: "Mit Leer" }).success).toBe(false);
|
||||
expect(erzeugenSchema.safeParse({ name: "x", kommentar: "a\nb" }).success).toBe(false);
|
||||
expect(erzeugenSchema.safeParse({ name: "x", ablaufAm: "2026-02-30" }).success).toBe(false);
|
||||
expect(erzeugenSchema.safeParse({ name: "x", typ: "DSA" }).success).toBe(false);
|
||||
const ok = erzeugenSchema.parse({ name: " Backup ", tags: "PVE, pve backup", ablaufAm: "2030-01-31" });
|
||||
expect(ok).toMatchObject({ name: "Backup", tags: ["pve", "backup"], typ: "ED25519", slug: null });
|
||||
expect(ok.ablaufAm?.toISOString()).toBe("2030-01-31T00:00:00.000Z");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Public Key importieren", () => {
|
||||
it("legt an, setzt Slug und Kommentar und protokolliert", async () => {
|
||||
const blob = ed25519Blob();
|
||||
const neu = await publicKeyImportieren(
|
||||
{
|
||||
name: "Laptop Florian",
|
||||
kategorie: "PERSON",
|
||||
publicKey: zeile("ssh-ed25519", blob, "florian@laptop"),
|
||||
},
|
||||
akteur,
|
||||
);
|
||||
const s = await schluesselDetail(neu.id);
|
||||
expect(s).toMatchObject({
|
||||
slug: "laptop-florian",
|
||||
typ: "ED25519",
|
||||
kategorie: "PERSON",
|
||||
kommentar: "florian@laptop",
|
||||
privatGespeichert: false,
|
||||
selbstHinterlegt: false,
|
||||
});
|
||||
expect(s?.publicKey).toBe(zeile("ssh-ed25519", blob, "florian@laptop"));
|
||||
const eintrag = await prisma.protokoll.findFirstOrThrow({ where: { aktion: "schluessel.importiert" } });
|
||||
expect(eintrag.fingerprint).toBe(s?.fingerprint);
|
||||
expect(eintrag.akteurName).toBe("Olga Operator");
|
||||
});
|
||||
|
||||
it("meldet einen schon verwalteten Key mit Verweis", async () => {
|
||||
const z = zeile("ssh-ed25519", ed25519Blob());
|
||||
const erster = await publicKeyImportieren({ name: "Erster", publicKey: z }, akteur);
|
||||
const fehler = await fachfehlerVon(publicKeyImportieren({ name: "Zweiter", publicKey: z }, akteur));
|
||||
expect(fehler.message).toContain("„Erster“");
|
||||
expect(fehler.message).toContain(`/schluessel/${erster.id}`);
|
||||
expect(fehler.feldfehler["publicKey"]).toBeDefined();
|
||||
});
|
||||
|
||||
it("nummeriert abgeleitete Slugs und lehnt vergebene Wunsch-Slugs ab", async () => {
|
||||
const a = await publicKeyImportieren(
|
||||
{ name: "Backup", publicKey: zeile("ssh-ed25519", ed25519Blob()) },
|
||||
akteur,
|
||||
);
|
||||
const b = await publicKeyImportieren(
|
||||
{ name: "Backup", publicKey: zeile("ssh-ed25519", ed25519Blob()) },
|
||||
akteur,
|
||||
);
|
||||
expect([a.slug, b.slug]).toEqual(["backup", "backup-2"]);
|
||||
const fehler = await fachfehlerVon(
|
||||
publicKeyImportieren(
|
||||
{ name: "X", slug: "backup", publicKey: zeile("ssh-ed25519", ed25519Blob()) },
|
||||
akteur,
|
||||
),
|
||||
);
|
||||
expect(fehler.feldfehler["slug"]).toMatch(/vergeben/);
|
||||
});
|
||||
|
||||
it("verlangt RSA mit mindestens 2048 Bit und erkennt RSA-4096", async () => {
|
||||
const fehler = await fachfehlerVon(
|
||||
publicKeyImportieren({ name: "Alt", publicKey: zeile("ssh-rsa", rsaBlob(1024)) }, akteur),
|
||||
);
|
||||
expect(fehler.feldfehler["publicKey"]).toMatch(/2048/);
|
||||
const neu = await publicKeyImportieren(
|
||||
{ name: "Neu", publicKey: zeile("ssh-rsa", rsaBlob(4096)) },
|
||||
akteur,
|
||||
);
|
||||
expect((await schluesselDetail(neu.id))?.typ).toBe("RSA4096");
|
||||
const mittel = await publicKeyImportieren(
|
||||
{ name: "Mittel", publicKey: zeile("ssh-rsa", rsaBlob(3072)) },
|
||||
akteur,
|
||||
);
|
||||
expect((await schluesselDetail(mittel.id))?.typ).toBe("ANDERER");
|
||||
});
|
||||
|
||||
it("lehnt Unsinn, Vergangenheit und unbekannte Bezuege ab", async () => {
|
||||
await fachfehlerVon(publicKeyImportieren({ name: "X", publicKey: "kein key" }, akteur));
|
||||
const vergangen = await fachfehlerVon(
|
||||
publicKeyImportieren(
|
||||
{ name: "X", publicKey: zeile("ssh-ed25519", ed25519Blob()), ablaufAm: "2020-01-01" },
|
||||
akteur,
|
||||
),
|
||||
);
|
||||
expect(vergangen.feldfehler["ablaufAm"]).toBeDefined();
|
||||
const person = await fachfehlerVon(
|
||||
publicKeyImportieren(
|
||||
{ name: "X", publicKey: zeile("ssh-ed25519", ed25519Blob()), personId: "gibtsnicht" },
|
||||
akteur,
|
||||
),
|
||||
);
|
||||
expect(person.feldfehler["personId"]).toBeDefined();
|
||||
expect(await prisma.schluessel.count()).toBe(0);
|
||||
});
|
||||
|
||||
it("setzt das Ablaufdatum aus der Laufzeit der Vorlage", async () => {
|
||||
const vorlage = await prisma.vorlage.create({
|
||||
data: { name: "Kurz", zielbenutzer: "deploy", standardLaufzeitTage: 10 },
|
||||
});
|
||||
const neu = await publicKeyImportieren(
|
||||
{ name: "Mit Vorlage", publicKey: zeile("ssh-ed25519", ed25519Blob()), vorlageId: vorlage.id },
|
||||
akteur,
|
||||
);
|
||||
const s = await schluesselDetail(neu.id);
|
||||
expect(ablaufZustand(s!.ablaufAm)).toEqual({ art: "bald", tage: 10 });
|
||||
expect(s?.vorlage?.name).toBe("Kurz");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Erzeugen im Worker", () => {
|
||||
it("erzeugt ed25519, versiegelt und verweist den Vorgang auf den Schluessel", async () => {
|
||||
const vorgangId = await erzeugenAnfordern(
|
||||
{ name: "Backup srv-backup01", tags: "backup" },
|
||||
undefined,
|
||||
akteur,
|
||||
);
|
||||
const { jobDaten } = await vorgangImWorker(vorgangId);
|
||||
expect(jobDaten.geheim).toBeUndefined();
|
||||
|
||||
const vorgang = await prisma.vorgang.findUniqueOrThrow({ where: { id: vorgangId } });
|
||||
expect(vorgang.status).toBe("ERLEDIGT");
|
||||
const { schluesselId, fingerprint } = vorgang.ergebnis as { schluesselId: string; fingerprint: string };
|
||||
expect(vorgang.schluesselId).toBe(schluesselId);
|
||||
|
||||
const s = await schluesselDetail(schluesselId);
|
||||
expect(s).toMatchObject({
|
||||
slug: "backup-srv-backup01",
|
||||
kommentar: "sshkv:backup-srv-backup01",
|
||||
kategorie: "DIENST",
|
||||
typ: "ED25519",
|
||||
privatGespeichert: true,
|
||||
passphraseGeschuetzt: false,
|
||||
fingerprint,
|
||||
tags: ["backup"],
|
||||
});
|
||||
expect(s?.erstelltVon?.name).toBe("Olga Operator");
|
||||
|
||||
const privat = await privateKeyLaden(schluesselId);
|
||||
expect(privateKeyLesen(privat.toString("utf8")).fingerprint).toBe(fingerprint);
|
||||
privat.fill(0);
|
||||
|
||||
const eintrag = await prisma.protokoll.findFirstOrThrow({ where: { aktion: "schluessel.erzeugt" } });
|
||||
expect(eintrag).toMatchObject({ fingerprint, akteurId: akteur.id, ergebnis: "ERFOLG" });
|
||||
});
|
||||
|
||||
it("speichert mit Passphrase den geschuetzten Key – die Passphrase nirgends", async () => {
|
||||
const passphrase = "korrekt-pferd-batterie-heftklammer";
|
||||
const vorgangId = await erzeugenAnfordern({ name: "Geschuetzt", typ: "ED25519" }, passphrase, akteur);
|
||||
const vorgang = await prisma.vorgang.findUniqueOrThrow({ where: { id: vorgangId } });
|
||||
expect(JSON.stringify(vorgang.eingabe)).not.toContain(passphrase);
|
||||
expect(vorgang.eingabe).toMatchObject({ mitPassphrase: true });
|
||||
|
||||
const { jobDaten } = await vorgangImWorker(vorgangId);
|
||||
expect(jobDaten.geheim).toBeDefined();
|
||||
expect(jobDaten.geheim).not.toContain(passphrase);
|
||||
expect(JSON.parse(entschluesseln(jobDaten.geheim!))).toEqual({ passphrase });
|
||||
|
||||
const { schluesselId } = (await prisma.vorgang.findUniqueOrThrow({ where: { id: vorgangId } }))
|
||||
.ergebnis as { schluesselId: string };
|
||||
expect((await schluesselDetail(schluesselId))?.passphraseGeschuetzt).toBe(true);
|
||||
const privat = (await privateKeyLaden(schluesselId)).toString("utf8");
|
||||
expect(() => privateKeyLesen(privat)).toThrow(/Passphrase/);
|
||||
expect(privateKeyLesen(privat, passphrase).verschluesselt).toBe(true);
|
||||
|
||||
const alles = await datenbankAlsText();
|
||||
expect(alles).not.toContain(passphrase);
|
||||
expect(alles).not.toContain("PRIVATE KEY");
|
||||
});
|
||||
|
||||
it("erzeugt RSA-4096", { timeout: 30_000 }, async () => {
|
||||
const vorgangId = await erzeugenAnfordern({ name: "Alt-System", typ: "RSA4096" }, undefined, akteur);
|
||||
await vorgangImWorker(vorgangId);
|
||||
const { schluesselId } = (await prisma.vorgang.findUniqueOrThrow({ where: { id: vorgangId } }))
|
||||
.ergebnis as { schluesselId: string };
|
||||
const s = await schluesselDetail(schluesselId);
|
||||
expect(s?.typ).toBe("RSA4096");
|
||||
expect(s?.publicKey.startsWith("ssh-rsa ")).toBe(true);
|
||||
});
|
||||
|
||||
it("scheitert verstaendlich, wenn der Slug inzwischen vergeben ist", async () => {
|
||||
const vorgangId = await erzeugenAnfordern({ name: "Doppelt" }, undefined, akteur);
|
||||
await publicKeyImportieren(
|
||||
{ name: "Doppelt", slug: "doppelt", publicKey: zeile("ssh-ed25519", ed25519Blob()) },
|
||||
akteur,
|
||||
);
|
||||
await vorgangImWorker(vorgangId);
|
||||
const vorgang = await prisma.vorgang.findUniqueOrThrow({ where: { id: vorgangId } });
|
||||
expect(vorgang.status).toBe("FEHLGESCHLAGEN");
|
||||
expect(vorgang.fehler).toMatch(/inzwischen vergeben/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Paar importieren im Worker", () => {
|
||||
it("prueft, versiegelt und laesst weder Vorgang noch Protokoll den Key sehen", async () => {
|
||||
const paar = await schluesselpaarErzeugen("ED25519", { kommentar: "alt@server" });
|
||||
const vorgangId = await paarImportAnfordern(
|
||||
{ name: "Altlast" },
|
||||
paar.privateKeyOpenssh,
|
||||
undefined,
|
||||
akteur,
|
||||
);
|
||||
const vorgang = await prisma.vorgang.findUniqueOrThrow({ where: { id: vorgangId } });
|
||||
expect(JSON.stringify(vorgang)).not.toContain("PRIVATE KEY");
|
||||
|
||||
const { jobDaten } = await vorgangImWorker(vorgangId);
|
||||
expect(jobDaten.geheim).not.toContain(kern(paar.privateKeyOpenssh));
|
||||
const fertig = await prisma.vorgang.findUniqueOrThrow({ where: { id: vorgangId } });
|
||||
expect(fertig.status).toBe("ERLEDIGT");
|
||||
const { schluesselId } = fertig.ergebnis as { schluesselId: string };
|
||||
const s = await schluesselDetail(schluesselId);
|
||||
expect(s).toMatchObject({
|
||||
fingerprint: paar.fingerprint,
|
||||
kommentar: "alt@server",
|
||||
privatGespeichert: true,
|
||||
});
|
||||
expect((await privateKeyLaden(schluesselId)).toString("utf8")).toBe(paar.privateKeyOpenssh);
|
||||
|
||||
const alles = await datenbankAlsText();
|
||||
expect(alles).not.toContain("PRIVATE KEY");
|
||||
expect(alles).not.toContain(kern(paar.privateKeyOpenssh));
|
||||
expect(await prisma.protokoll.count({ where: { aktion: "schluessel.importiert" } })).toBe(1);
|
||||
});
|
||||
|
||||
it("behaelt einen passphrasengeschuetzten Key so, wie er kam", async () => {
|
||||
const passphrase = "nur-zur-pruefung-1234";
|
||||
const paar = await schluesselpaarErzeugen("ED25519", { kommentar: "x", passphrase });
|
||||
const ohne = await paarImportAnfordern({ name: "Ohne" }, paar.privateKeyOpenssh, undefined, akteur);
|
||||
await vorgangImWorker(ohne);
|
||||
expect((await prisma.vorgang.findUniqueOrThrow({ where: { id: ohne } })).fehler).toMatch(/Passphrase/);
|
||||
|
||||
const falsch = await paarImportAnfordern(
|
||||
{ name: "Falsch" },
|
||||
paar.privateKeyOpenssh,
|
||||
"Zz9-unpassend",
|
||||
akteur,
|
||||
);
|
||||
await vorgangImWorker(falsch);
|
||||
const fehler = (await prisma.vorgang.findUniqueOrThrow({ where: { id: falsch } })).fehler ?? "";
|
||||
expect(fehler).toMatch(/Passphrase/);
|
||||
expect(fehler).not.toContain("Zz9-unpassend");
|
||||
|
||||
const richtig = await paarImportAnfordern(
|
||||
{ name: "Richtig" },
|
||||
paar.privateKeyOpenssh,
|
||||
passphrase,
|
||||
akteur,
|
||||
);
|
||||
await vorgangImWorker(richtig);
|
||||
const { schluesselId } = (await prisma.vorgang.findUniqueOrThrow({ where: { id: richtig } }))
|
||||
.ergebnis as {
|
||||
schluesselId: string;
|
||||
};
|
||||
expect((await schluesselDetail(schluesselId))?.passphraseGeschuetzt).toBe(true);
|
||||
expect((await privateKeyLaden(schluesselId)).toString("utf8")).toBe(paar.privateKeyOpenssh);
|
||||
expect(await datenbankAlsText()).not.toContain(passphrase);
|
||||
});
|
||||
|
||||
it("lehnt Fremdformate im Web und Duplikate im Worker ab", async () => {
|
||||
const pem = "-----BEGIN RSA PRIVATE KEY-----\nMIIE\n-----END RSA PRIVATE KEY-----";
|
||||
const fehler = await fachfehlerVon(paarImportAnfordern({ name: "PEM" }, pem, undefined, akteur));
|
||||
expect(fehler.message).not.toContain("MIIE");
|
||||
|
||||
const paar = await schluesselpaarErzeugen("ED25519", { kommentar: "x" });
|
||||
await publicKeyImportieren({ name: "Schon da", publicKey: paar.publicKeyZeile }, akteur);
|
||||
const id = await paarImportAnfordern({ name: "Nochmal" }, paar.privateKeyOpenssh, undefined, akteur);
|
||||
await vorgangImWorker(id);
|
||||
expect((await prisma.vorgang.findUniqueOrThrow({ where: { id } })).fehler).toMatch(/„Schon da“/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bearbeiten und Lesemodell", () => {
|
||||
it("aendert Metadaten, protokolliert nur Geaendertes und schuetzt den Status", async () => {
|
||||
const neu = await publicKeyImportieren(
|
||||
{ name: "Alt", publicKey: zeile("ssh-ed25519", ed25519Blob()) },
|
||||
akteur,
|
||||
);
|
||||
await schluesselBearbeiten(neu.id, { name: "Neu", tags: "a b", status: "ABGELAUFEN" }, akteur);
|
||||
const s = await schluesselDetail(neu.id);
|
||||
expect(s).toMatchObject({ name: "Neu", tags: ["a", "b"], status: "ABGELAUFEN", slug: "alt" });
|
||||
const eintrag = await prisma.protokoll.findFirstOrThrow({ where: { aktion: "schluessel.geaendert" } });
|
||||
expect(Object.keys(eintrag.details as object).sort()).toEqual(["name", "status", "tags"]);
|
||||
|
||||
await prisma.schluessel.update({
|
||||
where: { id: neu.id },
|
||||
data: { status: "WIDERRUFEN" },
|
||||
select: { id: true },
|
||||
});
|
||||
await fachfehlerVon(schluesselBearbeiten(neu.id, { name: "Neu", status: "AKTIV" }, akteur));
|
||||
});
|
||||
|
||||
it("filtert die Liste und liefert keine Chiffrat-Felder", async () => {
|
||||
await publicKeyImportieren(
|
||||
{
|
||||
name: "Person A",
|
||||
kategorie: "PERSON",
|
||||
publicKey: zeile("ssh-ed25519", ed25519Blob()),
|
||||
tags: "laptop",
|
||||
},
|
||||
akteur,
|
||||
);
|
||||
const vorgangId = await erzeugenAnfordern({ name: "Dienst B" }, undefined, akteur);
|
||||
await vorgangImWorker(vorgangId);
|
||||
|
||||
expect((await schluesselListe()).map((s) => s.name)).toEqual(["Dienst B", "Person A"]);
|
||||
expect((await schluesselListe({ kategorie: "PERSON" })).map((s) => s.name)).toEqual(["Person A"]);
|
||||
expect((await schluesselListe({ q: "laptop" })).map((s) => s.name)).toEqual(["Person A"]);
|
||||
expect((await schluesselListe({ status: "WIDERRUFEN" })).length).toBe(0);
|
||||
expect((await schluesselListe({ kategorie: "QUATSCH" })).length).toBe(2);
|
||||
|
||||
const liste = await schluesselListe();
|
||||
const dienst = liste.find((s) => s.name === "Dienst B")!;
|
||||
expect(dienst.privatGespeichert).toBe(true);
|
||||
const detail = await schluesselDetail(dienst.id);
|
||||
for (const antwort of [liste, detail]) {
|
||||
const text = JSON.stringify(antwort);
|
||||
expect(text).not.toMatch(/privatVerschluesselt|dekVerschluesselt|masterKeyVersion/);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user