@striae-org/striae 3.2.0 → 3.2.2

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 (109) hide show
  1. package/README.md +3 -32
  2. package/app/components/actions/case-export/core-export.ts +2 -2
  3. package/app/components/actions/case-export/data-processing.ts +65 -10
  4. package/app/components/actions/case-export/download-handlers.ts +130 -44
  5. package/app/components/actions/case-export/metadata-helpers.ts +32 -14
  6. package/app/components/actions/case-import/annotation-import.ts +2 -2
  7. package/app/components/actions/case-import/confirmation-import.ts +3 -3
  8. package/app/components/actions/case-import/image-operations.ts +1 -1
  9. package/app/components/actions/case-import/orchestrator.ts +4 -4
  10. package/app/components/actions/case-import/storage-operations.ts +7 -7
  11. package/app/components/actions/case-import/validation.ts +3 -3
  12. package/app/components/actions/case-import/zip-processing.ts +3 -3
  13. package/app/components/actions/case-manage.ts +3 -3
  14. package/app/components/actions/confirm-export.ts +3 -3
  15. package/app/components/actions/generate-pdf.ts +3 -3
  16. package/app/components/actions/image-manage.ts +3 -3
  17. package/app/components/actions/notes-manage.ts +3 -3
  18. package/app/components/actions/signout.tsx +1 -1
  19. package/app/components/audit/user-audit-viewer.tsx +2 -3
  20. package/app/components/auth/auth-provider.tsx +2 -2
  21. package/app/components/auth/mfa-enrollment.tsx +3 -3
  22. package/app/components/auth/mfa-verification.tsx +4 -4
  23. package/app/components/canvas/box-annotations/box-annotations.tsx +2 -2
  24. package/app/components/canvas/canvas.tsx +1 -1
  25. package/app/components/canvas/confirmation/confirmation.tsx +1 -1
  26. package/app/components/form/base-form.tsx +1 -1
  27. package/app/components/sidebar/case-export/case-export.tsx +15 -15
  28. package/app/components/sidebar/case-import/case-import.tsx +2 -2
  29. package/app/components/sidebar/case-import/components/CasePreviewSection.tsx +1 -1
  30. package/app/components/sidebar/case-import/components/ConfirmationDialog.tsx +1 -1
  31. package/app/components/sidebar/case-import/hooks/useFilePreview.ts +3 -3
  32. package/app/components/sidebar/case-import/hooks/useImportExecution.ts +2 -2
  33. package/app/components/sidebar/cases/case-sidebar.tsx +27 -19
  34. package/app/components/sidebar/cases/cases-modal.tsx +1 -1
  35. package/app/components/sidebar/files/files-modal.tsx +3 -2
  36. package/app/components/sidebar/notes/notes-sidebar.tsx +3 -3
  37. package/app/components/sidebar/sidebar-container.tsx +5 -4
  38. package/app/components/sidebar/sidebar.tsx +2 -2
  39. package/app/components/sidebar/upload/image-upload-zone.tsx +2 -2
  40. package/app/components/theme-provider/theme-provider.tsx +1 -1
  41. package/app/components/user/delete-account.tsx +1 -1
  42. package/app/components/user/manage-profile.tsx +2 -2
  43. package/app/components/user/mfa-phone-update.tsx +2 -2
  44. package/app/contexts/auth.context.ts +1 -1
  45. package/app/entry.client.tsx +12 -12
  46. package/app/entry.server.tsx +4 -4
  47. package/app/hooks/useInactivityTimeout.ts +1 -1
  48. package/app/root.tsx +3 -3
  49. package/app/routes/auth/emailActionHandler.tsx +3 -3
  50. package/app/routes/auth/emailVerification.tsx +3 -3
  51. package/app/routes/auth/login.tsx +6 -6
  52. package/app/routes/auth/passwordReset.tsx +3 -3
  53. package/app/routes/auth/route.ts +1 -1
  54. package/app/routes/striae/striae.tsx +2 -2
  55. package/app/services/audit/audit-console-logger.ts +46 -0
  56. package/app/services/audit/audit-export-csv.ts +126 -0
  57. package/app/services/audit/audit-export-report.ts +174 -0
  58. package/app/services/audit/audit-export-signing.ts +85 -0
  59. package/app/services/audit/audit-export.service.ts +334 -0
  60. package/app/services/audit/audit-file-type.ts +13 -0
  61. package/app/services/audit/audit-query-helpers.ts +88 -0
  62. package/app/services/audit/audit-worker-client.ts +95 -0
  63. package/app/services/audit/audit.service.ts +990 -0
  64. package/app/services/audit/builders/audit-entry-builder.ts +32 -0
  65. package/app/services/audit/builders/audit-event-builders-annotation.ts +150 -0
  66. package/app/services/audit/builders/audit-event-builders-case-file.ts +249 -0
  67. package/app/services/audit/builders/audit-event-builders-user-security.ts +449 -0
  68. package/app/services/audit/builders/audit-event-builders-workflow.ts +272 -0
  69. package/app/services/audit/builders/index.ts +40 -0
  70. package/app/services/audit/index.ts +2 -0
  71. package/app/types/case.ts +2 -2
  72. package/app/types/exceljs-bare.d.ts +9 -0
  73. package/app/types/user.ts +1 -1
  74. package/app/utils/audit-export-signature.ts +2 -2
  75. package/app/utils/confirmation-signature.ts +3 -3
  76. package/app/utils/data-operations.ts +5 -5
  77. package/app/utils/mfa-phone.ts +1 -1
  78. package/app/utils/mfa.ts +1 -1
  79. package/app/utils/permissions.ts +2 -2
  80. package/functions/[[path]].ts +2 -2
  81. package/package.json +34 -20
  82. package/public/vendor/exceljs.LICENSE +22 -0
  83. package/public/vendor/exceljs.bare.min.js +45 -0
  84. package/scripts/deploy-all.sh +52 -0
  85. package/scripts/deploy-config.sh +282 -1
  86. package/tsconfig.json +18 -8
  87. package/vite.config.ts +6 -22
  88. package/worker-configuration.d.ts +4435 -562
  89. package/workers/audit-worker/package.json +8 -4
  90. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  91. package/workers/data-worker/package.json +8 -4
  92. package/workers/data-worker/src/data-worker.example.ts +3 -3
  93. package/workers/data-worker/wrangler.jsonc.example +1 -1
  94. package/workers/image-worker/package.json +8 -4
  95. package/workers/image-worker/wrangler.jsonc.example +1 -1
  96. package/workers/keys-worker/package.json +8 -4
  97. package/workers/keys-worker/wrangler.jsonc.example +1 -1
  98. package/workers/pdf-worker/package.json +8 -4
  99. package/workers/pdf-worker/src/{generated-assets.ts → assets/generated-assets.ts} +117 -117
  100. package/workers/pdf-worker/src/{format-striae.ts → formats/format-striae.ts} +535 -535
  101. package/workers/pdf-worker/src/pdf-worker.example.ts +1 -1
  102. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  103. package/workers/user-worker/package.json +8 -4
  104. package/workers/user-worker/wrangler.jsonc.example +1 -1
  105. package/wrangler.toml.example +1 -1
  106. package/app/services/audit-export.service.ts +0 -755
  107. package/app/services/audit.service.ts +0 -1474
  108. /package/app/services/{firebase-errors.ts → firebase/errors.ts} +0 -0
  109. /package/app/services/{firebase.ts → firebase/index.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { User } from 'firebase/auth';
1
+ import type { User } from 'firebase/auth';
2
2
  import { useState, useEffect } from 'react';
3
3
  import { SidebarContainer } from '~/components/sidebar/sidebar-container';
4
4
  import { Toolbar } from '~/components/toolbar/toolbar';
@@ -9,7 +9,7 @@ import { getNotes, saveNotes } from '~/components/actions/notes-manage';
9
9
  import { generatePDF } from '~/components/actions/generate-pdf';
10
10
  import { getUserApiKey } from '~/utils/auth';
11
11
  import { resolveEarliestAnnotationTimestamp } from '~/utils/annotation-timestamp';
12
- import { AnnotationData, FileData } from '~/types';
12
+ import { type AnnotationData, type FileData } from '~/types';
13
13
  import { checkCaseIsReadOnly } from '~/components/actions/case-manage';
14
14
  import paths from '~/config/config.json';
15
15
  import styles from './striae.module.css';
@@ -0,0 +1,46 @@
1
+ import { type ValidationAuditEntry } from '~/types';
2
+
3
+ export const getAuditSecurityIssuesForConsole = (
4
+ entry: ValidationAuditEntry
5
+ ): string[] => {
6
+ const checks = entry.details.securityChecks;
7
+ if (!checks) {
8
+ return [];
9
+ }
10
+
11
+ const securityIssues = [];
12
+
13
+ // For console diagnostics, self-confirmation is relevant for import actions only.
14
+ if (entry.action === 'import' && checks.selfConfirmationPrevented === true) {
15
+ securityIssues.push('selfConfirmationPrevented');
16
+ }
17
+
18
+ if (checks.fileIntegrityValid === false) {
19
+ securityIssues.push('fileIntegrityValid');
20
+ }
21
+
22
+ if (checks.exporterUidValidated === false) {
23
+ securityIssues.push('exporterUidValidated');
24
+ }
25
+
26
+ return securityIssues;
27
+ };
28
+
29
+ export const logAuditEntryToConsole = (entry: ValidationAuditEntry): void => {
30
+ const icon = entry.result === 'success' ? '✅' :
31
+ entry.result === 'failure' ? '❌' : '⚠️';
32
+
33
+ console.log(
34
+ `${icon} Audit [${entry.action.toUpperCase()}]: ${entry.details.fileName} ` +
35
+ `(Case: ${entry.details.caseNumber || 'N/A'}) - ${entry.result.toUpperCase()}`
36
+ );
37
+
38
+ if (entry.details.validationErrors.length > 0) {
39
+ console.log(' Errors:', entry.details.validationErrors);
40
+ }
41
+
42
+ const securityIssues = getAuditSecurityIssuesForConsole(entry);
43
+ if (securityIssues.length > 0) {
44
+ console.warn(' Security Issues:', securityIssues);
45
+ }
46
+ };
@@ -0,0 +1,126 @@
1
+ import { type ValidationAuditEntry } from '~/types';
2
+
3
+ export const AUDIT_CSV_ENTRY_HEADERS = [
4
+ 'Timestamp',
5
+ 'User Email',
6
+ 'Action',
7
+ 'Result',
8
+ 'File Name',
9
+ 'File Type',
10
+ 'Case Number',
11
+ 'Confirmation ID',
12
+ 'Original Examiner UID',
13
+ 'Reviewing Examiner UID',
14
+ 'File ID',
15
+ 'Original Filename',
16
+ 'File Size (MB)',
17
+ 'MIME Type',
18
+ 'Upload Method',
19
+ 'Delete Reason',
20
+ 'Annotation ID',
21
+ 'Annotation Type',
22
+ 'Annotation Tool',
23
+ 'Session ID',
24
+ 'User Agent',
25
+ 'Processing Time (ms)',
26
+ 'Hash Valid',
27
+ 'Validation Errors',
28
+ 'Security Issues',
29
+ 'Workflow Phase',
30
+ 'Profile Field',
31
+ 'Old Value',
32
+ 'New Value',
33
+ 'Total Confirmations In File',
34
+ 'Confirmations Successfully Imported',
35
+ 'Validation Steps Failed',
36
+ 'Case Name',
37
+ 'Total Files',
38
+ 'MFA Method',
39
+ 'Security Incident Type',
40
+ 'Security Severity'
41
+ ];
42
+
43
+ export const formatForCSV = (value?: string | number | null): string => {
44
+ if (value === undefined || value === null) return '';
45
+ const str = String(value);
46
+ if (str.includes(',') || str.includes('"') || str.includes('\n')) {
47
+ return `"${str.replace(/"/g, '""')}"`;
48
+ }
49
+ return str;
50
+ };
51
+
52
+ const getSecurityIssues = (entry: ValidationAuditEntry): string => {
53
+ const securityChecks = entry.details.securityChecks;
54
+ if (!securityChecks) {
55
+ return '';
56
+ }
57
+
58
+ const issues = [];
59
+
60
+ if (securityChecks.selfConfirmationPrevented === true) {
61
+ issues.push('selfConfirmationPrevented');
62
+ }
63
+
64
+ if (securityChecks.fileIntegrityValid === false) {
65
+ issues.push('fileIntegrityValid');
66
+ }
67
+
68
+ if (securityChecks.exporterUidValidated === false) {
69
+ issues.push('exporterUidValidated');
70
+ }
71
+
72
+ return issues.join('; ');
73
+ };
74
+
75
+ export const entryToCSVRow = (entry: ValidationAuditEntry): string => {
76
+ const fileDetails = entry.details.fileDetails;
77
+ const annotationDetails = entry.details.annotationDetails;
78
+ const sessionDetails = entry.details.sessionDetails;
79
+ const userProfileDetails = entry.details.userProfileDetails;
80
+ const caseDetails = entry.details.caseDetails;
81
+ const performanceMetrics = entry.details.performanceMetrics;
82
+ const securityDetails = entry.details.securityDetails;
83
+ const securityIssues = getSecurityIssues(entry);
84
+
85
+ const values = [
86
+ formatForCSV(entry.timestamp),
87
+ formatForCSV(entry.userEmail),
88
+ formatForCSV(entry.action),
89
+ formatForCSV(entry.result),
90
+ formatForCSV(entry.details.fileName),
91
+ formatForCSV(entry.details.fileType),
92
+ formatForCSV(entry.details.caseNumber),
93
+ formatForCSV(entry.details.confirmationId),
94
+ formatForCSV(entry.details.originalExaminerUid),
95
+ formatForCSV(entry.details.reviewingExaminerUid),
96
+ formatForCSV(fileDetails?.fileId),
97
+ formatForCSV(fileDetails?.originalFileName),
98
+ fileDetails?.fileSize ? (fileDetails.fileSize / 1024 / 1024).toFixed(2) : '',
99
+ formatForCSV(fileDetails?.mimeType),
100
+ formatForCSV(fileDetails?.uploadMethod),
101
+ formatForCSV(fileDetails?.deleteReason),
102
+ formatForCSV(annotationDetails?.annotationId),
103
+ formatForCSV(annotationDetails?.annotationType),
104
+ formatForCSV(annotationDetails?.tool),
105
+ formatForCSV(sessionDetails?.sessionId),
106
+ formatForCSV(sessionDetails?.userAgent),
107
+ performanceMetrics?.processingTimeMs || '',
108
+ entry.details.hashValid !== undefined ? (entry.details.hashValid ? 'Yes' : 'No') : '',
109
+ formatForCSV(entry.details.validationErrors?.join('; ')),
110
+ formatForCSV(securityIssues),
111
+ formatForCSV(entry.details.workflowPhase),
112
+ formatForCSV(userProfileDetails?.profileField),
113
+ formatForCSV(userProfileDetails?.oldValue),
114
+ formatForCSV(userProfileDetails?.newValue),
115
+ caseDetails?.totalAnnotations?.toString() || '',
116
+ performanceMetrics?.validationStepsCompleted?.toString() || '',
117
+ performanceMetrics?.validationStepsFailed?.toString() || '',
118
+ formatForCSV(caseDetails?.newCaseName || caseDetails?.oldCaseName),
119
+ caseDetails?.totalFiles?.toString() || '',
120
+ formatForCSV(securityDetails?.mfaMethod),
121
+ formatForCSV(securityDetails?.incidentType),
122
+ formatForCSV(securityDetails?.severity)
123
+ ];
124
+
125
+ return values.join(',');
126
+ };
@@ -0,0 +1,174 @@
1
+ import { type AuditTrail, type ValidationAuditEntry } from '~/types';
2
+
3
+ const calculateDuration = (start: string, end: string): string => {
4
+ const startTime = new Date(start).getTime();
5
+ const endTime = new Date(end).getTime();
6
+ const durationMs = endTime - startTime;
7
+
8
+ const hours = Math.floor(durationMs / (1000 * 60 * 60));
9
+ const minutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60));
10
+ const seconds = Math.floor((durationMs % (1000 * 60)) / 1000);
11
+
12
+ if (hours > 0) {
13
+ return `${hours}h ${minutes}m ${seconds}s`;
14
+ }
15
+
16
+ if (minutes > 0) {
17
+ return `${minutes}m ${seconds}s`;
18
+ }
19
+
20
+ return `${seconds}s`;
21
+ };
22
+
23
+ const generateSecurityAnalysis = (entries: ValidationAuditEntry[]): string => {
24
+ const securityEntries = entries.filter(entry => entry.details.securityChecks);
25
+
26
+ if (securityEntries.length === 0) {
27
+ return 'No security-sensitive operations detected.';
28
+ }
29
+
30
+ let selfConfirmationAttempts = 0;
31
+ let fileIntegrityFailures = 0;
32
+ let exporterValidationFailures = 0;
33
+ let legitimateImports = 0;
34
+
35
+ securityEntries.forEach(entry => {
36
+ const checks = entry.details.securityChecks!;
37
+
38
+ if (checks.selfConfirmationPrevented === true) {
39
+ selfConfirmationAttempts++;
40
+ }
41
+ if (checks.fileIntegrityValid === false) {
42
+ fileIntegrityFailures++;
43
+ }
44
+ if (checks.exporterUidValidated === false) {
45
+ exporterValidationFailures++;
46
+ }
47
+
48
+ if (
49
+ entry.action === 'import' &&
50
+ entry.details.workflowPhase === 'confirmation' &&
51
+ entry.result === 'success' &&
52
+ checks.selfConfirmationPrevented === false
53
+ ) {
54
+ legitimateImports++;
55
+ }
56
+ });
57
+
58
+ return [
59
+ `Total Security-Sensitive Operations: ${securityEntries.length}`,
60
+ `Legitimate Confirmation Imports: ${legitimateImports}`,
61
+ `Self-Confirmation Attempts Blocked: ${selfConfirmationAttempts}`,
62
+ `File Integrity Failures: ${fileIntegrityFailures}`,
63
+ `Exporter Validation Failures: ${exporterValidationFailures}`,
64
+ '',
65
+ selfConfirmationAttempts === 0 && fileIntegrityFailures === 0 && exporterValidationFailures === 0
66
+ ? '✅ No security violations detected'
67
+ : '⚠️ Security violations detected - review required'
68
+ ].join('\n');
69
+ };
70
+
71
+ const generateConfirmationSummary = (entries: ValidationAuditEntry[]): string => {
72
+ const confirmationEntries = entries.filter(entry =>
73
+ entry.details.workflowPhase === 'confirmation' ||
74
+ (entry.action === 'import' && entry.details.fileType === 'confirmation-data')
75
+ );
76
+
77
+ if (confirmationEntries.length === 0) {
78
+ return 'No confirmation workflow operations detected.';
79
+ }
80
+
81
+ const imports = confirmationEntries.filter(entry => entry.action === 'import');
82
+ const exports = confirmationEntries.filter(entry => entry.action === 'export');
83
+ const creations = confirmationEntries.filter(entry => entry.action === 'confirm');
84
+
85
+ let totalConfirmationsImported = 0;
86
+ let totalConfirmationsInFiles = 0;
87
+ const reviewingExaminers = new Set<string>();
88
+
89
+ imports.forEach(entry => {
90
+ const metrics = entry.details.performanceMetrics;
91
+ const caseDetails = entry.details.caseDetails;
92
+
93
+ if (metrics?.validationStepsCompleted) {
94
+ totalConfirmationsImported += metrics.validationStepsCompleted;
95
+ }
96
+ if (caseDetails?.totalAnnotations) {
97
+ totalConfirmationsInFiles += caseDetails.totalAnnotations;
98
+ }
99
+ if (entry.details.reviewingExaminerUid) {
100
+ reviewingExaminers.add(entry.details.reviewingExaminerUid);
101
+ }
102
+ });
103
+
104
+ return [
105
+ `Confirmation Operations: ${confirmationEntries.length}`,
106
+ `- Imports: ${imports.length}`,
107
+ `- Exports: ${exports.length}`,
108
+ `- Creations: ${creations.length}`,
109
+ '',
110
+ `Total Confirmations Imported: ${totalConfirmationsImported}`,
111
+ `Total Confirmations in Import Files: ${totalConfirmationsInFiles}`,
112
+ `Reviewing Examiners Involved: ${reviewingExaminers.size}`,
113
+ '',
114
+ reviewingExaminers.size > 0
115
+ ? `External Reviewers: ${Array.from(reviewingExaminers).join(', ')}`
116
+ : 'No external reviewers detected'
117
+ ].join('\n');
118
+ };
119
+
120
+ export const buildAuditReportContent = (auditTrail: AuditTrail, generatedAt: string): string => {
121
+ const summary = auditTrail.summary;
122
+ const successRate = ((summary.successfulEvents / summary.totalEvents) * 100).toFixed(1);
123
+
124
+ return `
125
+ STRIAE AUDIT TRAIL REPORT
126
+ ============================
127
+
128
+ Case Number: ${auditTrail.caseNumber}
129
+ Workflow ID: ${auditTrail.workflowId}
130
+ Report Generated: ${new Date(generatedAt).toLocaleString()}
131
+
132
+ SUMMARY STATISTICS
133
+ ------------------
134
+ Total Events: ${summary.totalEvents}
135
+ Successful Events: ${summary.successfulEvents} (${successRate}%)
136
+ Failed Events: ${summary.failedEvents}
137
+ Warning Events: ${summary.warningEvents}
138
+ Security Incidents: ${summary.securityIncidents}
139
+
140
+ COMPLIANCE STATUS
141
+ -----------------
142
+ Status: ${summary.complianceStatus.toUpperCase()}
143
+ ${summary.complianceStatus === 'compliant'
144
+ ? '✅ All audit events completed successfully'
145
+ : '⚠️ Some audit events failed - requires investigation'}
146
+
147
+ TIMELINE
148
+ --------
149
+ Start Time: ${new Date(summary.startTimestamp).toLocaleString()}
150
+ End Time: ${new Date(summary.endTimestamp).toLocaleString()}
151
+ Duration: ${calculateDuration(summary.startTimestamp, summary.endTimestamp)}
152
+
153
+ PARTICIPANTS
154
+ ------------
155
+ Users Involved: ${summary.participatingUsers.length}
156
+ ${summary.participatingUsers.map(uid => `- User ID: ${uid}`).join('\n')}
157
+
158
+ WORKFLOW PHASES
159
+ ---------------
160
+ ${summary.workflowPhases.map(phase => `- ${phase}`).join('\n')}
161
+
162
+ SECURITY ANALYSIS
163
+ -----------------
164
+ ${generateSecurityAnalysis(auditTrail.entries)}
165
+
166
+ CONFIRMATION WORKFLOW DETAILS
167
+ ------------------------------
168
+ ${generateConfirmationSummary(auditTrail.entries)}
169
+
170
+ ---
171
+ This report contains ${summary.totalEvents} audit entries providing complete forensic accountability.
172
+ Generated by Striae
173
+ `.trim();
174
+ };
@@ -0,0 +1,85 @@
1
+ import type { User } from 'firebase/auth';
2
+ import { signAuditExportData } from '~/utils/data-operations';
3
+ import {
4
+ AUDIT_EXPORT_SIGNATURE_VERSION,
5
+ type AuditExportFormat,
6
+ type AuditExportSigningPayload,
7
+ type AuditExportType
8
+ } from '~/utils/audit-export-signature';
9
+
10
+ export interface AuditExportContext {
11
+ user: User;
12
+ scopeType: 'case' | 'user';
13
+ scopeIdentifier: string;
14
+ caseNumber?: string;
15
+ }
16
+
17
+ interface SignAuditExportInput {
18
+ exportFormat: AuditExportFormat;
19
+ exportType: AuditExportType;
20
+ generatedAt: string;
21
+ totalEntries: number;
22
+ hash: string;
23
+ }
24
+
25
+ export interface AuditExportSignature {
26
+ algorithm: string;
27
+ keyId: string;
28
+ signedAt: string;
29
+ value: string;
30
+ }
31
+
32
+ export interface SignedAuditExportPayload {
33
+ signatureMetadata: AuditExportSigningPayload;
34
+ signature: AuditExportSignature;
35
+ }
36
+
37
+ const buildAuditSignaturePayload = (
38
+ exportFormat: AuditExportFormat,
39
+ exportType: AuditExportType,
40
+ generatedAt: string,
41
+ totalEntries: number,
42
+ hash: string,
43
+ context: AuditExportContext
44
+ ): AuditExportSigningPayload => {
45
+ return {
46
+ signatureVersion: AUDIT_EXPORT_SIGNATURE_VERSION,
47
+ exportFormat,
48
+ exportType,
49
+ scopeType: context.scopeType,
50
+ scopeIdentifier: context.scopeIdentifier,
51
+ generatedAt,
52
+ totalEntries,
53
+ hash: hash.toUpperCase()
54
+ };
55
+ };
56
+
57
+ export const signAuditExport = async (
58
+ payload: SignAuditExportInput,
59
+ context: AuditExportContext
60
+ ): Promise<SignedAuditExportPayload> => {
61
+ const signatureMetadata = buildAuditSignaturePayload(
62
+ payload.exportFormat,
63
+ payload.exportType,
64
+ payload.generatedAt,
65
+ payload.totalEntries,
66
+ payload.hash,
67
+ context
68
+ );
69
+
70
+ const caseNumber =
71
+ context.scopeType === 'case'
72
+ ? (context.caseNumber || context.scopeIdentifier)
73
+ : undefined;
74
+
75
+ const signatureResponse = await signAuditExportData(
76
+ context.user,
77
+ signatureMetadata,
78
+ { caseNumber }
79
+ );
80
+
81
+ return {
82
+ signatureMetadata,
83
+ signature: signatureResponse.signature
84
+ };
85
+ };