@striae-org/striae 5.4.2 → 5.4.3

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 (36) hide show
  1. package/app/components/actions/case-export/download-handlers.ts +1 -1
  2. package/app/components/actions/case-export/metadata-helpers.ts +2 -4
  3. package/app/components/actions/case-import/zip-processing.ts +3 -3
  4. package/app/components/mobile-warning/mobile-warning.module.css +80 -0
  5. package/app/components/mobile-warning/mobile-warning.tsx +108 -0
  6. package/app/components/navbar/case-import/utils/file-validation.ts +1 -1
  7. package/app/config-example/config.json +2 -2
  8. package/app/root.tsx +2 -0
  9. package/app/services/audit/audit-file-type.ts +0 -1
  10. package/app/services/audit/audit.service.ts +1 -1
  11. package/app/services/audit/builders/audit-event-builders-workflow.ts +2 -6
  12. package/app/services/audit/index.ts +0 -1
  13. package/app/types/audit.ts +1 -1
  14. package/app/utils/data/permissions.ts +17 -15
  15. package/app/utils/forensics/audit-export-signature.ts +4 -4
  16. package/app/utils/forensics/export-verification.ts +3 -11
  17. package/package.json +2 -2
  18. package/workers/audit-worker/package.json +1 -1
  19. package/workers/audit-worker/src/audit-worker.example.ts +1 -1
  20. package/workers/audit-worker/src/handlers/audit-routes.ts +1 -30
  21. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  22. package/workers/data-worker/package.json +17 -17
  23. package/workers/data-worker/src/encryption-utils.ts +1 -1
  24. package/workers/data-worker/src/signing-payload-utils.ts +4 -4
  25. package/workers/data-worker/wrangler.jsonc.example +1 -1
  26. package/workers/image-worker/package.json +1 -1
  27. package/workers/image-worker/wrangler.jsonc.example +1 -1
  28. package/workers/pdf-worker/package.json +1 -1
  29. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  30. package/workers/user-worker/package.json +1 -1
  31. package/workers/user-worker/wrangler.jsonc.example +1 -1
  32. package/wrangler.toml.example +1 -1
  33. package/app/components/audit/viewer/use-audit-viewer-export.ts +0 -176
  34. package/app/services/audit/audit-export-csv.ts +0 -130
  35. package/app/services/audit/audit-export-report.ts +0 -205
  36. package/app/services/audit/audit-export.service.ts +0 -333
