feat(personen): Personen und Self-Service Meine Schluessel (#013)

Personen lassen sich anlegen, bearbeiten und archivieren; ihr Detail
zeigt ihre Schluessel. Unter "Meine Schluessel" hinterlegt jede Rolle
eigene Public Keys (Freigabe Frage 4), wirksam erst nach Zuweisung
durch einen Operator oder Admin.

Warum so:
- Die eigene Person entsteht beim ersten Aufruf oder wird verknuepft:
  per benutzerId, sonst per E-Mail - aber nur, wenn der OIDC-Anbieter
  die Adresse bestaetigt hat. Dafuer merkt sich Benutzer jetzt
  emailBestaetigt (Migration); sonst koennte jemand mit selbst
  gewaehlter Adresse die Keys einer fremden Person sehen.
- Die Self-Service-Services nehmen nur den angemeldeten Benutzer und
  leiten die Person selbst ab; fremde Schluessel-IDs gelten als nicht
  vorhanden.
- Entfernen nur fuer eigene, selbst hinterlegte Keys ohne Verwendung,
  bedingt geloescht, damit eine gleichzeitige Zuweisung nicht per
  Cascade verschwindet.
- Archivieren statt Loeschen: wer bis wann welchen Key hatte, bleibt
  nachvollziehbar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 10:38:55 +02:00
co-authored by Claude Opus 5.5
parent 263143134f
commit b5b43f41f6
22 changed files with 1569 additions and 10 deletions
+20
View File
@@ -72,3 +72,23 @@ test("Leser sieht Schlüssel, darf aber keine anlegen", async ({ page }) => {
await page.goto("/schluessel/neu");
await expect(page).toHaveURL(/\/kein-zugriff/);
});
test("Leser hinterlegt einen eigenen Public Key und entfernt ihn wieder", async ({ page }) => {
const name = `${eindeutig()} Laptop`;
const paar = await schluesselpaarErzeugen("ED25519", { kommentar: "reader@laptop" });
await oidcAnmelden(page, "reader");
await page.getByRole("link", { name: "Meine Schlüssel" }).first().click();
await expect(page.getByRole("heading", { name: "Meine Schlüssel" })).toBeVisible();
await expect(page.getByText(/Wirksam erst, wenn ein Operator oder Admin/).first()).toBeVisible();
await page.getByLabel("Bezeichnung").fill(name);
await page.getByLabel("Public Key").fill(paar.publicKeyZeile);
await page.getByRole("button", { name: "Hinterlegen" }).click();
await expect(page.getByText(`Hinterlegt: ${paar.fingerprint}`)).toBeVisible();
const eintrag = page.locator("li", { hasText: name });
await expect(eintrag.getByText("noch nicht zugewiesen")).toBeVisible();
await eintrag.getByRole("button", { name: `${name} entfernen` }).click();
await page.getByRole("dialog").getByRole("button", { name: "Entfernen", exact: true }).click();
await expect(page.locator("li", { hasText: name })).toHaveCount(0);
});