@striae-org/striae 4.0.3 → 4.2.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/.env.example +8 -0
- package/app/components/actions/case-export/core-export.ts +14 -8
- package/app/components/actions/case-export/data-processing.ts +1 -0
- package/app/components/actions/case-export/download-handlers.ts +7 -0
- package/app/components/actions/case-export/metadata-helpers.ts +2 -1
- package/app/components/actions/case-import/confirmation-import.ts +12 -2
- package/app/components/actions/case-import/orchestrator.ts +78 -32
- package/app/components/actions/case-import/storage-operations.ts +97 -8
- package/app/components/actions/case-import/zip-processing.ts +159 -86
- package/app/components/actions/case-manage.ts +430 -8
- package/app/components/actions/confirm-export.ts +13 -4
- package/app/components/actions/generate-pdf.ts +10 -2
- package/app/components/actions/image-manage.ts +77 -44
- package/app/components/audit/user-audit-viewer.tsx +137 -945
- package/app/components/audit/user-audit.module.css +41 -0
- package/app/components/audit/viewer/audit-activity-summary.tsx +52 -0
- package/app/components/audit/viewer/audit-entries-list.tsx +207 -0
- package/app/components/audit/viewer/audit-filters-panel.tsx +307 -0
- package/app/components/audit/viewer/audit-user-info-card.tsx +44 -0
- package/app/components/audit/viewer/audit-viewer-header.tsx +55 -0
- package/app/components/audit/viewer/audit-viewer-utils.ts +123 -0
- package/app/components/audit/viewer/types.ts +1 -0
- package/app/components/audit/viewer/use-audit-viewer-data.ts +186 -0
- package/app/components/audit/viewer/use-audit-viewer-export.ts +176 -0
- package/app/components/audit/viewer/use-audit-viewer-filters.ts +141 -0
- package/app/components/auth/mfa-enrollment.module.css +13 -5
- package/app/components/auth/mfa-verification.module.css +13 -5
- package/app/components/canvas/box-annotations/box-annotations.module.css +22 -18
- package/app/components/canvas/box-annotations/box-annotations.tsx +15 -0
- package/app/components/canvas/canvas.module.css +64 -54
- package/app/components/canvas/canvas.tsx +17 -16
- package/app/components/canvas/confirmation/confirmation.module.css +1 -0
- package/app/components/canvas/confirmation/confirmation.tsx +17 -47
- package/app/components/navbar/case-modals/archive-case-modal.module.css +110 -0
- package/app/components/navbar/case-modals/archive-case-modal.tsx +129 -0
- package/app/components/navbar/case-modals/open-case-modal.module.css +81 -0
- package/app/components/navbar/case-modals/open-case-modal.tsx +120 -0
- package/app/components/navbar/case-modals/rename-case-modal.module.css +81 -0
- package/app/components/navbar/case-modals/rename-case-modal.tsx +107 -0
- package/app/components/navbar/navbar.module.css +447 -0
- package/app/components/navbar/navbar.tsx +377 -0
- package/app/components/public-signing-key-modal/public-signing-key-modal.module.css +2 -0
- package/app/components/public-signing-key-modal/public-signing-key-modal.tsx +21 -51
- package/app/components/sidebar/case-export/case-export.module.css +1 -0
- package/app/components/sidebar/case-export/case-export.tsx +14 -77
- package/app/components/sidebar/case-import/case-import.module.css +25 -0
- package/app/components/sidebar/case-import/case-import.tsx +64 -40
- package/app/components/sidebar/case-import/components/CasePreviewSection.tsx +20 -1
- package/app/components/sidebar/case-import/components/ConfirmationDialog.tsx +15 -0
- package/app/components/sidebar/cases/case-sidebar.tsx +25 -519
- package/app/components/sidebar/cases/cases-modal.module.css +45 -9
- package/app/components/sidebar/cases/cases-modal.tsx +16 -16
- package/app/components/sidebar/cases/cases.module.css +62 -21
- package/app/components/sidebar/files/files-modal.module.css +46 -10
- package/app/components/sidebar/files/files-modal.tsx +22 -23
- package/app/components/sidebar/notes/notes-editor-modal.module.css +49 -0
- package/app/components/sidebar/notes/notes-editor-modal.tsx +66 -0
- package/app/components/sidebar/notes/notes-modal.tsx +18 -17
- package/app/components/sidebar/notes/notes-sidebar.tsx +199 -113
- package/app/components/sidebar/notes/notes.module.css +155 -0
- package/app/components/sidebar/sidebar-container.tsx +15 -28
- package/app/components/sidebar/sidebar.module.css +7 -71
- package/app/components/sidebar/sidebar.tsx +24 -125
- package/app/components/sidebar/upload/image-upload-zone.module.css +13 -13
- package/app/components/toast/toast.module.css +2 -1
- package/app/components/toast/toast.tsx +16 -11
- package/app/components/user/delete-account.tsx +10 -31
- package/app/components/user/inactivity-warning.module.css +9 -6
- package/app/components/user/inactivity-warning.tsx +15 -2
- package/app/components/user/manage-profile.module.css +2 -0
- package/app/components/user/manage-profile.tsx +108 -40
- package/app/hooks/useOverlayDismiss.ts +116 -0
- package/app/routes/auth/login.example.tsx +19 -8
- package/app/routes/auth/login.tsx +785 -774
- package/app/routes/auth/passwordReset.module.css +23 -13
- package/app/routes/striae/striae.module.css +10 -3
- package/app/routes/striae/striae.tsx +477 -31
- package/app/routes.ts +7 -0
- package/app/services/audit/audit-export-csv.ts +2 -0
- package/app/services/audit/audit.service.ts +202 -32
- package/app/services/audit/builders/audit-entry-builder.ts +2 -1
- package/app/services/audit/builders/audit-event-builders-case-file.ts +43 -0
- package/app/services/audit/builders/audit-event-builders-user-security.ts +4 -2
- package/app/services/audit/builders/audit-event-builders-workflow.ts +8 -0
- package/app/services/audit/builders/index.ts +1 -0
- package/app/types/audit.ts +5 -2
- package/app/types/case.ts +29 -0
- package/app/types/import.ts +3 -0
- package/app/types/user.ts +1 -0
- package/app/utils/data/permissions.ts +17 -1
- package/app/utils/forensics/audit-export-signature.ts +5 -1
- package/app/utils/forensics/confirmation-signature.ts +3 -0
- package/app/utils/forensics/export-verification.ts +497 -22
- package/functions/api/pdf/[[path]].ts +32 -1
- package/load-context.ts +9 -0
- package/package.json +6 -2
- package/primershear.emails.example +6 -0
- package/scripts/deploy-pages-secrets.sh +6 -0
- package/scripts/deploy-primershear-emails.sh +167 -0
- package/worker-configuration.d.ts +7493 -7491
- package/workers/audit-worker/worker-configuration.d.ts +7448 -11323
- package/workers/audit-worker/wrangler.jsonc.example +1 -1
- package/workers/data-worker/worker-configuration.d.ts +7448 -11323
- package/workers/data-worker/wrangler.jsonc.example +1 -1
- package/workers/image-worker/worker-configuration.d.ts +7447 -11322
- package/workers/image-worker/wrangler.jsonc.example +1 -1
- package/workers/keys-worker/worker-configuration.d.ts +7447 -11322
- package/workers/keys-worker/wrangler.jsonc.example +1 -1
- package/workers/pdf-worker/src/formats/format-striae.ts +8 -7
- package/workers/pdf-worker/src/pdf-worker.example.ts +3 -0
- package/workers/pdf-worker/src/report-types.ts +3 -0
- package/workers/pdf-worker/worker-configuration.d.ts +7448 -11323
- package/workers/pdf-worker/wrangler.jsonc.example +1 -1
- package/workers/user-worker/src/user-worker.example.ts +6 -1
- package/workers/user-worker/worker-configuration.d.ts +7448 -11323
- package/workers/user-worker/wrangler.jsonc.example +1 -1
- package/wrangler.toml.example +1 -1
- package/public/.well-known/keybase.txt +0 -56
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
AuditResult,
|
|
10
10
|
PerformanceMetrics
|
|
11
11
|
} from '~/types';
|
|
12
|
+
import { getCaseData, getUserData } from '~/utils/data';
|
|
12
13
|
import { generateWorkflowId } from '~/utils/common';
|
|
13
14
|
import {
|
|
14
15
|
fetchAuditEntriesForUser,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
buildAnnotationCreateAuditParams,
|
|
27
28
|
buildAnnotationDeleteAuditParams,
|
|
28
29
|
buildAnnotationEditAuditParams,
|
|
30
|
+
buildCaseArchiveAuditParams,
|
|
29
31
|
buildCaseCreationAuditParams,
|
|
30
32
|
buildCaseDeletionAuditParams,
|
|
31
33
|
buildCaseExportAuditParams,
|
|
@@ -60,6 +62,7 @@ export class AuditService {
|
|
|
60
62
|
private static instance: AuditService;
|
|
61
63
|
private auditBuffer: ValidationAuditEntry[] = [];
|
|
62
64
|
private workflowId: string | null = null;
|
|
65
|
+
private userBadgeIdByUserId = new Map<string, string>();
|
|
63
66
|
|
|
64
67
|
private constructor() {}
|
|
65
68
|
|
|
@@ -97,7 +100,26 @@ export class AuditService {
|
|
|
97
100
|
const startTime = Date.now();
|
|
98
101
|
|
|
99
102
|
try {
|
|
100
|
-
const
|
|
103
|
+
const providedBadgeId = params.userProfileDetails?.badgeId?.trim();
|
|
104
|
+
if (providedBadgeId && params.userId) {
|
|
105
|
+
this.userBadgeIdByUserId.set(params.userId, providedBadgeId);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const resolvedBadgeId =
|
|
109
|
+
providedBadgeId ||
|
|
110
|
+
(params.userId ? this.userBadgeIdByUserId.get(params.userId) : undefined);
|
|
111
|
+
|
|
112
|
+
const paramsWithBadgeId: CreateAuditEntryParams = resolvedBadgeId
|
|
113
|
+
? {
|
|
114
|
+
...params,
|
|
115
|
+
userProfileDetails: {
|
|
116
|
+
...(params.userProfileDetails || {}),
|
|
117
|
+
badgeId: resolvedBadgeId
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
: params;
|
|
121
|
+
|
|
122
|
+
const auditEntry = buildValidationAuditEntry(paramsWithBadgeId);
|
|
101
123
|
|
|
102
124
|
// Add to buffer for batch processing
|
|
103
125
|
this.auditBuffer.push(auditEntry);
|
|
@@ -117,6 +139,67 @@ export class AuditService {
|
|
|
117
139
|
}
|
|
118
140
|
}
|
|
119
141
|
|
|
142
|
+
private normalizeBadgeId(badgeId?: string): string | undefined {
|
|
143
|
+
const normalized = badgeId?.trim();
|
|
144
|
+
return normalized ? normalized : undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private applyBadgeIdToParams(
|
|
148
|
+
params: CreateAuditEntryParams,
|
|
149
|
+
badgeId?: string
|
|
150
|
+
): CreateAuditEntryParams {
|
|
151
|
+
const normalizedBadgeId = this.normalizeBadgeId(badgeId);
|
|
152
|
+
if (!normalizedBadgeId) {
|
|
153
|
+
return params;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
...params,
|
|
158
|
+
userProfileDetails: {
|
|
159
|
+
...(params.userProfileDetails || {}),
|
|
160
|
+
badgeId: normalizedBadgeId
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private async resolveBadgeIdForUser(user: User): Promise<string | undefined> {
|
|
166
|
+
const cachedBadgeId = this.userBadgeIdByUserId.get(user.uid);
|
|
167
|
+
if (cachedBadgeId) {
|
|
168
|
+
return cachedBadgeId;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const userData = await getUserData(user);
|
|
173
|
+
const resolvedBadgeId = this.normalizeBadgeId(userData?.badgeId);
|
|
174
|
+
|
|
175
|
+
if (resolvedBadgeId) {
|
|
176
|
+
this.userBadgeIdByUserId.set(user.uid, resolvedBadgeId);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return resolvedBadgeId;
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error('🚨 Audit: Failed to resolve badge ID for user:', error);
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async logEventForUser(user: User, params: CreateAuditEntryParams): Promise<void> {
|
|
187
|
+
const resolvedBadgeId = await this.resolveBadgeIdForUser(user);
|
|
188
|
+
await this.logEvent(this.applyBadgeIdToParams(params, resolvedBadgeId));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private async logEventForOptionalUser(
|
|
192
|
+
user: User | null,
|
|
193
|
+
params: CreateAuditEntryParams
|
|
194
|
+
): Promise<void> {
|
|
195
|
+
if (!user) {
|
|
196
|
+
await this.logEvent(params);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
await this.logEventForUser(user, params);
|
|
201
|
+
}
|
|
202
|
+
|
|
120
203
|
/**
|
|
121
204
|
* Log case export event
|
|
122
205
|
*/
|
|
@@ -135,7 +218,7 @@ export class AuditService {
|
|
|
135
218
|
keyId?: string;
|
|
136
219
|
}
|
|
137
220
|
): Promise<void> {
|
|
138
|
-
await this.
|
|
221
|
+
await this.logEventForUser(user,
|
|
139
222
|
buildCaseExportAuditParams({
|
|
140
223
|
user,
|
|
141
224
|
caseNumber,
|
|
@@ -168,7 +251,7 @@ export class AuditService {
|
|
|
168
251
|
keyId?: string;
|
|
169
252
|
}
|
|
170
253
|
): Promise<void> {
|
|
171
|
-
await this.
|
|
254
|
+
await this.logEventForUser(user,
|
|
172
255
|
buildCaseImportAuditParams({
|
|
173
256
|
user,
|
|
174
257
|
caseNumber,
|
|
@@ -196,13 +279,15 @@ export class AuditService {
|
|
|
196
279
|
originalExaminerUid?: string,
|
|
197
280
|
performanceMetrics?: PerformanceMetrics,
|
|
198
281
|
imageFileId?: string,
|
|
199
|
-
originalImageFileName?: string
|
|
282
|
+
originalImageFileName?: string,
|
|
283
|
+
badgeId?: string
|
|
200
284
|
): Promise<void> {
|
|
201
|
-
await this.
|
|
285
|
+
await this.logEventForUser(user,
|
|
202
286
|
buildConfirmationCreationAuditParams({
|
|
203
287
|
user,
|
|
204
288
|
caseNumber,
|
|
205
289
|
confirmationId,
|
|
290
|
+
badgeId,
|
|
206
291
|
result,
|
|
207
292
|
errors,
|
|
208
293
|
originalExaminerUid,
|
|
@@ -231,7 +316,7 @@ export class AuditService {
|
|
|
231
316
|
keyId?: string;
|
|
232
317
|
}
|
|
233
318
|
): Promise<void> {
|
|
234
|
-
await this.
|
|
319
|
+
await this.logEventForUser(user,
|
|
235
320
|
buildConfirmationExportAuditParams({
|
|
236
321
|
user,
|
|
237
322
|
caseNumber,
|
|
@@ -264,9 +349,10 @@ export class AuditService {
|
|
|
264
349
|
present: boolean;
|
|
265
350
|
valid: boolean;
|
|
266
351
|
keyId?: string;
|
|
267
|
-
}
|
|
352
|
+
},
|
|
353
|
+
reviewerBadgeId?: string // Badge/ID number of the reviewing examiner who exported the file
|
|
268
354
|
): Promise<void> {
|
|
269
|
-
await this.
|
|
355
|
+
await this.logEventForUser(user,
|
|
270
356
|
buildConfirmationImportAuditParams({
|
|
271
357
|
user,
|
|
272
358
|
caseNumber,
|
|
@@ -276,6 +362,7 @@ export class AuditService {
|
|
|
276
362
|
confirmationsImported,
|
|
277
363
|
errors,
|
|
278
364
|
reviewingExaminerUid,
|
|
365
|
+
reviewerBadgeId,
|
|
279
366
|
performanceMetrics,
|
|
280
367
|
exporterUidValidated,
|
|
281
368
|
totalConfirmationsInFile,
|
|
@@ -296,7 +383,7 @@ export class AuditService {
|
|
|
296
383
|
caseNumber: string,
|
|
297
384
|
caseName: string
|
|
298
385
|
): Promise<void> {
|
|
299
|
-
await this.
|
|
386
|
+
await this.logEventForUser(user,
|
|
300
387
|
buildCaseCreationAuditParams({
|
|
301
388
|
user,
|
|
302
389
|
caseNumber,
|
|
@@ -314,7 +401,7 @@ export class AuditService {
|
|
|
314
401
|
oldName: string,
|
|
315
402
|
newName: string
|
|
316
403
|
): Promise<void> {
|
|
317
|
-
await this.
|
|
404
|
+
await this.logEventForUser(user,
|
|
318
405
|
buildCaseRenameAuditParams({
|
|
319
406
|
user,
|
|
320
407
|
caseNumber,
|
|
@@ -334,7 +421,7 @@ export class AuditService {
|
|
|
334
421
|
deleteReason: string,
|
|
335
422
|
backupCreated: boolean = false
|
|
336
423
|
): Promise<void> {
|
|
337
|
-
await this.
|
|
424
|
+
await this.logEventForUser(user,
|
|
338
425
|
buildCaseDeletionAuditParams({
|
|
339
426
|
user,
|
|
340
427
|
caseNumber,
|
|
@@ -345,6 +432,35 @@ export class AuditService {
|
|
|
345
432
|
);
|
|
346
433
|
}
|
|
347
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Log case archive event
|
|
437
|
+
*/
|
|
438
|
+
public async logCaseArchive(
|
|
439
|
+
user: User,
|
|
440
|
+
caseNumber: string,
|
|
441
|
+
caseName: string,
|
|
442
|
+
archiveReason: string,
|
|
443
|
+
result: AuditResult = 'success',
|
|
444
|
+
errors: string[] = [],
|
|
445
|
+
totalFiles?: number,
|
|
446
|
+
archivedAt?: string,
|
|
447
|
+
processingTimeMs?: number
|
|
448
|
+
): Promise<void> {
|
|
449
|
+
await this.logEventForUser(user,
|
|
450
|
+
buildCaseArchiveAuditParams({
|
|
451
|
+
user,
|
|
452
|
+
caseNumber,
|
|
453
|
+
caseName,
|
|
454
|
+
archiveReason,
|
|
455
|
+
result,
|
|
456
|
+
errors,
|
|
457
|
+
totalFiles,
|
|
458
|
+
archivedAt,
|
|
459
|
+
processingTimeMs
|
|
460
|
+
})
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
348
464
|
/**
|
|
349
465
|
* Log file upload event
|
|
350
466
|
*/
|
|
@@ -359,7 +475,7 @@ export class AuditService {
|
|
|
359
475
|
processingTime?: number,
|
|
360
476
|
fileId?: string
|
|
361
477
|
): Promise<void> {
|
|
362
|
-
await this.
|
|
478
|
+
await this.logEventForUser(user,
|
|
363
479
|
buildFileUploadAuditParams({
|
|
364
480
|
user,
|
|
365
481
|
fileName,
|
|
@@ -386,7 +502,7 @@ export class AuditService {
|
|
|
386
502
|
fileId?: string,
|
|
387
503
|
originalFileName?: string
|
|
388
504
|
): Promise<void> {
|
|
389
|
-
await this.
|
|
505
|
+
await this.logEventForUser(user,
|
|
390
506
|
buildFileDeletionAuditParams({
|
|
391
507
|
user,
|
|
392
508
|
fileName,
|
|
@@ -413,7 +529,7 @@ export class AuditService {
|
|
|
413
529
|
accessReason?: string,
|
|
414
530
|
originalFileName?: string
|
|
415
531
|
): Promise<void> {
|
|
416
|
-
await this.
|
|
532
|
+
await this.logEventForUser(user,
|
|
417
533
|
buildFileAccessAuditParams({
|
|
418
534
|
user,
|
|
419
535
|
fileName,
|
|
@@ -441,7 +557,7 @@ export class AuditService {
|
|
|
441
557
|
imageFileId?: string,
|
|
442
558
|
originalImageFileName?: string
|
|
443
559
|
): Promise<void> {
|
|
444
|
-
await this.
|
|
560
|
+
await this.logEventForUser(user,
|
|
445
561
|
buildAnnotationCreateAuditParams({
|
|
446
562
|
user,
|
|
447
563
|
annotationId,
|
|
@@ -468,7 +584,7 @@ export class AuditService {
|
|
|
468
584
|
imageFileId?: string,
|
|
469
585
|
originalImageFileName?: string
|
|
470
586
|
): Promise<void> {
|
|
471
|
-
await this.
|
|
587
|
+
await this.logEventForUser(user,
|
|
472
588
|
buildAnnotationEditAuditParams({
|
|
473
589
|
user,
|
|
474
590
|
annotationId,
|
|
@@ -494,7 +610,7 @@ export class AuditService {
|
|
|
494
610
|
imageFileId?: string,
|
|
495
611
|
originalImageFileName?: string
|
|
496
612
|
): Promise<void> {
|
|
497
|
-
await this.
|
|
613
|
+
await this.logEventForUser(user,
|
|
498
614
|
buildAnnotationDeleteAuditParams({
|
|
499
615
|
user,
|
|
500
616
|
annotationId,
|
|
@@ -516,7 +632,7 @@ export class AuditService {
|
|
|
516
632
|
loginMethod: 'firebase' | 'sso' | 'api-key' | 'manual',
|
|
517
633
|
userAgent?: string
|
|
518
634
|
): Promise<void> {
|
|
519
|
-
await this.
|
|
635
|
+
await this.logEventForUser(user,
|
|
520
636
|
buildUserLoginAuditParams({
|
|
521
637
|
user,
|
|
522
638
|
sessionId,
|
|
@@ -535,7 +651,7 @@ export class AuditService {
|
|
|
535
651
|
sessionDuration: number,
|
|
536
652
|
logoutReason: 'user-initiated' | 'timeout' | 'security' | 'error'
|
|
537
653
|
): Promise<void> {
|
|
538
|
-
await this.
|
|
654
|
+
await this.logEventForUser(user,
|
|
539
655
|
buildUserLogoutAuditParams({
|
|
540
656
|
user,
|
|
541
657
|
sessionId,
|
|
@@ -550,14 +666,15 @@ export class AuditService {
|
|
|
550
666
|
*/
|
|
551
667
|
public async logUserProfileUpdate(
|
|
552
668
|
user: User,
|
|
553
|
-
profileField: 'displayName' | 'email' | 'organization' | 'role' | 'preferences' | 'avatar',
|
|
669
|
+
profileField: 'displayName' | 'email' | 'organization' | 'role' | 'preferences' | 'avatar' | 'badgeId',
|
|
554
670
|
oldValue: string,
|
|
555
671
|
newValue: string,
|
|
556
672
|
result: AuditResult,
|
|
557
673
|
sessionId?: string,
|
|
558
|
-
errors: string[] = []
|
|
674
|
+
errors: string[] = [],
|
|
675
|
+
badgeId?: string
|
|
559
676
|
): Promise<void> {
|
|
560
|
-
await this.
|
|
677
|
+
await this.logEventForUser(user,
|
|
561
678
|
buildUserProfileUpdateAuditParams({
|
|
562
679
|
user,
|
|
563
680
|
profileField,
|
|
@@ -565,7 +682,8 @@ export class AuditService {
|
|
|
565
682
|
newValue,
|
|
566
683
|
result,
|
|
567
684
|
sessionId,
|
|
568
|
-
errors
|
|
685
|
+
errors,
|
|
686
|
+
badgeId
|
|
569
687
|
})
|
|
570
688
|
);
|
|
571
689
|
}
|
|
@@ -677,7 +795,7 @@ export class AuditService {
|
|
|
677
795
|
userAgent?: string,
|
|
678
796
|
sessionId?: string
|
|
679
797
|
): Promise<void> {
|
|
680
|
-
await this.
|
|
798
|
+
await this.logEventForUser(user,
|
|
681
799
|
buildUserRegistrationAuditParams({
|
|
682
800
|
user,
|
|
683
801
|
firstName,
|
|
@@ -703,7 +821,7 @@ export class AuditService {
|
|
|
703
821
|
userAgent?: string,
|
|
704
822
|
errors: string[] = []
|
|
705
823
|
): Promise<void> {
|
|
706
|
-
await this.
|
|
824
|
+
await this.logEventForUser(user,
|
|
707
825
|
buildMfaEnrollmentAuditParams({
|
|
708
826
|
user,
|
|
709
827
|
phoneNumber,
|
|
@@ -729,7 +847,7 @@ export class AuditService {
|
|
|
729
847
|
userAgent?: string,
|
|
730
848
|
errors: string[] = []
|
|
731
849
|
): Promise<void> {
|
|
732
|
-
await this.
|
|
850
|
+
await this.logEventForUser(user,
|
|
733
851
|
buildMfaAuthenticationAuditParams({
|
|
734
852
|
user,
|
|
735
853
|
mfaMethod,
|
|
@@ -754,7 +872,7 @@ export class AuditService {
|
|
|
754
872
|
userAgent?: string,
|
|
755
873
|
errors: string[] = []
|
|
756
874
|
): Promise<void> {
|
|
757
|
-
await this.
|
|
875
|
+
await this.logEventForUser(user,
|
|
758
876
|
buildEmailVerificationAuditParams({
|
|
759
877
|
user,
|
|
760
878
|
result,
|
|
@@ -804,7 +922,7 @@ export class AuditService {
|
|
|
804
922
|
sessionId?: string,
|
|
805
923
|
userAgent?: string
|
|
806
924
|
): Promise<void> {
|
|
807
|
-
await this.
|
|
925
|
+
await this.logEventForUser(user,
|
|
808
926
|
buildMarkEmailVerificationSuccessfulAuditParams({
|
|
809
927
|
user,
|
|
810
928
|
reason,
|
|
@@ -828,7 +946,7 @@ export class AuditService {
|
|
|
828
946
|
sourceFileId?: string,
|
|
829
947
|
sourceFileName?: string
|
|
830
948
|
): Promise<void> {
|
|
831
|
-
await this.
|
|
949
|
+
await this.logEventForUser(user,
|
|
832
950
|
buildPDFGenerationAuditParams({
|
|
833
951
|
user,
|
|
834
952
|
fileName,
|
|
@@ -854,7 +972,7 @@ export class AuditService {
|
|
|
854
972
|
targetResource?: string,
|
|
855
973
|
blockedBySystem: boolean = true
|
|
856
974
|
): Promise<void> {
|
|
857
|
-
await this.
|
|
975
|
+
await this.logEventForOptionalUser(user,
|
|
858
976
|
buildSecurityViolationAuditParams({
|
|
859
977
|
user,
|
|
860
978
|
incidentType,
|
|
@@ -874,6 +992,7 @@ export class AuditService {
|
|
|
874
992
|
* Get audit entries for display (public method for components)
|
|
875
993
|
*/
|
|
876
994
|
public async getAuditEntriesForUser(userId: string, params?: {
|
|
995
|
+
requestingUser?: User;
|
|
877
996
|
startDate?: string;
|
|
878
997
|
endDate?: string;
|
|
879
998
|
caseNumber?: string;
|
|
@@ -887,7 +1006,7 @@ export class AuditService {
|
|
|
887
1006
|
userId,
|
|
888
1007
|
...params
|
|
889
1008
|
};
|
|
890
|
-
return await this.getAuditEntries(queryParams);
|
|
1009
|
+
return await this.getAuditEntries(queryParams, params?.requestingUser);
|
|
891
1010
|
}
|
|
892
1011
|
|
|
893
1012
|
/**
|
|
@@ -919,8 +1038,59 @@ export class AuditService {
|
|
|
919
1038
|
/**
|
|
920
1039
|
* Get audit entries based on query parameters
|
|
921
1040
|
*/
|
|
922
|
-
private
|
|
1041
|
+
private applyDateRangeFilter(
|
|
1042
|
+
entries: ValidationAuditEntry[],
|
|
1043
|
+
startDate?: string,
|
|
1044
|
+
endDate?: string
|
|
1045
|
+
): ValidationAuditEntry[] {
|
|
1046
|
+
const startMs = startDate ? new Date(startDate).getTime() : Number.NEGATIVE_INFINITY;
|
|
1047
|
+
const endMs = endDate ? new Date(endDate).getTime() : Number.POSITIVE_INFINITY;
|
|
1048
|
+
|
|
1049
|
+
return entries.filter((entry) => {
|
|
1050
|
+
const timestampMs = new Date(entry.timestamp).getTime();
|
|
1051
|
+
return timestampMs >= startMs && timestampMs <= endMs;
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
private async getBundledArchivedCaseAuditEntries(
|
|
1056
|
+
params: AuditQueryParams,
|
|
1057
|
+
requestingUser?: User
|
|
1058
|
+
): Promise<ValidationAuditEntry[] | null> {
|
|
1059
|
+
if (!requestingUser || !params.caseNumber) {
|
|
1060
|
+
return null;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
const caseData = await getCaseData(requestingUser, params.caseNumber);
|
|
1064
|
+
if (!caseData?.isReadOnly || caseData.archived !== true) {
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
const bundledEntries = caseData.bundledAuditTrail?.entries;
|
|
1069
|
+
if (!Array.isArray(bundledEntries)) {
|
|
1070
|
+
return [];
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
const sortedEntries = sortAuditEntriesNewestFirst(bundledEntries);
|
|
1074
|
+
const dateFilteredEntries = this.applyDateRangeFilter(
|
|
1075
|
+
sortedEntries,
|
|
1076
|
+
params.startDate,
|
|
1077
|
+
params.endDate
|
|
1078
|
+
);
|
|
1079
|
+
const filteredEntries = applyAuditEntryFilters(dateFilteredEntries, {
|
|
1080
|
+
...params,
|
|
1081
|
+
userId: undefined
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
return applyAuditPagination(filteredEntries, params);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
private async getAuditEntries(params: AuditQueryParams, requestingUser?: User): Promise<ValidationAuditEntry[]> {
|
|
923
1088
|
try {
|
|
1089
|
+
const bundledArchivedEntries = await this.getBundledArchivedCaseAuditEntries(params, requestingUser);
|
|
1090
|
+
if (bundledArchivedEntries) {
|
|
1091
|
+
return bundledArchivedEntries;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
924
1094
|
// If userId is provided, fetch from server
|
|
925
1095
|
if (params.userId) {
|
|
926
1096
|
const serverEntries = await fetchAuditEntriesForUser({
|
|
@@ -26,7 +26,8 @@ export const buildValidationAuditEntry = (
|
|
|
26
26
|
fileDetails: params.fileDetails,
|
|
27
27
|
annotationDetails: params.annotationDetails,
|
|
28
28
|
sessionDetails: params.sessionDetails,
|
|
29
|
-
securityDetails: params.securityDetails
|
|
29
|
+
securityDetails: params.securityDetails,
|
|
30
|
+
userProfileDetails: params.userProfileDetails
|
|
30
31
|
}
|
|
31
32
|
};
|
|
32
33
|
};
|
|
@@ -88,6 +88,49 @@ export const buildCaseDeletionAuditParams = (
|
|
|
88
88
|
};
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
+
interface BuildCaseArchiveAuditParamsInput {
|
|
92
|
+
user: User;
|
|
93
|
+
caseNumber: string;
|
|
94
|
+
caseName: string;
|
|
95
|
+
archiveReason: string;
|
|
96
|
+
result?: AuditResult;
|
|
97
|
+
errors?: string[];
|
|
98
|
+
totalFiles?: number;
|
|
99
|
+
archivedAt?: string;
|
|
100
|
+
processingTimeMs?: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const buildCaseArchiveAuditParams = (
|
|
104
|
+
input: BuildCaseArchiveAuditParamsInput
|
|
105
|
+
): CreateAuditEntryParams => {
|
|
106
|
+
const result = input.result || 'success';
|
|
107
|
+
const archivedAt = input.archivedAt || new Date().toISOString();
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
userId: input.user.uid,
|
|
111
|
+
userEmail: input.user.email || '',
|
|
112
|
+
action: 'case-archive',
|
|
113
|
+
result,
|
|
114
|
+
fileName: `${input.caseNumber}.case`,
|
|
115
|
+
fileType: 'case-package',
|
|
116
|
+
validationErrors: input.errors || [],
|
|
117
|
+
caseNumber: input.caseNumber,
|
|
118
|
+
workflowPhase: 'casework',
|
|
119
|
+
caseDetails: {
|
|
120
|
+
newCaseName: input.caseName,
|
|
121
|
+
deleteReason: input.archiveReason,
|
|
122
|
+
totalFiles: input.totalFiles,
|
|
123
|
+
lastModified: archivedAt,
|
|
124
|
+
},
|
|
125
|
+
performanceMetrics: input.processingTimeMs
|
|
126
|
+
? {
|
|
127
|
+
processingTimeMs: input.processingTimeMs,
|
|
128
|
+
fileSizeBytes: 0,
|
|
129
|
+
}
|
|
130
|
+
: undefined,
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
|
|
91
134
|
interface BuildFileUploadAuditParamsInput {
|
|
92
135
|
user: User;
|
|
93
136
|
fileName: string;
|
|
@@ -57,9 +57,10 @@ export const buildUserLogoutAuditParams = (
|
|
|
57
57
|
|
|
58
58
|
interface BuildUserProfileUpdateAuditParamsInput {
|
|
59
59
|
user: User;
|
|
60
|
-
profileField: 'displayName' | 'email' | 'organization' | 'role' | 'preferences' | 'avatar';
|
|
60
|
+
profileField: 'displayName' | 'email' | 'organization' | 'role' | 'preferences' | 'avatar' | 'badgeId';
|
|
61
61
|
oldValue: string;
|
|
62
62
|
newValue: string;
|
|
63
|
+
badgeId?: string;
|
|
63
64
|
result: AuditResult;
|
|
64
65
|
sessionId?: string;
|
|
65
66
|
errors?: string[];
|
|
@@ -85,7 +86,8 @@ export const buildUserProfileUpdateAuditParams = (
|
|
|
85
86
|
userProfileDetails: {
|
|
86
87
|
profileField: input.profileField,
|
|
87
88
|
oldValue: input.oldValue,
|
|
88
|
-
newValue: input.newValue
|
|
89
|
+
newValue: input.newValue,
|
|
90
|
+
badgeId: input.badgeId
|
|
89
91
|
}
|
|
90
92
|
};
|
|
91
93
|
};
|
|
@@ -125,6 +125,7 @@ interface BuildConfirmationCreationAuditParamsInput {
|
|
|
125
125
|
user: User;
|
|
126
126
|
caseNumber: string;
|
|
127
127
|
confirmationId: string;
|
|
128
|
+
badgeId?: string;
|
|
128
129
|
result: AuditResult;
|
|
129
130
|
errors?: string[];
|
|
130
131
|
originalExaminerUid?: string;
|
|
@@ -156,6 +157,11 @@ export const buildConfirmationCreationAuditParams = (
|
|
|
156
157
|
performanceMetrics: input.performanceMetrics,
|
|
157
158
|
originalExaminerUid: input.originalExaminerUid,
|
|
158
159
|
reviewingExaminerUid: input.user.uid,
|
|
160
|
+
userProfileDetails: input.badgeId
|
|
161
|
+
? {
|
|
162
|
+
badgeId: input.badgeId
|
|
163
|
+
}
|
|
164
|
+
: undefined,
|
|
159
165
|
fileDetails: input.imageFileId && input.originalImageFileName
|
|
160
166
|
? {
|
|
161
167
|
fileId: input.imageFileId,
|
|
@@ -214,6 +220,7 @@ interface BuildConfirmationImportAuditParamsInput {
|
|
|
214
220
|
confirmationsImported: number;
|
|
215
221
|
errors?: string[];
|
|
216
222
|
reviewingExaminerUid?: string;
|
|
223
|
+
reviewerBadgeId?: string;
|
|
217
224
|
performanceMetrics?: PerformanceMetrics;
|
|
218
225
|
exporterUidValidated?: boolean;
|
|
219
226
|
totalConfirmationsInFile?: number;
|
|
@@ -263,6 +270,7 @@ export const buildConfirmationImportAuditParams = (
|
|
|
263
270
|
},
|
|
264
271
|
originalExaminerUid: input.user.uid,
|
|
265
272
|
reviewingExaminerUid: input.reviewingExaminerUid,
|
|
273
|
+
reviewerBadgeId: input.reviewerBadgeId,
|
|
266
274
|
caseDetails: input.totalConfirmationsInFile !== undefined
|
|
267
275
|
? {
|
|
268
276
|
totalAnnotations: input.totalConfirmationsInFile
|
package/app/types/audit.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
export type AuditAction =
|
|
5
5
|
// Case Management Actions
|
|
6
|
-
| 'case-create' | 'case-rename' | 'case-delete'
|
|
6
|
+
| 'case-create' | 'case-rename' | 'case-delete' | 'case-archive'
|
|
7
7
|
// Confirmation Workflow Actions
|
|
8
8
|
| 'case-export' | 'case-import' | 'confirmation-create' | 'confirmation-export' | 'confirmation-import'
|
|
9
9
|
// File Operations
|
|
@@ -59,6 +59,7 @@ export interface AuditDetails {
|
|
|
59
59
|
// Context & Workflow
|
|
60
60
|
originalExaminerUid?: string;
|
|
61
61
|
reviewingExaminerUid?: string;
|
|
62
|
+
reviewerBadgeId?: string;
|
|
62
63
|
workflowPhase?: WorkflowPhase;
|
|
63
64
|
|
|
64
65
|
// Performance & Metrics
|
|
@@ -160,6 +161,7 @@ export interface CreateAuditEntryParams {
|
|
|
160
161
|
performanceMetrics?: PerformanceMetrics;
|
|
161
162
|
originalExaminerUid?: string;
|
|
162
163
|
reviewingExaminerUid?: string;
|
|
164
|
+
reviewerBadgeId?: string;
|
|
163
165
|
// Extended detail fields
|
|
164
166
|
caseDetails?: CaseAuditDetails;
|
|
165
167
|
fileDetails?: FileAuditDetails;
|
|
@@ -269,9 +271,10 @@ export interface SecurityAuditDetails {
|
|
|
269
271
|
* User profile and authentication specific audit details
|
|
270
272
|
*/
|
|
271
273
|
export interface UserProfileAuditDetails {
|
|
272
|
-
profileField?: 'displayName' | 'email' | 'organization' | 'role' | 'preferences' | 'avatar';
|
|
274
|
+
profileField?: 'displayName' | 'email' | 'organization' | 'role' | 'preferences' | 'avatar' | 'badgeId';
|
|
273
275
|
oldValue?: string;
|
|
274
276
|
newValue?: string;
|
|
277
|
+
badgeId?: string;
|
|
275
278
|
resetMethod?: 'email' | 'sms' | 'security-questions' | 'admin-reset';
|
|
276
279
|
resetToken?: string; // Partial token for tracking (last 4 chars)
|
|
277
280
|
verificationMethod?: 'email-link' | 'sms-code' | 'totp' | 'backup-codes' | 'admin-verification';
|