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>
95 lines
4.6 KiB
TypeScript
95 lines
4.6 KiB
TypeScript
import { expect, test, type Page } from "@playwright/test";
|
||
import "dotenv/config";
|
||
import { schluesselpaarErzeugen } from "../../src/lib/ssh/openssh";
|
||
|
||
/**
|
||
* Schluessel im gebauten Stand mit laufendem Worker (#012–#014). Testdaten heissen zz-e2e-…
|
||
* und bleiben liegen (Schluessel werden nicht geloescht, nur spaeter widerrufen).
|
||
*/
|
||
test.setTimeout(90_000);
|
||
|
||
async function oidcAnmelden(page: Page, rolle: "admin" | "operator" | "reader") {
|
||
await page.goto("/anmelden");
|
||
await page.getByRole("link", { name: /Mit Mock-OIDC anmelden/ }).click();
|
||
await page.locator(`button[data-rolle="${rolle}"]`).click();
|
||
await page.getByRole("button", { name: "Anmelden", exact: true }).click();
|
||
await expect(page.getByRole("heading", { name: "Übersicht" })).toBeVisible();
|
||
}
|
||
|
||
function eindeutig(): string {
|
||
return `zz-e2e-${Date.now().toString(36)}`;
|
||
}
|
||
|
||
test("Operator erzeugt einen Schlüssel und landet im Detail", async ({ page }) => {
|
||
const name = `${eindeutig()} Backup`;
|
||
await oidcAnmelden(page, "operator");
|
||
await page.goto("/schluessel/neu");
|
||
await page.getByLabel("Zweck").fill(name);
|
||
await page.getByLabel("Tags").fill("e2e");
|
||
await page.getByRole("button", { name: "Schlüssel erzeugen" }).click();
|
||
|
||
await expect(page.getByRole("heading", { name })).toBeVisible({ timeout: 30_000 });
|
||
await expect(page).toHaveURL(/\/schluessel\/[^/]+$/);
|
||
await expect(page.getByText("verschlüsselt gespeichert")).toBeVisible();
|
||
await expect(page.getByText("ed25519", { exact: true })).toBeVisible();
|
||
await expect(page.getByLabel("Public Key", { exact: true })).toContainText("ssh-ed25519 AAAA");
|
||
await expect(page.getByLabel("Public Key", { exact: true })).toContainText(
|
||
`sshkv:${name.toLowerCase().replace(/ /g, "-")}`,
|
||
);
|
||
|
||
await page.goto(`/schluessel?q=${encodeURIComponent(name)}`);
|
||
await expect(page.getByRole("link", { name })).toBeVisible();
|
||
});
|
||
|
||
test("Public Key importieren, Duplikat wird erkannt", async ({ page }) => {
|
||
const name = `${eindeutig()} Laptop`;
|
||
const paar = await schluesselpaarErzeugen("ED25519", { kommentar: "e2e@laptop" });
|
||
await oidcAnmelden(page, "operator");
|
||
await page.goto("/schluessel/neu");
|
||
await page.getByRole("radio", { name: "Importieren" }).click();
|
||
await page.getByLabel("Zweck").fill(name);
|
||
await page.getByLabel("Public Key").fill(paar.publicKeyZeile);
|
||
await page.locator(`input[name="kategorie"][value="PERSON"]`).check();
|
||
await page.getByRole("button", { name: "Schlüssel importieren" }).click();
|
||
|
||
await expect(page.getByRole("heading", { name })).toBeVisible();
|
||
await expect(page.getByText("nicht gespeichert (Personen-Key)")).toBeVisible();
|
||
await expect(page.getByText(paar.fingerprint)).toBeVisible();
|
||
|
||
await page.goto("/schluessel/neu");
|
||
await page.getByRole("radio", { name: "Importieren" }).click();
|
||
await page.getByLabel("Zweck").fill(`${name} nochmal`);
|
||
await page.getByLabel("Public Key").fill(paar.publicKeyZeile);
|
||
await page.getByRole("button", { name: "Schlüssel importieren" }).click();
|
||
await expect(page.getByRole("alert").first()).toContainText(`bereits verwaltet: „${name}“`);
|
||
});
|
||
|
||
test("Leser sieht Schlüssel, darf aber keine anlegen", async ({ page }) => {
|
||
await oidcAnmelden(page, "reader");
|
||
await page.goto("/schluessel");
|
||
await expect(page.getByRole("heading", { name: "Schlüssel", exact: true })).toBeVisible();
|
||
await expect(page.getByRole("link", { name: "Schlüssel anlegen" })).toHaveCount(0);
|
||
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);
|
||
});
|