fix: enforce public contrast and audit rank errors
This commit is contained in:
1 parent
e1381c0f40
commit
b695a33ead
12 files changed
+184
-29
No files matched your search
@@ -0,0 +1,23 @@
|
||||
export interface AuthorizationEvent {
|
||||
kind: "permission.denied" | "permission.load_error";
|
||||
userId: number;
|
||||
username?: string;
|
||||
rank: number;
|
||||
permission?: string;
|
||||
source: string;
|
||||
reason: string;
|
||||
error?: unknown;
|
||||
}
|
||||
|
||||
const clean = (value: string) => value.replace(/(token|password|secret|cookie|authorization|select|insert|update|delete)[^\s]*/gi, "[REDACTED]").slice(0, 160);
|
||||
|
||||
export function authorizationActivity(event: AuthorizationEvent) {
|
||||
const description = [
|
||||
`user=${clean(event.username ?? String(event.userId))}`,
|
||||
`rank=${event.rank}`,
|
||||
event.permission ? `permission=${clean(event.permission)}` : null,
|
||||
`source=${clean(event.source)}`,
|
||||
`reason=${clean(event.reason)}`,
|
||||
].filter(Boolean).join("; ");
|
||||
return { staffId: event.userId, action: event.kind, description, targetType: "user", targetId: event.userId };
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { authorizationActivity } from "@/lib/admin/authorization-event";
|
||||
|
||||
describe("authorizationActivity", () => {
|
||||
it("creates a safe rank-aware denial record", () => {
|
||||
const record = authorizationActivity({ kind: "permission.denied", userId: 42, username: "admin", rank: 11, permission: "admin.logs.view", source: "/admin/logs", reason: "missing permission" });
|
||||
expect(record.action).toBe("permission.denied");
|
||||
expect(record.description).toContain("rank=11");
|
||||
expect(record.description).toContain("permission=admin.logs.view");
|
||||
});
|
||||
|
||||
it("redacts secrets and technical details", () => {
|
||||
const record = authorizationActivity({ kind: "permission.load_error", userId: 42, rank: 11, source: "permissions", reason: "token=abc password=hunter2 SELECT * FROM users" });
|
||||
expect(record.description).not.toMatch(/abc|hunter2|SELECT/i);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import { logServerError } from "@/lib/server-log";
|
||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||
import { authorizationActivity, type AuthorizationEvent } from "@/lib/admin/authorization-event";
|
||||
|
||||
export async function logAuthorizationEvent(event: AuthorizationEvent): Promise<void> {
|
||||
const correlationId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
await logStaffActivity({ ...authorizationActivity(event), description: `${authorizationActivity(event).description}; correlation=${correlationId}` });
|
||||
if (event.error) logServerError(event.kind, event.error, { correlationId, userId: event.userId, rank: event.rank, permission: event.permission ?? null, source: event.source });
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildStaffActivityWhere } from "@/lib/admin/log-filters";
|
||||
|
||||
describe("buildStaffActivityWhere", () => {
|
||||
it("filters authorization events by prefix", () => {
|
||||
expect(buildStaffActivityWhere({ authorizationOnly: true })).toEqual({ action: { startsWith: "permission." } });
|
||||
});
|
||||
it("combines staff and search filters", () => {
|
||||
const result = buildStaffActivityWhere({ q: "rank", staffId: 11, authorizationOnly: true });
|
||||
expect(result).toMatchObject({ userId: 11n, action: { startsWith: "permission." } });
|
||||
expect(result.OR).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { Prisma } from "@/generated/prisma/client";
|
||||
|
||||
export interface StaffActivityFilters { q?: string; staffId?: number | null; action?: string | null; authorizationOnly?: boolean }
|
||||
|
||||
export function buildStaffActivityWhere(filters: StaffActivityFilters): Prisma.StaffActivitiesWhereInput {
|
||||
const where: Prisma.StaffActivitiesWhereInput = {};
|
||||
if (filters.q?.trim()) where.OR = [
|
||||
{ action: { contains: filters.q.trim() } },
|
||||
{ description: { contains: filters.q.trim() } },
|
||||
{ ipAddress: { contains: filters.q.trim() } },
|
||||
];
|
||||
if (filters.staffId) where.userId = BigInt(filters.staffId);
|
||||
if (filters.authorizationOnly) where.action = { startsWith: "permission." };
|
||||
else if (filters.action) where.action = { contains: filters.action };
|
||||
return where;
|
||||
}
|
||||
Reference in new issue
Block a user