@@ -438,7 +438,7 @@ async function generateJSONContent(
438
438
 
439
439
  // Add forensic protection warning if enabled
440
440
  if (protectForensicData) {
441
- return addForensicDataWarning(finalJsonString, 'json');
441
+ return addForensicDataWarning(finalJsonString);
442
442
  }
443
443
 
444
444
  return finalJsonString;
@@ -32,10 +32,8 @@ export async function getUserExportMetadata(user: User) {
32
32
  /**
33
33
  * Add data protection warning to content
34
34
  */
35
- export function addForensicDataWarning(content: string, format: 'csv' | 'json'): string {
36
- const warning = format === 'csv'
37
- ? `"CASE DATA WARNING: This file contains evidence data for forensic examination. Any modification may compromise the integrity of the evidence. Handle according to your organization's chain of custody procedures."\n\n`
38
- : `/* CASE DATA WARNING
35
+ export function addForensicDataWarning(content: string): string {
36
+ const warning = `/* CASE DATA WARNING
39
37
  * This file contains evidence data for forensic examination.
40
38
  * Any modification may compromise the integrity of the evidence.
41
39
  * Handle according to your organization's chain of custody procedures.
@@ -281,13 +281,13 @@ export async function parseImportZip(zipFile: File): Promise<{
281
281
  const zip = await JSZip.loadAsync(zipFile);
282
282
  const verificationPublicKeyPem = await extractVerificationPublicKeyFromZip(zip);
283
283
 
284
- // Find the main data file (JSON or CSV)
284
+ // Find the main data file (JSON)
285
285
  const dataFiles = Object.keys(zip.files).filter(name =>
286
- name.endsWith('_data.json') || name.endsWith('_data.csv')
286
+ name.endsWith('_data.json')
287
287
  );
288
288
 
289
289
  if (dataFiles.length === 0) {
290
- throw new Error('No valid data file found in ZIP archive');
290
+ throw new Error('No valid JSON data file found in ZIP archive');
291
291
  }
292
292
 
293
293
  if (dataFiles.length > 1) {
@@ -0,0 +1,80 @@
1
+ @layer layout {
2
+ .overlay {
3
+ display: none;
4
+ position: fixed;
5
+ inset: 0;
6
+ z-index: 10000;
7
+ background: var(--background);
8
+ align-items: center;
9
+ justify-content: center;
10
+ padding: var(--spaceXL);
11
+ }
12
+
13
+ .backdrop {
14
+ appearance: none;
15
+ position: absolute;
16
+ inset: 0;
17
+ background: transparent;
18
+ border: none;
19
+ cursor: default;
20
+ }
21
+
22
+ .content {
23
+ display: flex;
24
+ flex-direction: column;
25
+ align-items: center;
26
+ text-align: center;
27
+ max-width: 420px;
28
+ gap: var(--spaceL);
29
+ }
30
+
31
+ .icon {
32
+ color: var(--primary);
33
+ }
34
+
35
+ .title {
36
+ font-size: var(--fontSizeH4);
37
+ font-weight: var(--fontWeightBold);
38
+ color: var(--textTitle);
39
+ line-height: var(--lineHeightTitle);
40
+ margin: 0;
41
+ }
42
+
43
+ .message {
44
+ font-size: var(--fontSizeBodyS);
45
+ color: var(--textBody);
46
+ line-height: var(--lineHeightBody);
47
+ margin: 0;
48
+ }
49
+
50
+ .dismissButton {
51
+ appearance: none;
52
+ background-color: var(--primary);
53
+ border: 1px solid var(--primary);
54
+ border-radius: var(--radiusM);
55
+ color: var(--white);
56
+ cursor: pointer;
57
+ font-size: var(--fontSizeBodyS);
58
+ font-weight: var(--fontWeightMedium);
59
+ padding: var(--spaceM) var(--spaceXL);
60
+ transition:
61
+ background-color var(--durationS) var(--bezierFastoutSlowin),
62
+ border-color var(--durationS) var(--bezierFastoutSlowin);
63
+ }
64
+
65
+ .dismissButton:hover {
66
+ background-color: color-mix(in lab, var(--primary) 82%, var(--black));
67
+ border-color: color-mix(in lab, var(--primary) 82%, var(--black));
68
+ }
69
+
70
+ .dismissButton:focus-visible {
71
+ outline: 3px solid color-mix(in lab, var(--white) 65%, var(--primary));
72
+ outline-offset: 3px;
73
+ }
74
+
75
+ @media (max-width: 1024px) {
76
+ .overlay {
77
+ display: flex;
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,108 @@
1
+ import { useState, useCallback, useRef, useEffect } from 'react';
2
+ import styles from './mobile-warning.module.css';
3
+
4
+ const DISMISSED_KEY = 'striae-mobile-warning-dismissed';
5
+
6
+ const isBrowser =
7
+ typeof window !== 'undefined' && typeof window.sessionStorage !== 'undefined';
8
+
9
+ export function MobileWarning() {
10
+ const [dismissed, setDismissed] = useState<boolean>(() => {
11
+ if (!isBrowser) {
12
+ // Match previous server behavior: render null during SSR.
13
+ return true;
14
+ }
15
+
16
+ try {
17
+ return window.sessionStorage.getItem(DISMISSED_KEY) === '1';
18
+ } catch {
19
+ return false;
20
+ }
21
+ });
22
+
23
+ const buttonRef = useRef<HTMLButtonElement>(null);
24
+
25
+ useEffect(() => {
26
+ if (!dismissed && buttonRef.current) {
27
+ buttonRef.current.focus();
28
+ }
29
+ }, [dismissed]);
30
+
31
+ const handleDismiss = useCallback(() => {
32
+ if (isBrowser) {
33
+ try {
34
+ window.sessionStorage.setItem(DISMISSED_KEY, '1');
35
+ } catch {
36
+ // Ignore storage errors and still dismiss for this session.
37
+ }
38
+ }
39
+ setDismissed(true);
40
+ }, []);
41
+
42
+ useEffect(() => {
43
+ if (dismissed) return;
44
+ const onKeyDown = (e: KeyboardEvent) => {
45
+ if (e.key === 'Escape') {
46
+ handleDismiss();
47
+ }
48
+ };
49
+ document.addEventListener('keydown', onKeyDown);
50
+ return () => document.removeEventListener('keydown', onKeyDown);
51
+ }, [dismissed, handleDismiss]);
52
+
53
+ if (dismissed) {
54
+ return null;
55
+ }
56
+
57
+ return (
58
+ <div
59
+ className={styles.overlay}
60
+ role="dialog"
61
+ aria-modal="true"
62
+ aria-labelledby="mobile-warning-title"
63
+ aria-describedby="mobile-warning-message"
64
+ >
65
+ {/* Backdrop dismiss button covers area outside dialog content */}
66
+ <button
67
+ type="button"
68
+ className={styles.backdrop}
69
+ onClick={handleDismiss}
70
+ aria-label="Dismiss"
71
+ tabIndex={-1}
72
+ />
73
+ <div className={styles.content}>
74
+ <div className={styles.icon}>
75
+ <svg
76
+ width="48"
77
+ height="48"
78
+ viewBox="0 0 24 24"
79
+ fill="none"
80
+ stroke="currentColor"
81
+ strokeWidth="1.5"
82
+ strokeLinecap="round"
83
+ strokeLinejoin="round"
84
+ >
85
+ <rect x="5" y="2" width="14" height="20" rx="2" ry="2" />
86
+ <line x1="12" y1="18" x2="12.01" y2="18" />
87
+ </svg>
88
+ </div>
89
+ <h2 className={styles.title} id="mobile-warning-title">
90
+ Desktop Experience Only
91
+ </h2>
92
+ <p className={styles.message} id="mobile-warning-message">
93
+ Striae is designed for desktop browsers and is not optimized for
94
+ mobile devices or tablets. For the best experience, please use a
95
+ desktop computer.
96
+ </p>
97
+ <button
98
+ ref={buttonRef}
99
+ type="button"
100
+ className={styles.dismissButton}
101
+ onClick={handleDismiss}
102
+ >
103
+ Continue Anyway
104
+ </button>
105
+ </div>
106
+ </div>
107
+ );
108
+ }
@@ -1,4 +1,4 @@
1
- const CASE_EXPORT_DATA_FILE_REGEX = /_data\.(json|csv)$/i;
1
+ const CASE_EXPORT_DATA_FILE_REGEX = /_data\.json$/i;
2
2
  const CONFIRMATION_EXPORT_FILE_REGEX = /^confirmation-data-.*\.json$/i;
