feat(authz): add pluggable authorization audit sink

This commit is contained in:
2026-08-04 17:10:03 +05:30
parent dc0771308a
commit f033197850
2 changed files with 87 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
import type { AuthzScope } from "./types.ts";
export interface AuthzAuditEvent {
subjectId?: string;
scope?: AuthzScope;
permission: string;
allowed: boolean;
reason?: string;
policy?: string;
/** Epoch milliseconds. */
at: number;
}
export interface AuthzAuditSink {
record(event: AuthzAuditEvent): void | Promise<void>;
}
export interface MemoryAuditSink extends AuthzAuditSink {
events: AuthzAuditEvent[];
clear(): void;
}
export function memoryAuditSink(): MemoryAuditSink {
const events: AuthzAuditEvent[] = [];
return {
events,
record: (event) => void events.push(event),
clear: () => void events.splice(0, events.length),
};
}
export function consoleAuditSink(): AuthzAuditSink {
return {
record(event) {
const verdict = event.allowed ? "allow" : "deny";
console.info(
`[wrnexus:authz] ${verdict} ${event.permission} subject=${event.subjectId ?? "anonymous"}` +
`${event.scope?.tenantId ? ` tenant=${event.scope.tenantId}` : ""}` +
`${event.reason ? ` reason=${event.reason}` : ""}`,
);
},
};
}
/** Record without ever letting a sink failure escape into the request path. */
export function safeRecord(sink: AuthzAuditSink | undefined, event: AuthzAuditEvent): void {
if (!sink) return;
try {
const result = sink.record(event);
if (result instanceof Promise) {
result.catch((error) => console.warn("[wrnexus:authz] audit sink failed", error));
}
} catch (error) {
console.warn("[wrnexus:authz] audit sink failed", error);
}
}