@striae-org/striae 3.2.1 → 3.3.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.
Files changed (104) hide show
  1. package/app/components/actions/case-export/core-export.ts +2 -2
  2. package/app/components/actions/case-export/data-processing.ts +19 -4
  3. package/app/components/actions/case-export/download-handlers.ts +57 -8
  4. package/app/components/actions/case-export/metadata-helpers.ts +1 -1
  5. package/app/components/actions/case-import/annotation-import.ts +2 -2
  6. package/app/components/actions/case-import/confirmation-import.ts +44 -20
  7. package/app/components/actions/case-import/confirmation-package.ts +86 -0
  8. package/app/components/actions/case-import/image-operations.ts +1 -1
  9. package/app/components/actions/case-import/index.ts +1 -0
  10. package/app/components/actions/case-import/orchestrator.ts +16 -6
  11. package/app/components/actions/case-import/storage-operations.ts +7 -7
  12. package/app/components/actions/case-import/validation.ts +7 -100
  13. package/app/components/actions/case-import/zip-processing.ts +47 -5
  14. package/app/components/actions/case-manage.ts +3 -3
  15. package/app/components/actions/confirm-export.ts +47 -16
  16. package/app/components/actions/generate-pdf.ts +3 -3
  17. package/app/components/actions/image-manage.ts +3 -3
  18. package/app/components/actions/notes-manage.ts +3 -3
  19. package/app/components/actions/signout.tsx +1 -1
  20. package/app/components/audit/user-audit-viewer.tsx +2 -3
  21. package/app/components/auth/auth-provider.tsx +2 -2
  22. package/app/components/auth/mfa-enrollment.tsx +3 -3
  23. package/app/components/auth/mfa-verification.tsx +4 -4
  24. package/app/components/canvas/box-annotations/box-annotations.tsx +2 -2
  25. package/app/components/canvas/canvas.tsx +1 -1
  26. package/app/components/canvas/confirmation/confirmation.tsx +1 -1
  27. package/app/components/form/form-button.tsx +1 -1
  28. package/app/components/form/form.module.css +9 -0
  29. package/app/components/public-signing-key-modal/public-signing-key-modal.module.css +163 -49
  30. package/app/components/public-signing-key-modal/public-signing-key-modal.tsx +365 -88
  31. package/app/components/sidebar/case-export/case-export.tsx +2 -54
  32. package/app/components/sidebar/case-import/case-import.tsx +20 -8
  33. package/app/components/sidebar/case-import/components/CasePreviewSection.tsx +1 -1
  34. package/app/components/sidebar/case-import/components/ConfirmationDialog.tsx +1 -1
  35. package/app/components/sidebar/case-import/hooks/useFilePreview.ts +9 -7
  36. package/app/components/sidebar/case-import/hooks/useImportExecution.ts +2 -2
  37. package/app/components/sidebar/case-import/utils/file-validation.ts +57 -2
  38. package/app/components/sidebar/cases/case-sidebar.tsx +106 -50
  39. package/app/components/sidebar/cases/cases-modal.tsx +1 -1
  40. package/app/components/sidebar/cases/cases.module.css +101 -18
  41. package/app/components/sidebar/files/files-modal.tsx +3 -2
  42. package/app/components/sidebar/notes/notes-sidebar.tsx +3 -3
  43. package/app/components/sidebar/notes/notes.module.css +33 -13
  44. package/app/components/sidebar/sidebar-container.tsx +4 -3
  45. package/app/components/sidebar/sidebar.tsx +2 -2
  46. package/app/components/sidebar/upload/image-upload-zone.tsx +2 -2
  47. package/app/components/theme-provider/theme-provider.tsx +1 -1
  48. package/app/components/user/delete-account.tsx +1 -1
  49. package/app/components/user/manage-profile.tsx +3 -3
  50. package/app/components/user/mfa-phone-update.tsx +17 -14
  51. package/app/contexts/auth.context.ts +1 -1
  52. package/app/root.tsx +2 -2
  53. package/app/routes/auth/emailActionHandler.tsx +2 -2
  54. package/app/routes/auth/emailVerification.tsx +2 -2
  55. package/app/routes/auth/login.tsx +134 -11
  56. package/app/routes/auth/passwordReset.tsx +2 -2
  57. package/app/routes/striae/striae.tsx +2 -2
  58. package/app/services/audit/audit-console-logger.ts +46 -0
  59. package/app/services/audit/audit-export-csv.ts +126 -0
  60. package/app/services/audit/audit-export-report.ts +174 -0
  61. package/app/services/audit/audit-export-signing.ts +85 -0
  62. package/app/services/audit/audit-export.service.ts +334 -0
  63. package/app/services/audit/audit-file-type.ts +13 -0
  64. package/app/services/audit/audit-query-helpers.ts +88 -0
  65. package/app/services/audit/audit-worker-client.ts +95 -0
  66. package/app/services/audit/audit.service.ts +990 -0
  67. package/app/services/audit/builders/audit-entry-builder.ts +32 -0
  68. package/app/services/audit/builders/audit-event-builders-annotation.ts +150 -0
  69. package/app/services/audit/builders/audit-event-builders-case-file.ts +249 -0
  70. package/app/services/audit/builders/audit-event-builders-user-security.ts +449 -0
  71. package/app/services/audit/builders/audit-event-builders-workflow.ts +272 -0
  72. package/app/services/audit/builders/index.ts +40 -0
  73. package/app/services/audit/index.ts +2 -0
  74. package/app/types/case.ts +2 -2
  75. package/app/types/exceljs-bare.d.ts +3 -1
  76. package/app/types/user.ts +1 -1
  77. package/app/utils/SHA256.ts +5 -1
  78. package/app/utils/audit-export-signature.ts +2 -2
  79. package/app/utils/confirmation-signature.ts +8 -4
  80. package/app/utils/data-operations.ts +5 -5
  81. package/app/utils/export-verification.ts +353 -0
  82. package/app/utils/mfa-phone.ts +1 -1
  83. package/app/utils/mfa.ts +1 -1
  84. package/app/utils/permissions.ts +2 -2
  85. package/app/utils/signature-utils.ts +74 -4
  86. package/package.json +11 -9
  87. package/public/favicon.ico +0 -0
  88. package/public/icon-256.png +0 -0
  89. package/public/icon-512.png +0 -0
  90. package/public/manifest.json +39 -0
  91. package/public/shortcut.png +0 -0
  92. package/public/social-image.png +0 -0
  93. package/react-router.config.ts +5 -0
  94. package/worker-configuration.d.ts +4435 -562
  95. package/workers/data-worker/src/data-worker.example.ts +3 -3
  96. package/workers/pdf-worker/scripts/generate-assets.js +94 -0
  97. package/workers/pdf-worker/src/{generated-assets.ts → assets/generated-assets.ts} +117 -117
  98. package/workers/pdf-worker/src/{format-striae.ts → formats/format-striae.ts} +535 -535
  99. package/workers/pdf-worker/src/pdf-worker.example.ts +1 -1
  100. package/app/services/audit-export.service.ts +0 -755
  101. package/app/services/audit.service.ts +0 -1474
  102. package/public/favicon.svg +0 -9
  103. /package/app/services/{firebase-errors.ts → firebase/errors.ts} +0 -0
  104. /package/app/services/{firebase.ts → firebase/index.ts} +0 -0
