feat(audit): extensible event type system with shared constants

This commit is contained in:
SnapOtter
2026-06-13 16:27:53 +08:00
parent 5d2f520d78
commit 37b2b9c2ee
3 changed files with 49 additions and 26 deletions
+11 -26
View File
@@ -8,36 +8,14 @@ export function sanitizeAuditInput(raw: string): string {
return raw.replace(/[<>&"']/g, "").slice(0, MAX_AUDIT_INPUT_LENGTH) || "(empty)"; return raw.replace(/[<>&"']/g, "").slice(0, MAX_AUDIT_INPUT_LENGTH) || "(empty)";
} }
type AuditEvent =
| "LOGIN_SUCCESS"
| "LOGIN_FAILED"
| "LOGOUT"
| "PASSWORD_CHANGED"
| "PASSWORD_RESET"
| "USER_CREATED"
| "USER_DELETED"
| "USER_UPDATED"
| "FILE_UPLOADED"
| "FILE_DELETED"
| "API_KEY_CREATED"
| "API_KEY_DELETED"
| "ROLE_CREATED"
| "ROLE_UPDATED"
| "ROLE_DELETED"
| "SETTINGS_UPDATED"
| "OIDC_LOGIN_SUCCESS"
| "OIDC_USER_CREATED"
| "OIDC_USER_LINKED"
| "OIDC_LOGIN_FAILED";
/** /**
* Emit a structured audit log entry for security-relevant events. * Emit a structured audit log entry for security-relevant events.
* *
* Dual-writes: structured stdout log (for aggregators) + SQLite row. * Dual-writes: structured stdout log (for aggregators) + DB row.
*/ */
export async function auditLog( export async function auditLog(
logger: FastifyBaseLogger, logger: FastifyBaseLogger,
event: AuditEvent, event: string,
details: Record<string, unknown> = {}, details: Record<string, unknown> = {},
ip: string | null = null, ip: string | null = null,
): Promise<void> { ): Promise<void> {
@@ -64,18 +42,25 @@ export async function auditLog(
} }
} }
function deriveTargetType(event: AuditEvent): string | null { function deriveTargetType(event: string): string | null {
if ( if (
event.startsWith("USER_") || event.startsWith("USER_") ||
event.startsWith("LOGIN") || event.startsWith("LOGIN") ||
event.startsWith("PASSWORD") || event.startsWith("PASSWORD") ||
event.startsWith("OIDC_") || event.startsWith("OIDC_") ||
event.startsWith("SAML_") ||
event.startsWith("SCIM_") ||
event.startsWith("MFA_") ||
event === "LOGOUT" event === "LOGOUT"
) )
return "user"; return "user";
if (event.startsWith("API_KEY")) return "api_key"; if (event.startsWith("API_KEY")) return "api_key";
if (event.startsWith("FILE")) return "file"; if (event.startsWith("FILE")) return "file";
if (event.startsWith("ROLE")) return "role"; if (event.startsWith("ROLE")) return "role";
if (event === "SETTINGS_UPDATED") return "setting"; if (event === "SETTINGS_UPDATED" || event === "IP_ALLOWLIST_UPDATED") return "setting";
if (event.startsWith("TOOL_") || event.startsWith("BATCH_") || event.startsWith("PIPELINE_"))
return "tool";
if (event.startsWith("LEGAL_HOLD")) return "compliance";
if (event.startsWith("SIEM_") || event.startsWith("WEBHOOK_")) return "integration";
return null; return null;
} }
+37
View File
@@ -0,0 +1,37 @@
export const CORE_AUDIT_EVENTS = [
"LOGIN_SUCCESS",
"LOGIN_FAILED",
"LOGOUT",
"PASSWORD_CHANGED",
"PASSWORD_RESET",
"USER_CREATED",
"USER_DELETED",
"USER_UPDATED",
"FILE_UPLOADED",
"FILE_DELETED",
"API_KEY_CREATED",
"API_KEY_DELETED",
"ROLE_CREATED",
"ROLE_UPDATED",
"ROLE_DELETED",
"SETTINGS_UPDATED",
"OIDC_LOGIN_SUCCESS",
"OIDC_USER_CREATED",
"OIDC_USER_LINKED",
"OIDC_LOGIN_FAILED",
"TOOL_EXECUTED",
"BATCH_EXECUTED",
"PIPELINE_EXECUTED",
] as const;
export type CoreAuditEvent = (typeof CORE_AUDIT_EVENTS)[number];
export const ALL_AUDIT_EVENTS = [...CORE_AUDIT_EVENTS] as string[];
export function registerAuditEvents(events: string[]) {
for (const e of events) {
if (!ALL_AUDIT_EVENTS.includes(e)) {
ALL_AUDIT_EVENTS.push(e);
}
}
}
+1
View File
@@ -1,6 +1,7 @@
export * from "./analytics/consent.js"; export * from "./analytics/consent.js";
export * from "./analytics/events.js"; export * from "./analytics/events.js";
export * from "./analytics/types.js"; export * from "./analytics/types.js";
export * from "./audit-events.js";
export * from "./constants.js"; export * from "./constants.js";
export * from "./features.js"; export * from "./features.js";
export * from "./i18n/index.js"; export * from "./i18n/index.js";