Files
pleno-vue/tests/e2e/superuser-security.spec.ts
T

265 lines
8.8 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { apiPathPattern, mockApi, primeMockSession } from "./support/network.js";
function json(body: unknown, status = 200) {
return {
status,
contentType: "application/json",
body: JSON.stringify(body),
};
}
const policyRules = [
{
id: 1,
rule_key: "failed_login_attempts",
enabled: true,
threshold_count: 5,
window_seconds: 900,
mode: "observe",
exempt_permission_nodes: ["superuser_security_limits_exempt"],
},
{
id: 2,
rule_key: "bookings_created",
enabled: true,
threshold_count: 25,
window_seconds: 86400,
mode: "observe",
exempt_permission_nodes: [],
},
{
id: 3,
rule_key: "vehicles_created",
enabled: true,
threshold_count: 20,
window_seconds: 86400,
mode: "observe",
exempt_permission_nodes: [],
},
{
id: 4,
rule_key: "requests_per_ip",
enabled: true,
threshold_count: 300,
window_seconds: 60,
mode: "observe",
exempt_permission_nodes: [],
},
{
id: 5,
rule_key: "requests_per_customer",
enabled: true,
threshold_count: 600,
window_seconds: 60,
mode: "observe",
exempt_permission_nodes: [],
},
];
const baseIncident = {
id: 7,
incident_key: "firewall:12:test",
type: "firewall_block",
severity: "high",
status: "open",
title: "Block firewall rule matched: ip 203.0.113.9",
source_ip: "203.0.113.9",
customer_number: 12345,
route_path: "/auth/login",
route_template: "/auth/login",
occurrence_count: 3,
first_seen_at: "2026-07-13 10:00:00",
last_seen_at: "2026-07-13 10:05:00",
notes: [],
};
test.describe("Superuser system security", () => {
test.beforeEach(async ({ page }) => {
await page.addInitScript(() => {
window.localStorage.setItem("locale", "en");
});
await mockApi(page, {
authenticated: true,
permissions: [
"superuser",
"superuser_security_view",
"superuser_security_settings_manage",
"superuser_security_firewall_manage",
"superuser_security_incidents_manage",
],
});
await primeMockSession(page, { token: "superuser-security-token", bootPath: null });
});
test("supports overview, firewall, settings, and incident management", async ({ page }) => {
const settingsPayloads: Array<Record<string, unknown>> = [];
const firewallCreatePayloads: Array<Record<string, unknown>> = [];
const incidentPatchPayloads: Array<Record<string, unknown>> = [];
const incidentNotePayloads: Array<Record<string, unknown>> = [];
let firewallRules = [
{
id: 12,
action: "watch",
target_type: "ip",
target_value: "203.0.113.9",
route_pattern: "/auth/*",
priority: 50,
reason: "Repeated login failures",
enabled: true,
expires_at: null,
},
];
let selectedIncident = { ...baseIncident };
await page.route(apiPathPattern("/superuser/system/security/summary"), async (route) => {
await route.fulfill(
json({
success: true,
data: {
settings: { mode: "observe", rules: policyRules },
firewall: { active_rules: firewallRules.length, block_rules: 0, watch_rules: firewallRules.length },
incidents: { open: 1, acknowledged: 0, resolved: 0, recent: [selectedIncident] },
},
meta: {},
includes: {},
})
);
});
await page.route(apiPathPattern("/superuser/system/security/settings"), async (route) => {
if (route.request().method() === "PATCH") {
settingsPayloads.push(route.request().postDataJSON() as Record<string, unknown>);
}
await route.fulfill(
json({
success: true,
data: { mode: "observe", rules: policyRules },
meta: {},
includes: {},
})
);
});
await page.route(apiPathPattern("/superuser/system/security/firewall-rules"), async (route) => {
if (route.request().method() === "POST") {
const payload = route.request().postDataJSON() as Record<string, unknown>;
firewallCreatePayloads.push(payload);
firewallRules = [...firewallRules, { id: 13, ...payload }];
}
await route.fulfill(
json({
success: true,
data: { rules: firewallRules },
meta: {},
includes: {},
})
);
});
await page.route(apiPathPattern("/superuser/system/security/incidents"), async (route) => {
await route.fulfill(
json({
success: true,
data: { incidents: [selectedIncident] },
meta: {},
includes: {},
})
);
});
await page.route(apiPathPattern("/superuser/system/security/incidents/7"), async (route) => {
if (route.request().method() === "PATCH") {
const payload = route.request().postDataJSON() as Record<string, unknown>;
incidentPatchPayloads.push(payload);
selectedIncident = { ...selectedIncident, status: String(payload.status || selectedIncident.status) };
}
await route.fulfill(
json({
success: true,
data: selectedIncident,
meta: {},
includes: {},
})
);
});
await page.route(apiPathPattern("/superuser/system/security/incidents/7/notes"), async (route) => {
const payload = route.request().postDataJSON() as Record<string, unknown>;
incidentNotePayloads.push(payload);
selectedIncident = {
...selectedIncident,
notes: [
{
id: 1,
incident_id: 7,
note: String(payload.note),
created_at: "2026-07-13 10:10:00",
},
],
};
await route.fulfill(
json({
success: true,
data: selectedIncident,
meta: {},
includes: {},
})
);
});
await page.goto("/superuser/system/security/overview");
await expect(page.getByTestId("superuser-security-page")).toBeVisible();
await expect(page.getByText("Security overview")).toBeVisible();
await expect(page.getByRole("link", { name: "Open incidents" })).toBeVisible();
await expect(page.getByText("Block firewall rule matched: ip 203.0.113.9")).toBeVisible();
await page.getByTestId("security-tab-settings").click();
await expect(page).toHaveURL(/\/superuser\/system\/security\/settings$/);
await expect(page.getByText("Max failed login attempts")).toBeVisible();
await page.getByTestId("security-setting-threshold-failed_login_attempts").fill("6");
await page
.getByTestId("security-setting-exemptions-failed_login_attempts")
.fill("superuser_security_limits_exempt, support_login_bypass");
await page.getByRole("button", { name: "Save" }).click();
await expect.poll(() => settingsPayloads.length).toBe(1);
expect(settingsPayloads[0].rules).toEqual(
expect.arrayContaining([
expect.objectContaining({
rule_key: "failed_login_attempts",
threshold_count: 6,
exempt_permission_nodes: ["superuser_security_limits_exempt", "support_login_bypass"],
}),
])
);
await page.getByTestId("security-tab-firewall").click();
await expect(page.getByText("Firewall management")).toBeVisible();
await page.getByTestId("security-firewall-action").selectOption("block");
await page.getByTestId("security-firewall-target-type").selectOption("ip");
await page.getByTestId("security-firewall-target-value").fill("198.51.100.44");
await page.getByTestId("security-firewall-route-pattern").fill("/auth/*");
await page.getByTestId("security-firewall-reason").fill("Temporary login abuse block");
await page.getByRole("button", { name: "Create" }).click();
await expect.poll(() => firewallCreatePayloads.length).toBe(1);
expect(firewallCreatePayloads[0]).toMatchObject({
action: "block",
target_type: "ip",
target_value: "198.51.100.44",
route_pattern: "/auth/*",
});
await page.getByTestId("security-tab-incidents").click();
await expect(page.getByText("Incident management")).toBeVisible();
await page.getByText("Block firewall rule matched: ip 203.0.113.9").click();
await expect(page.getByTestId("security-incident-detail")).toContainText("203.0.113.9");
await page.getByRole("button", { name: "Resolve" }).click();
await expect.poll(() => incidentPatchPayloads.length).toBe(1);
expect(incidentPatchPayloads[0]).toMatchObject({ status: "resolved" });
await page.getByTestId("security-incident-note").fill("Confirmed temporary abusive source.");
await page.getByRole("button", { name: "Add note" }).click();
await expect.poll(() => incidentNotePayloads.length).toBe(1);
expect(incidentNotePayloads[0]).toMatchObject({ note: "Confirmed temporary abusive source." });
});
});