SPB Git

Repository Public MIT

audit.ts 1.6 KB 38 lines typescript

1import { actorLabel, userOf, type Ctx } from "./context";23export interface AuditInput {4  action: string;5  targetType?: string;6  targetId?: string;7  targetLabel?: string;8  result?: "success" | "denied" | "error";9  detail?: Record<string, unknown>;10}1112const SECRET_KEY = /pass|secret|token|hash|authorization|cookie|pepper|csrf/i;1314/** Defence in depth: values under secret-looking keys never reach the audit table. */15export function redactDetail(v: unknown, depth = 0): unknown {16  if (depth > 6) return "[truncated]";17  if (Array.isArray(v)) return v.slice(0, 100).map((x) => redactDetail(x, depth + 1));18  if (v && typeof v === "object") {19    const out: Record<string, unknown> = {};20    for (const [k, val] of Object.entries(v)) out[k] = SECRET_KEY.test(k) ? "[redacted]" : redactDetail(val, depth + 1);21    return out;22  }23  if (typeof v === "string") {24    const s = v.replace(/skr_[a-z0-9]{10}_[A-Za-z0-9_-]{43}/g, "skr_[redacted]");25    return s.length > 2000 ? s.slice(0, 2000) + "…" : s;26  }27  return v;28}2930export async function audit(ctx: Ctx, a: AuditInput): Promise<void> {31  const p = ctx.principal;32  await ctx.sql`33    INSERT INTO audit_events (actor_user_id, actor_token_id, actor_label, action, target_type, target_id, target_label, result, ip, user_agent, request_id, detail)34    VALUES (${userOf(p)?.id ?? null}, ${p.kind === "token" ? p.token.id : null}, ${actorLabel(p)}, ${a.action}, ${a.targetType ?? null},35            ${a.targetId ?? null}, ${a.targetLabel ?? null}, ${a.result ?? "success"}, ${ctx.ip}, ${ctx.userAgent.slice(0, 300)}, ${ctx.requestId},36            ${ctx.sql.json(redactDetail(a.detail ?? {}) as never)})`;37}38
Files main
Menu

Theme