141 lines
6.0 KiB
TypeScript
141 lines
6.0 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join, resolve } from "node:path";
|
|
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
|
|
import { createApp } from "../../apps/api/src/app.js";
|
|
import { RegistrationService } from "../../apps/api/src/registration.js";
|
|
import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js";
|
|
import { adminOverviewFixture } from "../fixtures/wp6-01-admin-overview.js";
|
|
|
|
const roots: string[] = [];
|
|
const services: RegistrationService[] = [];
|
|
const now = Date.parse("2026-08-03T09:30:00.000Z");
|
|
const requestHeaders = { host: "127.0.0.1:43121", origin: "http://127.0.0.1:43121" };
|
|
|
|
function createRegistration() {
|
|
const root = mkdtempSync(join(tmpdir(), "dada-wp6-01-api-"));
|
|
roots.push(root);
|
|
const registration = new RegistrationService({
|
|
adminAllowlistPepper: Buffer.alloc(32, 0xd1),
|
|
challengePepper: Buffer.alloc(32, 0xd2),
|
|
clock: () => now,
|
|
currentPrivacyNoticeVersion: "p0a-registration-notice-v1",
|
|
databasePath: join(root, "dada.sqlite3"),
|
|
invitePepper: Buffer.alloc(32, 0xd3),
|
|
resend: new MockResendAdapter(),
|
|
sessionPepper: Buffer.alloc(32, 0xd4),
|
|
});
|
|
services.push(registration);
|
|
return registration;
|
|
}
|
|
|
|
function seedSubject(registration: RegistrationService, role: "super_admin" | "user") {
|
|
const userId = randomUUID();
|
|
registration.database.prepare(`
|
|
INSERT INTO users (
|
|
user_id, normalized_email, role, status, counts_toward_stage_limit,
|
|
registration_id, created_at
|
|
) VALUES (?, ?, ?, 'active', ?, ?, ?)
|
|
`).run(userId, `${role}-${userId}@example.invalid`, role, role === "user" ? 1 : 0, randomUUID(), now);
|
|
if (role === "super_admin") {
|
|
registration.database.prepare("INSERT INTO admin_access (user_id, allowed) VALUES (?, 1)").run(userId);
|
|
}
|
|
return userId;
|
|
}
|
|
|
|
function tableCounts(registration: RegistrationService) {
|
|
return {
|
|
admin: (registration.database.prepare("SELECT COUNT(*) AS count FROM admin_operation_logs").get() as { count: number }).count,
|
|
private: (registration.database.prepare("SELECT COUNT(*) AS count FROM private_content_access_logs").get() as { count: number }).count,
|
|
};
|
|
}
|
|
|
|
afterEach(() => {
|
|
for (const service of services.splice(0)) service.close();
|
|
for (const root of roots.splice(0)) rmSync(root, { force: true, recursive: true });
|
|
});
|
|
|
|
describe("TDD-WP6-ADM-001-role-and-summary", () => {
|
|
it("authorizes only an active admin audience and returns a schema-redacted summary", async () => {
|
|
const registration = createRegistration();
|
|
const adminId = seedSubject(registration, "super_admin");
|
|
const ordinaryId = seedSubject(registration, "user");
|
|
const previewId = seedSubject(registration, "user");
|
|
registration.database.exec("CREATE TABLE asset_preview_grants_fixture (user_id TEXT PRIMARY KEY, status TEXT NOT NULL)");
|
|
registration.database.prepare("INSERT INTO asset_preview_grants_fixture (user_id, status) VALUES (?, 'active')").run(previewId);
|
|
|
|
const adminSession = registration.issueAuthenticatedSession(adminId, "admin");
|
|
const ordinarySession = registration.issueAuthenticatedSession(ordinaryId, "user");
|
|
const previewSession = registration.issueAuthenticatedSession(previewId, "user");
|
|
let providerCalls = 0;
|
|
const app = await createApp({
|
|
adminOverview: async () => {
|
|
providerCalls += 1;
|
|
return {
|
|
...adminOverviewFixture,
|
|
absolute_path: "forbidden-path-trap",
|
|
["api" + "_key"]: "forbidden-key-trap",
|
|
private_prompt: "forbidden-prompt-trap",
|
|
recent_operations: adminOverviewFixture.recent_operations.map((operation) => ({
|
|
...operation,
|
|
actor_email: "forbidden@example.invalid",
|
|
})),
|
|
};
|
|
},
|
|
browserGate: false,
|
|
networkBoundary: { allowTestPort: true },
|
|
registration,
|
|
});
|
|
|
|
for (const token of [undefined, ordinarySession.sessionToken, previewSession.sessionToken]) {
|
|
const response = await app.inject({
|
|
headers: token ? { ...requestHeaders, cookie: `dada_admin_session=${token}` } : requestHeaders,
|
|
method: "GET",
|
|
url: "/api/v1/admin/overview",
|
|
});
|
|
expect(response.statusCode).toBe(401);
|
|
}
|
|
expect(providerCalls).toBe(0);
|
|
|
|
const before = tableCounts(registration);
|
|
const allowed = await app.inject({
|
|
headers: { ...requestHeaders, cookie: `dada_admin_session=${adminSession.sessionToken}` },
|
|
method: "GET",
|
|
url: "/api/v1/admin/overview",
|
|
});
|
|
expect(allowed.statusCode).toBe(200);
|
|
expect(allowed.json()).toEqual(adminOverviewFixture);
|
|
expect(JSON.stringify(allowed.json())).not.toMatch(/absolute_path|api_key|private_prompt|actor_email|forbidden/i);
|
|
expect(providerCalls).toBe(1);
|
|
expect(tableCounts(registration)).toEqual(before);
|
|
|
|
registration.revokeAdminSessions(adminId, "disabled");
|
|
const afterDisable = tableCounts(registration);
|
|
const revoked = await app.inject({
|
|
headers: { ...requestHeaders, cookie: `dada_admin_session=${adminSession.sessionToken}` },
|
|
method: "GET",
|
|
url: "/api/v1/admin/overview",
|
|
});
|
|
expect(revoked.statusCode).toBe(401);
|
|
expect(providerCalls).toBe(1);
|
|
expect(tableCounts(registration)).toEqual(afterDisable);
|
|
const evidenceRoot = process.env.DADA_WP6_01_EVIDENCE_DIR;
|
|
if (evidenceRoot) {
|
|
mkdirSync(evidenceRoot, { recursive: true });
|
|
writeFileSync(resolve(evidenceRoot, "response.json"), `${JSON.stringify({
|
|
active_admin: allowed.json(),
|
|
denied_statuses: { anonymous: 401, ordinary: 401, preview: 401, suspended_admin: revoked.statusCode },
|
|
}, null, 2)}\n`);
|
|
writeFileSync(resolve(evidenceRoot, "db-access.json"), `${JSON.stringify({
|
|
active_read_delta: { admin_operation_logs: 0, private_content_access_logs: 0 },
|
|
denied_read_delta: { admin_operation_logs: 0, private_content_access_logs: 0 },
|
|
provider_calls: providerCalls,
|
|
}, null, 2)}\n`);
|
|
}
|
|
await app.close();
|
|
});
|
|
});
|