@@ -1,1474 +0,0 @@
1
- import { User } from 'firebase/auth';
2
- import {
3
- ValidationAuditEntry,
4
- CreateAuditEntryParams,
5
- AuditTrail,
6
- AuditQueryParams,
7
- AuditSummary,
8
- WorkflowPhase,
9
- AuditAction,
10
- AuditResult,
11
- AuditFileType,
12
- SecurityCheckResults,
13
- PerformanceMetrics
14
- } from '~/types';
15
- import paths from '~/config/config.json';
16
- import { getDataApiKey } from '~/utils/auth';
17
- import { generateWorkflowId } from '../utils/id-generator';
18
-
19
- const AUDIT_WORKER_URL = paths.audit_worker_url;
20
-
21
- type AnnotationSnapshot = Record<string, unknown> & {
22
- type?: 'measurement' | 'identification' | 'comparison' | 'note' | 'region';
23
- position?: { x: number; y: number };
24
- size?: { width: number; height: number };
25
- };
26
-
27
- const toAnnotationSnapshot = (value: unknown): AnnotationSnapshot | undefined => {
28
- if (typeof value !== 'object' || value === null) {
29
- return undefined;
30
- }
31
-
32
- return value as AnnotationSnapshot;
33
- };
34
-
35
- /**
36
- * Audit Service for ValidationAuditEntry system
37
- * Provides comprehensive audit logging throughout the confirmation workflow
38
- */
39
- export class AuditService {
40
- private static instance: AuditService;
41
- private auditBuffer: ValidationAuditEntry[] = [];
42
- private workflowId: string | null = null;
43
-
44
- private constructor() {}
45
-
46
- public static getInstance(): AuditService {
47
- if (!AuditService.instance) {
48
- AuditService.instance = new AuditService();
49
- }
50
- return AuditService.instance;
51
- }
52
-
53
- /**
54
- * Initialize a new workflow session with unique ID
55
- */
56
- public startWorkflow(caseNumber: string): string {
57
- const workflowId = generateWorkflowId(caseNumber);
58
- this.workflowId = workflowId;
59
- console.log(`🔍 Audit: Started workflow ${this.workflowId}`);
60
- return workflowId;
61
- }
62
-
63
- /**
64
- * End current workflow session
65
- */
66
- public endWorkflow(): void {
67
- if (this.workflowId) {
68
- console.log(`🔍 Audit: Ended workflow ${this.workflowId}`);
69
- this.workflowId = null;
70
- }
71
- }
72
-
73
- /**
74
- * Create and log an audit entry
75
- */
76
- public async logEvent(params: CreateAuditEntryParams): Promise<void> {
77
- const startTime = Date.now();
78
-
79
- try {
80
- const auditEntry: ValidationAuditEntry = {
81
- timestamp: new Date().toISOString(),
82
- userId: params.userId,
83
- userEmail: params.userEmail,
84
- action: params.action,
85
- result: params.result,
86
- details: {
87
- fileName: params.fileName,
88
- fileType: params.fileType,
89
- hashValid: params.hashValid,
90
- validationErrors: params.validationErrors || [],
91
- caseNumber: params.caseNumber,
92
- confirmationId: params.confirmationId,
93
- originalExaminerUid: params.originalExaminerUid,
94
- reviewingExaminerUid: params.reviewingExaminerUid,
95
- workflowPhase: params.workflowPhase,
96
- securityChecks: params.securityChecks,
97
- performanceMetrics: params.performanceMetrics,
98
- // Extended detail fields
99
- caseDetails: params.caseDetails,
100
- fileDetails: params.fileDetails,
101
- annotationDetails: params.annotationDetails,
102
- sessionDetails: params.sessionDetails,
103
- securityDetails: params.securityDetails
104
- }
105
- };
106
-
107
- // Add to buffer for batch processing
108
- this.auditBuffer.push(auditEntry);
109
-
110
- // Log to console for immediate feedback
111
- this.logToConsole(auditEntry);
112
-
113
- // Persist to storage asynchronously
114
- await this.persistAuditEntry(auditEntry);
115
-
116
- const endTime = Date.now();
117
- console.log(`🔍 Audit: Event logged in ${endTime - startTime}ms`);
118
-
119
- } catch (error) {
120
- console.error('🚨 Audit: Failed to log event:', error);
121
- // Don't throw - audit failures shouldn't break the main workflow
122
- }
123
- }
124
-
125
- /**
126
- * Log case export event
127
- */
128
- public async logCaseExport(
129
- user: User,
130
- caseNumber: string,
131
- fileName: string,
132
- result: AuditResult,
133
- errors: string[] = [],
134
- performanceMetrics?: PerformanceMetrics,
135
- exportFormat?: 'json' | 'csv' | 'xlsx' | 'zip',
136
- protectionEnabled?: boolean,
137
- signatureDetails?: {
138
- present?: boolean;
139
- valid?: boolean;
140
- keyId?: string;
141
- }
142
- ): Promise<void> {
143
- const securityChecks: SecurityCheckResults = {
144
- selfConfirmationPrevented: false, // Not applicable for exports
145
- fileIntegrityValid: result === 'success',
146
- manifestSignaturePresent: signatureDetails?.present,
147
- manifestSignatureValid: signatureDetails?.valid,
148
- manifestSignatureKeyId: signatureDetails?.keyId
149
- };
150
-
151
- // Determine file type based on format or fallback to filename
152
- let fileType: AuditFileType = 'case-package';
153
- if (exportFormat) {
154
- switch (exportFormat) {
155
- case 'json':
156
- fileType = 'json-data';
157
- break;
158
- case 'csv':
159
- case 'xlsx':
160
- fileType = 'csv-export';
161
- break;
162
- case 'zip':
163
- fileType = 'case-package';
164
- break;
165
- default:
166
- fileType = 'case-package';
167
- }
168
- } else {
169
- // Fallback: extract from filename
170
- if (fileName.includes('.json')) fileType = 'json-data';
171
- else if (fileName.includes('.csv') || fileName.includes('.xlsx')) fileType = 'csv-export';
172
- else fileType = 'case-package';
173
- }
174
-
175
- await this.logEvent({
176
- userId: user.uid,
177
- userEmail: user.email || '',
178
- action: 'export',
179
- result,
180
- fileName,
181
- fileType,
182
- validationErrors: errors,
183
- caseNumber,
184
- workflowPhase: 'case-export',
185
- securityChecks,
186
- performanceMetrics,
187
- originalExaminerUid: user.uid
188
- });
189
- }
190
-
191
- /**
192
- * Log case import event
193
- */
194
- public async logCaseImport(
195
- user: User,
196
- caseNumber: string,
197
- fileName: string,
198
- result: AuditResult,
199
- hashValid: boolean,
200
- errors: string[] = [],
201
- originalExaminerUid?: string,
202
- performanceMetrics?: PerformanceMetrics,
203
- exporterUidValidated?: boolean, // Separate flag for validation status
204
- signatureDetails?: {
205
- present?: boolean;
206
- valid?: boolean;
207
- keyId?: string;
208
- }
209
- ): Promise<void> {
210
- const securityChecks: SecurityCheckResults = {
211
- selfConfirmationPrevented: originalExaminerUid ? originalExaminerUid !== user.uid : false,
212
- fileIntegrityValid: hashValid,
213
- exporterUidValidated: exporterUidValidated !== undefined ? exporterUidValidated : !!originalExaminerUid,
214
- manifestSignaturePresent: signatureDetails?.present,
215
- manifestSignatureValid: signatureDetails?.valid,
216
- manifestSignatureKeyId: signatureDetails?.keyId
217
- };
218
-
219
- await this.logEvent({
220
- userId: user.uid,
221
- userEmail: user.email || '',
222
- action: 'import',
223
- result,
224
- fileName,
225
- fileType: 'case-package',
226
- hashValid,
227
- validationErrors: errors,
228
- caseNumber,
229
- workflowPhase: 'case-import',
230
- securityChecks,
231
- performanceMetrics,
232
- originalExaminerUid,
233
- reviewingExaminerUid: user.uid
234
- });
235
- }
236
-
237
- /**
238
- * Log confirmation creation event
239
- */
240
- public async logConfirmationCreation(
241
- user: User,
242
- caseNumber: string,
243
- confirmationId: string,
244
- result: AuditResult,
245
- errors: string[] = [],
246
- originalExaminerUid?: string,
247
- performanceMetrics?: PerformanceMetrics,
248
- imageFileId?: string,
249
- originalImageFileName?: string
250
- ): Promise<void> {
251
- const securityChecks: SecurityCheckResults = {
252
- selfConfirmationPrevented: false, // Not applicable for confirmation creation
253
- fileIntegrityValid: true // Confirmation creation doesn't involve file integrity validation
254
- };
255
-
256
- await this.logEvent({
257
- userId: user.uid,
258
- userEmail: user.email || '',
259
- action: 'confirm',
260
- result,
261
- fileName: `confirmation-${confirmationId}`,
262
- fileType: 'confirmation-data',
263
- validationErrors: errors,
264
- caseNumber,
265
- confirmationId,
266
- workflowPhase: 'confirmation',
267
- securityChecks,
268
- performanceMetrics,
269
- originalExaminerUid,
270
- reviewingExaminerUid: user.uid,
271
- fileDetails: imageFileId && originalImageFileName ? {
272
- fileId: imageFileId,
273
- originalFileName: originalImageFileName,
274
- fileSize: 0 // Not applicable for confirmation creation
275
- } : undefined
276
- });
277
- }
278
-
279
- /**
280
- * Log confirmation export event
281
- */
282
- public async logConfirmationExport(
283
- user: User,
284
- caseNumber: string,
285
- fileName: string,
286
- confirmationCount: number,
287
- result: AuditResult,
288
- errors: string[] = [],
289
- originalExaminerUid?: string,
290
- performanceMetrics?: PerformanceMetrics,
291
- signatureDetails?: {
292
- present: boolean;
293
- valid: boolean;
294
- keyId?: string;
295
- }
296
- ): Promise<void> {
297
- const securityChecks: SecurityCheckResults = {
298
- selfConfirmationPrevented: false, // Not applicable for exports
299
- fileIntegrityValid: result === 'success',
300
- manifestSignaturePresent: signatureDetails?.present,
301
- manifestSignatureValid: signatureDetails?.valid,
302
- manifestSignatureKeyId: signatureDetails?.keyId
303
- };
304
-
305
- await this.logEvent({
306
- userId: user.uid,
307
- userEmail: user.email || '',
308
- action: 'export',
309
- result,
310
- fileName,
311
- fileType: 'confirmation-data',
312
- validationErrors: errors,
313
- caseNumber,
314
- workflowPhase: 'confirmation',
315
- securityChecks,
316
- performanceMetrics,
317
- originalExaminerUid,
318
- reviewingExaminerUid: user.uid
319
- });
320
- }
321
-
322
- /**
323
- * Log confirmation import event
324
- */
325
- public async logConfirmationImport(
326
- user: User,
327
- caseNumber: string,
328
- fileName: string,
329
- result: AuditResult,
330
- hashValid: boolean,
331
- confirmationsImported: number,
332
- errors: string[] = [],
333
- reviewingExaminerUid?: string,
334
- performanceMetrics?: PerformanceMetrics,
335
- exporterUidValidated?: boolean, // Separate flag for validation status
336
- totalConfirmationsInFile?: number, // Total confirmations in the import file
337
- signatureDetails?: {
338
- present: boolean;
339
- valid: boolean;
340
- keyId?: string;
341
- }
342
- ): Promise<void> {
343
- const securityChecks: SecurityCheckResults = {
344
- selfConfirmationPrevented: reviewingExaminerUid ? reviewingExaminerUid === user.uid : false,
345
- fileIntegrityValid: hashValid,
346
- exporterUidValidated: exporterUidValidated !== undefined ? exporterUidValidated : !!reviewingExaminerUid,
347
- manifestSignaturePresent: signatureDetails?.present,
348
- manifestSignatureValid: signatureDetails?.valid,
349
- manifestSignatureKeyId: signatureDetails?.keyId
350
- };
351
-
352
- await this.logEvent({
353
- userId: user.uid,
354
- userEmail: user.email || '',
355
- action: 'import',
356
- result,
357
- fileName,
358
- fileType: 'confirmation-data',
359
- hashValid,
360
- validationErrors: errors,
361
- caseNumber,
362
- workflowPhase: 'confirmation',
363
- securityChecks,
364
- performanceMetrics: performanceMetrics ? {
365
- ...performanceMetrics,
366
- validationStepsCompleted: confirmationsImported, // Successfully imported
367
- validationStepsFailed: errors.length
368
- } : {
369
- processingTimeMs: 0,
370
- fileSizeBytes: 0,
371
- validationStepsCompleted: confirmationsImported, // Successfully imported
372
- validationStepsFailed: errors.length
373
- },
374
- originalExaminerUid: user.uid,
375
- reviewingExaminerUid: reviewingExaminerUid, // Pass through the reviewing examiner UID
376
- // Store total confirmations in file using caseDetails
377
- caseDetails: totalConfirmationsInFile !== undefined ? {
378
- totalAnnotations: totalConfirmationsInFile // Total confirmations in the import file
379
- } : undefined
380
- });
381
- }
382
-
383
- // =============================================================================
384
- // COMPREHENSIVE AUDIT LOGGING METHODS
385
- // =============================================================================
386
-
387
- /**
388
- * Log case creation event
389
- */
390
- public async logCaseCreation(
391
- user: User,
392
- caseNumber: string,
393
- caseName: string
394
- ): Promise<void> {
395
- await this.logEvent({
396
- userId: user.uid,
397
- userEmail: user.email || '',
398
- action: 'case-create',
399
- result: 'success',
400
- fileName: `${caseNumber}.case`,
401
- fileType: 'case-package',
402
- validationErrors: [],
403
- caseNumber,
404
- workflowPhase: 'casework',
405
- caseDetails: {
406
- newCaseName: caseName,
407
- createdDate: new Date().toISOString(),
408
- totalFiles: 0,
409
- totalAnnotations: 0
410
- }
411
- });
412
- }
413
-
414
- /**
415
- * Log case rename event
416
- */
417
- public async logCaseRename(
418
- user: User,
419
- caseNumber: string,
420
- oldName: string,
421
- newName: string
422
- ): Promise<void> {
423
- await this.logEvent({
424
- userId: user.uid,
425
- userEmail: user.email || '',
426
- action: 'case-rename',
427
- result: 'success',
428
- fileName: `${caseNumber}.case`,
429
- fileType: 'case-package',
430
- validationErrors: [],
431
- caseNumber,
432
- workflowPhase: 'casework',
433
- caseDetails: {
434
- oldCaseName: oldName,
435
- newCaseName: newName,
436
- lastModified: new Date().toISOString()
437
- }
438
- });
439
- }
440
-
441
- /**
442
- * Log case deletion event
443
- */
444
- public async logCaseDeletion(
445
- user: User,
446
- caseNumber: string,
447
- caseName: string,
448
- deleteReason: string,
449
- backupCreated: boolean = false
450
- ): Promise<void> {
451
- await this.logEvent({
452
- userId: user.uid,
453
- userEmail: user.email || '',
454
- action: 'case-delete',
455
- result: 'success',
456
- fileName: `${caseNumber}.case`,
457
- fileType: 'case-package',
458
- validationErrors: [],
459
- caseNumber,
460
- workflowPhase: 'casework',
461
- caseDetails: {
462
- newCaseName: caseName,
463
- deleteReason,
464
- backupCreated,
465
- lastModified: new Date().toISOString()
466
- }
467
- });
468
- }
469
-
470
- /**
471
- * Log file upload event
472
- */
473
- public async logFileUpload(
474
- user: User,
475
- fileName: string,
476
- fileSize: number,
477
- mimeType: string,
478
- uploadMethod: 'drag-drop' | 'file-picker' | 'api' | 'import',
479
- caseNumber: string,
480
- result: AuditResult = 'success',
481
- processingTime?: number,
482
- fileId?: string
483
- ): Promise<void> {
484
- await this.logEvent({
485
- userId: user.uid,
486
- userEmail: user.email || '',
487
- action: 'file-upload',
488
- result,
489
- fileName,
490
- fileType: this.getFileTypeFromMime(mimeType),
491
- validationErrors: [],
492
- caseNumber,
493
- workflowPhase: 'casework',
494
- fileDetails: {
495
- fileId: fileId || undefined,
496
- originalFileName: fileName,
497
- fileSize,
498
- mimeType,
499
- uploadMethod,
500
- processingTime,
501
- thumbnailGenerated: result === 'success' && this.isImageFile(mimeType)
502
- },
503
- performanceMetrics: processingTime ? {
504
- processingTimeMs: processingTime,
505
- fileSizeBytes: fileSize
506
- } : undefined
507
- });
508
- }
509
-
510
- /**
511
- * Log file deletion event
512
- */
513
- public async logFileDeletion(
514
- user: User,
515
- fileName: string,
516
- fileSize: number,
517
- deleteReason: string,
518
- caseNumber: string,
519
- fileId?: string,
520
- originalFileName?: string
521
- ): Promise<void> {
522
- await this.logEvent({
523
- userId: user.uid,
524
- userEmail: user.email || '',
525
- action: 'file-delete',
526
- result: 'success',
527
- fileName,
528
- fileType: 'unknown',
529
- validationErrors: [],
530
- caseNumber,
531
- workflowPhase: 'casework',
532
- fileDetails: {
533
- fileId: fileId || undefined,
534
- originalFileName,
535
- fileSize,
536
- deleteReason
537
- }
538
- });
539
- }
540
-
541
- /**
542
- * Log file access event (e.g., viewing an image)
543
- */
544
- public async logFileAccess(
545
- user: User,
546
- fileName: string,
547
- fileId: string,
548
- accessMethod: 'direct-url' | 'signed-url' | 'download',
549
- caseNumber: string,
550
- result: AuditResult = 'success',
551
- processingTime?: number,
552
- accessReason?: string,
553
- originalFileName?: string
554
- ): Promise<void> {
555
- await this.logEvent({
556
- userId: user.uid,
557
- userEmail: user.email || '',
558
- action: 'file-access',
559
- result,
560
- fileName,
561
- fileType: 'image-file', // Most file access in Striae is for images
562
- validationErrors: result === 'failure' ? ['File access failed'] : [],
563
- caseNumber,
564
- workflowPhase: 'casework',
565
- fileDetails: {
566
- fileId,
567
- originalFileName,
568
- fileSize: 0, // File size not available for access events
569
- uploadMethod: accessMethod,
570
- processingTime,
571
- sourceLocation: accessReason || 'Image viewer'
572
- },
573
- performanceMetrics: processingTime ? {
574
- processingTimeMs: processingTime,
575
- fileSizeBytes: 0
576
- } : undefined
577
- });
578
- }
579
-
580
- /**
581
- * Log annotation creation event
582
- */
583
- public async logAnnotationCreate(
584
- user: User,
585
- annotationId: string,
586
- annotationType: 'measurement' | 'identification' | 'comparison' | 'note' | 'region',
587
- annotationData: unknown,
588
- caseNumber: string,
589
- tool?: string,
590
- imageFileId?: string,
591
- originalImageFileName?: string
592
- ): Promise<void> {
593
- const annotationSnapshot = toAnnotationSnapshot(annotationData);
594
-
595
- await this.logEvent({
596
- userId: user.uid,
597
- userEmail: user.email || '',
598
- action: 'annotation-create',
599
- result: 'success',
600
- fileName: `annotation-${annotationId}.json`,
601
- fileType: 'json-data',
602
- validationErrors: [],
603
- caseNumber,
604
- workflowPhase: 'casework',
605
- annotationDetails: {
606
- annotationId,
607
- annotationType,
608
- annotationData,
609
- tool,
610
- canvasPosition: annotationSnapshot?.position,
611
- annotationSize: annotationSnapshot?.size
612
- },
613
- fileDetails: imageFileId || originalImageFileName ? {
614
- fileId: imageFileId,
615
- originalFileName: originalImageFileName,
616
- fileSize: 0, // Not available for image annotations
617
- mimeType: 'image/*', // Generic image type
618
- uploadMethod: 'api'
619
- } : undefined
620
- });
621
- }
622
-
623
- /**
624
- * Log annotation edit event
625
- */
626
- public async logAnnotationEdit(
627
- user: User,
628
- annotationId: string,
629
- previousValue: unknown,
630
- newValue: unknown,
631
- caseNumber: string,
632
- tool?: string,
633
- imageFileId?: string,
634
- originalImageFileName?: string
635
- ): Promise<void> {
636
- const newValueSnapshot = toAnnotationSnapshot(newValue);
637
-
638
- await this.logEvent({
639
- userId: user.uid,
640
- userEmail: user.email || '',
641
- action: 'annotation-edit',
642
- result: 'success',
643
- fileName: `annotation-${annotationId}.json`,
644
- fileType: 'json-data',
645
- validationErrors: [],
646
- caseNumber,
647
- workflowPhase: 'casework',
648
- annotationDetails: {
649
- annotationId,
650
- annotationType: newValueSnapshot?.type,
651
- annotationData: newValue,
652
- previousValue,
653
- tool
654
- },
655
- fileDetails: imageFileId || originalImageFileName ? {
656
- fileId: imageFileId,
657
- originalFileName: originalImageFileName,
658
- fileSize: 0, // Not available for image annotations
659
- mimeType: 'image/*', // Generic image type
660
- uploadMethod: 'api'
661
- } : undefined
662
- });
663
- }
664
-
665
- /**
666
- * Log annotation deletion event
667
- */
668
- public async logAnnotationDelete(
669
- user: User,
670
- annotationId: string,
671
- annotationData: unknown,
672
- caseNumber: string,
673
- deleteReason?: string,
674
- imageFileId?: string,
675
- originalImageFileName?: string
676
- ): Promise<void> {
677
- const annotationSnapshot = toAnnotationSnapshot(annotationData);
678
-
679
- await this.logEvent({
680
- userId: user.uid,
681
- userEmail: user.email || '',
682
- action: 'annotation-delete',
683
- result: 'success',
684
- fileName: `annotation-${annotationId}.json`,
685
- fileType: 'json-data',
686
- validationErrors: [],
687
- caseNumber,
688
- workflowPhase: 'casework',
689
- annotationDetails: {
690
- annotationId,
691
- annotationType: annotationSnapshot?.type,
692
- annotationData,
693
- tool: deleteReason
694
- },
695
- fileDetails: imageFileId || originalImageFileName ? {
696
- fileId: imageFileId,
697
- originalFileName: originalImageFileName,
698
- fileSize: 0, // Not available for image annotations
699
- mimeType: 'image/*', // Generic image type
700
- uploadMethod: 'api'
701
- } : undefined
702
- });
703
- }
704
-
705
- /**
706
- * Log user login event
707
- */
708
- public async logUserLogin(
709
- user: User,
710
- sessionId: string,
711
- loginMethod: 'firebase' | 'sso' | 'api-key' | 'manual',
712
- userAgent?: string
713
- ): Promise<void> {
714
- await this.logEvent({
715
- userId: user.uid,
716
- userEmail: user.email || '',
717
- action: 'user-login',
718
- result: 'success',
719
- fileName: `session-${sessionId}.log`,
720
- fileType: 'log-file',
721
- validationErrors: [],
722
- workflowPhase: 'user-management',
723
- sessionDetails: {
724
- sessionId,
725
- userAgent,
726
- loginMethod
727
- }
728
- });
729
- }
730
-
731
- /**
732
- * Log user logout event
733
- */
734
- public async logUserLogout(
735
- user: User,
736
- sessionId: string,
737
- sessionDuration: number,
738
- logoutReason: 'user-initiated' | 'timeout' | 'security' | 'error'
739
- ): Promise<void> {
740
- await this.logEvent({
741
- userId: user.uid,
742
- userEmail: user.email || '',
743
- action: 'user-logout',
744
- result: 'success',
745
- fileName: `session-${sessionId}.log`,
746
- fileType: 'log-file',
747
- validationErrors: [],
748
- workflowPhase: 'user-management',
749
- sessionDetails: {
750
- sessionId,
751
- sessionDuration,
752
- logoutReason
753
- }
754
- });
755
- }
756
-
757
- /**
758
- * Log user profile update event
759
- */
760
- public async logUserProfileUpdate(
761
- user: User,
762
- profileField: 'displayName' | 'email' | 'organization' | 'role' | 'preferences' | 'avatar',
763
- oldValue: string,
764
- newValue: string,
765
- result: AuditResult,
766
- sessionId?: string,
767
- errors: string[] = []
768
- ): Promise<void> {
769
- await this.logEvent({
770
- userId: user.uid,
771
- userEmail: user.email || '',
772
- action: 'user-profile-update',
773
- result,
774
- fileName: `profile-update-${profileField}.log`,
775
- fileType: 'log-file',
776
- validationErrors: errors,
777
- workflowPhase: 'user-management',
778
- sessionDetails: sessionId ? {
779
- sessionId
780
- } : undefined,
781
- userProfileDetails: {
782
- profileField,
783
- oldValue,
784
- newValue
785
- }
786
- });
787
- }
788
-
789
- /**
790
- * Log password reset event
791
- */
792
- public async logPasswordReset(
793
- userEmail: string,
794
- resetMethod: 'email' | 'sms' | 'security-questions' | 'admin-reset',
795
- result: AuditResult,
796
- resetToken?: string,
797
- verificationMethod?: 'email-link' | 'sms-code' | 'totp' | 'backup-codes',
798
- verificationAttempts?: number,
799
- passwordComplexityMet?: boolean,
800
- previousPasswordReused?: boolean,
801
- sessionId?: string,
802
- errors: string[] = []
803
- ): Promise<void> {
804
- // For password resets, we might not have the full user object yet
805
- const userId = ''; // No user ID available during password reset
806
-
807
- await this.logEvent({
808
- userId,
809
- userEmail,
810
- action: 'user-password-reset',
811
- result,
812
- fileName: `password-reset-${resetMethod}.log`,
813
- fileType: 'log-file',
814
- validationErrors: errors,
815
- workflowPhase: 'user-management',
816
- sessionDetails: sessionId ? {
817
- sessionId
818
- } : undefined,
819
- userProfileDetails: {
820
- resetMethod,
821
- resetToken: resetToken ? `***${resetToken.slice(-4)}` : undefined, // Only store last 4 chars
822
- verificationMethod,
823
- verificationAttempts,
824
- passwordComplexityMet,
825
- previousPasswordReused
826
- }
827
- });
828
- }
829
-
830
- /**
831
- * Log user account deletion event
832
- */
833
- public async logAccountDeletion(
834
- user: User,
835
- result: AuditResult,
836
- deletionReason: 'user-requested' | 'admin-initiated' | 'policy-violation' | 'inactive-account' = 'user-requested',
837
- confirmationMethod: 'uid-email' | 'password' | 'admin-override' = 'uid-email',
838
- casesCount?: number,
839
- filesCount?: number,
840
- dataRetentionPeriod?: number,
841
- emailNotificationSent?: boolean,
842
- sessionId?: string,
843
- errors: string[] = []
844
- ): Promise<void> {
845
- // Wrapper that extracts user data and calls the simplified version
846
- return this.logAccountDeletionSimple(
847
- user.uid,
848
- user.email || '',
849
- result,
850
- deletionReason,
851
- confirmationMethod,
852
- casesCount,
853
- filesCount,
854
- dataRetentionPeriod,
855
- emailNotificationSent,
856
- sessionId,
857
- errors
858
- );
859
- }
860
-
861
- /**
862
- * Log user account deletion event with simplified user data
863
- */
864
- public async logAccountDeletionSimple(
865
- userId: string,
866
- userEmail: string,
867
- result: AuditResult,
868
- deletionReason: 'user-requested' | 'admin-initiated' | 'policy-violation' | 'inactive-account' = 'user-requested',
869
- confirmationMethod: 'uid-email' | 'password' | 'admin-override' = 'uid-email',
870
- casesCount?: number,
871
- filesCount?: number,
872
- dataRetentionPeriod?: number,
873
- emailNotificationSent?: boolean,
874
- sessionId?: string,
875
- errors: string[] = []
876
- ): Promise<void> {
877
- await this.logEvent({
878
- userId,
879
- userEmail: userEmail || '',
880
- action: 'user-account-delete',
881
- result,
882
- fileName: `account-deletion-${userId}.log`,
883
- fileType: 'log-file',
884
- validationErrors: errors,
885
- workflowPhase: 'user-management',
886
- sessionDetails: sessionId ? {
887
- sessionId,
888
- } : undefined,
889
- userProfileDetails: {
890
- deletionReason,
891
- confirmationMethod,
892
- casesCount,
893
- filesCount,
894
- dataRetentionPeriod,
895
- emailNotificationSent
896
- }
897
- });
898
- }
899
-
900
- /**
901
- * Log user registration/creation event
902
- */
903
- public async logUserRegistration(
904
- user: User,
905
- firstName: string,
906
- lastName: string,
907
- company: string,
908
- registrationMethod: 'email-password' | 'sso' | 'admin-created' | 'api',
909
- userAgent?: string,
910
- sessionId?: string
911
- ): Promise<void> {
912
- await this.logEvent({
913
- userId: user.uid,
914
- userEmail: user.email || '',
915
- action: 'user-registration',
916
- result: 'success',
917
- fileName: `registration-${user.uid}.log`,
918
- fileType: 'log-file',
919
- validationErrors: [],
920
- workflowPhase: 'user-management',
921
- sessionDetails: sessionId ? {
922
- sessionId,
923
- userAgent
924
- } : { userAgent },
925
- userProfileDetails: {
926
- registrationMethod,
927
- firstName,
928
- lastName,
929
- company,
930
- emailVerificationRequired: true,
931
- mfaEnrollmentRequired: true
932
- }
933
- });
934
- }
935
-
936
- /**
937
- * Log successful MFA enrollment event
938
- */
939
- public async logMfaEnrollment(
940
- user: User,
941
- phoneNumber: string,
942
- mfaMethod: 'sms' | 'totp' | 'hardware-key',
943
- result: AuditResult,
944
- enrollmentAttempts?: number,
945
- sessionId?: string,
946
- userAgent?: string,
947
- errors: string[] = []
948
- ): Promise<void> {
949
- // Mask phone number for privacy (show only last 4 digits)
950
- const maskedPhone = phoneNumber.length > 4
951
- ? `***-***-${phoneNumber.slice(-4)}`
952
- : '***-***-****';
953
-
954
- await this.logEvent({
955
- userId: user.uid,
956
- userEmail: user.email || '',
957
- action: 'mfa-enrollment',
958
- result,
959
- fileName: `mfa-enrollment-${user.uid}.log`,
960
- fileType: 'log-file',
961
- validationErrors: errors,
962
- workflowPhase: 'user-management',
963
- sessionDetails: sessionId ? {
964
- sessionId,
965
- userAgent
966
- } : { userAgent },
967
- securityDetails: {
968
- mfaMethod,
969
- phoneNumber: maskedPhone,
970
- enrollmentAttempts,
971
- enrollmentDate: new Date().toISOString(),
972
- mandatoryEnrollment: true,
973
- backupCodesGenerated: false // SMS doesn't generate backup codes
974
- }
975
- });
976
- }
977
-
978
- /**
979
- * Log MFA authentication/verification event
980
- */
981
- public async logMfaAuthentication(
982
- user: User,
983
- mfaMethod: 'sms' | 'totp' | 'hardware-key',
984
- result: AuditResult,
985
- verificationAttempts?: number,
986
- sessionId?: string,
987
- userAgent?: string,
988
- errors: string[] = []
989
- ): Promise<void> {
990
- await this.logEvent({
991
- userId: user.uid,
992
- userEmail: user.email || '',
993
- action: 'mfa-authentication',
994
- result,
995
- fileName: `mfa-auth-${sessionId || Date.now()}.log`,
996
- fileType: 'log-file',
997
- validationErrors: errors,
998
- workflowPhase: 'user-management',
999
- sessionDetails: sessionId ? {
1000
- sessionId,
1001
- userAgent
1002
- } : { userAgent },
1003
- securityDetails: {
1004
- mfaMethod,
1005
- verificationAttempts,
1006
- authenticationDate: new Date().toISOString(),
1007
- loginFlowStep: 'second-factor'
1008
- }
1009
- });
1010
- }
1011
-
1012
- /**
1013
- * Log email verification event
1014
- */
1015
- public async logEmailVerification(
1016
- user: User,
1017
- result: AuditResult,
1018
- verificationMethod: 'email-link' | 'admin-verification',
1019
- verificationAttempts?: number,
1020
- sessionId?: string,
1021
- userAgent?: string,
1022
- errors: string[] = []
1023
- ): Promise<void> {
1024
- await this.logEvent({
1025
- userId: user.uid,
1026
- userEmail: user.email || '',
1027
- action: 'email-verification',
1028
- result,
1029
- fileName: `email-verification-${user.uid}.log`,
1030
- fileType: 'log-file',
1031
- validationErrors: errors,
1032
- workflowPhase: 'user-management',
1033
- sessionDetails: sessionId ? {
1034
- sessionId,
1035
- userAgent
1036
- } : { userAgent },
1037
- userProfileDetails: {
1038
- verificationMethod,
1039
- verificationAttempts,
1040
- verificationDate: new Date().toISOString(),
1041
- emailVerified: result === 'success'
1042
- }
1043
- });
1044
- }
1045
-
1046
- /**
1047
- * Log email verification event when no authenticated User object is available.
1048
- */
1049
- public async logEmailVerificationByEmail(
1050
- userEmail: string,
1051
- result: AuditResult,
1052
- verificationMethod: 'email-link' | 'admin-verification',
1053
- verificationAttempts?: number,
1054
- sessionId?: string,
1055
- userAgent?: string,
1056
- errors: string[] = [],
1057
- userId: string = ''
1058
- ): Promise<void> {
1059
- await this.logEvent({
1060
- userId,
1061
- userEmail,
1062
- action: 'email-verification',
1063
- result,
1064
- fileName: `email-verification-${userId || Date.now()}.log`,
1065
- fileType: 'log-file',
1066
- validationErrors: errors,
1067
- workflowPhase: 'user-management',
1068
- sessionDetails: sessionId ? {
1069
- sessionId,
1070
- userAgent
1071
- } : { userAgent },
1072
- userProfileDetails: {
1073
- verificationMethod,
1074
- verificationAttempts,
1075
- verificationDate: new Date().toISOString(),
1076
- emailVerified: result === 'success'
1077
- }
1078
- });
1079
- }
1080
-
1081
- /**
1082
- * Mark pending email verification as successful (retroactive)
1083
- * Called when user completes MFA enrollment, which implies email verification was successful
1084
- */
1085
- public async markEmailVerificationSuccessful(
1086
- user: User,
1087
- reason: string = 'MFA enrollment completed',
1088
- sessionId?: string,
1089
- userAgent?: string
1090
- ): Promise<void> {
1091
- await this.logEvent({
1092
- userId: user.uid,
1093
- userEmail: user.email || '',
1094
- action: 'email-verification',
1095
- result: 'success',
1096
- fileName: `email-verification-${user.uid}.log`,
1097
- fileType: 'log-file',
1098
- validationErrors: [],
1099
- workflowPhase: 'user-management',
1100
- sessionDetails: sessionId ? {
1101
- sessionId,
1102
- userAgent
1103
- } : { userAgent },
1104
- userProfileDetails: {
1105
- verificationMethod: 'email-link',
1106
- verificationAttempts: 1,
1107
- verificationDate: new Date().toISOString(),
1108
- emailVerified: true,
1109
- retroactiveVerification: true,
1110
- retroactiveReason: reason
1111
- }
1112
- });
1113
- }
1114
-
1115
- /**
1116
- * Log PDF generation event
1117
- */
1118
- public async logPDFGeneration(
1119
- user: User,
1120
- fileName: string,
1121
- caseNumber: string,
1122
- result: AuditResult,
1123
- processingTime: number,
1124
- fileSize?: number,
1125
- errors: string[] = [],
1126
- sourceFileId?: string,
1127
- sourceFileName?: string
1128
- ): Promise<void> {
1129
- await this.logEvent({
1130
- userId: user.uid,
1131
- userEmail: user.email || '',
1132
- action: 'pdf-generate',
1133
- result,
1134
- fileName,
1135
- fileType: 'pdf-document',
1136
- validationErrors: errors,
1137
- caseNumber,
1138
- workflowPhase: 'casework',
1139
- performanceMetrics: {
1140
- processingTimeMs: processingTime,
1141
- fileSizeBytes: fileSize || 0
1142
- },
1143
- fileDetails: sourceFileId && sourceFileName ? {
1144
- fileId: sourceFileId,
1145
- originalFileName: sourceFileName,
1146
- fileSize: 0 // PDF generation doesn't modify source file size
1147
- } : undefined
1148
- });
1149
- }
1150
-
1151
- /**
1152
- * Log security violation event
1153
- */
1154
- public async logSecurityViolation(
1155
- user: User | null,
1156
- incidentType: 'unauthorized-access' | 'data-breach' | 'malware' | 'injection' | 'brute-force' | 'privilege-escalation',
1157
- severity: 'low' | 'medium' | 'high' | 'critical',
1158
- description: string,
1159
- targetResource?: string,
1160
- blockedBySystem: boolean = true
1161
- ): Promise<void> {
1162
- await this.logEvent({
1163
- userId: user?.uid || 'unknown',
1164
- userEmail: user?.email || 'unknown@system.com',
1165
- action: 'security-violation',
1166
- result: blockedBySystem ? 'blocked' : 'failure',
1167
- fileName: `security-incident-${Date.now()}.log`,
1168
- fileType: 'log-file',
1169
- validationErrors: [description],
1170
- securityDetails: {
1171
- incidentType,
1172
- severity,
1173
- targetResource,
1174
- blockedBySystem,
1175
- investigationId: `INV-${Date.now()}`,
1176
- reportedToAuthorities: severity === 'critical',
1177
- mitigationSteps: [
1178
- blockedBySystem ? 'Automatically blocked by system' : 'Manual intervention required'
1179
- ]
1180
- }
1181
- });
1182
- }
1183
-
1184
- // =============================================================================
1185
- // HELPER METHODS
1186
- // =============================================================================
1187
-
1188
- /**
1189
- * Determine file type from MIME type
1190
- */
1191
- private getFileTypeFromMime(mimeType: string): AuditFileType {
1192
- if (mimeType.startsWith('image/')) return 'image-file';
1193
- if (mimeType === 'application/pdf') return 'pdf-document';
1194
- if (mimeType === 'application/json') return 'json-data';
1195
- if (mimeType === 'text/csv') return 'csv-export';
1196
- return 'unknown';
1197
- }
1198
-
1199
- /**
1200
- * Check if file is an image
1201
- */
1202
- private isImageFile(mimeType: string): boolean {
1203
- return mimeType.startsWith('image/');
1204
- }
1205
-
1206
- /**
1207
- * Get audit entries for display (public method for components)
1208
- */
1209
- public async getAuditEntriesForUser(userId: string, params?: {
1210
- startDate?: string;
1211
- endDate?: string;
1212
- caseNumber?: string;
1213
- action?: AuditAction;
1214
- result?: AuditResult;
1215
- workflowPhase?: WorkflowPhase;
1216
- offset?: number;
1217
- limit?: number;
1218
- }): Promise<ValidationAuditEntry[]> {
1219
- const queryParams: AuditQueryParams = {
1220
- userId,
1221
- ...params
1222
- };
1223
- return await this.getAuditEntries(queryParams);
1224
- }
1225
-
1226
- /**
1227
- * Get audit trail for a case
1228
- */
1229
- public async getAuditTrail(caseNumber: string): Promise<AuditTrail | null> {
1230
- try {
1231
- // Implement retrieval from storage
1232
- const entries = await this.getAuditEntries({ caseNumber });
1233
- if (!entries || entries.length === 0) {
1234
- return null;
1235
- }
1236
-
1237
- const summary = this.generateAuditSummary(entries);
1238
- const workflowId = this.workflowId || `${caseNumber}-archived`;
1239
-
1240
- return {
1241
- caseNumber,
1242
- workflowId,
1243
- entries,
1244
- summary
1245
- };
1246
- } catch (error) {
1247
- console.error('🚨 Audit: Failed to get audit trail:', error);
1248
- return null;
1249
- }
1250
- }
1251
-
1252
- /**
1253
- * Generate audit summary from entries
1254
- */
1255
- private generateAuditSummary(entries: ValidationAuditEntry[]): AuditSummary {
1256
- const successCount = entries.filter(e => e.result === 'success').length;
1257
- const failureCount = entries.filter(e => e.result === 'failure').length;
1258
- const warningCount = entries.filter(e => e.result === 'warning').length;
1259
-
1260
- const phases = [...new Set(entries
1261
- .map(e => e.details.workflowPhase)
1262
- .filter(Boolean))] as WorkflowPhase[];
1263
-
1264
- const users = [...new Set(entries.map(e => e.userId))];
1265
-
1266
- const timestamps = entries.map(e => e.timestamp).sort();
1267
- const securityIncidents = entries.filter(e =>
1268
- e.result === 'failure' &&
1269
- (e.details.securityChecks?.selfConfirmationPrevented === true ||
1270
- !e.details.securityChecks?.fileIntegrityValid)
1271
- ).length;
1272
-
1273
- return {
1274
- totalEvents: entries.length,
1275
- successfulEvents: successCount,
1276
- failedEvents: failureCount,
1277
- warningEvents: warningCount,
1278
- workflowPhases: phases,
1279
- participatingUsers: users,
1280
- startTimestamp: timestamps[0] || new Date().toISOString(),
1281
- endTimestamp: timestamps[timestamps.length - 1] || new Date().toISOString(),
1282
- complianceStatus: failureCount === 0 ? 'compliant' : 'non-compliant',
1283
- securityIncidents
1284
- };
1285
- }
1286
-
1287
- /**
1288
- * Get audit entries based on query parameters
1289
- */
1290
- private async getAuditEntries(params: AuditQueryParams): Promise<ValidationAuditEntry[]> {
1291
- try {
1292
- // If userId is provided, fetch from server
1293
- if (params.userId) {
1294
- const apiKey = await getDataApiKey();
1295
- const url = new URL(`${AUDIT_WORKER_URL}/audit/`);
1296
- url.searchParams.set('userId', params.userId);
1297
-
1298
- if (params.startDate) {
1299
- url.searchParams.set('startDate', params.startDate);
1300
- }
1301
-
1302
- if (params.endDate) {
1303
- url.searchParams.set('endDate', params.endDate);
1304
- }
1305
-
1306
- const response = await fetch(url.toString(), {
1307
- method: 'GET',
1308
- headers: {
1309
- 'X-Custom-Auth-Key': apiKey
1310
- }
1311
- });
1312
-
1313
- if (response.ok) {
1314
- const result = await response.json() as { entries: ValidationAuditEntry[]; total: number };
1315
- let entries = result.entries;
1316
-
1317
- // Apply client-side filters
1318
- if (params.caseNumber) {
1319
- entries = entries.filter(e => e.details.caseNumber === params.caseNumber);
1320
- }
1321
-
1322
- if (params.action) {
1323
- entries = entries.filter(e => e.action === params.action);
1324
- }
1325
-
1326
- if (params.result) {
1327
- entries = entries.filter(e => e.result === params.result);
1328
- }
1329
-
1330
- if (params.workflowPhase) {
1331
- entries = entries.filter(e => e.details.workflowPhase === params.workflowPhase);
1332
- }
1333
-
1334
- // Apply pagination
1335
- if (params.offset || params.limit) {
1336
- const offset = params.offset || 0;
1337
- const limit = params.limit || 100;
1338
- entries = entries.slice(offset, offset + limit);
1339
- }
1340
-
1341
- return entries;
1342
- } else {
1343
- console.error('🚨 Audit: Failed to fetch entries from server');
1344
- }
1345
- }
1346
-
1347
- // Fallback to buffer for backward compatibility
1348
- let entries = [...this.auditBuffer];
1349
-
1350
- if (params.caseNumber) {
1351
- entries = entries.filter(e => e.details.caseNumber === params.caseNumber);
1352
- }
1353
-
1354
- if (params.userId) {
1355
- entries = entries.filter(e => e.userId === params.userId);
1356
- }
1357
-
1358
- if (params.action) {
1359
- entries = entries.filter(e => e.action === params.action);
1360
- }
1361
-
1362
- if (params.result) {
1363
- entries = entries.filter(e => e.result === params.result);
1364
- }
1365
-
1366
- if (params.workflowPhase) {
1367
- entries = entries.filter(e => e.details.workflowPhase === params.workflowPhase);
1368
- }
1369
-
1370
- // Sort by timestamp (newest first)
1371
- entries.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
1372
-
1373
- // Apply pagination
1374
- if (params.offset || params.limit) {
1375
- const offset = params.offset || 0;
1376
- const limit = params.limit || 100;
1377
- entries = entries.slice(offset, offset + limit);
1378
- }
1379
-
1380
- return entries;
1381
- } catch (error) {
1382
- console.error('🚨 Audit: Failed to get audit entries:', error);
1383
- return [];
1384
- }
1385
- }
1386
-
1387
- /**
1388
- * Persist audit entry to storage
1389
- */
1390
- private async persistAuditEntry(entry: ValidationAuditEntry): Promise<void> {
1391
- try {
1392
- // Store to audit worker
1393
- const apiKey = await getDataApiKey();
1394
- const url = new URL(`${AUDIT_WORKER_URL}/audit/`);
1395
- url.searchParams.set('userId', entry.userId);
1396
-
1397
- const response = await fetch(url.toString(), {
1398
- method: 'POST',
1399
- headers: {
1400
- 'Content-Type': 'application/json',
1401
- 'X-Custom-Auth-Key': apiKey
1402
- },
1403
- body: JSON.stringify(entry)
1404
- });
1405
-
1406
- if (!response.ok) {
1407
- const errorData = await response.json().catch(() => ({}));
1408
- console.error('🚨 Audit: Failed to persist entry:', response.status, errorData);
1409
- } else {
1410
- const result = await response.json() as { success: boolean; entryCount: number; filename: string };
1411
- console.log(`🔍 Audit: Entry persisted (${result.entryCount} total entries)`);
1412
- }
1413
- } catch (error) {
1414
- console.error('🚨 Audit: Storage error:', error);
1415
- }
1416
- }
1417
-
1418
- /**
1419
- * Log audit entry to console for development
1420
- */
1421
- private logToConsole(entry: ValidationAuditEntry): void {
1422
- const icon = entry.result === 'success' ? '✅' :
1423
- entry.result === 'failure' ? '❌' : '⚠️';
1424
-
1425
- console.log(
1426
- `${icon} Audit [${entry.action.toUpperCase()}]: ${entry.details.fileName} ` +
1427
- `(Case: ${entry.details.caseNumber || 'N/A'}) - ${entry.result.toUpperCase()}`
1428
- );
1429
-
1430
- if (entry.details.validationErrors.length > 0) {
1431
- console.log(' Errors:', entry.details.validationErrors);
1432
- }
1433
-
1434
- if (entry.details.securityChecks) {
1435
- const securityIssues = [];
1436
-
1437
- // selfConfirmationPrevented: Only check for import actions when self-confirmation was actually prevented
1438
- if (entry.action === 'import' && entry.details.securityChecks.selfConfirmationPrevented === true) {
1439
- securityIssues.push('selfConfirmationPrevented');
1440
- }
1441
-
1442
- // fileIntegrityValid: false means issue (integrity failed)
1443
- if (entry.details.securityChecks.fileIntegrityValid === false) {
1444
- securityIssues.push('fileIntegrityValid');
1445
- }
1446
-
1447
- // exporterUidValidated: false means issue (validation failed)
1448
- if (entry.details.securityChecks.exporterUidValidated === false) {
1449
- securityIssues.push('exporterUidValidated');
1450
- }
1451
-
1452
- if (securityIssues.length > 0) {
1453
- console.warn(' Security Issues:', securityIssues);
1454
- }
1455
- }
1456
- }
1457
-
1458
- /**
1459
- * Clear audit buffer (for testing)
1460
- */
1461
- public clearBuffer(): void {
1462
- this.auditBuffer = [];
1463
- }
1464
-
1465
- /**
1466
- * Get current buffer size (for monitoring)
1467
- */
1468
- public getBufferSize(): number {
1469
- return this.auditBuffer.length;
1470
- }
1471
- }
1472
-
1473
- // Export singleton instance
1474
- export const auditService = AuditService.getInstance();