@striae-org/striae 5.2.1 → 5.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 (105) hide show
  1. package/.env.example +2 -10
  2. package/README.md +5 -46
  3. package/app/components/actions/case-export/core-export.ts +2 -174
  4. package/app/components/actions/case-export/download-handlers.ts +83 -750
  5. package/app/components/actions/case-export/index.ts +6 -30
  6. package/app/components/actions/case-export/metadata-helpers.ts +0 -78
  7. package/app/components/actions/case-export/types-constants.ts +0 -43
  8. package/app/components/actions/case-import/confirmation-import.ts +13 -14
  9. package/app/components/actions/case-import/zip-processing.ts +92 -12
  10. package/app/components/actions/generate-pdf.ts +3 -2
  11. package/app/components/audit/user-audit-viewer.tsx +0 -19
  12. package/app/components/audit/viewer/audit-viewer-header.tsx +0 -33
  13. package/app/components/navbar/case-modals/archive-case-modal.tsx +1 -1
  14. package/app/components/navbar/navbar.tsx +1 -1
  15. package/app/components/sidebar/case-import/case-import.module.css +35 -0
  16. package/app/components/sidebar/case-import/components/CasePreviewSection.tsx +59 -3
  17. package/app/components/sidebar/case-import/components/ConfirmationDialog.tsx +2 -4
  18. package/app/components/sidebar/case-import/components/ConfirmationPreviewSection.tsx +1 -1
  19. package/app/components/sidebar/notes/class-details-shared.ts +2 -2
  20. package/app/components/toast/toast.module.css +36 -0
  21. package/app/components/toast/toast.tsx +6 -2
  22. package/app/components/user/manage-profile.tsx +4 -3
  23. package/app/config-example/config.json +1 -2
  24. package/app/root.tsx +0 -7
  25. package/app/routes/_index.tsx +1 -1
  26. package/app/routes/auth/login.example.tsx +22 -103
  27. package/app/routes/auth/route.ts +1 -1
  28. package/app/routes/striae/striae.tsx +53 -59
  29. package/app/services/firebase/index.ts +0 -3
  30. package/app/types/export.ts +1 -2
  31. package/app/utils/auth/index.ts +0 -1
  32. package/app/utils/data/permissions.ts +3 -2
  33. package/package.json +9 -16
  34. package/public/_headers +0 -4
  35. package/public/_routes.json +0 -1
  36. package/worker-configuration.d.ts +20 -17
  37. package/workers/audit-worker/src/audit-worker.example.ts +9 -806
  38. package/workers/audit-worker/src/config.ts +7 -0
  39. package/workers/audit-worker/src/crypto/data-at-rest.ts +410 -0
  40. package/workers/audit-worker/src/handlers/audit-routes.ts +125 -0
  41. package/workers/audit-worker/src/storage/audit-storage.ts +99 -0
  42. package/workers/audit-worker/src/types.ts +56 -0
  43. package/workers/audit-worker/worker-configuration.d.ts +1 -1
  44. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  45. package/workers/data-worker/src/config.ts +11 -0
  46. package/workers/data-worker/src/data-worker.example.ts +21 -942
  47. package/workers/data-worker/src/handlers/decrypt-export.ts +118 -0
  48. package/workers/data-worker/src/handlers/signing.ts +174 -0
  49. package/workers/data-worker/src/handlers/storage-routes.ts +129 -0
  50. package/workers/data-worker/src/registry/key-registry.ts +368 -0
  51. package/workers/data-worker/src/types.ts +46 -0
  52. package/workers/data-worker/worker-configuration.d.ts +1 -1
  53. package/workers/data-worker/wrangler.jsonc.example +1 -1
  54. package/workers/image-worker/worker-configuration.d.ts +1 -1
  55. package/workers/image-worker/wrangler.jsonc.example +1 -1
  56. package/workers/pdf-worker/worker-configuration.d.ts +2 -3
  57. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  58. package/workers/user-worker/src/auth.ts +30 -0
  59. package/workers/user-worker/src/cleanup/account-deletion.ts +337 -0
  60. package/workers/user-worker/src/config.ts +4 -0
  61. package/workers/user-worker/src/encryption-utils.ts +25 -0
  62. package/workers/user-worker/src/firebase/admin.ts +152 -0
  63. package/workers/user-worker/src/handlers/user-routes.ts +242 -0
  64. package/workers/user-worker/src/registry/user-kv.ts +172 -0
  65. package/workers/user-worker/src/storage/user-records.ts +34 -0
  66. package/workers/user-worker/src/types.ts +106 -0
  67. package/workers/user-worker/src/user-worker.example.ts +18 -964
  68. package/workers/user-worker/worker-configuration.d.ts +4 -2
  69. package/workers/user-worker/wrangler.jsonc.example +12 -1
  70. package/wrangler.toml.example +1 -1
  71. package/app/components/actions/case-export/data-processing.ts +0 -223
  72. package/app/components/sidebar/case-export/case-export.module.css +0 -418
  73. package/app/components/sidebar/case-export/case-export.tsx +0 -310
  74. package/app/types/exceljs-bare.d.ts +0 -9
  75. package/app/utils/auth/auth.ts +0 -11
  76. package/public/.well-known/security.txt +0 -6
  77. package/public/favicon.ico +0 -0
  78. package/public/icon-256.png +0 -0
  79. package/public/icon-512.png +0 -0
  80. package/public/manifest.json +0 -39
  81. package/public/shortcut.png +0 -0
  82. package/public/social-image.png +0 -0
  83. package/public/vendor/exceljs.LICENSE +0 -22
  84. package/public/vendor/exceljs.bare.min.js +0 -45
  85. package/scripts/deploy-all.sh +0 -166
  86. package/scripts/deploy-config/modules/env-utils.sh +0 -322
  87. package/scripts/deploy-config/modules/keys.sh +0 -404
  88. package/scripts/deploy-config/modules/prompt.sh +0 -372
  89. package/scripts/deploy-config/modules/scaffolding.sh +0 -344
  90. package/scripts/deploy-config/modules/validation.sh +0 -365
  91. package/scripts/deploy-config.sh +0 -236
  92. package/scripts/deploy-pages-secrets.sh +0 -231
  93. package/scripts/deploy-pages.sh +0 -34
  94. package/scripts/deploy-primershear-emails.sh +0 -167
  95. package/scripts/deploy-worker-secrets.sh +0 -374
  96. package/scripts/dev.cjs +0 -23
  97. package/scripts/install-workers.sh +0 -88
  98. package/scripts/run-eslint.cjs +0 -43
  99. package/scripts/update-compatibility-dates.cjs +0 -124
  100. package/scripts/update-markdown-versions.cjs +0 -43
  101. package/workers/keys-worker/package.json +0 -18
  102. package/workers/keys-worker/src/keys.example.ts +0 -67
  103. package/workers/keys-worker/src/keys.ts +0 -67
  104. package/workers/keys-worker/worker-configuration.d.ts +0 -7447
  105. package/workers/keys-worker/wrangler.jsonc.example +0 -15