3
3
  const FORENSIC_MANIFEST_FILE_NAME = 'forensic_manifest.json';
4
4
  const ENCRYPTION_MANIFEST_FILE_NAME = 'encryption_manifest.json';
@@ -10,6 +10,6 @@
10
10
  "export_encryption_public_keys": {
11
11
  "EXPORT_ENCRYPTION_KEY_ID": "EXPORT_ENCRYPTION_PUBLIC_KEY"
12
12
  },
13
- "max_cases_review": 0,
14
- "max_files_per_case_review": 0
13
+ "max_cases_demo": 0,
14
+ "max_files_per_case_demo": 0
15
15
  }
package/app/root.tsx CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  themeStyles
14
14
  } from '~/components/theme-provider/theme-provider';
15
15
  import { AuthProvider } from '~/components/auth/auth-provider';
16
+ import { MobileWarning } from '~/components/mobile-warning/mobile-warning';
16
17
  import { auth } from '~/services/firebase';
17
18
  import styles from '~/styles/root.module.css';
18
19
  import './global.css';
@@ -68,6 +69,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
68
69
  </head>
69
70
  <body className="flex flex-col h-screen w-full overflow-x-hidden">
70
71
  <ThemeProvider theme={theme} className="">
72
+ <MobileWarning />
71
73
  <main>
