o134 commited on
Commit
1396b1e
·
verified ·
1 Parent(s): 160d722

Upload api-server\src\lib\audit-logger.ts with huggingface_hub

Browse files
Files changed (1) hide show
  1. api-server//src//lib//audit-logger.ts +104 -0
api-server//src//lib//audit-logger.ts ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { logger } from "./logger.js";
2
+ import { Request } from "express";
3
+ import { db, auditLogsTable } from "@workspace/db";
4
+
5
+ export type AuditAction =
6
+ | "login_success" | "LOGIN_SUCCESS"
7
+ | "login_failure" | "LOGIN_FAILURE"
8
+ | "unauthorized_access"
9
+ | "role_change" | "USER_ROLE_CHANGE" | "USER_CREATE"
10
+ | "task_created" | "TASK_CREATE"
11
+ | "task_updated" | "TASK_STATUS_CHANGE"
12
+ | "task_deleted"
13
+ | "client_created" | "CLIENT_CREATE"
14
+ | "client_updated" | "CLIENT_UPDATE" | "CLIENT_TOGGLE_ACTIVE"
15
+ | string;
16
+
17
+ export interface AuditLogOptions {
18
+ userId?: number | string;
19
+ action: AuditAction;
20
+ entityType?: string;
21
+ entityId?: number;
22
+ details?: Record<string, any>;
23
+ req?: Request;
24
+ }
25
+
26
+ function mapActionToEvent(action: string): string {
27
+ const upper = action.toUpperCase();
28
+ if (upper === "LOGIN_SUCCESS") return "login_success";
29
+ if (upper === "LOGIN_FAILURE") return "login_failure";
30
+ if (upper === "UNAUTHORIZED_ACCESS") return "unauthorized_access";
31
+ if (upper === "USER_ROLE_CHANGE" || upper === "ROLE_CHANGE" || upper === "USER_CREATE") return "role_change";
32
+ if (upper === "TASK_CREATE" || upper === "TASK_CREATED") return "task_created";
33
+ if (upper === "TASK_STATUS_CHANGE" || upper === "TASK_UPDATED") return "task_updated";
34
+ if (upper === "TASK_DELETED") return "task_deleted";
35
+ if (upper === "CLIENT_CREATE" || upper === "CLIENT_CREATED") return "client_created";
36
+ if (upper === "CLIENT_UPDATE" || upper === "CLIENT_TOGGLE_ACTIVE" || upper === "CLIENT_UPDATED") return "client_updated";
37
+ return action.toLowerCase();
38
+ }
39
+
40
+ function sanitizeDetails(details?: Record<string, any>): Record<string, any> | undefined {
41
+ if (!details) return undefined;
42
+ const sanitized: Record<string, any> = {};
43
+ for (const [key, value] of Object.entries(details)) {
44
+ const lowerKey = key.toLowerCase();
45
+ if (
46
+ lowerKey.includes("password") ||
47
+ lowerKey.includes("jwt") ||
48
+ lowerKey.includes("token") ||
49
+ lowerKey.includes("secret") ||
50
+ lowerKey.includes("authorization")
51
+ ) {
52
+ sanitized[key] = "[REDACTED]";
53
+ } else if (typeof value === "object" && value !== null) {
54
+ sanitized[key] = sanitizeDetails(value);
55
+ } else {
56
+ sanitized[key] = value;
57
+ }
58
+ }
59
+ return sanitized;
60
+ }
61
+
62
+ export function logAudit(options: AuditLogOptions) {
63
+ const { userId, action, entityType, entityId, details, req } = options;
64
+ const authReq = req as any;
65
+
66
+ let ipAddress = req?.ip || req?.headers["x-forwarded-for"] || null;
67
+ if (Array.isArray(ipAddress)) {
68
+ ipAddress = ipAddress[0];
69
+ }
70
+ const userAgent = req?.headers["user-agent"] || null;
71
+
72
+ const event = mapActionToEvent(action);
73
+ const supabaseUid = authReq?.userUid || details?.supabaseUid || (userId && String(userId).includes("-") ? String(userId) : null);
74
+
75
+ const sanitizedDetails = sanitizeDetails({
76
+ ...details,
77
+ supabaseUid,
78
+ ipAddress,
79
+ userAgent,
80
+ });
81
+
82
+ // Prefer internal integer userId from req.userId or options.userId
83
+ const internalUserId = authReq?.userId ? String(authReq.userId) : (userId && !String(userId).includes("-") ? String(userId) : "system");
84
+
85
+ logger.info({
86
+ audit: true,
87
+ userId: internalUserId,
88
+ event,
89
+ entityType,
90
+ entityId,
91
+ details: sanitizedDetails,
92
+ timestamp: new Date().toISOString()
93
+ }, `[AUDIT] ${event} on ${entityType ?? "system"}${entityId ? ` #${entityId}` : ""} by user #${internalUserId}`);
94
+
95
+ db.insert(auditLogsTable).values({
96
+ event,
97
+ entityId: entityId ? Number(entityId) : null,
98
+ entityType: entityType ? String(entityType) : null,
99
+ performedById: internalUserId,
100
+ details: sanitizedDetails ?? null,
101
+ }).catch(err => {
102
+ logger.error({ err: err instanceof Error ? err.message : String(err) }, "Failed to write audit log to database");
103
+ });
104
+ }