feat(authz): add pluggable authorization audit sink
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user