@axova/shared 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/lib/db.d.ts +34406 -1
- package/dist/lib/db.js +21 -1
- package/dist/middleware/storeOwnership.js +22 -3
- package/dist/middleware/storeValidationMiddleware.js +16 -39
- package/dist/schemas/admin/admin-schema.d.ts +2 -2
- package/dist/schemas/ai-moderation/ai-moderation-schema.d.ts +6 -6
- package/dist/schemas/common/common-schemas.d.ts +71 -71
- package/dist/schemas/compliance/compliance-schema.d.ts +20 -20
- package/dist/schemas/compliance/kyc-schema.d.ts +8 -8
- package/dist/schemas/customer/customer-schema.d.ts +18 -18
- package/dist/schemas/index.d.ts +28 -0
- package/dist/schemas/index.js +134 -3
- package/dist/schemas/inventory/inventory-tables.d.ts +188 -188
- package/dist/schemas/inventory/lot-tables.d.ts +102 -102
- package/dist/schemas/order/cart-schema.d.ts +2865 -0
- package/dist/schemas/order/cart-schema.js +396 -0
- package/dist/schemas/order/order-schema.d.ts +19 -19
- package/dist/schemas/order/order-schema.js +8 -2
- package/dist/schemas/product/discount-schema.d.ts +3 -3
- package/dist/schemas/product/product-schema.d.ts +3 -3
- package/dist/schemas/store/store-audit-schema.d.ts +20 -20
- package/dist/schemas/store/store-schema.d.ts +182 -2
- package/dist/schemas/store/store-schema.js +19 -0
- package/dist/schemas/store/storefront-config-schema.d.ts +434 -823
- package/dist/schemas/store/storefront-config-schema.js +35 -62
- package/dist/utils/subdomain.d.ts +1 -1
- package/dist/utils/subdomain.js +10 -15
- package/package.json +1 -1
- package/src/configs/index.ts +654 -654
- package/src/index.ts +26 -23
- package/src/interfaces/customer-events.ts +106 -106
- package/src/interfaces/inventory-events.ts +545 -545
- package/src/interfaces/inventory-types.ts +1004 -1004
- package/src/interfaces/order-events.ts +381 -381
- package/src/lib/auditLogger.ts +1117 -1117
- package/src/lib/authOrganization.ts +153 -153
- package/src/lib/db.ts +84 -64
- package/src/middleware/serviceAuth.ts +328 -328
- package/src/middleware/storeOwnership.ts +199 -181
- package/src/middleware/storeValidationMiddleware.ts +17 -50
- package/src/middleware/userAuth.ts +248 -248
- package/src/schemas/admin/admin-schema.ts +208 -208
- package/src/schemas/ai-moderation/ai-moderation-schema.ts +180 -180
- package/src/schemas/common/common-schemas.ts +108 -108
- package/src/schemas/compliance/compliance-schema.ts +927 -0
- package/src/schemas/compliance/kyc-schema.ts +649 -0
- package/src/schemas/customer/customer-schema.ts +576 -0
- package/src/schemas/index.ts +202 -3
- package/src/schemas/inventory/inventory-tables.ts +1927 -0
- package/src/schemas/inventory/lot-tables.ts +799 -0
- package/src/schemas/order/cart-schema.ts +652 -0
- package/src/schemas/order/order-schema.ts +1406 -0
- package/src/schemas/product/discount-relations.ts +44 -0
- package/src/schemas/product/discount-schema.ts +464 -0
- package/src/schemas/product/product-relations.ts +187 -0
- package/src/schemas/product/product-schema.ts +955 -0
- package/src/schemas/store/ethiopian_business_api.md.resolved +212 -0
- package/src/schemas/store/store-audit-schema.ts +1257 -0
- package/src/schemas/store/store-schema.ts +682 -0
- package/src/schemas/store/store-settings-schema.ts +231 -0
- package/src/schemas/store/storefront-config-schema.ts +382 -0
- package/src/schemas/types.ts +67 -67
- package/src/types/events.ts +646 -646
- package/src/utils/errorHandler.ts +44 -44
- package/src/utils/subdomain.ts +19 -23
- package/tsconfig.json +21 -21
package/src/lib/auditLogger.ts
CHANGED
|
@@ -1,1117 +1,1117 @@
|
|
|
1
|
-
import { performance } from "node:perf_hooks";
|
|
2
|
-
import { createId } from "@paralleldrive/cuid2";
|
|
3
|
-
|
|
4
|
-
// Re-export types from audit service for convenience
|
|
5
|
-
export interface AuditLogEntry {
|
|
6
|
-
eventType:
|
|
7
|
-
| "CREATE"
|
|
8
|
-
| "UPDATE"
|
|
9
|
-
| "DELETE"
|
|
10
|
-
| "LOGIN"
|
|
11
|
-
| "LOGOUT"
|
|
12
|
-
| "ACCESS"
|
|
13
|
-
| "PERMISSION_CHANGE"
|
|
14
|
-
| "SYSTEM_ACTION"
|
|
15
|
-
| "COMPLIANCE_ACTION"
|
|
16
|
-
| "SECURITY_EVENT";
|
|
17
|
-
eventCategory:
|
|
18
|
-
| "USER_ACTION"
|
|
19
|
-
| "SYSTEM_ACTION"
|
|
20
|
-
| "ADMIN_ACTION"
|
|
21
|
-
| "COMPLIANCE_ACTION"
|
|
22
|
-
| "SECURITY_ACTION"
|
|
23
|
-
| "DATA_ACTION"
|
|
24
|
-
| "NOTIFICATION_ACTION";
|
|
25
|
-
action: string;
|
|
26
|
-
resource: string;
|
|
27
|
-
resourceId: string;
|
|
28
|
-
resourceType?:
|
|
29
|
-
| "STORE"
|
|
30
|
-
| "USER"
|
|
31
|
-
| "PRODUCT"
|
|
32
|
-
| "ORDER"
|
|
33
|
-
| "COMPLIANCE"
|
|
34
|
-
| "POLICY"
|
|
35
|
-
| "VIOLATION"
|
|
36
|
-
| "APPEAL"
|
|
37
|
-
| "NOTIFICATION"
|
|
38
|
-
| "SYSTEM"
|
|
39
|
-
| "OTHER";
|
|
40
|
-
performedBy: string;
|
|
41
|
-
performedByType:
|
|
42
|
-
| "USER"
|
|
43
|
-
| "SYSTEM"
|
|
44
|
-
| "ADMIN"
|
|
45
|
-
| "SERVICE"
|
|
46
|
-
| "AI"
|
|
47
|
-
| "AUTOMATION"
|
|
48
|
-
| "ANONYMOUS";
|
|
49
|
-
performedByName?: string;
|
|
50
|
-
sessionId?: string;
|
|
51
|
-
requestId?: string;
|
|
52
|
-
correlationId?: string;
|
|
53
|
-
ipAddress?: string;
|
|
54
|
-
userAgent?: string;
|
|
55
|
-
clientType?:
|
|
56
|
-
| "WEB"
|
|
57
|
-
| "MOBILE_APP"
|
|
58
|
-
| "API"
|
|
59
|
-
| "ADMIN_PANEL"
|
|
60
|
-
| "SYSTEM"
|
|
61
|
-
| "WEBHOOK"
|
|
62
|
-
| "CRON_JOB";
|
|
63
|
-
clientId?: string;
|
|
64
|
-
clientVersion?: string;
|
|
65
|
-
location?: {
|
|
66
|
-
country?: string;
|
|
67
|
-
region?: string;
|
|
68
|
-
city?: string;
|
|
69
|
-
coordinates?: { lat: number; lng: number };
|
|
70
|
-
timezone?: string;
|
|
71
|
-
};
|
|
72
|
-
changes?: {
|
|
73
|
-
before?: Record<string, unknown>;
|
|
74
|
-
after?: Record<string, unknown>;
|
|
75
|
-
fields?: string[];
|
|
76
|
-
};
|
|
77
|
-
metadata?: {
|
|
78
|
-
businessImpact?: string;
|
|
79
|
-
severity?: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
|
|
80
|
-
tags?: string[];
|
|
81
|
-
department?: string;
|
|
82
|
-
project?: string;
|
|
83
|
-
feature?: string;
|
|
84
|
-
version?: string;
|
|
85
|
-
environment?: "DEVELOPMENT" | "STAGING" | "PRODUCTION";
|
|
86
|
-
customFields?: Record<string, unknown>;
|
|
87
|
-
};
|
|
88
|
-
success?: boolean;
|
|
89
|
-
errorCode?: string;
|
|
90
|
-
errorMessage?: string;
|
|
91
|
-
stackTrace?: string;
|
|
92
|
-
duration?: number;
|
|
93
|
-
size?: number;
|
|
94
|
-
sensitiveData?: boolean;
|
|
95
|
-
piiInvolved?: boolean;
|
|
96
|
-
complianceRelevant?: boolean;
|
|
97
|
-
retentionPeriod?: number;
|
|
98
|
-
riskLevel?: "NONE" | "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
|
|
99
|
-
securityImpact?: "NONE" | "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
|
|
100
|
-
workflowId?: string;
|
|
101
|
-
workflowStep?: string;
|
|
102
|
-
parentEventId?: string;
|
|
103
|
-
storeId?: string;
|
|
104
|
-
storeName?: string;
|
|
105
|
-
storeSubdomain?: string;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Context interface for automatic enrichment
|
|
109
|
-
export interface AuditContext {
|
|
110
|
-
serviceName: string;
|
|
111
|
-
serviceVersion?: string;
|
|
112
|
-
requestId?: string;
|
|
113
|
-
correlationId?: string;
|
|
114
|
-
sessionId?: string;
|
|
115
|
-
userId?: string;
|
|
116
|
-
userType?: "USER" | "ADMIN" | "SYSTEM" | "SERVICE";
|
|
117
|
-
ipAddress?: string;
|
|
118
|
-
userAgent?: string;
|
|
119
|
-
clientId?: string;
|
|
120
|
-
clientType?:
|
|
121
|
-
| "WEB"
|
|
122
|
-
| "MOBILE_APP"
|
|
123
|
-
| "API"
|
|
124
|
-
| "ADMIN_PANEL"
|
|
125
|
-
| "SYSTEM"
|
|
126
|
-
| "WEBHOOK"
|
|
127
|
-
| "CRON_JOB";
|
|
128
|
-
storeId?: string;
|
|
129
|
-
storeName?: string;
|
|
130
|
-
environment?: "DEVELOPMENT" | "STAGING" | "PRODUCTION";
|
|
131
|
-
features?: string[];
|
|
132
|
-
tags?: string[];
|
|
133
|
-
customMetadata?: Record<string, unknown>;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Performance tracker for duration logging
|
|
137
|
-
export interface PerformanceTracker {
|
|
138
|
-
start: number;
|
|
139
|
-
end(): number;
|
|
140
|
-
duration(): number;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Audit logger configuration
|
|
144
|
-
export interface AuditLoggerConfig {
|
|
145
|
-
serviceName: string;
|
|
146
|
-
serviceVersion?: string;
|
|
147
|
-
environment?: "DEVELOPMENT" | "STAGING" | "PRODUCTION";
|
|
148
|
-
auditServiceUrl?: string;
|
|
149
|
-
auditServiceApiKey?: string;
|
|
150
|
-
enableLocalLogging?: boolean;
|
|
151
|
-
enableRemoteLogging?: boolean;
|
|
152
|
-
bufferSize?: number;
|
|
153
|
-
flushInterval?: number;
|
|
154
|
-
defaultRetentionDays?: number;
|
|
155
|
-
autoEnrichment?: boolean;
|
|
156
|
-
sensitiveFieldMasking?: boolean;
|
|
157
|
-
geoLocationEnabled?: boolean;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Quick action builders for common operations
|
|
161
|
-
export interface QuickActionBuilders {
|
|
162
|
-
userLogin: (
|
|
163
|
-
userId: string,
|
|
164
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
165
|
-
) => AuditLogEntry;
|
|
166
|
-
userLogout: (
|
|
167
|
-
userId: string,
|
|
168
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
169
|
-
) => AuditLogEntry;
|
|
170
|
-
dataAccess: (
|
|
171
|
-
resource: string,
|
|
172
|
-
resourceId: string,
|
|
173
|
-
userId: string,
|
|
174
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
175
|
-
) => AuditLogEntry;
|
|
176
|
-
dataCreate: (
|
|
177
|
-
resource: string,
|
|
178
|
-
resourceId: string,
|
|
179
|
-
userId: string,
|
|
180
|
-
data?: unknown,
|
|
181
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
182
|
-
) => AuditLogEntry;
|
|
183
|
-
dataUpdate: (
|
|
184
|
-
resource: string,
|
|
185
|
-
resourceId: string,
|
|
186
|
-
userId: string,
|
|
187
|
-
changes?: unknown,
|
|
188
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
189
|
-
) => AuditLogEntry;
|
|
190
|
-
dataDelete: (
|
|
191
|
-
resource: string,
|
|
192
|
-
resourceId: string,
|
|
193
|
-
userId: string,
|
|
194
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
195
|
-
) => AuditLogEntry;
|
|
196
|
-
permissionChange: (
|
|
197
|
-
targetUserId: string,
|
|
198
|
-
changedBy: string,
|
|
199
|
-
changes: unknown,
|
|
200
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
201
|
-
) => AuditLogEntry;
|
|
202
|
-
securityEvent: (
|
|
203
|
-
eventType: string,
|
|
204
|
-
description: string,
|
|
205
|
-
severity: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL",
|
|
206
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
207
|
-
) => AuditLogEntry;
|
|
208
|
-
complianceEvent: (
|
|
209
|
-
framework: string,
|
|
210
|
-
requirement: string,
|
|
211
|
-
status: "MET" | "NOT_MET",
|
|
212
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
213
|
-
) => AuditLogEntry;
|
|
214
|
-
systemAction: (
|
|
215
|
-
action: string,
|
|
216
|
-
resource: string,
|
|
217
|
-
resourceId: string,
|
|
218
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
219
|
-
) => AuditLogEntry;
|
|
220
|
-
storeAction: (
|
|
221
|
-
storeId: string,
|
|
222
|
-
action: string,
|
|
223
|
-
resource: string,
|
|
224
|
-
resourceId: string,
|
|
225
|
-
userId?: string,
|
|
226
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
227
|
-
) => AuditLogEntry;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Shared Audit Logger Class
|
|
231
|
-
export class AuditLogger {
|
|
232
|
-
private config: AuditLoggerConfig;
|
|
233
|
-
private context: AuditContext;
|
|
234
|
-
private buffer: AuditLogEntry[] = [];
|
|
235
|
-
private flushTimer: NodeJS.Timeout | null = null;
|
|
236
|
-
private performanceTrackers = new Map<string, PerformanceTracker>();
|
|
237
|
-
|
|
238
|
-
constructor(
|
|
239
|
-
config: AuditLoggerConfig,
|
|
240
|
-
initialContext?: Partial<AuditContext>,
|
|
241
|
-
) {
|
|
242
|
-
this.config = {
|
|
243
|
-
bufferSize: 100,
|
|
244
|
-
flushInterval: 5000,
|
|
245
|
-
defaultRetentionDays: 2555,
|
|
246
|
-
autoEnrichment: true,
|
|
247
|
-
sensitiveFieldMasking: true,
|
|
248
|
-
geoLocationEnabled: false,
|
|
249
|
-
enableLocalLogging: true,
|
|
250
|
-
enableRemoteLogging: true,
|
|
251
|
-
...config,
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
this.context = {
|
|
255
|
-
serviceName: config.serviceName,
|
|
256
|
-
serviceVersion: config.serviceVersion,
|
|
257
|
-
environment:
|
|
258
|
-
config.environment ||
|
|
259
|
-
(process.env.NODE_ENV as "DEVELOPMENT" | "STAGING" | "PRODUCTION") ||
|
|
260
|
-
"DEVELOPMENT",
|
|
261
|
-
...initialContext,
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
this.setupAutoFlush();
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Update context (useful for request-scoped information)
|
|
268
|
-
updateContext(contextUpdate: Partial<AuditContext>): void {
|
|
269
|
-
this.context = { ...this.context, ...contextUpdate };
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Get current context
|
|
273
|
-
getContext(): AuditContext {
|
|
274
|
-
return { ...this.context };
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Log a custom audit event
|
|
278
|
-
async log(entry: Partial<AuditLogEntry>): Promise<string> {
|
|
279
|
-
try {
|
|
280
|
-
const enrichedEntry = await this.enrichEntry(entry);
|
|
281
|
-
const auditId = createId();
|
|
282
|
-
|
|
283
|
-
// Add to buffer
|
|
284
|
-
this.addToBuffer(enrichedEntry);
|
|
285
|
-
|
|
286
|
-
// Local logging
|
|
287
|
-
if (this.config.enableLocalLogging) {
|
|
288
|
-
this.logLocally(enrichedEntry, auditId);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Check if immediate flush is needed for critical events
|
|
292
|
-
if (this.isCriticalEvent(enrichedEntry)) {
|
|
293
|
-
await this.flush();
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return auditId;
|
|
297
|
-
} catch (error) {
|
|
298
|
-
console.error("Failed to log audit event:", error);
|
|
299
|
-
throw error;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Performance tracking helpers
|
|
304
|
-
startPerformanceTracking(operationId?: string): string {
|
|
305
|
-
const id = operationId || createId();
|
|
306
|
-
const tracker: PerformanceTracker = {
|
|
307
|
-
start: performance.now(),
|
|
308
|
-
end: () => performance.now(),
|
|
309
|
-
duration: function () {
|
|
310
|
-
return this.end() - this.start;
|
|
311
|
-
},
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
this.performanceTrackers.set(id, tracker);
|
|
315
|
-
return id;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
endPerformanceTracking(operationId: string): number {
|
|
319
|
-
const tracker = this.performanceTrackers.get(operationId);
|
|
320
|
-
if (tracker) {
|
|
321
|
-
const duration = tracker.duration();
|
|
322
|
-
this.performanceTrackers.delete(operationId);
|
|
323
|
-
return Math.round(duration);
|
|
324
|
-
}
|
|
325
|
-
return 0;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Quick action builders
|
|
329
|
-
get quick(): QuickActionBuilders {
|
|
330
|
-
return {
|
|
331
|
-
userLogin: (userId: string, additionalData?: Partial<AuditLogEntry>) => ({
|
|
332
|
-
eventType: "LOGIN",
|
|
333
|
-
eventCategory: "USER_ACTION",
|
|
334
|
-
action: "user_login",
|
|
335
|
-
resource: "authentication",
|
|
336
|
-
resourceId: userId,
|
|
337
|
-
resourceType: "USER",
|
|
338
|
-
performedBy: userId,
|
|
339
|
-
performedByType: "USER",
|
|
340
|
-
riskLevel: "LOW",
|
|
341
|
-
complianceRelevant: true,
|
|
342
|
-
...additionalData,
|
|
343
|
-
}),
|
|
344
|
-
|
|
345
|
-
userLogout: (
|
|
346
|
-
userId: string,
|
|
347
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
348
|
-
) => ({
|
|
349
|
-
eventType: "LOGOUT",
|
|
350
|
-
eventCategory: "USER_ACTION",
|
|
351
|
-
action: "user_logout",
|
|
352
|
-
resource: "authentication",
|
|
353
|
-
resourceId: userId,
|
|
354
|
-
resourceType: "USER",
|
|
355
|
-
performedBy: userId,
|
|
356
|
-
performedByType: "USER",
|
|
357
|
-
riskLevel: "NONE",
|
|
358
|
-
...additionalData,
|
|
359
|
-
}),
|
|
360
|
-
|
|
361
|
-
dataAccess: (
|
|
362
|
-
resource: string,
|
|
363
|
-
resourceId: string,
|
|
364
|
-
userId: string,
|
|
365
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
366
|
-
) => ({
|
|
367
|
-
eventType: "ACCESS",
|
|
368
|
-
eventCategory: "DATA_ACTION",
|
|
369
|
-
action: "data_access",
|
|
370
|
-
resource,
|
|
371
|
-
resourceId,
|
|
372
|
-
performedBy: userId,
|
|
373
|
-
performedByType: "USER",
|
|
374
|
-
riskLevel: "LOW",
|
|
375
|
-
complianceRelevant: true,
|
|
376
|
-
...additionalData,
|
|
377
|
-
}),
|
|
378
|
-
|
|
379
|
-
dataCreate: (
|
|
380
|
-
resource: string,
|
|
381
|
-
resourceId: string,
|
|
382
|
-
userId: string,
|
|
383
|
-
data?: unknown,
|
|
384
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
385
|
-
): AuditLogEntry => ({
|
|
386
|
-
eventType: "CREATE",
|
|
387
|
-
eventCategory: "DATA_ACTION",
|
|
388
|
-
action: "data_create",
|
|
389
|
-
resource,
|
|
390
|
-
resourceId,
|
|
391
|
-
performedBy: userId,
|
|
392
|
-
performedByType: "USER",
|
|
393
|
-
changes: data ? { after: data as Record<string, unknown> } : undefined,
|
|
394
|
-
riskLevel: "LOW",
|
|
395
|
-
...additionalData,
|
|
396
|
-
}),
|
|
397
|
-
|
|
398
|
-
dataUpdate: (
|
|
399
|
-
resource: string,
|
|
400
|
-
resourceId: string,
|
|
401
|
-
userId: string,
|
|
402
|
-
changes?: unknown,
|
|
403
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
404
|
-
): AuditLogEntry => ({
|
|
405
|
-
eventType: "UPDATE",
|
|
406
|
-
eventCategory: "DATA_ACTION",
|
|
407
|
-
action: "data_update",
|
|
408
|
-
resource,
|
|
409
|
-
resourceId,
|
|
410
|
-
performedBy: userId,
|
|
411
|
-
performedByType: "USER",
|
|
412
|
-
changes: changes
|
|
413
|
-
? {
|
|
414
|
-
before: (changes as any)?.before,
|
|
415
|
-
after: (changes as any)?.after,
|
|
416
|
-
fields: (changes as any)?.fields,
|
|
417
|
-
}
|
|
418
|
-
: undefined,
|
|
419
|
-
riskLevel: "MEDIUM",
|
|
420
|
-
...additionalData,
|
|
421
|
-
}),
|
|
422
|
-
|
|
423
|
-
dataDelete: (
|
|
424
|
-
resource: string,
|
|
425
|
-
resourceId: string,
|
|
426
|
-
userId: string,
|
|
427
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
428
|
-
) => ({
|
|
429
|
-
eventType: "DELETE",
|
|
430
|
-
eventCategory: "DATA_ACTION",
|
|
431
|
-
action: "data_delete",
|
|
432
|
-
resource,
|
|
433
|
-
resourceId,
|
|
434
|
-
performedBy: userId,
|
|
435
|
-
performedByType: "USER",
|
|
436
|
-
riskLevel: "HIGH",
|
|
437
|
-
complianceRelevant: true,
|
|
438
|
-
...additionalData,
|
|
439
|
-
}),
|
|
440
|
-
|
|
441
|
-
permissionChange: (
|
|
442
|
-
targetUserId: string,
|
|
443
|
-
changedBy: string,
|
|
444
|
-
changes: unknown,
|
|
445
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
446
|
-
): AuditLogEntry => ({
|
|
447
|
-
eventType: "PERMISSION_CHANGE",
|
|
448
|
-
eventCategory: "ADMIN_ACTION",
|
|
449
|
-
action: "permission_change",
|
|
450
|
-
resource: "user_permissions",
|
|
451
|
-
resourceId: targetUserId,
|
|
452
|
-
performedBy: changedBy,
|
|
453
|
-
performedByType: "ADMIN",
|
|
454
|
-
changes: changes
|
|
455
|
-
? {
|
|
456
|
-
before: (changes as any)?.before,
|
|
457
|
-
after: (changes as any)?.after,
|
|
458
|
-
fields: (changes as any)?.fields,
|
|
459
|
-
}
|
|
460
|
-
: undefined,
|
|
461
|
-
riskLevel: "HIGH",
|
|
462
|
-
securityImpact: "MEDIUM",
|
|
463
|
-
...additionalData,
|
|
464
|
-
}),
|
|
465
|
-
|
|
466
|
-
securityEvent: (
|
|
467
|
-
eventType: string,
|
|
468
|
-
description: string,
|
|
469
|
-
severity: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL",
|
|
470
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
471
|
-
) => ({
|
|
472
|
-
eventType: "SECURITY_EVENT",
|
|
473
|
-
eventCategory: "SECURITY_ACTION",
|
|
474
|
-
action: eventType,
|
|
475
|
-
resource: "security",
|
|
476
|
-
resourceId: createId(),
|
|
477
|
-
performedBy: "SYSTEM",
|
|
478
|
-
performedByType: "SYSTEM",
|
|
479
|
-
riskLevel: severity,
|
|
480
|
-
securityImpact: severity,
|
|
481
|
-
metadata: {
|
|
482
|
-
severity,
|
|
483
|
-
description,
|
|
484
|
-
businessImpact:
|
|
485
|
-
severity === "CRITICAL"
|
|
486
|
-
? "Service disruption possible"
|
|
487
|
-
: "Monitoring required",
|
|
488
|
-
},
|
|
489
|
-
...additionalData,
|
|
490
|
-
}),
|
|
491
|
-
|
|
492
|
-
complianceEvent: (
|
|
493
|
-
framework: string,
|
|
494
|
-
requirement: string,
|
|
495
|
-
status: "MET" | "NOT_MET",
|
|
496
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
497
|
-
) => ({
|
|
498
|
-
eventType: "COMPLIANCE_ACTION",
|
|
499
|
-
eventCategory: "COMPLIANCE_ACTION",
|
|
500
|
-
action: "compliance_check",
|
|
501
|
-
resource: "compliance_requirement",
|
|
502
|
-
resourceId: `${framework}_${requirement}`,
|
|
503
|
-
performedBy: "SYSTEM",
|
|
504
|
-
performedByType: "SYSTEM",
|
|
505
|
-
complianceRelevant: true,
|
|
506
|
-
riskLevel: status === "NOT_MET" ? "HIGH" : "LOW",
|
|
507
|
-
metadata: {
|
|
508
|
-
framework,
|
|
509
|
-
requirement,
|
|
510
|
-
status,
|
|
511
|
-
businessImpact:
|
|
512
|
-
status === "NOT_MET"
|
|
513
|
-
? "Compliance violation detected"
|
|
514
|
-
: "Compliance maintained",
|
|
515
|
-
},
|
|
516
|
-
...additionalData,
|
|
517
|
-
}),
|
|
518
|
-
|
|
519
|
-
systemAction: (
|
|
520
|
-
action: string,
|
|
521
|
-
resource: string,
|
|
522
|
-
resourceId: string,
|
|
523
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
524
|
-
) => ({
|
|
525
|
-
eventType: "SYSTEM_ACTION",
|
|
526
|
-
eventCategory: "SYSTEM_ACTION",
|
|
527
|
-
action,
|
|
528
|
-
resource,
|
|
529
|
-
resourceId,
|
|
530
|
-
performedBy: this.context.serviceName,
|
|
531
|
-
performedByType: "SYSTEM",
|
|
532
|
-
riskLevel: "NONE",
|
|
533
|
-
...additionalData,
|
|
534
|
-
}),
|
|
535
|
-
|
|
536
|
-
storeAction: (
|
|
537
|
-
storeId: string,
|
|
538
|
-
action: string,
|
|
539
|
-
resource: string,
|
|
540
|
-
resourceId: string,
|
|
541
|
-
userId?: string,
|
|
542
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
543
|
-
) => ({
|
|
544
|
-
eventType: "UPDATE",
|
|
545
|
-
eventCategory: userId ? "USER_ACTION" : "SYSTEM_ACTION",
|
|
546
|
-
action,
|
|
547
|
-
resource,
|
|
548
|
-
resourceId,
|
|
549
|
-
resourceType: "STORE",
|
|
550
|
-
performedBy: userId || this.context.serviceName,
|
|
551
|
-
performedByType: userId ? "USER" : "SYSTEM",
|
|
552
|
-
storeId,
|
|
553
|
-
riskLevel: "LOW",
|
|
554
|
-
...additionalData,
|
|
555
|
-
}),
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// Convenience methods for common audit events
|
|
560
|
-
async logUserLogin(
|
|
561
|
-
userId: string,
|
|
562
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
563
|
-
): Promise<string> {
|
|
564
|
-
return this.log(this.quick.userLogin(userId, additionalData));
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
async logUserLogout(
|
|
568
|
-
userId: string,
|
|
569
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
570
|
-
): Promise<string> {
|
|
571
|
-
return this.log(this.quick.userLogout(userId, additionalData));
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
async logDataAccess(
|
|
575
|
-
resource: string,
|
|
576
|
-
resourceId: string,
|
|
577
|
-
userId: string,
|
|
578
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
579
|
-
): Promise<string> {
|
|
580
|
-
return this.log(
|
|
581
|
-
this.quick.dataAccess(resource, resourceId, userId, additionalData),
|
|
582
|
-
);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
async logDataCreate(
|
|
586
|
-
resource: string,
|
|
587
|
-
resourceId: string,
|
|
588
|
-
userId: string,
|
|
589
|
-
data?: unknown,
|
|
590
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
591
|
-
): Promise<string> {
|
|
592
|
-
return this.log(
|
|
593
|
-
this.quick.dataCreate(resource, resourceId, userId, data, additionalData),
|
|
594
|
-
);
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
async logDataUpdate(
|
|
598
|
-
resource: string,
|
|
599
|
-
resourceId: string,
|
|
600
|
-
userId: string,
|
|
601
|
-
changes?: unknown,
|
|
602
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
603
|
-
): Promise<string> {
|
|
604
|
-
return this.log(
|
|
605
|
-
this.quick.dataUpdate(
|
|
606
|
-
resource,
|
|
607
|
-
resourceId,
|
|
608
|
-
userId,
|
|
609
|
-
changes,
|
|
610
|
-
additionalData,
|
|
611
|
-
),
|
|
612
|
-
);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
async logDataDelete(
|
|
616
|
-
resource: string,
|
|
617
|
-
resourceId: string,
|
|
618
|
-
userId: string,
|
|
619
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
620
|
-
): Promise<string> {
|
|
621
|
-
return this.log(
|
|
622
|
-
this.quick.dataDelete(resource, resourceId, userId, additionalData),
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
async logSecurityEvent(
|
|
627
|
-
eventType: string,
|
|
628
|
-
description: string,
|
|
629
|
-
severity: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL",
|
|
630
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
631
|
-
): Promise<string> {
|
|
632
|
-
return this.log(
|
|
633
|
-
this.quick.securityEvent(
|
|
634
|
-
eventType,
|
|
635
|
-
description,
|
|
636
|
-
severity,
|
|
637
|
-
additionalData,
|
|
638
|
-
),
|
|
639
|
-
);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
async logStoreAction(
|
|
643
|
-
storeId: string,
|
|
644
|
-
action: string,
|
|
645
|
-
resource: string,
|
|
646
|
-
resourceId: string,
|
|
647
|
-
userId?: string,
|
|
648
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
649
|
-
): Promise<string> {
|
|
650
|
-
return this.log(
|
|
651
|
-
this.quick.storeAction(
|
|
652
|
-
storeId,
|
|
653
|
-
action,
|
|
654
|
-
resource,
|
|
655
|
-
resourceId,
|
|
656
|
-
userId,
|
|
657
|
-
additionalData,
|
|
658
|
-
),
|
|
659
|
-
);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
// Wrapper for async operations with automatic audit logging
|
|
663
|
-
async auditedOperation<T>(
|
|
664
|
-
operationName: string,
|
|
665
|
-
operation: () => Promise<T>,
|
|
666
|
-
auditData: Partial<AuditLogEntry>,
|
|
667
|
-
): Promise<{ result: T; auditId: string; duration: number }> {
|
|
668
|
-
const trackingId = this.startPerformanceTracking();
|
|
669
|
-
const _startTime = Date.now();
|
|
670
|
-
|
|
671
|
-
try {
|
|
672
|
-
const result = await operation();
|
|
673
|
-
const duration = this.endPerformanceTracking(trackingId);
|
|
674
|
-
|
|
675
|
-
const _auditId = await this.log({
|
|
676
|
-
...auditData,
|
|
677
|
-
action: operationName,
|
|
678
|
-
success: true,
|
|
679
|
-
duration,
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
return { result, auditId: _auditId, duration };
|
|
683
|
-
} catch (error) {
|
|
684
|
-
const duration = this.endPerformanceTracking(trackingId);
|
|
685
|
-
|
|
686
|
-
const _auditId = await this.log({
|
|
687
|
-
...auditData,
|
|
688
|
-
action: operationName,
|
|
689
|
-
success: false,
|
|
690
|
-
duration,
|
|
691
|
-
errorMessage: error instanceof Error ? error.message : "Unknown error",
|
|
692
|
-
stackTrace: error instanceof Error ? error.stack : undefined,
|
|
693
|
-
riskLevel: "MEDIUM",
|
|
694
|
-
});
|
|
695
|
-
|
|
696
|
-
throw error;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// Bulk logging for batch operations
|
|
701
|
-
async logBatch(entries: Partial<AuditLogEntry>[]): Promise<string[]> {
|
|
702
|
-
try {
|
|
703
|
-
const enrichedEntries = await Promise.all(
|
|
704
|
-
entries.map((entry) => this.enrichEntry(entry)),
|
|
705
|
-
);
|
|
706
|
-
|
|
707
|
-
const auditIds = enrichedEntries.map(() => createId());
|
|
708
|
-
|
|
709
|
-
// Add all to buffer
|
|
710
|
-
enrichedEntries.forEach((entry) => this.addToBuffer(entry));
|
|
711
|
-
|
|
712
|
-
// Local logging
|
|
713
|
-
if (this.config.enableLocalLogging) {
|
|
714
|
-
enrichedEntries.forEach((entry, index) => {
|
|
715
|
-
this.logLocally(entry, auditIds[index]);
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
// Check if any critical events require immediate flush
|
|
720
|
-
const hasCriticalEvents = enrichedEntries.some((entry) =>
|
|
721
|
-
this.isCriticalEvent(entry),
|
|
722
|
-
);
|
|
723
|
-
if (hasCriticalEvents) {
|
|
724
|
-
await this.flush();
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
return auditIds;
|
|
728
|
-
} catch (error) {
|
|
729
|
-
console.error("Failed to log batch audit events:", error);
|
|
730
|
-
throw error;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
// Flush buffer manually
|
|
735
|
-
async flush(): Promise<void> {
|
|
736
|
-
if (this.buffer.length === 0) return;
|
|
737
|
-
|
|
738
|
-
try {
|
|
739
|
-
const entriesToFlush = this.buffer.splice(0);
|
|
740
|
-
|
|
741
|
-
if (this.config.enableRemoteLogging) {
|
|
742
|
-
await this.sendToAuditService(entriesToFlush);
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
console.log(`✅ Flushed ${entriesToFlush.length} audit log entries`);
|
|
746
|
-
} catch (error) {
|
|
747
|
-
console.error("Failed to flush audit log buffer:", error);
|
|
748
|
-
// Re-add entries to buffer for retry
|
|
749
|
-
this.buffer.unshift(...this.buffer);
|
|
750
|
-
throw error;
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
// Private helper methods
|
|
755
|
-
|
|
756
|
-
private async enrichEntry(
|
|
757
|
-
entry: Partial<AuditLogEntry>,
|
|
758
|
-
): Promise<AuditLogEntry> {
|
|
759
|
-
const baseEntry: AuditLogEntry = {
|
|
760
|
-
eventType: entry.eventType || "SYSTEM_ACTION",
|
|
761
|
-
eventCategory: entry.eventCategory || "SYSTEM_ACTION",
|
|
762
|
-
action: entry.action || "unknown_action",
|
|
763
|
-
resource: entry.resource || "unknown_resource",
|
|
764
|
-
resourceId: entry.resourceId || createId(),
|
|
765
|
-
performedBy: entry.performedBy || this.context.serviceName,
|
|
766
|
-
performedByType: entry.performedByType || "SYSTEM",
|
|
767
|
-
success: entry.success ?? true,
|
|
768
|
-
riskLevel: entry.riskLevel || "NONE",
|
|
769
|
-
securityImpact: entry.securityImpact || "NONE",
|
|
770
|
-
retentionPeriod:
|
|
771
|
-
entry.retentionPeriod || this.config.defaultRetentionDays,
|
|
772
|
-
...entry,
|
|
773
|
-
};
|
|
774
|
-
|
|
775
|
-
// Auto-enrichment from context
|
|
776
|
-
if (this.config.autoEnrichment) {
|
|
777
|
-
if (this.context.requestId && !baseEntry.requestId) {
|
|
778
|
-
baseEntry.requestId = this.context.requestId;
|
|
779
|
-
}
|
|
780
|
-
if (this.context.correlationId && !baseEntry.correlationId) {
|
|
781
|
-
baseEntry.correlationId = this.context.correlationId;
|
|
782
|
-
}
|
|
783
|
-
if (this.context.sessionId && !baseEntry.sessionId) {
|
|
784
|
-
baseEntry.sessionId = this.context.sessionId;
|
|
785
|
-
}
|
|
786
|
-
if (this.context.ipAddress && !baseEntry.ipAddress) {
|
|
787
|
-
baseEntry.ipAddress = this.context.ipAddress;
|
|
788
|
-
}
|
|
789
|
-
if (this.context.userAgent && !baseEntry.userAgent) {
|
|
790
|
-
baseEntry.userAgent = this.context.userAgent;
|
|
791
|
-
}
|
|
792
|
-
if (this.context.clientId && !baseEntry.clientId) {
|
|
793
|
-
baseEntry.clientId = this.context.clientId;
|
|
794
|
-
}
|
|
795
|
-
if (this.context.clientType && !baseEntry.clientType) {
|
|
796
|
-
baseEntry.clientType = this.context.clientType;
|
|
797
|
-
}
|
|
798
|
-
if (this.context.storeId && !baseEntry.storeId) {
|
|
799
|
-
baseEntry.storeId = this.context.storeId;
|
|
800
|
-
}
|
|
801
|
-
if (this.context.storeName && !baseEntry.storeName) {
|
|
802
|
-
baseEntry.storeName = this.context.storeName;
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
// Enrich metadata
|
|
806
|
-
if (!baseEntry.metadata) {
|
|
807
|
-
baseEntry.metadata = {};
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
baseEntry.metadata.environment = this.context.environment;
|
|
811
|
-
baseEntry.metadata.version = this.context.serviceVersion;
|
|
812
|
-
|
|
813
|
-
if (this.context.features?.length) {
|
|
814
|
-
baseEntry.metadata.tags = [
|
|
815
|
-
...(baseEntry.metadata.tags || []),
|
|
816
|
-
...this.context.features,
|
|
817
|
-
];
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
if (this.context.tags?.length) {
|
|
821
|
-
baseEntry.metadata.tags = [
|
|
822
|
-
...(baseEntry.metadata.tags || []),
|
|
823
|
-
...this.context.tags,
|
|
824
|
-
];
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
if (this.context.customMetadata) {
|
|
828
|
-
baseEntry.metadata.customFields = {
|
|
829
|
-
...baseEntry.metadata.customFields,
|
|
830
|
-
...this.context.customMetadata,
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// Mask sensitive data if enabled
|
|
836
|
-
if (this.config.sensitiveFieldMasking) {
|
|
837
|
-
baseEntry.changes = this.maskSensitiveData(
|
|
838
|
-
baseEntry.changes,
|
|
839
|
-
) as typeof baseEntry.changes;
|
|
840
|
-
baseEntry.metadata = this.maskSensitiveData(
|
|
841
|
-
baseEntry.metadata,
|
|
842
|
-
) as typeof baseEntry.metadata;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
return baseEntry;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
private addToBuffer(entry: AuditLogEntry): void {
|
|
849
|
-
this.buffer.push(entry);
|
|
850
|
-
|
|
851
|
-
if (this.buffer.length >= (this.config.bufferSize || 100)) {
|
|
852
|
-
setImmediate(() => this.flush());
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
private logLocally(entry: AuditLogEntry, auditId: string): void {
|
|
857
|
-
const logLevel = this.getLogLevel(entry);
|
|
858
|
-
const message = `AUDIT [${auditId}] ${entry.action} on ${entry.resource}:${entry.resourceId} by ${entry.performedBy}`;
|
|
859
|
-
|
|
860
|
-
switch (logLevel) {
|
|
861
|
-
case "error":
|
|
862
|
-
console.error(message, { auditEntry: entry });
|
|
863
|
-
break;
|
|
864
|
-
case "warn":
|
|
865
|
-
console.warn(message, { auditEntry: entry });
|
|
866
|
-
break;
|
|
867
|
-
default:
|
|
868
|
-
console.log(message, { auditEntry: entry });
|
|
869
|
-
break;
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
private getLogLevel(entry: AuditLogEntry): "error" | "warn" | "info" {
|
|
874
|
-
if (
|
|
875
|
-
!entry.success ||
|
|
876
|
-
entry.riskLevel === "CRITICAL" ||
|
|
877
|
-
entry.securityImpact === "CRITICAL"
|
|
878
|
-
) {
|
|
879
|
-
return "error";
|
|
880
|
-
}
|
|
881
|
-
if (entry.riskLevel === "HIGH" || entry.securityImpact === "HIGH") {
|
|
882
|
-
return "warn";
|
|
883
|
-
}
|
|
884
|
-
return "info";
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
private isCriticalEvent(entry: AuditLogEntry): boolean {
|
|
888
|
-
return (
|
|
889
|
-
entry.riskLevel === "CRITICAL" ||
|
|
890
|
-
entry.securityImpact === "CRITICAL" ||
|
|
891
|
-
entry.eventCategory === "SECURITY_ACTION" ||
|
|
892
|
-
(entry.success === false && entry.eventType === "LOGIN")
|
|
893
|
-
);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
private async sendToAuditService(entries: AuditLogEntry[]): Promise<void> {
|
|
897
|
-
if (!this.config.auditServiceUrl) return;
|
|
898
|
-
|
|
899
|
-
try {
|
|
900
|
-
const response = await fetch(
|
|
901
|
-
`${this.config.auditServiceUrl}/audit/bulk`,
|
|
902
|
-
{
|
|
903
|
-
method: "POST",
|
|
904
|
-
headers: {
|
|
905
|
-
"Content-Type": "application/json",
|
|
906
|
-
...(this.config.auditServiceApiKey && {
|
|
907
|
-
Authorization: `Bearer ${this.config.auditServiceApiKey}`,
|
|
908
|
-
}),
|
|
909
|
-
},
|
|
910
|
-
body: JSON.stringify({ entries }),
|
|
911
|
-
},
|
|
912
|
-
);
|
|
913
|
-
|
|
914
|
-
if (!response.ok) {
|
|
915
|
-
throw new Error(
|
|
916
|
-
`Audit service responded with status: ${response.status}`,
|
|
917
|
-
);
|
|
918
|
-
}
|
|
919
|
-
} catch (error) {
|
|
920
|
-
console.error("Failed to send audit logs to audit service:", error);
|
|
921
|
-
throw error;
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
private maskSensitiveData(data: unknown): unknown {
|
|
926
|
-
if (!data || typeof data !== "object") {
|
|
927
|
-
return data;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
const sensitiveKeys = [
|
|
931
|
-
"password",
|
|
932
|
-
"token",
|
|
933
|
-
"secret",
|
|
934
|
-
"key",
|
|
935
|
-
"ssn",
|
|
936
|
-
"credit",
|
|
937
|
-
"card",
|
|
938
|
-
"bank",
|
|
939
|
-
"account",
|
|
940
|
-
];
|
|
941
|
-
|
|
942
|
-
const masked = { ...(data as Record<string, unknown>) };
|
|
943
|
-
|
|
944
|
-
Object.keys(masked).forEach((key) => {
|
|
945
|
-
const lowerKey = key.toLowerCase();
|
|
946
|
-
if (sensitiveKeys.some((sensitive) => lowerKey.includes(sensitive))) {
|
|
947
|
-
masked[key] = "[MASKED]";
|
|
948
|
-
} else if (typeof masked[key] === "object" && masked[key] !== null) {
|
|
949
|
-
masked[key] = this.maskSensitiveData(masked[key]);
|
|
950
|
-
}
|
|
951
|
-
});
|
|
952
|
-
|
|
953
|
-
return masked;
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
private setupAutoFlush(): void {
|
|
957
|
-
if (this.config.flushInterval && this.config.flushInterval > 0) {
|
|
958
|
-
this.flushTimer = setInterval(() => {
|
|
959
|
-
if (this.buffer.length > 0) {
|
|
960
|
-
this.flush().catch((error) => {
|
|
961
|
-
console.error("Auto-flush failed:", error);
|
|
962
|
-
});
|
|
963
|
-
}
|
|
964
|
-
}, this.config.flushInterval);
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
// Cleanup
|
|
969
|
-
async shutdown(): Promise<void> {
|
|
970
|
-
try {
|
|
971
|
-
if (this.flushTimer) {
|
|
972
|
-
clearInterval(this.flushTimer);
|
|
973
|
-
this.flushTimer = null;
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
// Flush any remaining entries
|
|
977
|
-
if (this.buffer.length > 0) {
|
|
978
|
-
await this.flush();
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
console.log("✅ Audit Logger shutdown complete");
|
|
982
|
-
} catch (error) {
|
|
983
|
-
console.error("❌ Error during audit logger shutdown:", error);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
// Factory function for creating audit logger instances
|
|
989
|
-
export function createAuditLogger(
|
|
990
|
-
config: AuditLoggerConfig,
|
|
991
|
-
initialContext?: Partial<AuditContext>,
|
|
992
|
-
): AuditLogger {
|
|
993
|
-
return new AuditLogger(config, initialContext);
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
// Middleware factory for Express/Fastify request context
|
|
997
|
-
export function createAuditMiddleware(logger: AuditLogger) {
|
|
998
|
-
return (req: any, _res: unknown, next: () => void) => {
|
|
999
|
-
const requestId = req.headers?.["x-request-id"] || createId();
|
|
1000
|
-
const correlationId = req.headers?.["x-correlation-id"];
|
|
1001
|
-
const sessionId = req.headers?.["x-session-id"] || req.session?.id;
|
|
1002
|
-
|
|
1003
|
-
// Update audit context for this request
|
|
1004
|
-
logger.updateContext({
|
|
1005
|
-
requestId,
|
|
1006
|
-
correlationId,
|
|
1007
|
-
sessionId,
|
|
1008
|
-
ipAddress: req.ip || req.connection?.remoteAddress,
|
|
1009
|
-
userAgent: req.headers?.["user-agent"],
|
|
1010
|
-
userId: req.user?.id,
|
|
1011
|
-
userType: req.user?.type,
|
|
1012
|
-
clientId: req.headers?.["x-client-id"],
|
|
1013
|
-
clientType: req.headers?.["x-client-type"] as
|
|
1014
|
-
| "WEB"
|
|
1015
|
-
| "MOBILE_APP"
|
|
1016
|
-
| "API"
|
|
1017
|
-
| "ADMIN_PANEL"
|
|
1018
|
-
| "SYSTEM"
|
|
1019
|
-
| "WEBHOOK"
|
|
1020
|
-
| "CRON_JOB"
|
|
1021
|
-
| undefined,
|
|
1022
|
-
});
|
|
1023
|
-
|
|
1024
|
-
// Attach audit logger to request for easy access
|
|
1025
|
-
(req as any).audit = logger;
|
|
1026
|
-
|
|
1027
|
-
next();
|
|
1028
|
-
};
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
// Export convenience functions
|
|
1032
|
-
export async function logAuditEvent(
|
|
1033
|
-
serviceName: string,
|
|
1034
|
-
entry: Partial<AuditLogEntry>,
|
|
1035
|
-
context?: Partial<AuditContext>,
|
|
1036
|
-
): Promise<string> {
|
|
1037
|
-
const logger = createAuditLogger({ serviceName }, context);
|
|
1038
|
-
return logger.log(entry);
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
export async function logStoreAuditEvent(
|
|
1042
|
-
serviceName: string,
|
|
1043
|
-
storeId: string,
|
|
1044
|
-
action: string,
|
|
1045
|
-
resource: string,
|
|
1046
|
-
resourceId: string,
|
|
1047
|
-
userId?: string,
|
|
1048
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
1049
|
-
): Promise<string> {
|
|
1050
|
-
const logger = createAuditLogger({ serviceName }, { storeId });
|
|
1051
|
-
return logger.logStoreAction(
|
|
1052
|
-
storeId,
|
|
1053
|
-
action,
|
|
1054
|
-
resource,
|
|
1055
|
-
resourceId,
|
|
1056
|
-
userId,
|
|
1057
|
-
additionalData,
|
|
1058
|
-
);
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
// Default audit logger instance (can be configured once and reused)
|
|
1062
|
-
let defaultLogger: AuditLogger | null = null;
|
|
1063
|
-
|
|
1064
|
-
export function setDefaultAuditLogger(logger: AuditLogger): void {
|
|
1065
|
-
defaultLogger = logger;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
export function getDefaultAuditLogger(): AuditLogger {
|
|
1069
|
-
if (!defaultLogger) {
|
|
1070
|
-
throw new Error(
|
|
1071
|
-
"Default audit logger not configured. Call setDefaultAuditLogger() first.",
|
|
1072
|
-
);
|
|
1073
|
-
}
|
|
1074
|
-
return defaultLogger;
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
// Quick logging functions using default logger
|
|
1078
|
-
export async function quickLog(entry: Partial<AuditLogEntry>): Promise<string> {
|
|
1079
|
-
return getDefaultAuditLogger().log(entry);
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
export async function quickLogUserAction(
|
|
1083
|
-
action: string,
|
|
1084
|
-
resource: string,
|
|
1085
|
-
resourceId: string,
|
|
1086
|
-
userId: string,
|
|
1087
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
1088
|
-
): Promise<string> {
|
|
1089
|
-
return getDefaultAuditLogger().log({
|
|
1090
|
-
eventType: "UPDATE",
|
|
1091
|
-
eventCategory: "USER_ACTION",
|
|
1092
|
-
action,
|
|
1093
|
-
resource,
|
|
1094
|
-
resourceId,
|
|
1095
|
-
performedBy: userId,
|
|
1096
|
-
performedByType: "USER",
|
|
1097
|
-
...additionalData,
|
|
1098
|
-
});
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
export async function quickLogSystemAction(
|
|
1102
|
-
action: string,
|
|
1103
|
-
resource: string,
|
|
1104
|
-
resourceId: string,
|
|
1105
|
-
additionalData?: Partial<AuditLogEntry>,
|
|
1106
|
-
): Promise<string> {
|
|
1107
|
-
return getDefaultAuditLogger().log({
|
|
1108
|
-
eventType: "SYSTEM_ACTION",
|
|
1109
|
-
eventCategory: "SYSTEM_ACTION",
|
|
1110
|
-
action,
|
|
1111
|
-
resource,
|
|
1112
|
-
resourceId,
|
|
1113
|
-
performedBy: "SYSTEM",
|
|
1114
|
-
performedByType: "SYSTEM",
|
|
1115
|
-
...additionalData,
|
|
1116
|
-
});
|
|
1117
|
-
}
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
import { createId } from "@paralleldrive/cuid2";
|
|
3
|
+
|
|
4
|
+
// Re-export types from audit service for convenience
|
|
5
|
+
export interface AuditLogEntry {
|
|
6
|
+
eventType:
|
|
7
|
+
| "CREATE"
|
|
8
|
+
| "UPDATE"
|
|
9
|
+
| "DELETE"
|
|
10
|
+
| "LOGIN"
|
|
11
|
+
| "LOGOUT"
|
|
12
|
+
| "ACCESS"
|
|
13
|
+
| "PERMISSION_CHANGE"
|
|
14
|
+
| "SYSTEM_ACTION"
|
|
15
|
+
| "COMPLIANCE_ACTION"
|
|
16
|
+
| "SECURITY_EVENT";
|
|
17
|
+
eventCategory:
|
|
18
|
+
| "USER_ACTION"
|
|
19
|
+
| "SYSTEM_ACTION"
|
|
20
|
+
| "ADMIN_ACTION"
|
|
21
|
+
| "COMPLIANCE_ACTION"
|
|
22
|
+
| "SECURITY_ACTION"
|
|
23
|
+
| "DATA_ACTION"
|
|
24
|
+
| "NOTIFICATION_ACTION";
|
|
25
|
+
action: string;
|
|
26
|
+
resource: string;
|
|
27
|
+
resourceId: string;
|
|
28
|
+
resourceType?:
|
|
29
|
+
| "STORE"
|
|
30
|
+
| "USER"
|
|
31
|
+
| "PRODUCT"
|
|
32
|
+
| "ORDER"
|
|
33
|
+
| "COMPLIANCE"
|
|
34
|
+
| "POLICY"
|
|
35
|
+
| "VIOLATION"
|
|
36
|
+
| "APPEAL"
|
|
37
|
+
| "NOTIFICATION"
|
|
38
|
+
| "SYSTEM"
|
|
39
|
+
| "OTHER";
|
|
40
|
+
performedBy: string;
|
|
41
|
+
performedByType:
|
|
42
|
+
| "USER"
|
|
43
|
+
| "SYSTEM"
|
|
44
|
+
| "ADMIN"
|
|
45
|
+
| "SERVICE"
|
|
46
|
+
| "AI"
|
|
47
|
+
| "AUTOMATION"
|
|
48
|
+
| "ANONYMOUS";
|
|
49
|
+
performedByName?: string;
|
|
50
|
+
sessionId?: string;
|
|
51
|
+
requestId?: string;
|
|
52
|
+
correlationId?: string;
|
|
53
|
+
ipAddress?: string;
|
|
54
|
+
userAgent?: string;
|
|
55
|
+
clientType?:
|
|
56
|
+
| "WEB"
|
|
57
|
+
| "MOBILE_APP"
|
|
58
|
+
| "API"
|
|
59
|
+
| "ADMIN_PANEL"
|
|
60
|
+
| "SYSTEM"
|
|
61
|
+
| "WEBHOOK"
|
|
62
|
+
| "CRON_JOB";
|
|
63
|
+
clientId?: string;
|
|
64
|
+
clientVersion?: string;
|
|
65
|
+
location?: {
|
|
66
|
+
country?: string;
|
|
67
|
+
region?: string;
|
|
68
|
+
city?: string;
|
|
69
|
+
coordinates?: { lat: number; lng: number };
|
|
70
|
+
timezone?: string;
|
|
71
|
+
};
|
|
72
|
+
changes?: {
|
|
73
|
+
before?: Record<string, unknown>;
|
|
74
|
+
after?: Record<string, unknown>;
|
|
75
|
+
fields?: string[];
|
|
76
|
+
};
|
|
77
|
+
metadata?: {
|
|
78
|
+
businessImpact?: string;
|
|
79
|
+
severity?: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
|
|
80
|
+
tags?: string[];
|
|
81
|
+
department?: string;
|
|
82
|
+
project?: string;
|
|
83
|
+
feature?: string;
|
|
84
|
+
version?: string;
|
|
85
|
+
environment?: "DEVELOPMENT" | "STAGING" | "PRODUCTION";
|
|
86
|
+
customFields?: Record<string, unknown>;
|
|
87
|
+
};
|
|
88
|
+
success?: boolean;
|
|
89
|
+
errorCode?: string;
|
|
90
|
+
errorMessage?: string;
|
|
91
|
+
stackTrace?: string;
|
|
92
|
+
duration?: number;
|
|
93
|
+
size?: number;
|
|
94
|
+
sensitiveData?: boolean;
|
|
95
|
+
piiInvolved?: boolean;
|
|
96
|
+
complianceRelevant?: boolean;
|
|
97
|
+
retentionPeriod?: number;
|
|
98
|
+
riskLevel?: "NONE" | "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
|
|
99
|
+
securityImpact?: "NONE" | "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
|
|
100
|
+
workflowId?: string;
|
|
101
|
+
workflowStep?: string;
|
|
102
|
+
parentEventId?: string;
|
|
103
|
+
storeId?: string;
|
|
104
|
+
storeName?: string;
|
|
105
|
+
storeSubdomain?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Context interface for automatic enrichment
|
|
109
|
+
export interface AuditContext {
|
|
110
|
+
serviceName: string;
|
|
111
|
+
serviceVersion?: string;
|
|
112
|
+
requestId?: string;
|
|
113
|
+
correlationId?: string;
|
|
114
|
+
sessionId?: string;
|
|
115
|
+
userId?: string;
|
|
116
|
+
userType?: "USER" | "ADMIN" | "SYSTEM" | "SERVICE";
|
|
117
|
+
ipAddress?: string;
|
|
118
|
+
userAgent?: string;
|
|
119
|
+
clientId?: string;
|
|
120
|
+
clientType?:
|
|
121
|
+
| "WEB"
|
|
122
|
+
| "MOBILE_APP"
|
|
123
|
+
| "API"
|
|
124
|
+
| "ADMIN_PANEL"
|
|
125
|
+
| "SYSTEM"
|
|
126
|
+
| "WEBHOOK"
|
|
127
|
+
| "CRON_JOB";
|
|
128
|
+
storeId?: string;
|
|
129
|
+
storeName?: string;
|
|
130
|
+
environment?: "DEVELOPMENT" | "STAGING" | "PRODUCTION";
|
|
131
|
+
features?: string[];
|
|
132
|
+
tags?: string[];
|
|
133
|
+
customMetadata?: Record<string, unknown>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Performance tracker for duration logging
|
|
137
|
+
export interface PerformanceTracker {
|
|
138
|
+
start: number;
|
|
139
|
+
end(): number;
|
|
140
|
+
duration(): number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Audit logger configuration
|
|
144
|
+
export interface AuditLoggerConfig {
|
|
145
|
+
serviceName: string;
|
|
146
|
+
serviceVersion?: string;
|
|
147
|
+
environment?: "DEVELOPMENT" | "STAGING" | "PRODUCTION";
|
|
148
|
+
auditServiceUrl?: string;
|
|
149
|
+
auditServiceApiKey?: string;
|
|
150
|
+
enableLocalLogging?: boolean;
|
|
151
|
+
enableRemoteLogging?: boolean;
|
|
152
|
+
bufferSize?: number;
|
|
153
|
+
flushInterval?: number;
|
|
154
|
+
defaultRetentionDays?: number;
|
|
155
|
+
autoEnrichment?: boolean;
|
|
156
|
+
sensitiveFieldMasking?: boolean;
|
|
157
|
+
geoLocationEnabled?: boolean;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Quick action builders for common operations
|
|
161
|
+
export interface QuickActionBuilders {
|
|
162
|
+
userLogin: (
|
|
163
|
+
userId: string,
|
|
164
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
165
|
+
) => AuditLogEntry;
|
|
166
|
+
userLogout: (
|
|
167
|
+
userId: string,
|
|
168
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
169
|
+
) => AuditLogEntry;
|
|
170
|
+
dataAccess: (
|
|
171
|
+
resource: string,
|
|
172
|
+
resourceId: string,
|
|
173
|
+
userId: string,
|
|
174
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
175
|
+
) => AuditLogEntry;
|
|
176
|
+
dataCreate: (
|
|
177
|
+
resource: string,
|
|
178
|
+
resourceId: string,
|
|
179
|
+
userId: string,
|
|
180
|
+
data?: unknown,
|
|
181
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
182
|
+
) => AuditLogEntry;
|
|
183
|
+
dataUpdate: (
|
|
184
|
+
resource: string,
|
|
185
|
+
resourceId: string,
|
|
186
|
+
userId: string,
|
|
187
|
+
changes?: unknown,
|
|
188
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
189
|
+
) => AuditLogEntry;
|
|
190
|
+
dataDelete: (
|
|
191
|
+
resource: string,
|
|
192
|
+
resourceId: string,
|
|
193
|
+
userId: string,
|
|
194
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
195
|
+
) => AuditLogEntry;
|
|
196
|
+
permissionChange: (
|
|
197
|
+
targetUserId: string,
|
|
198
|
+
changedBy: string,
|
|
199
|
+
changes: unknown,
|
|
200
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
201
|
+
) => AuditLogEntry;
|
|
202
|
+
securityEvent: (
|
|
203
|
+
eventType: string,
|
|
204
|
+
description: string,
|
|
205
|
+
severity: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL",
|
|
206
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
207
|
+
) => AuditLogEntry;
|
|
208
|
+
complianceEvent: (
|
|
209
|
+
framework: string,
|
|
210
|
+
requirement: string,
|
|
211
|
+
status: "MET" | "NOT_MET",
|
|
212
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
213
|
+
) => AuditLogEntry;
|
|
214
|
+
systemAction: (
|
|
215
|
+
action: string,
|
|
216
|
+
resource: string,
|
|
217
|
+
resourceId: string,
|
|
218
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
219
|
+
) => AuditLogEntry;
|
|
220
|
+
storeAction: (
|
|
221
|
+
storeId: string,
|
|
222
|
+
action: string,
|
|
223
|
+
resource: string,
|
|
224
|
+
resourceId: string,
|
|
225
|
+
userId?: string,
|
|
226
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
227
|
+
) => AuditLogEntry;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Shared Audit Logger Class
|
|
231
|
+
export class AuditLogger {
|
|
232
|
+
private config: AuditLoggerConfig;
|
|
233
|
+
private context: AuditContext;
|
|
234
|
+
private buffer: AuditLogEntry[] = [];
|
|
235
|
+
private flushTimer: NodeJS.Timeout | null = null;
|
|
236
|
+
private performanceTrackers = new Map<string, PerformanceTracker>();
|
|
237
|
+
|
|
238
|
+
constructor(
|
|
239
|
+
config: AuditLoggerConfig,
|
|
240
|
+
initialContext?: Partial<AuditContext>,
|
|
241
|
+
) {
|
|
242
|
+
this.config = {
|
|
243
|
+
bufferSize: 100,
|
|
244
|
+
flushInterval: 5000,
|
|
245
|
+
defaultRetentionDays: 2555,
|
|
246
|
+
autoEnrichment: true,
|
|
247
|
+
sensitiveFieldMasking: true,
|
|
248
|
+
geoLocationEnabled: false,
|
|
249
|
+
enableLocalLogging: true,
|
|
250
|
+
enableRemoteLogging: true,
|
|
251
|
+
...config,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
this.context = {
|
|
255
|
+
serviceName: config.serviceName,
|
|
256
|
+
serviceVersion: config.serviceVersion,
|
|
257
|
+
environment:
|
|
258
|
+
config.environment ||
|
|
259
|
+
(process.env.NODE_ENV as "DEVELOPMENT" | "STAGING" | "PRODUCTION") ||
|
|
260
|
+
"DEVELOPMENT",
|
|
261
|
+
...initialContext,
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
this.setupAutoFlush();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Update context (useful for request-scoped information)
|
|
268
|
+
updateContext(contextUpdate: Partial<AuditContext>): void {
|
|
269
|
+
this.context = { ...this.context, ...contextUpdate };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Get current context
|
|
273
|
+
getContext(): AuditContext {
|
|
274
|
+
return { ...this.context };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Log a custom audit event
|
|
278
|
+
async log(entry: Partial<AuditLogEntry>): Promise<string> {
|
|
279
|
+
try {
|
|
280
|
+
const enrichedEntry = await this.enrichEntry(entry);
|
|
281
|
+
const auditId = createId();
|
|
282
|
+
|
|
283
|
+
// Add to buffer
|
|
284
|
+
this.addToBuffer(enrichedEntry);
|
|
285
|
+
|
|
286
|
+
// Local logging
|
|
287
|
+
if (this.config.enableLocalLogging) {
|
|
288
|
+
this.logLocally(enrichedEntry, auditId);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Check if immediate flush is needed for critical events
|
|
292
|
+
if (this.isCriticalEvent(enrichedEntry)) {
|
|
293
|
+
await this.flush();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return auditId;
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.error("Failed to log audit event:", error);
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Performance tracking helpers
|
|
304
|
+
startPerformanceTracking(operationId?: string): string {
|
|
305
|
+
const id = operationId || createId();
|
|
306
|
+
const tracker: PerformanceTracker = {
|
|
307
|
+
start: performance.now(),
|
|
308
|
+
end: () => performance.now(),
|
|
309
|
+
duration: function () {
|
|
310
|
+
return this.end() - this.start;
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
this.performanceTrackers.set(id, tracker);
|
|
315
|
+
return id;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
endPerformanceTracking(operationId: string): number {
|
|
319
|
+
const tracker = this.performanceTrackers.get(operationId);
|
|
320
|
+
if (tracker) {
|
|
321
|
+
const duration = tracker.duration();
|
|
322
|
+
this.performanceTrackers.delete(operationId);
|
|
323
|
+
return Math.round(duration);
|
|
324
|
+
}
|
|
325
|
+
return 0;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Quick action builders
|
|
329
|
+
get quick(): QuickActionBuilders {
|
|
330
|
+
return {
|
|
331
|
+
userLogin: (userId: string, additionalData?: Partial<AuditLogEntry>) => ({
|
|
332
|
+
eventType: "LOGIN",
|
|
333
|
+
eventCategory: "USER_ACTION",
|
|
334
|
+
action: "user_login",
|
|
335
|
+
resource: "authentication",
|
|
336
|
+
resourceId: userId,
|
|
337
|
+
resourceType: "USER",
|
|
338
|
+
performedBy: userId,
|
|
339
|
+
performedByType: "USER",
|
|
340
|
+
riskLevel: "LOW",
|
|
341
|
+
complianceRelevant: true,
|
|
342
|
+
...additionalData,
|
|
343
|
+
}),
|
|
344
|
+
|
|
345
|
+
userLogout: (
|
|
346
|
+
userId: string,
|
|
347
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
348
|
+
) => ({
|
|
349
|
+
eventType: "LOGOUT",
|
|
350
|
+
eventCategory: "USER_ACTION",
|
|
351
|
+
action: "user_logout",
|
|
352
|
+
resource: "authentication",
|
|
353
|
+
resourceId: userId,
|
|
354
|
+
resourceType: "USER",
|
|
355
|
+
performedBy: userId,
|
|
356
|
+
performedByType: "USER",
|
|
357
|
+
riskLevel: "NONE",
|
|
358
|
+
...additionalData,
|
|
359
|
+
}),
|
|
360
|
+
|
|
361
|
+
dataAccess: (
|
|
362
|
+
resource: string,
|
|
363
|
+
resourceId: string,
|
|
364
|
+
userId: string,
|
|
365
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
366
|
+
) => ({
|
|
367
|
+
eventType: "ACCESS",
|
|
368
|
+
eventCategory: "DATA_ACTION",
|
|
369
|
+
action: "data_access",
|
|
370
|
+
resource,
|
|
371
|
+
resourceId,
|
|
372
|
+
performedBy: userId,
|
|
373
|
+
performedByType: "USER",
|
|
374
|
+
riskLevel: "LOW",
|
|
375
|
+
complianceRelevant: true,
|
|
376
|
+
...additionalData,
|
|
377
|
+
}),
|
|
378
|
+
|
|
379
|
+
dataCreate: (
|
|
380
|
+
resource: string,
|
|
381
|
+
resourceId: string,
|
|
382
|
+
userId: string,
|
|
383
|
+
data?: unknown,
|
|
384
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
385
|
+
): AuditLogEntry => ({
|
|
386
|
+
eventType: "CREATE",
|
|
387
|
+
eventCategory: "DATA_ACTION",
|
|
388
|
+
action: "data_create",
|
|
389
|
+
resource,
|
|
390
|
+
resourceId,
|
|
391
|
+
performedBy: userId,
|
|
392
|
+
performedByType: "USER",
|
|
393
|
+
changes: data ? { after: data as Record<string, unknown> } : undefined,
|
|
394
|
+
riskLevel: "LOW",
|
|
395
|
+
...additionalData,
|
|
396
|
+
}),
|
|
397
|
+
|
|
398
|
+
dataUpdate: (
|
|
399
|
+
resource: string,
|
|
400
|
+
resourceId: string,
|
|
401
|
+
userId: string,
|
|
402
|
+
changes?: unknown,
|
|
403
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
404
|
+
): AuditLogEntry => ({
|
|
405
|
+
eventType: "UPDATE",
|
|
406
|
+
eventCategory: "DATA_ACTION",
|
|
407
|
+
action: "data_update",
|
|
408
|
+
resource,
|
|
409
|
+
resourceId,
|
|
410
|
+
performedBy: userId,
|
|
411
|
+
performedByType: "USER",
|
|
412
|
+
changes: changes
|
|
413
|
+
? {
|
|
414
|
+
before: (changes as any)?.before,
|
|
415
|
+
after: (changes as any)?.after,
|
|
416
|
+
fields: (changes as any)?.fields,
|
|
417
|
+
}
|
|
418
|
+
: undefined,
|
|
419
|
+
riskLevel: "MEDIUM",
|
|
420
|
+
...additionalData,
|
|
421
|
+
}),
|
|
422
|
+
|
|
423
|
+
dataDelete: (
|
|
424
|
+
resource: string,
|
|
425
|
+
resourceId: string,
|
|
426
|
+
userId: string,
|
|
427
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
428
|
+
) => ({
|
|
429
|
+
eventType: "DELETE",
|
|
430
|
+
eventCategory: "DATA_ACTION",
|
|
431
|
+
action: "data_delete",
|
|
432
|
+
resource,
|
|
433
|
+
resourceId,
|
|
434
|
+
performedBy: userId,
|
|
435
|
+
performedByType: "USER",
|
|
436
|
+
riskLevel: "HIGH",
|
|
437
|
+
complianceRelevant: true,
|
|
438
|
+
...additionalData,
|
|
439
|
+
}),
|
|
440
|
+
|
|
441
|
+
permissionChange: (
|
|
442
|
+
targetUserId: string,
|
|
443
|
+
changedBy: string,
|
|
444
|
+
changes: unknown,
|
|
445
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
446
|
+
): AuditLogEntry => ({
|
|
447
|
+
eventType: "PERMISSION_CHANGE",
|
|
448
|
+
eventCategory: "ADMIN_ACTION",
|
|
449
|
+
action: "permission_change",
|
|
450
|
+
resource: "user_permissions",
|
|
451
|
+
resourceId: targetUserId,
|
|
452
|
+
performedBy: changedBy,
|
|
453
|
+
performedByType: "ADMIN",
|
|
454
|
+
changes: changes
|
|
455
|
+
? {
|
|
456
|
+
before: (changes as any)?.before,
|
|
457
|
+
after: (changes as any)?.after,
|
|
458
|
+
fields: (changes as any)?.fields,
|
|
459
|
+
}
|
|
460
|
+
: undefined,
|
|
461
|
+
riskLevel: "HIGH",
|
|
462
|
+
securityImpact: "MEDIUM",
|
|
463
|
+
...additionalData,
|
|
464
|
+
}),
|
|
465
|
+
|
|
466
|
+
securityEvent: (
|
|
467
|
+
eventType: string,
|
|
468
|
+
description: string,
|
|
469
|
+
severity: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL",
|
|
470
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
471
|
+
) => ({
|
|
472
|
+
eventType: "SECURITY_EVENT",
|
|
473
|
+
eventCategory: "SECURITY_ACTION",
|
|
474
|
+
action: eventType,
|
|
475
|
+
resource: "security",
|
|
476
|
+
resourceId: createId(),
|
|
477
|
+
performedBy: "SYSTEM",
|
|
478
|
+
performedByType: "SYSTEM",
|
|
479
|
+
riskLevel: severity,
|
|
480
|
+
securityImpact: severity,
|
|
481
|
+
metadata: {
|
|
482
|
+
severity,
|
|
483
|
+
description,
|
|
484
|
+
businessImpact:
|
|
485
|
+
severity === "CRITICAL"
|
|
486
|
+
? "Service disruption possible"
|
|
487
|
+
: "Monitoring required",
|
|
488
|
+
},
|
|
489
|
+
...additionalData,
|
|
490
|
+
}),
|
|
491
|
+
|
|
492
|
+
complianceEvent: (
|
|
493
|
+
framework: string,
|
|
494
|
+
requirement: string,
|
|
495
|
+
status: "MET" | "NOT_MET",
|
|
496
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
497
|
+
) => ({
|
|
498
|
+
eventType: "COMPLIANCE_ACTION",
|
|
499
|
+
eventCategory: "COMPLIANCE_ACTION",
|
|
500
|
+
action: "compliance_check",
|
|
501
|
+
resource: "compliance_requirement",
|
|
502
|
+
resourceId: `${framework}_${requirement}`,
|
|
503
|
+
performedBy: "SYSTEM",
|
|
504
|
+
performedByType: "SYSTEM",
|
|
505
|
+
complianceRelevant: true,
|
|
506
|
+
riskLevel: status === "NOT_MET" ? "HIGH" : "LOW",
|
|
507
|
+
metadata: {
|
|
508
|
+
framework,
|
|
509
|
+
requirement,
|
|
510
|
+
status,
|
|
511
|
+
businessImpact:
|
|
512
|
+
status === "NOT_MET"
|
|
513
|
+
? "Compliance violation detected"
|
|
514
|
+
: "Compliance maintained",
|
|
515
|
+
},
|
|
516
|
+
...additionalData,
|
|
517
|
+
}),
|
|
518
|
+
|
|
519
|
+
systemAction: (
|
|
520
|
+
action: string,
|
|
521
|
+
resource: string,
|
|
522
|
+
resourceId: string,
|
|
523
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
524
|
+
) => ({
|
|
525
|
+
eventType: "SYSTEM_ACTION",
|
|
526
|
+
eventCategory: "SYSTEM_ACTION",
|
|
527
|
+
action,
|
|
528
|
+
resource,
|
|
529
|
+
resourceId,
|
|
530
|
+
performedBy: this.context.serviceName,
|
|
531
|
+
performedByType: "SYSTEM",
|
|
532
|
+
riskLevel: "NONE",
|
|
533
|
+
...additionalData,
|
|
534
|
+
}),
|
|
535
|
+
|
|
536
|
+
storeAction: (
|
|
537
|
+
storeId: string,
|
|
538
|
+
action: string,
|
|
539
|
+
resource: string,
|
|
540
|
+
resourceId: string,
|
|
541
|
+
userId?: string,
|
|
542
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
543
|
+
) => ({
|
|
544
|
+
eventType: "UPDATE",
|
|
545
|
+
eventCategory: userId ? "USER_ACTION" : "SYSTEM_ACTION",
|
|
546
|
+
action,
|
|
547
|
+
resource,
|
|
548
|
+
resourceId,
|
|
549
|
+
resourceType: "STORE",
|
|
550
|
+
performedBy: userId || this.context.serviceName,
|
|
551
|
+
performedByType: userId ? "USER" : "SYSTEM",
|
|
552
|
+
storeId,
|
|
553
|
+
riskLevel: "LOW",
|
|
554
|
+
...additionalData,
|
|
555
|
+
}),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Convenience methods for common audit events
|
|
560
|
+
async logUserLogin(
|
|
561
|
+
userId: string,
|
|
562
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
563
|
+
): Promise<string> {
|
|
564
|
+
return this.log(this.quick.userLogin(userId, additionalData));
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async logUserLogout(
|
|
568
|
+
userId: string,
|
|
569
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
570
|
+
): Promise<string> {
|
|
571
|
+
return this.log(this.quick.userLogout(userId, additionalData));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
async logDataAccess(
|
|
575
|
+
resource: string,
|
|
576
|
+
resourceId: string,
|
|
577
|
+
userId: string,
|
|
578
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
579
|
+
): Promise<string> {
|
|
580
|
+
return this.log(
|
|
581
|
+
this.quick.dataAccess(resource, resourceId, userId, additionalData),
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
async logDataCreate(
|
|
586
|
+
resource: string,
|
|
587
|
+
resourceId: string,
|
|
588
|
+
userId: string,
|
|
589
|
+
data?: unknown,
|
|
590
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
591
|
+
): Promise<string> {
|
|
592
|
+
return this.log(
|
|
593
|
+
this.quick.dataCreate(resource, resourceId, userId, data, additionalData),
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
async logDataUpdate(
|
|
598
|
+
resource: string,
|
|
599
|
+
resourceId: string,
|
|
600
|
+
userId: string,
|
|
601
|
+
changes?: unknown,
|
|
602
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
603
|
+
): Promise<string> {
|
|
604
|
+
return this.log(
|
|
605
|
+
this.quick.dataUpdate(
|
|
606
|
+
resource,
|
|
607
|
+
resourceId,
|
|
608
|
+
userId,
|
|
609
|
+
changes,
|
|
610
|
+
additionalData,
|
|
611
|
+
),
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
async logDataDelete(
|
|
616
|
+
resource: string,
|
|
617
|
+
resourceId: string,
|
|
618
|
+
userId: string,
|
|
619
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
620
|
+
): Promise<string> {
|
|
621
|
+
return this.log(
|
|
622
|
+
this.quick.dataDelete(resource, resourceId, userId, additionalData),
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
async logSecurityEvent(
|
|
627
|
+
eventType: string,
|
|
628
|
+
description: string,
|
|
629
|
+
severity: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL",
|
|
630
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
631
|
+
): Promise<string> {
|
|
632
|
+
return this.log(
|
|
633
|
+
this.quick.securityEvent(
|
|
634
|
+
eventType,
|
|
635
|
+
description,
|
|
636
|
+
severity,
|
|
637
|
+
additionalData,
|
|
638
|
+
),
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async logStoreAction(
|
|
643
|
+
storeId: string,
|
|
644
|
+
action: string,
|
|
645
|
+
resource: string,
|
|
646
|
+
resourceId: string,
|
|
647
|
+
userId?: string,
|
|
648
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
649
|
+
): Promise<string> {
|
|
650
|
+
return this.log(
|
|
651
|
+
this.quick.storeAction(
|
|
652
|
+
storeId,
|
|
653
|
+
action,
|
|
654
|
+
resource,
|
|
655
|
+
resourceId,
|
|
656
|
+
userId,
|
|
657
|
+
additionalData,
|
|
658
|
+
),
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Wrapper for async operations with automatic audit logging
|
|
663
|
+
async auditedOperation<T>(
|
|
664
|
+
operationName: string,
|
|
665
|
+
operation: () => Promise<T>,
|
|
666
|
+
auditData: Partial<AuditLogEntry>,
|
|
667
|
+
): Promise<{ result: T; auditId: string; duration: number }> {
|
|
668
|
+
const trackingId = this.startPerformanceTracking();
|
|
669
|
+
const _startTime = Date.now();
|
|
670
|
+
|
|
671
|
+
try {
|
|
672
|
+
const result = await operation();
|
|
673
|
+
const duration = this.endPerformanceTracking(trackingId);
|
|
674
|
+
|
|
675
|
+
const _auditId = await this.log({
|
|
676
|
+
...auditData,
|
|
677
|
+
action: operationName,
|
|
678
|
+
success: true,
|
|
679
|
+
duration,
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
return { result, auditId: _auditId, duration };
|
|
683
|
+
} catch (error) {
|
|
684
|
+
const duration = this.endPerformanceTracking(trackingId);
|
|
685
|
+
|
|
686
|
+
const _auditId = await this.log({
|
|
687
|
+
...auditData,
|
|
688
|
+
action: operationName,
|
|
689
|
+
success: false,
|
|
690
|
+
duration,
|
|
691
|
+
errorMessage: error instanceof Error ? error.message : "Unknown error",
|
|
692
|
+
stackTrace: error instanceof Error ? error.stack : undefined,
|
|
693
|
+
riskLevel: "MEDIUM",
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
throw error;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Bulk logging for batch operations
|
|
701
|
+
async logBatch(entries: Partial<AuditLogEntry>[]): Promise<string[]> {
|
|
702
|
+
try {
|
|
703
|
+
const enrichedEntries = await Promise.all(
|
|
704
|
+
entries.map((entry) => this.enrichEntry(entry)),
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
const auditIds = enrichedEntries.map(() => createId());
|
|
708
|
+
|
|
709
|
+
// Add all to buffer
|
|
710
|
+
enrichedEntries.forEach((entry) => this.addToBuffer(entry));
|
|
711
|
+
|
|
712
|
+
// Local logging
|
|
713
|
+
if (this.config.enableLocalLogging) {
|
|
714
|
+
enrichedEntries.forEach((entry, index) => {
|
|
715
|
+
this.logLocally(entry, auditIds[index]);
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Check if any critical events require immediate flush
|
|
720
|
+
const hasCriticalEvents = enrichedEntries.some((entry) =>
|
|
721
|
+
this.isCriticalEvent(entry),
|
|
722
|
+
);
|
|
723
|
+
if (hasCriticalEvents) {
|
|
724
|
+
await this.flush();
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
return auditIds;
|
|
728
|
+
} catch (error) {
|
|
729
|
+
console.error("Failed to log batch audit events:", error);
|
|
730
|
+
throw error;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Flush buffer manually
|
|
735
|
+
async flush(): Promise<void> {
|
|
736
|
+
if (this.buffer.length === 0) return;
|
|
737
|
+
|
|
738
|
+
try {
|
|
739
|
+
const entriesToFlush = this.buffer.splice(0);
|
|
740
|
+
|
|
741
|
+
if (this.config.enableRemoteLogging) {
|
|
742
|
+
await this.sendToAuditService(entriesToFlush);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
console.log(`✅ Flushed ${entriesToFlush.length} audit log entries`);
|
|
746
|
+
} catch (error) {
|
|
747
|
+
console.error("Failed to flush audit log buffer:", error);
|
|
748
|
+
// Re-add entries to buffer for retry
|
|
749
|
+
this.buffer.unshift(...this.buffer);
|
|
750
|
+
throw error;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Private helper methods
|
|
755
|
+
|
|
756
|
+
private async enrichEntry(
|
|
757
|
+
entry: Partial<AuditLogEntry>,
|
|
758
|
+
): Promise<AuditLogEntry> {
|
|
759
|
+
const baseEntry: AuditLogEntry = {
|
|
760
|
+
eventType: entry.eventType || "SYSTEM_ACTION",
|
|
761
|
+
eventCategory: entry.eventCategory || "SYSTEM_ACTION",
|
|
762
|
+
action: entry.action || "unknown_action",
|
|
763
|
+
resource: entry.resource || "unknown_resource",
|
|
764
|
+
resourceId: entry.resourceId || createId(),
|
|
765
|
+
performedBy: entry.performedBy || this.context.serviceName,
|
|
766
|
+
performedByType: entry.performedByType || "SYSTEM",
|
|
767
|
+
success: entry.success ?? true,
|
|
768
|
+
riskLevel: entry.riskLevel || "NONE",
|
|
769
|
+
securityImpact: entry.securityImpact || "NONE",
|
|
770
|
+
retentionPeriod:
|
|
771
|
+
entry.retentionPeriod || this.config.defaultRetentionDays,
|
|
772
|
+
...entry,
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
// Auto-enrichment from context
|
|
776
|
+
if (this.config.autoEnrichment) {
|
|
777
|
+
if (this.context.requestId && !baseEntry.requestId) {
|
|
778
|
+
baseEntry.requestId = this.context.requestId;
|
|
779
|
+
}
|
|
780
|
+
if (this.context.correlationId && !baseEntry.correlationId) {
|
|
781
|
+
baseEntry.correlationId = this.context.correlationId;
|
|
782
|
+
}
|
|
783
|
+
if (this.context.sessionId && !baseEntry.sessionId) {
|
|
784
|
+
baseEntry.sessionId = this.context.sessionId;
|
|
785
|
+
}
|
|
786
|
+
if (this.context.ipAddress && !baseEntry.ipAddress) {
|
|
787
|
+
baseEntry.ipAddress = this.context.ipAddress;
|
|
788
|
+
}
|
|
789
|
+
if (this.context.userAgent && !baseEntry.userAgent) {
|
|
790
|
+
baseEntry.userAgent = this.context.userAgent;
|
|
791
|
+
}
|
|
792
|
+
if (this.context.clientId && !baseEntry.clientId) {
|
|
793
|
+
baseEntry.clientId = this.context.clientId;
|
|
794
|
+
}
|
|
795
|
+
if (this.context.clientType && !baseEntry.clientType) {
|
|
796
|
+
baseEntry.clientType = this.context.clientType;
|
|
797
|
+
}
|
|
798
|
+
if (this.context.storeId && !baseEntry.storeId) {
|
|
799
|
+
baseEntry.storeId = this.context.storeId;
|
|
800
|
+
}
|
|
801
|
+
if (this.context.storeName && !baseEntry.storeName) {
|
|
802
|
+
baseEntry.storeName = this.context.storeName;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Enrich metadata
|
|
806
|
+
if (!baseEntry.metadata) {
|
|
807
|
+
baseEntry.metadata = {};
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
baseEntry.metadata.environment = this.context.environment;
|
|
811
|
+
baseEntry.metadata.version = this.context.serviceVersion;
|
|
812
|
+
|
|
813
|
+
if (this.context.features?.length) {
|
|
814
|
+
baseEntry.metadata.tags = [
|
|
815
|
+
...(baseEntry.metadata.tags || []),
|
|
816
|
+
...this.context.features,
|
|
817
|
+
];
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (this.context.tags?.length) {
|
|
821
|
+
baseEntry.metadata.tags = [
|
|
822
|
+
...(baseEntry.metadata.tags || []),
|
|
823
|
+
...this.context.tags,
|
|
824
|
+
];
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (this.context.customMetadata) {
|
|
828
|
+
baseEntry.metadata.customFields = {
|
|
829
|
+
...baseEntry.metadata.customFields,
|
|
830
|
+
...this.context.customMetadata,
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Mask sensitive data if enabled
|
|
836
|
+
if (this.config.sensitiveFieldMasking) {
|
|
837
|
+
baseEntry.changes = this.maskSensitiveData(
|
|
838
|
+
baseEntry.changes,
|
|
839
|
+
) as typeof baseEntry.changes;
|
|
840
|
+
baseEntry.metadata = this.maskSensitiveData(
|
|
841
|
+
baseEntry.metadata,
|
|
842
|
+
) as typeof baseEntry.metadata;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return baseEntry;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
private addToBuffer(entry: AuditLogEntry): void {
|
|
849
|
+
this.buffer.push(entry);
|
|
850
|
+
|
|
851
|
+
if (this.buffer.length >= (this.config.bufferSize || 100)) {
|
|
852
|
+
setImmediate(() => this.flush());
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
private logLocally(entry: AuditLogEntry, auditId: string): void {
|
|
857
|
+
const logLevel = this.getLogLevel(entry);
|
|
858
|
+
const message = `AUDIT [${auditId}] ${entry.action} on ${entry.resource}:${entry.resourceId} by ${entry.performedBy}`;
|
|
859
|
+
|
|
860
|
+
switch (logLevel) {
|
|
861
|
+
case "error":
|
|
862
|
+
console.error(message, { auditEntry: entry });
|
|
863
|
+
break;
|
|
864
|
+
case "warn":
|
|
865
|
+
console.warn(message, { auditEntry: entry });
|
|
866
|
+
break;
|
|
867
|
+
default:
|
|
868
|
+
console.log(message, { auditEntry: entry });
|
|
869
|
+
break;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
private getLogLevel(entry: AuditLogEntry): "error" | "warn" | "info" {
|
|
874
|
+
if (
|
|
875
|
+
!entry.success ||
|
|
876
|
+
entry.riskLevel === "CRITICAL" ||
|
|
877
|
+
entry.securityImpact === "CRITICAL"
|
|
878
|
+
) {
|
|
879
|
+
return "error";
|
|
880
|
+
}
|
|
881
|
+
if (entry.riskLevel === "HIGH" || entry.securityImpact === "HIGH") {
|
|
882
|
+
return "warn";
|
|
883
|
+
}
|
|
884
|
+
return "info";
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
private isCriticalEvent(entry: AuditLogEntry): boolean {
|
|
888
|
+
return (
|
|
889
|
+
entry.riskLevel === "CRITICAL" ||
|
|
890
|
+
entry.securityImpact === "CRITICAL" ||
|
|
891
|
+
entry.eventCategory === "SECURITY_ACTION" ||
|
|
892
|
+
(entry.success === false && entry.eventType === "LOGIN")
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
private async sendToAuditService(entries: AuditLogEntry[]): Promise<void> {
|
|
897
|
+
if (!this.config.auditServiceUrl) return;
|
|
898
|
+
|
|
899
|
+
try {
|
|
900
|
+
const response = await fetch(
|
|
901
|
+
`${this.config.auditServiceUrl}/audit/bulk`,
|
|
902
|
+
{
|
|
903
|
+
method: "POST",
|
|
904
|
+
headers: {
|
|
905
|
+
"Content-Type": "application/json",
|
|
906
|
+
...(this.config.auditServiceApiKey && {
|
|
907
|
+
Authorization: `Bearer ${this.config.auditServiceApiKey}`,
|
|
908
|
+
}),
|
|
909
|
+
},
|
|
910
|
+
body: JSON.stringify({ entries }),
|
|
911
|
+
},
|
|
912
|
+
);
|
|
913
|
+
|
|
914
|
+
if (!response.ok) {
|
|
915
|
+
throw new Error(
|
|
916
|
+
`Audit service responded with status: ${response.status}`,
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
} catch (error) {
|
|
920
|
+
console.error("Failed to send audit logs to audit service:", error);
|
|
921
|
+
throw error;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
private maskSensitiveData(data: unknown): unknown {
|
|
926
|
+
if (!data || typeof data !== "object") {
|
|
927
|
+
return data;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
const sensitiveKeys = [
|
|
931
|
+
"password",
|
|
932
|
+
"token",
|
|
933
|
+
"secret",
|
|
934
|
+
"key",
|
|
935
|
+
"ssn",
|
|
936
|
+
"credit",
|
|
937
|
+
"card",
|
|
938
|
+
"bank",
|
|
939
|
+
"account",
|
|
940
|
+
];
|
|
941
|
+
|
|
942
|
+
const masked = { ...(data as Record<string, unknown>) };
|
|
943
|
+
|
|
944
|
+
Object.keys(masked).forEach((key) => {
|
|
945
|
+
const lowerKey = key.toLowerCase();
|
|
946
|
+
if (sensitiveKeys.some((sensitive) => lowerKey.includes(sensitive))) {
|
|
947
|
+
masked[key] = "[MASKED]";
|
|
948
|
+
} else if (typeof masked[key] === "object" && masked[key] !== null) {
|
|
949
|
+
masked[key] = this.maskSensitiveData(masked[key]);
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
return masked;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
private setupAutoFlush(): void {
|
|
957
|
+
if (this.config.flushInterval && this.config.flushInterval > 0) {
|
|
958
|
+
this.flushTimer = setInterval(() => {
|
|
959
|
+
if (this.buffer.length > 0) {
|
|
960
|
+
this.flush().catch((error) => {
|
|
961
|
+
console.error("Auto-flush failed:", error);
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
}, this.config.flushInterval);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Cleanup
|
|
969
|
+
async shutdown(): Promise<void> {
|
|
970
|
+
try {
|
|
971
|
+
if (this.flushTimer) {
|
|
972
|
+
clearInterval(this.flushTimer);
|
|
973
|
+
this.flushTimer = null;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// Flush any remaining entries
|
|
977
|
+
if (this.buffer.length > 0) {
|
|
978
|
+
await this.flush();
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
console.log("✅ Audit Logger shutdown complete");
|
|
982
|
+
} catch (error) {
|
|
983
|
+
console.error("❌ Error during audit logger shutdown:", error);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Factory function for creating audit logger instances
|
|
989
|
+
export function createAuditLogger(
|
|
990
|
+
config: AuditLoggerConfig,
|
|
991
|
+
initialContext?: Partial<AuditContext>,
|
|
992
|
+
): AuditLogger {
|
|
993
|
+
return new AuditLogger(config, initialContext);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Middleware factory for Express/Fastify request context
|
|
997
|
+
export function createAuditMiddleware(logger: AuditLogger) {
|
|
998
|
+
return (req: any, _res: unknown, next: () => void) => {
|
|
999
|
+
const requestId = req.headers?.["x-request-id"] || createId();
|
|
1000
|
+
const correlationId = req.headers?.["x-correlation-id"];
|
|
1001
|
+
const sessionId = req.headers?.["x-session-id"] || req.session?.id;
|
|
1002
|
+
|
|
1003
|
+
// Update audit context for this request
|
|
1004
|
+
logger.updateContext({
|
|
1005
|
+
requestId,
|
|
1006
|
+
correlationId,
|
|
1007
|
+
sessionId,
|
|
1008
|
+
ipAddress: req.ip || req.connection?.remoteAddress,
|
|
1009
|
+
userAgent: req.headers?.["user-agent"],
|
|
1010
|
+
userId: req.user?.id,
|
|
1011
|
+
userType: req.user?.type,
|
|
1012
|
+
clientId: req.headers?.["x-client-id"],
|
|
1013
|
+
clientType: req.headers?.["x-client-type"] as
|
|
1014
|
+
| "WEB"
|
|
1015
|
+
| "MOBILE_APP"
|
|
1016
|
+
| "API"
|
|
1017
|
+
| "ADMIN_PANEL"
|
|
1018
|
+
| "SYSTEM"
|
|
1019
|
+
| "WEBHOOK"
|
|
1020
|
+
| "CRON_JOB"
|
|
1021
|
+
| undefined,
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
// Attach audit logger to request for easy access
|
|
1025
|
+
(req as any).audit = logger;
|
|
1026
|
+
|
|
1027
|
+
next();
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// Export convenience functions
|
|
1032
|
+
export async function logAuditEvent(
|
|
1033
|
+
serviceName: string,
|
|
1034
|
+
entry: Partial<AuditLogEntry>,
|
|
1035
|
+
context?: Partial<AuditContext>,
|
|
1036
|
+
): Promise<string> {
|
|
1037
|
+
const logger = createAuditLogger({ serviceName }, context);
|
|
1038
|
+
return logger.log(entry);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
export async function logStoreAuditEvent(
|
|
1042
|
+
serviceName: string,
|
|
1043
|
+
storeId: string,
|
|
1044
|
+
action: string,
|
|
1045
|
+
resource: string,
|
|
1046
|
+
resourceId: string,
|
|
1047
|
+
userId?: string,
|
|
1048
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
1049
|
+
): Promise<string> {
|
|
1050
|
+
const logger = createAuditLogger({ serviceName }, { storeId });
|
|
1051
|
+
return logger.logStoreAction(
|
|
1052
|
+
storeId,
|
|
1053
|
+
action,
|
|
1054
|
+
resource,
|
|
1055
|
+
resourceId,
|
|
1056
|
+
userId,
|
|
1057
|
+
additionalData,
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// Default audit logger instance (can be configured once and reused)
|
|
1062
|
+
let defaultLogger: AuditLogger | null = null;
|
|
1063
|
+
|
|
1064
|
+
export function setDefaultAuditLogger(logger: AuditLogger): void {
|
|
1065
|
+
defaultLogger = logger;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
export function getDefaultAuditLogger(): AuditLogger {
|
|
1069
|
+
if (!defaultLogger) {
|
|
1070
|
+
throw new Error(
|
|
1071
|
+
"Default audit logger not configured. Call setDefaultAuditLogger() first.",
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
return defaultLogger;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// Quick logging functions using default logger
|
|
1078
|
+
export async function quickLog(entry: Partial<AuditLogEntry>): Promise<string> {
|
|
1079
|
+
return getDefaultAuditLogger().log(entry);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
export async function quickLogUserAction(
|
|
1083
|
+
action: string,
|
|
1084
|
+
resource: string,
|
|
1085
|
+
resourceId: string,
|
|
1086
|
+
userId: string,
|
|
1087
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
1088
|
+
): Promise<string> {
|
|
1089
|
+
return getDefaultAuditLogger().log({
|
|
1090
|
+
eventType: "UPDATE",
|
|
1091
|
+
eventCategory: "USER_ACTION",
|
|
1092
|
+
action,
|
|
1093
|
+
resource,
|
|
1094
|
+
resourceId,
|
|
1095
|
+
performedBy: userId,
|
|
1096
|
+
performedByType: "USER",
|
|
1097
|
+
...additionalData,
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
export async function quickLogSystemAction(
|
|
1102
|
+
action: string,
|
|
1103
|
+
resource: string,
|
|
1104
|
+
resourceId: string,
|
|
1105
|
+
additionalData?: Partial<AuditLogEntry>,
|
|
1106
|
+
): Promise<string> {
|
|
1107
|
+
return getDefaultAuditLogger().log({
|
|
1108
|
+
eventType: "SYSTEM_ACTION",
|
|
1109
|
+
eventCategory: "SYSTEM_ACTION",
|
|
1110
|
+
action,
|
|
1111
|
+
resource,
|
|
1112
|
+
resourceId,
|
|
1113
|
+
performedBy: "SYSTEM",
|
|
1114
|
+
performedByType: "SYSTEM",
|
|
1115
|
+
...additionalData,
|
|
1116
|
+
});
|
|
1117
|
+
}
|