72
74
  {children}
73
75
  </main>
@@ -4,7 +4,6 @@ export const getAuditFileTypeFromMime = (mimeType: string): AuditFileType => {
4
4
  if (mimeType.startsWith('image/')) return 'image-file';
5
5
  if (mimeType === 'application/pdf') return 'pdf-document';
6
6
  if (mimeType === 'application/json') return 'json-data';
7
- if (mimeType === 'text/csv') return 'csv-export';
8
7
  return 'unknown';
9
8
  };
10
9
 
@@ -208,7 +208,7 @@ class AuditService {
208
208
  result: AuditResult,
209
209
  errors: string[] = [],
210
210
  performanceMetrics?: PerformanceMetrics,
211
- exportFormat?: 'json' | 'csv' | 'xlsx' | 'zip',
211
+ exportFormat?: 'json' | 'zip',
212
212
  protectionEnabled?: boolean,
213
213
  signatureDetails?: {
214
214
  present?: boolean;
@@ -20,21 +20,18 @@ interface BuildCaseExportAuditParamsInput {
20
20
  result: AuditResult;
21
21
  errors?: string[];
22
22
  performanceMetrics?: PerformanceMetrics;
23
- exportFormat?: 'json' | 'csv' | 'xlsx' | 'zip';
23
+ exportFormat?: 'json' | 'zip';
24
24
  signatureDetails?: SignatureDetailsInput;
25
25
  }
26
26
 
27
27
  const resolveCaseExportFileType = (
28
28
  fileName: string,
29
- exportFormat?: 'json' | 'csv' | 'xlsx' | 'zip'
29
+ exportFormat?: 'json' | 'zip'
30
30
  ): AuditFileType => {
31
31
  if (exportFormat) {
32
32
  switch (exportFormat) {
33
33
  case 'json':
34
34
  return 'json-data';
35
- case 'csv':
36
- case 'xlsx':
37
- return 'csv-export';
38
35
  case 'zip':
39
36
  default:
40
37
  return 'case-package';
@@ -42,7 +39,6 @@ const resolveCaseExportFileType = (
42
39
  }
43
40
 
44
41
  if (fileName.includes('.json')) return 'json-data';
45
- if (fileName.includes('.csv') || fileName.includes('.xlsx')) return 'csv-export';
46
42
  return 'case-package';
47
43
  };
48
44
 
@@ -1,2 +1 @@
1
1
  export { auditService } from './audit.service';
2
- export { auditExportService } from './audit-export.service';
@@ -25,7 +25,7 @@ export type AuditResult = 'success' | 'failure' | 'warning' | 'blocked' | 'pendi
25
25
 
26
26
  export type AuditFileType =
27
27
  | 'case-package' | 'confirmation-data' | 'image-file' | 'pdf-document'
28
- | 'json-data' | 'csv-export' | 'log-file' | 'unknown';
28
+ | 'json-data' | 'log-file' | 'unknown';
29
29
 
30
30
  /**
31
31
  * Core audit entry structure for all validation events
@@ -3,8 +3,8 @@ import type { UserData, ExtendedUserData, UserLimits, ReadOnlyCaseMetadata } fro
3
3
  import paths from '~/config/config.json';
4
4
  import { fetchDataApi, fetchUserApi } from '../api';
5
5
 
6
- const MAX_CASES_REVIEW = paths.max_cases_review;
7
- const MAX_FILES_PER_CASE_REVIEW = paths.max_files_per_case_review;
6
+ const MAX_CASES_DEMO = paths.max_cases_demo;
7
+ const MAX_FILES_PER_CASE_DEMO = paths.max_files_per_case_demo;
8
8
 
9
9
  export interface UserUsage {
10
10
  currentCases: number;
@@ -63,8 +63,8 @@ export const getUserLimits = (userData: UserData): UserLimits => {
63
63
  };
64
64
  } else {
65
65
  return {
66
- maxCases: MAX_CASES_REVIEW, // Use config value for review users
67
- maxFilesPerCase: MAX_FILES_PER_CASE_REVIEW // Use config value for review users
66
+ maxCases: MAX_CASES_DEMO, // Use config value for demo users
67
+ maxFilesPerCase: MAX_FILES_PER_CASE_DEMO // Use config value for demo users
68
68
  };
69
69
  }
70
70
  };
@@ -155,7 +155,7 @@ export const canCreateCase = async (user: User): Promise<{ canCreate: boolean; r
155
155
  if (usage.currentCases >= limits.maxCases) {
156
156
  return {
157
157
  canCreate: false,
158
- reason: `Read-Only Account: Case creation disabled`
158
+ reason: `Demo account only: Maximum of ${limits.maxCases} case${limits.maxCases === 1 ? '' : 's'} reached`
159
159
  };
160
160
  }
161
161
 
@@ -181,7 +181,7 @@ export const canUploadFile = async (user: User, currentFileCount: number): Promi
181
181
  if (currentFileCount >= limits.maxFilesPerCase) {
182
182
  return {
183
183
  canUpload: false,
184
- reason: `Read-Only Account: File uploads disabled`
184
+ reason: `Demo account only: Maximum of ${limits.maxFilesPerCase} file${limits.maxFilesPerCase === 1 ? '' : 's'} per case reached`
185
185
  };
186
186
  }
187
187
 
@@ -199,13 +199,13 @@ export const getLimitsDescription = async (user: User): Promise<string> => {
199
199
  try {
200
200
  const userData = await getUserData(user);
201
201
  if (!userData) {
202
- return `Account limits: ${MAX_CASES_REVIEW} case, ${MAX_FILES_PER_CASE_REVIEW} files per case`;
202
+ return `Account limits: ${MAX_CASES_DEMO} case${MAX_CASES_DEMO === 1 ? '' : 's'}, ${MAX_FILES_PER_CASE_DEMO} file${MAX_FILES_PER_CASE_DEMO === 1 ? '' : 's'} per case`;
203
203
  }
204
204
 
205
205
  if (userData.permitted) {
206
206
  return '';
207
207
  } else {
208
- return `Read-Only Account: Case review only.`;
208
+ return `Demo account only: ${MAX_CASES_DEMO} case${MAX_CASES_DEMO === 1 ? '' : 's'}, ${MAX_FILES_PER_CASE_DEMO} file${MAX_FILES_PER_CASE_DEMO === 1 ? '' : 's'} per case`;
209
209
  }
210
210
  } catch (error) {
211
211
  console.error('Error getting limits description:', error);
@@ -391,8 +391,9 @@ export const canAccessCase = async (user: User, caseNumber: string): Promise<Per
391
391
  /**
392
392
  * Check if user can modify a specific case
393
393
  * - Regular users (permitted=true) can modify their owned cases
394
- * - Read-only users (permitted=false) can modify read-only cases for review
395
- * - Nobody can modify cases marked as truly read-only in the case data itself
394
+ * - Demo users (permitted=false) can modify their owned cases
395
+ * - Both permitted and demo users can modify read-only cases for review
396
+ * - Nobody can modify cases marked as archived in the case data itself
396
397
  */
397
398
  export const canModifyCase = async (user: User, caseNumber: string): Promise<PermissionResult> => {
398
399
  try {
@@ -419,20 +420,21 @@ export const canModifyCase = async (user: User, caseNumber: string): Promise<Per
419
420
  if (caseData.archived) {
420
421
  return { allowed: false, reason: 'Archived cases are immutable and read-only' };
421
422
  }
423
+ } else if (archiveCheckResponse.status !== 404) {
424
+ // Fail closed: if archive status can't be verified (worker error/timeout),
425
+ // block modification rather than risk mutating an archived case
426
+ return { allowed: false, reason: 'Unable to verify case archive status' };
422
427
  }
423
428
 
424
429
  // Check if user owns the case (regular cases)
425
430
  if (userData.cases && userData.cases.some(c => c.caseNumber === caseNumber)) {
426
- // For owned cases, user must be permitted
427
- if (!userData.permitted) {
428
- return { allowed: false, reason: 'Read-Only Account: Cannot modify owned cases' };
429
- }
431
+ // Both permitted and demo users can modify their owned cases
430
432
  return { allowed: true };
431
433
  }
432
434
 
433
435
  // Check if it's a read-only case that user can review
434
436
  if (userData.readOnlyCases && userData.readOnlyCases.some(c => c.caseNumber === caseNumber)) {
435
- // For read-only cases, both permitted and non-permitted users can modify for review
437
+ // For read-only cases, both permitted and demo users can modify for review
436
438
  // The actual read-only restrictions should be enforced at the case data level, not user level
437
439
  return { allowed: true };
438
440
  }
@@ -9,8 +9,8 @@ export const AUDIT_EXPORT_SIGNATURE_VERSION = '1.0';
9
9
 
10
10
  const SHA256_HEX_REGEX = /^[a-f0-9]{64}$/i;
11
11
 
12
- export type AuditExportFormat = 'csv' | 'json' | 'txt';
13
- export type AuditExportType = 'entries' | 'trail' | 'report';
12
+ export type AuditExportFormat = 'json';
13
+ export type AuditExportType = 'trail';
14
14
  export type AuditExportScopeType = 'case' | 'user';
15
15
 
16
16
  export interface AuditExportSigningPayload {
@@ -35,11 +35,11 @@ export function isValidAuditExportSigningPayload(
35
35
  return false;
36
36
  }
37
37
 
38
- if (payload.exportFormat !== 'csv' && payload.exportFormat !== 'json' && payload.exportFormat !== 'txt') {
38
+ if (payload.exportFormat !== 'json') {
39
39
  return false;
40
40
  }
41
41
 
42
- if (payload.exportType !== 'entries' && payload.exportType !== 'trail' && payload.exportType !== 'report') {
42
+ if (payload.exportType !== 'trail') {
43
43
  return false;
44
44
  }
45
45
 
@@ -23,7 +23,7 @@ interface BundledAuditExportFile {
23
23
  exportVersion?: string;
24
24
  totalEntries?: number;
25
25
  application?: string;
26
- exportType?: 'entries' | 'trail' | 'report';
26
+ exportType?: 'trail';
27
27
  scopeType?: 'case' | 'user';
28
28
  scopeIdentifier?: string;
29
29
  hash?: string;
@@ -164,7 +164,7 @@ async function verifyBundledAuditExport(
164
164
  const embeddedSignaturePayload: Partial<AuditExportSigningPayload> = metadata.signatureMetadata ?? {
165
165
  signatureVersion: metadata.signatureVersion,
166
166
  exportFormat: 'json',
167
- exportType: metadata.exportType,
167
+ exportType: 'trail',
168
168
  scopeType: metadata.scopeType,
169
169
  scopeIdentifier: metadata.scopeIdentifier,
170
170
  generatedAt: metadata.exportTimestamp,
@@ -209,23 +209,15 @@ async function verifyBundledAuditExport(
209
209
 
210
210
  /**
211
211
  * Remove forensic warning from content for hash validation.
212
- * Supports the warning formats added to JSON and CSV case exports.
212
+ * Supports the warning format added to JSON case exports.
213
213
  */
214
214
  export function removeForensicWarning(content: string): string {
215
215
  const jsonForensicWarningRegex = /^\/\*\s*CASE\s+DATA\s+WARNING[\s\S]*?\*\/\s*\r?\n*/;
216
- const csvForensicWarningRegex = /^"CASE DATA WARNING: This file contains evidence data for forensic examination\. Any modification may compromise the integrity of the evidence\. Handle according to your organization's chain of custody procedures\."(?:\r?\n){2}/;
217
216
 
218
217
  let cleaned = content;
219
218
 
220
219
  if (jsonForensicWarningRegex.test(content)) {
221
220
  cleaned = content.replace(jsonForensicWarningRegex, '');
222
- } else if (csvForensicWarningRegex.test(content)) {
223
- cleaned = content.replace(csvForensicWarningRegex, '');
224
- } else if (content.startsWith('"CASE DATA WARNING:')) {
225
- const match = content.match(/^"[^"]*"(?:\r?\n)+/);
226
- if (match) {
227
- cleaned = content.substring(match[0].length);
228
- }
229
221
  }
230
222
 
231
223
  return cleaned.replace(/^\s+/, '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@striae-org/striae",
3
- "version": "5.4.2",
3
+ "version": "5.4.3",
4
4
  "private": false,
5
5
  "description": "Striae is a specialized, cloud-native platform designed to streamline forensic firearms identification by providing an intuitive environment for digital comparison image annotation, authenticated confirmations, and automated report generation.",
6
6
  "license": "Apache-2.0",
@@ -128,7 +128,7 @@
128
128
  "typescript": "^5.9.3",
129
129
  "vite": "^6.4.1",
130
130
  "vite-tsconfig-paths": "^6.1.1",
131
- "wrangler": "^4.79.0"
131
+ "wrangler": "^4.80.0"
132
132
  },
133
133
  "overrides": {
134
134
  "@tootallnate/once": "3.0.1",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "devDependencies": {
11
11
  "@cloudflare/puppeteer": "^1.0.6",
12
- "wrangler": "^4.78.0"
12
+ "wrangler": "^4.80.0"
13
13
  },
14
14
  "overrides": {
15
15
  "undici": "7.24.1",
@@ -4,7 +4,7 @@ import type { CreateResponse, Env } from './types';
4
4
 
5
5
  const corsHeaders: Record<string, string> = {
6
6
  'Access-Control-Allow-Origin': 'PAGES_CUSTOM_DOMAIN',
7
- 'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
7
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
8
8
  'Access-Control-Allow-Headers': 'Content-Type, X-Custom-Auth-Key',
9
9
  'Content-Type': 'application/json'
10
10
  };
@@ -92,34 +92,5 @@ export async function handleAuditRequest(
92
92
  }
93
93
  }
94
94
 
95
- if (request.method === 'DELETE') {
96
- if (!userId) {
97
- return respond({ error: 'userId parameter is required' }, 400);
98
- }
99
-
100
- try {
101
- const prefix = `audit-trails/${userId}/`;
102
- let deletedCount = 0;
103
- let cursor: string | undefined;
104
-
105
- do {
106
- const listed = await bucket.list({ prefix, cursor, limit: 1000 });
107
-
108
- const keys = listed.objects.map((obj) => obj.key);
109
- if (keys.length > 0) {
110
- await bucket.delete(keys);
111
- deletedCount += keys.length;
112
- }
113
-
114
- cursor = listed.truncated ? listed.cursor : undefined;
115
- } while (cursor !== undefined);
116
-
117
- return respond({ success: true, deletedCount });
118
- } catch (error) {
119
- const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
120
- return respond({ error: `Failed to delete audit entries: ${errorMessage}` }, 500);
121
- }
122
- }
123
-
124
- return respond({ error: 'Method not allowed for audit endpoints. Only GET, POST, and DELETE are supported.' }, 405);
95
+ return respond({ error: 'Method not allowed for audit endpoints. Only GET and POST are supported.' }, 405);
125
96
  }
@@ -7,7 +7,7 @@
7
7
  "name": "AUDIT_WORKER_NAME",
8
8
  "account_id": "ACCOUNT_ID",
9
9
  "main": "src/audit-worker.ts",
10
- "compatibility_date": "2026-03-31",
10
+ "compatibility_date": "2026-04-02",
11
11
  "compatibility_flags": [
12
12
  "nodejs_compat"
13
13
  ],
@@ -1,18 +1,18 @@
1
1
  {
2
- "name": "data-worker",
3
- "version": "0.0.0",
4
- "private": true,
5
- "scripts": {
6
- "deploy": "wrangler deploy",
7
- "dev": "wrangler dev",
8
- "start": "wrangler dev"
9
- },
10
- "devDependencies": {
11
- "@cloudflare/puppeteer": "^1.0.6",
12
- "wrangler": "^4.78.0"
13
- },
14
- "overrides": {
15
- "undici": "7.24.1",
16
- "yauzl": "3.2.1"
17
- }
18
- }
2
+ "name": "data-worker",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "deploy": "wrangler deploy",
7
+ "dev": "wrangler dev",
8
+ "start": "wrangler dev"
9
+ },
10
+ "devDependencies": {
11
+ "@cloudflare/puppeteer": "^1.0.6",
12
+ "wrangler": "^4.80.0"
13
+ },
14
+ "overrides": {
15
+ "undici": "7.24.1",
16
+ "yauzl": "3.2.1"
17
+ }
18
+ }
@@ -224,7 +224,7 @@ export async function decryptJsonFromStorage(
224
224
  }
225
225
 
226
226
  /**
227
- * Decrypt data file (plaintext JSON/CSV)
227
+ * Decrypt data file (plaintext JSON)
228
228
  */
229
229
  export async function decryptExportData(
230
230
  encryptedDataBase64: string,
@@ -37,8 +37,8 @@ export interface ConfirmationSigningPayload {
37
37
  confirmations: Record<string, ConfirmationRecord[]>;
38
38
  }
39
39
 
40
- export type AuditExportFormat = 'csv' | 'json' | 'txt';
41
- export type AuditExportType = 'entries' | 'trail' | 'report';
40
+ export type AuditExportFormat = 'json';
41
+ export type AuditExportType = 'trail';
42
42
  export type AuditExportScopeType = 'case' | 'user';
43
43
 
44
44
  export interface AuditExportSigningPayload {
@@ -187,11 +187,11 @@ export function isValidAuditExportPayload(
187
187
  return false;
188
188
  }
189
189
 
190
- if (candidate.exportFormat !== 'csv' && candidate.exportFormat !== 'json' && candidate.exportFormat !== 'txt') {
190
+ if (candidate.exportFormat !== 'json') {
191
191
  return false;
192
192
  }
193
193
 
194
- if (candidate.exportType !== 'entries' && candidate.exportType !== 'trail' && candidate.exportType !== 'report') {
194
+ if (candidate.exportType !== 'trail') {
195
195
  return false;
196
196
  }
197
197
 
@@ -5,7 +5,7 @@
5
5
  "name": "DATA_WORKER_NAME",
6
6
  "account_id": "ACCOUNT_ID",
7
7
  "main": "src/data-worker.ts",
8
- "compatibility_date": "2026-03-31",
8
+ "compatibility_date": "2026-04-02",
9
9
  "compatibility_flags": [
10
10
  "nodejs_compat"
11
11
  ],