@@ -1,310 +0,0 @@
1
- import { useState, useEffect, useContext } from 'react';
2
- import styles from './case-export.module.css';
3
- import { AuthContext } from '~/contexts/auth.context';
4
- import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
5
- import { getCaseConfirmations, exportConfirmationData } from '../../actions/confirm-export';
6
-
7
- export type ExportFormat = 'json' | 'csv';
8
-
9
- interface CaseExportProps {
10
- isOpen: boolean;
11
- onClose: () => void;
12
- onExport: (caseNumber: string, format: ExportFormat, includeImages?: boolean, onProgress?: (progress: number, label: string) => void) => Promise<void>;
13
- onExportAll: (onProgress: (current: number, total: number, caseName: string) => void, format: ExportFormat) => Promise<void>;
14
- currentCaseNumber?: string;
15
- isReadOnly?: boolean;
16
- }
17
-
18
- export const CaseExport = ({
19
- isOpen,
20
- onClose,
21
- onExport,
22
- onExportAll,
23
- currentCaseNumber = '',
24
- isReadOnly = false
25
- }: CaseExportProps) => {
26
- const { user } = useContext(AuthContext);
27
- const [caseNumber, setCaseNumber] = useState(currentCaseNumber);
28
- const [isExporting, setIsExporting] = useState(false);
29
- const [isExportingAll, setIsExportingAll] = useState(false);
30
- const [isExportingConfirmations, setIsExportingConfirmations] = useState(false);
31
- const [error, setError] = useState<string>('');
32
- const [exportProgress, setExportProgress] = useState<{ current: number; total: number; caseName: string; mode?: 'single' | 'all' } | null>(null);
33
- const [selectedFormat, setSelectedFormat] = useState<ExportFormat>('json');
34
- const [includeImages, setIncludeImages] = useState(false);
35
- const [hasConfirmationData, setHasConfirmationData] = useState(false);
36
- const {
37
- requestClose,
38
- overlayProps,
39
- getCloseButtonProps
40
- } = useOverlayDismiss({
41
- isOpen,
42
- onClose,
43
- });
44
-
45
- // Update caseNumber when currentCaseNumber prop changes
46
- useEffect(() => {
47
- setCaseNumber(currentCaseNumber);
48
- }, [currentCaseNumber]);
49
-
50
- // Disable images option when exporting all cases or when no case number is entered
51
- useEffect(() => {
52
- if ((isExportingAll || !caseNumber.trim()) && includeImages) {
53
- setIncludeImages(false);
54
- }
55
- }, [isExportingAll, caseNumber, includeImages]);
56
-
57
- // Check for confirmation data when case changes (for read-only cases)
58
- useEffect(() => {
59
- const checkConfirmationData = async () => {
60
- if (isReadOnly && user && caseNumber.trim()) {
61
- try {
62
- const confirmations = await getCaseConfirmations(user, caseNumber.trim());
63
- const hasData = !!confirmations && Object.keys(confirmations).length > 0;
64
- setHasConfirmationData(hasData);
65
- } catch (error) {
66
- console.error('Failed to check confirmation data:', error);
67
- setHasConfirmationData(false);
68
- }
69
- } else {
70
- setHasConfirmationData(false);
71
- }
72
- };
73
-
74
- checkConfirmationData();
75
- }, [isReadOnly, user, caseNumber]);
76
-
77
- // Additional useEffect to check when modal opens
78
- useEffect(() => {
79
- if (isOpen && isReadOnly && user && caseNumber.trim()) {
80
- const checkOnOpen = async () => {
81
- try {
82
- const confirmations = await getCaseConfirmations(user, caseNumber.trim());
83
- const hasData = !!confirmations && Object.keys(confirmations).length > 0;
84
- setHasConfirmationData(hasData);
85
- } catch (error) {
86
- console.error('Modal open confirmation check failed:', error);
87
- setHasConfirmationData(false);
88
- }
89
- };
90
- checkOnOpen();
91
- }
92
- }, [isOpen, isReadOnly, user, caseNumber]);
93
-
94
- // Force JSON format and disable images for read-only cases
95
- useEffect(() => {
96
- if (isReadOnly) {
97
- setSelectedFormat('json');
98
- setIncludeImages(false);
99
- }
100
- }, [isReadOnly]);
101
-
102
- if (!isOpen) return null;
103
-
104
- const handleExport = async () => {
105
- if (!caseNumber.trim()) {
106
- setError('Please enter a case number');
107
- return;
108
- }
109
-
110
- setIsExporting(true);
111
- setError('');
112
- setExportProgress(null);
113
-
114
- try {
115
- await onExport(caseNumber.trim(), selectedFormat, includeImages, (progress, label) => {
116
- setExportProgress({ current: progress, total: 100, caseName: label, mode: 'single' });
117
- });
118
- requestClose();
119
- } catch (error) {
120
- console.error('Export failed:', error);
121
- setError(error instanceof Error ? error.message : 'Export failed. Please try again.');
122
- } finally {
123
- setIsExporting(false);
124
- setExportProgress(null);
125
- }
126
- };
127
-
128
- const handleExportAll = async () => {
129
- setIsExportingAll(true);
130
- setError('');
131
- setExportProgress(null); // Don't show progress until we have real data
132
-
133
- try {
134
- await onExportAll((current: number, total: number, caseName: string) => {
135
- setExportProgress({ current, total, caseName });
136
- }, selectedFormat);
137
- requestClose();
138
- } catch (error) {
139
- console.error('Export all failed:', error);
140
- setError(error instanceof Error ? error.message : 'Export all cases failed. Please try again.');
141
- } finally {
142
- setIsExportingAll(false);
143
- setExportProgress(null);
144
- }
145
- };
146
-
147
- const handleExportConfirmations = async () => {
148
- if (!caseNumber.trim() || !user) {
149
- setError('Unable to export confirmation data');
150
- return;
151
- }
152
-
153
- setIsExportingConfirmations(true);
154
- setError('');
155
-
156
- try {
157
- await exportConfirmationData(user, caseNumber.trim());
158
- requestClose();
159
- } catch (error) {
160
- console.error('Confirmation export failed:', error);
161
- setError(error instanceof Error ? error.message : 'Confirmation export failed. Please try again.');
162
- } finally {
163
- setIsExportingConfirmations(false);
164
- }
165
- };
166
-
167
- return (
168
- <div
169
- className={styles.overlay}
170
- aria-label="Close case export dialog"
171
- {...overlayProps}
172
- >
173
- <div className={styles.modal}>
174
- <div className={styles.header}>
175
- <h2 className={styles.title}>Export Case Data</h2>
176
- <button className={styles.closeButton} {...getCloseButtonProps({ ariaLabel: 'Close case export dialog' })}>
177
- ×
178
- </button>
179
- </div>
180
-
181
- <div className={styles.content}>
182
- <div className={styles.fieldGroup}>
183
- {/* 1. Case number input */}
184
- <div className={styles.inputGroup}>
185
- <input
186
- id="caseNumber"
187
- type="text"
188
- className={styles.input}
189
- value={caseNumber}
190
- onChange={(e) => {
191
- setCaseNumber(e.target.value);
192
- if (error) setError('');
193
- }}
194
- placeholder="Enter case number"
195
- disabled={isExporting || isExportingAll || isReadOnly}
196
- />
197
- </div>
198
-
199
- {/* 2. Format choice - disabled for read-only cases */}
200
- <div className={styles.formatSelector}>
201
- <span className={styles.formatLabel}>Data Format:</span>
202
- <div className={styles.formatToggle}>
203
- <button
204
- type="button"
205
- className={`${styles.formatOption} ${selectedFormat === 'json' ? styles.formatOptionActive : ''}`}
206
- onClick={() => setSelectedFormat('json')}
207
- disabled={isExporting || isExportingAll || isReadOnly}
208
- title="JSON for case imports"
209
- >
210
- JSON
211
- </button>
212
- <button
213
- type="button"
214
- className={`${styles.formatOption} ${selectedFormat === 'csv' ? styles.formatOptionActive : ''}`}
215
- onClick={() => setSelectedFormat('csv')}
216
- disabled={isExporting || isExportingAll || isReadOnly}
217
- title="CSV for single case, Excel (.xlsx) with multiple worksheets for all cases"
218
- >
219
- CSV/Excel
220
- </button>
221
- </div>
222
- </div>
223
-
224
- {/* 3. Image inclusion option - disabled for read-only cases */}
225
- <div className={styles.imageOption}>
226
- <div className={styles.checkboxLabel}>
227
- <input
228
- id="includeImagesOption"
229
- type="checkbox"
230
- className={styles.checkbox}
231
- checked={includeImages}
232
- onChange={(e) => setIncludeImages(e.target.checked)}
233
- disabled={!caseNumber.trim() || isExporting || isExportingAll || isReadOnly}
234
- aria-label="Include images in ZIP export"
235
- />
236
- <label htmlFor="includeImagesOption" className={styles.checkboxText}>
237
- <span>Include Images (ZIP)</span>
238
- <span className={styles.checkboxTooltip}>
239
- Available for single case exports only. Downloads a ZIP file containing data and all associated image files. Case imports support only JSON data format.
240
- </span>
241
- </label>
242
- </div>
243
- </div>
244
-
245
- {/* 4. Export buttons (case OR all cases) */}
246
- <div className={styles.inputGroup}>
247
- <button
248
- className={isReadOnly ? styles.confirmationExportButton : styles.exportButton}
249
- onClick={isReadOnly ? handleExportConfirmations : handleExport}
250
- disabled={!caseNumber.trim() || isExporting || isExportingAll || isExportingConfirmations || (isReadOnly && !hasConfirmationData)}
251
- >
252
- {isExporting || isExportingConfirmations ? 'Exporting...' :
253
- isReadOnly ? 'Export Confirmation Data' : 'Export Case Data'}
254
- </button>
255
- </div>
256
-
257
- {/* Hide "Export All Cases" for read-only cases */}
258
- {!isReadOnly && (
259
- <>
260
- <div className={styles.divider}>
261
- <span>OR</span>
262
- </div>
263
-
264
- <div className={styles.exportAllSection}>
265
- <button
266
- className={styles.exportAllButton}
267
- onClick={handleExportAll}
268
- disabled={isExporting || isExportingAll}
269
- >
270
- {isExportingAll ? 'Exporting All Cases...' : 'Export All Cases'}
271
- </button>
272
- </div>
273
- </>
274
- )}
275
-
276
- {exportProgress && exportProgress.total > 0 && (
277
- <div className={styles.progressSection}>
278
- <div className={styles.progressText}>
279
- {exportProgress.mode === 'single'
280
- ? `${exportProgress.caseName} (${exportProgress.current}%)`
281
- : `Exporting case ${exportProgress.current} of ${exportProgress.total}: ${exportProgress.caseName}`}
282
- </div>
283
- <div className={styles.progressBar}>
284
- <div
285
- className={styles.progressFill}
286
- style={{ width: `${(exportProgress.current / exportProgress.total) * 100}%` }}
287
- />
288
- </div>
289
- </div>
290
- )}
291
-
292
- {(isExporting || isExportingAll) && !exportProgress && (
293
- <div className={styles.progressSection}>
294
- <div className={styles.progressText}>
295
- Preparing export...
296
- </div>
297
- </div>
298
- )}
299
-
300
- {error && (
301
- <div className={styles.error}>
302
- {error}
303
- </div>
304
- )}
305
- </div>
306
- </div>
307
- </div>
308
- </div>
309
- );
310
- };
@@ -1,9 +0,0 @@
1
- import type * as ExcelJSModule from 'exceljs';
2
-
3
- declare global {
4
- interface Window {
5
- ExcelJS?: typeof ExcelJSModule;
6
- }
7
- }
8
-
9
- export {};
@@ -1,11 +0,0 @@
1
- import paths from '~/config/config.json';
2
-
3
- const ACCOUNT_HASH = typeof paths.account_hash === 'string' ? paths.account_hash.trim() : '';
4
-
5
- export async function getAccountHash(): Promise<string> {
6
- if (!ACCOUNT_HASH) {
7
- throw new Error('ACCOUNT_HASH is not configured in app/config/config.json');
8
- }
9
-
10
- return ACCOUNT_HASH;
11
- }
@@ -1,6 +0,0 @@
1
- Contact: mailto:security@striae.org
2
- Contact: https://github.com/striae-org/striae/security/advisories/new
3
- Expires: 2035-08-15T00:00:00.000Z
4
- Preferred-Languages: en
5
- Canonical: https://striae.app/.well-known/security.txt
6
- Policy: https://striae.org/security
Binary file
Binary file
Binary file
@@ -1,39 +0,0 @@
1
- {
2
- "id": "/",
3
- "name": "Striae: A Firearms Examiner's Comparison Companion",
4
- "short_name": "Striae",
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
- "start_url": "/",
7
- "scope": "/",
8
- "display": "standalone",
9
- "orientation": "any",
10
- "lang": "en-US",
11
- "dir": "ltr",
12
- "theme_color": "#377087",
13
- "background_color": "#f5f5f5",
14
- "categories": [
15
- "business",
16
- "productivity",
17
- "utilities"
18
- ],
19
- "icons": [
20
- {
21
- "src": "shortcut.png",
22
- "sizes": "64x64",
23
- "type": "image/png",
24
- "purpose": "any"
25
- },
26
- {
27
- "src": "icon-256.png",
28
- "sizes": "256x256",
29
- "type": "image/png",
30
- "purpose": "any"
31
- },
32
- {
33
- "src": "icon-512.png",
34
- "sizes": "512x512",
35
- "type": "image/png",
36
- "purpose": "any"
37
- }
38
- ]
39
- }
Binary file
Binary file
@@ -1,22 +0,0 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2014-2019 Guyon Roche
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
22
-