@striae-org/striae 4.3.4 → 5.0.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 +4 -0
- package/app/components/actions/case-export/download-handlers.ts +60 -4
- package/app/components/actions/case-import/confirmation-import.ts +50 -7
- package/app/components/actions/case-import/confirmation-package.ts +99 -22
- package/app/components/actions/case-import/orchestrator.ts +116 -13
- package/app/components/actions/case-import/validation.ts +171 -7
- package/app/components/actions/case-import/zip-processing.ts +224 -127
- package/app/components/actions/case-manage.ts +64 -4
- package/app/components/actions/confirm-export.ts +32 -3
- package/app/components/navbar/navbar.module.css +0 -10
- package/app/components/navbar/navbar.tsx +0 -22
- package/app/components/sidebar/case-import/case-import.module.css +7 -131
- package/app/components/sidebar/case-import/case-import.tsx +7 -14
- package/app/components/sidebar/case-import/components/CasePreviewSection.tsx +17 -60
- package/app/components/sidebar/case-import/components/ConfirmationDialog.tsx +23 -39
- package/app/components/sidebar/case-import/components/ConfirmationPreviewSection.tsx +5 -45
- package/app/components/sidebar/case-import/components/FileSelector.tsx +5 -6
- package/app/components/sidebar/case-import/hooks/useFilePreview.ts +2 -48
- package/app/components/sidebar/case-import/utils/file-validation.ts +9 -21
- package/app/config-example/config.json +5 -0
- package/app/routes/auth/login.tsx +1 -1
- package/app/utils/data/operations/signing-operations.ts +93 -0
- package/app/utils/data/operations/types.ts +6 -0
- package/app/utils/forensics/export-encryption.ts +316 -0
- package/app/utils/forensics/export-verification.ts +1 -409
- package/app/utils/forensics/index.ts +1 -0
- package/app/utils/ui/case-messages.ts +5 -2
- package/package.json +1 -1
- package/scripts/deploy-config.sh +97 -3
- package/scripts/deploy-worker-secrets.sh +1 -1
- package/workers/audit-worker/wrangler.jsonc.example +1 -1
- package/workers/data-worker/src/data-worker.example.ts +130 -0
- package/workers/data-worker/src/encryption-utils.ts +125 -0
- package/workers/data-worker/worker-configuration.d.ts +1 -1
- package/workers/data-worker/wrangler.jsonc.example +2 -2
- package/workers/image-worker/wrangler.jsonc.example +1 -1
- package/workers/keys-worker/wrangler.jsonc.example +1 -1
- package/workers/pdf-worker/wrangler.jsonc.example +1 -1
- package/workers/user-worker/wrangler.jsonc.example +1 -1
- package/wrangler.toml.example +1 -1
- package/app/components/public-signing-key-modal/public-signing-key-modal.module.css +0 -287
- package/app/components/public-signing-key-modal/public-signing-key-modal.tsx +0 -470
|
@@ -1,470 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useEffect,
|
|
3
|
-
useId,
|
|
4
|
-
useRef,
|
|
5
|
-
useState,
|
|
6
|
-
type ChangeEvent,
|
|
7
|
-
type DragEvent,
|
|
8
|
-
} from 'react';
|
|
9
|
-
import styles from './public-signing-key-modal.module.css';
|
|
10
|
-
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
11
|
-
import { verifyExportFile } from '~/utils/forensics';
|
|
12
|
-
|
|
13
|
-
const NO_PUBLIC_KEY_MESSAGE = 'No public signing key is configured for this environment.';
|
|
14
|
-
|
|
15
|
-
interface SelectedPublicKeyFile {
|
|
16
|
-
name: string;
|
|
17
|
-
content: string;
|
|
18
|
-
source: 'download' | 'upload';
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface VerificationOutcome {
|
|
22
|
-
state: 'pass' | 'fail';
|
|
23
|
-
message: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface VerificationDropZoneProps {
|
|
27
|
-
inputId: string;
|
|
28
|
-
label: string;
|
|
29
|
-
accept: string;
|
|
30
|
-
emptyText: string;
|
|
31
|
-
helperText: string;
|
|
32
|
-
selectedFileName?: string | null;
|
|
33
|
-
selectedDescription?: string;
|
|
34
|
-
errorMessage?: string;
|
|
35
|
-
isDisabled?: boolean;
|
|
36
|
-
onFileSelected: (file: File) => void | Promise<void>;
|
|
37
|
-
onClear?: () => void;
|
|
38
|
-
actionButton?: {
|
|
39
|
-
label: string;
|
|
40
|
-
onClick: () => void;
|
|
41
|
-
disabled?: boolean;
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const VerificationDropZone = ({
|
|
46
|
-
inputId,
|
|
47
|
-
label,
|
|
48
|
-
accept,
|
|
49
|
-
emptyText,
|
|
50
|
-
helperText,
|
|
51
|
-
selectedFileName,
|
|
52
|
-
selectedDescription,
|
|
53
|
-
errorMessage,
|
|
54
|
-
isDisabled = false,
|
|
55
|
-
onFileSelected,
|
|
56
|
-
onClear,
|
|
57
|
-
actionButton
|
|
58
|
-
}: VerificationDropZoneProps) => {
|
|
59
|
-
const [isDragOver, setIsDragOver] = useState(false);
|
|
60
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
61
|
-
|
|
62
|
-
const openFilePicker = () => {
|
|
63
|
-
if (!isDisabled) {
|
|
64
|
-
inputRef.current?.click();
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const handleSelectedFile = (file?: File) => {
|
|
69
|
-
if (!file || isDisabled) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
void onFileSelected(file);
|
|
74
|
-
|
|
75
|
-
if (inputRef.current) {
|
|
76
|
-
inputRef.current.value = '';
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
81
|
-
handleSelectedFile(event.target.files?.[0]);
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const handleDragOver = (event: DragEvent<HTMLDivElement>) => {
|
|
85
|
-
event.preventDefault();
|
|
86
|
-
if (!isDisabled) {
|
|
87
|
-
setIsDragOver(true);
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const handleDragLeave = (event: DragEvent<HTMLDivElement>) => {
|
|
92
|
-
event.preventDefault();
|
|
93
|
-
const relatedTarget = event.relatedTarget as HTMLElement | null;
|
|
94
|
-
|
|
95
|
-
if (!relatedTarget || !event.currentTarget.contains(relatedTarget)) {
|
|
96
|
-
setIsDragOver(false);
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const handleDrop = (event: DragEvent<HTMLDivElement>) => {
|
|
101
|
-
event.preventDefault();
|
|
102
|
-
setIsDragOver(false);
|
|
103
|
-
handleSelectedFile(event.dataTransfer.files?.[0]);
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
<div className={styles.verificationField}>
|
|
108
|
-
<div className={styles.fieldHeader}>
|
|
109
|
-
<label htmlFor={inputId} className={styles.fieldLabel}>
|
|
110
|
-
{label}
|
|
111
|
-
</label>
|
|
112
|
-
{selectedFileName && onClear && (
|
|
113
|
-
<button type="button" className={styles.clearButton} onClick={onClear}>
|
|
114
|
-
Clear
|
|
115
|
-
</button>
|
|
116
|
-
)}
|
|
117
|
-
</div>
|
|
118
|
-
|
|
119
|
-
<input
|
|
120
|
-
id={inputId}
|
|
121
|
-
ref={inputRef}
|
|
122
|
-
type="file"
|
|
123
|
-
accept={accept}
|
|
124
|
-
onChange={handleInputChange}
|
|
125
|
-
disabled={isDisabled}
|
|
126
|
-
className={styles.hiddenFileInput}
|
|
127
|
-
/>
|
|
128
|
-
|
|
129
|
-
<div
|
|
130
|
-
className={`${styles.dropZone} ${isDragOver ? styles.dropZoneActive : ''} ${isDisabled ? styles.dropZoneDisabled : ''}`}
|
|
131
|
-
onClick={openFilePicker}
|
|
132
|
-
onDragOver={handleDragOver}
|
|
133
|
-
onDragLeave={handleDragLeave}
|
|
134
|
-
onDrop={handleDrop}
|
|
135
|
-
role="button"
|
|
136
|
-
tabIndex={isDisabled ? -1 : 0}
|
|
137
|
-
aria-disabled={isDisabled}
|
|
138
|
-
aria-label={label}
|
|
139
|
-
onKeyDown={(event) => {
|
|
140
|
-
if ((event.key === 'Enter' || event.key === ' ') && !isDisabled) {
|
|
141
|
-
if (event.key === ' ') {
|
|
142
|
-
event.preventDefault();
|
|
143
|
-
}
|
|
144
|
-
openFilePicker();
|
|
145
|
-
}
|
|
146
|
-
}}
|
|
147
|
-
>
|
|
148
|
-
<p className={styles.dropZonePrimary}>
|
|
149
|
-
{isDragOver ? 'Drop file to continue' : selectedFileName || emptyText}
|
|
150
|
-
</p>
|
|
151
|
-
<p className={styles.dropZoneSecondary}>{selectedFileName ? selectedDescription : helperText}</p>
|
|
152
|
-
</div>
|
|
153
|
-
|
|
154
|
-
<div className={styles.fieldActions}>
|
|
155
|
-
<button type="button" className={styles.secondaryButton} onClick={openFilePicker} disabled={isDisabled}>
|
|
156
|
-
Choose File
|
|
157
|
-
</button>
|
|
158
|
-
{actionButton && (
|
|
159
|
-
<button
|
|
160
|
-
type="button"
|
|
161
|
-
className={styles.secondaryButton}
|
|
162
|
-
onClick={actionButton.onClick}
|
|
163
|
-
disabled={isDisabled || actionButton.disabled}
|
|
164
|
-
>
|
|
165
|
-
{actionButton.label}
|
|
166
|
-
</button>
|
|
167
|
-
)}
|
|
168
|
-
</div>
|
|
169
|
-
|
|
170
|
-
{errorMessage && <p className={styles.fieldError}>{errorMessage}</p>}
|
|
171
|
-
</div>
|
|
172
|
-
);
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
function createPublicKeyDownloadFileName(publicSigningKeyId?: string | null): string {
|
|
176
|
-
const normalizedKeyId =
|
|
177
|
-
typeof publicSigningKeyId === 'string' && publicSigningKeyId.trim().length > 0
|
|
178
|
-
? `-${publicSigningKeyId.trim().replace(/[^a-z0-9_-]+/gi, '-')}`
|
|
179
|
-
: '';
|
|
180
|
-
|
|
181
|
-
return `striae-public-signing-key${normalizedKeyId}.pem`;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function downloadTextFile(fileName: string, content: string, mimeType: string): void {
|
|
185
|
-
const blob = new Blob([content], { type: mimeType });
|
|
186
|
-
const objectUrl = URL.createObjectURL(blob);
|
|
187
|
-
const linkElement = document.createElement('a');
|
|
188
|
-
|
|
189
|
-
linkElement.href = objectUrl;
|
|
190
|
-
linkElement.download = fileName;
|
|
191
|
-
linkElement.style.display = 'none';
|
|
192
|
-
|
|
193
|
-
document.body.appendChild(linkElement);
|
|
194
|
-
linkElement.click();
|
|
195
|
-
document.body.removeChild(linkElement);
|
|
196
|
-
|
|
197
|
-
window.setTimeout(() => {
|
|
198
|
-
URL.revokeObjectURL(objectUrl);
|
|
199
|
-
}, 0);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function formatFileSize(bytes: number): string {
|
|
203
|
-
if (bytes < 1024 * 1024) {
|
|
204
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
interface PublicSigningKeyModalProps {
|
|
211
|
-
isOpen: boolean;
|
|
212
|
-
onClose: () => void;
|
|
213
|
-
publicSigningKeyId?: string | null;
|
|
214
|
-
publicKeyPem?: string | null;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export const PublicSigningKeyModal = ({
|
|
218
|
-
isOpen,
|
|
219
|
-
onClose,
|
|
220
|
-
publicSigningKeyId,
|
|
221
|
-
publicKeyPem
|
|
222
|
-
}: PublicSigningKeyModalProps) => {
|
|
223
|
-
const [selectedPublicKey, setSelectedPublicKey] = useState<SelectedPublicKeyFile | null>(null);
|
|
224
|
-
const [selectedExportFile, setSelectedExportFile] = useState<File | null>(null);
|
|
225
|
-
const [keyError, setKeyError] = useState('');
|
|
226
|
-
const [exportFileError, setExportFileError] = useState('');
|
|
227
|
-
const [verificationOutcome, setVerificationOutcome] = useState<VerificationOutcome | null>(null);
|
|
228
|
-
const [isVerifying, setIsVerifying] = useState(false);
|
|
229
|
-
const publicSigningKeyTitleId = useId();
|
|
230
|
-
const publicKeyInputId = useId();
|
|
231
|
-
const exportFileInputId = useId();
|
|
232
|
-
const {
|
|
233
|
-
overlayProps,
|
|
234
|
-
getCloseButtonProps
|
|
235
|
-
} = useOverlayDismiss({
|
|
236
|
-
isOpen,
|
|
237
|
-
onClose
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
useEffect(() => {
|
|
241
|
-
if (!isOpen) {
|
|
242
|
-
setSelectedPublicKey(null);
|
|
243
|
-
setSelectedExportFile(null);
|
|
244
|
-
setKeyError('');
|
|
245
|
-
setExportFileError('');
|
|
246
|
-
setVerificationOutcome(null);
|
|
247
|
-
setIsVerifying(false);
|
|
248
|
-
}
|
|
249
|
-
}, [isOpen]);
|
|
250
|
-
|
|
251
|
-
if (!isOpen) {
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const resetVerificationState = () => {
|
|
256
|
-
setVerificationOutcome(null);
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
const handlePublicKeySelected = async (file: File) => {
|
|
260
|
-
try {
|
|
261
|
-
const lowerName = file.name.toLowerCase();
|
|
262
|
-
if (!lowerName.endsWith('.pem')) {
|
|
263
|
-
setKeyError('Select a PEM public key file.');
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const content = await file.text();
|
|
268
|
-
if (!content.includes('-----BEGIN PUBLIC KEY-----') || !content.includes('-----END PUBLIC KEY-----')) {
|
|
269
|
-
setKeyError('The selected file is not a valid PEM public key file.');
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
setSelectedPublicKey({
|
|
274
|
-
name: file.name,
|
|
275
|
-
content,
|
|
276
|
-
source: 'upload'
|
|
277
|
-
});
|
|
278
|
-
setKeyError('');
|
|
279
|
-
resetVerificationState();
|
|
280
|
-
} catch {
|
|
281
|
-
setKeyError('The public key file could not be read.');
|
|
282
|
-
}
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
const handleDownloadCurrentPublicKey = () => {
|
|
286
|
-
if (!publicKeyPem) {
|
|
287
|
-
setKeyError(NO_PUBLIC_KEY_MESSAGE);
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const fileName = createPublicKeyDownloadFileName(publicSigningKeyId);
|
|
292
|
-
downloadTextFile(fileName, publicKeyPem, 'application/x-pem-file');
|
|
293
|
-
setSelectedPublicKey({
|
|
294
|
-
name: fileName,
|
|
295
|
-
content: publicKeyPem,
|
|
296
|
-
source: 'download'
|
|
297
|
-
});
|
|
298
|
-
setKeyError('');
|
|
299
|
-
resetVerificationState();
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
const handleExportFileSelected = async (file: File) => {
|
|
303
|
-
const lowerName = file.name.toLowerCase();
|
|
304
|
-
|
|
305
|
-
if (!lowerName.endsWith('.zip') && !lowerName.endsWith('.json')) {
|
|
306
|
-
setExportFileError('Select a confirmation JSON/ZIP file, standalone audit JSON export, or a case export ZIP file.');
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
setSelectedExportFile(file);
|
|
311
|
-
setExportFileError('');
|
|
312
|
-
resetVerificationState();
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
const handleVerify = async () => {
|
|
316
|
-
const hasPublicKey = !!selectedPublicKey?.content;
|
|
317
|
-
const hasExportFile = !!selectedExportFile;
|
|
318
|
-
|
|
319
|
-
setKeyError(hasPublicKey ? '' : 'Select or download a public key PEM file first.');
|
|
320
|
-
setExportFileError(hasExportFile ? '' : 'Select a confirmation JSON/ZIP file, standalone audit JSON export, or a case export ZIP file.');
|
|
321
|
-
|
|
322
|
-
if (!hasPublicKey || !hasExportFile || !selectedPublicKey || !selectedExportFile) {
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
setIsVerifying(true);
|
|
327
|
-
setVerificationOutcome(null);
|
|
328
|
-
|
|
329
|
-
try {
|
|
330
|
-
const result = await verifyExportFile(selectedExportFile, selectedPublicKey.content);
|
|
331
|
-
setVerificationOutcome({
|
|
332
|
-
state: result.isValid ? 'pass' : 'fail',
|
|
333
|
-
message: result.message
|
|
334
|
-
});
|
|
335
|
-
} finally {
|
|
336
|
-
setIsVerifying(false);
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
const selectedKeyDescription = selectedPublicKey
|
|
341
|
-
? selectedPublicKey.source === 'download'
|
|
342
|
-
? 'Downloaded from this Striae environment and ready to use.'
|
|
343
|
-
: 'Loaded from your device and ready to use.'
|
|
344
|
-
: undefined;
|
|
345
|
-
|
|
346
|
-
const selectedExportDescription = selectedExportFile
|
|
347
|
-
? `${(() => {
|
|
348
|
-
const lowerName = selectedExportFile.name.toLowerCase();
|
|
349
|
-
if (lowerName.endsWith('.zip')) {
|
|
350
|
-
return lowerName.includes('confirmation-data-') ? 'Confirmation ZIP' : 'Case export ZIP';
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (lowerName.includes('audit')) {
|
|
354
|
-
return 'Audit JSON';
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
return 'JSON export';
|
|
358
|
-
})()} • ${formatFileSize(selectedExportFile.size)}`
|
|
359
|
-
: undefined;
|
|
360
|
-
|
|
361
|
-
return (
|
|
362
|
-
<div
|
|
363
|
-
className={styles.overlay}
|
|
364
|
-
aria-label="Close public signing key dialog"
|
|
365
|
-
{...overlayProps}
|
|
366
|
-
>
|
|
367
|
-
<div
|
|
368
|
-
className={styles.modal}
|
|
369
|
-
role="dialog"
|
|
370
|
-
aria-modal="true"
|
|
371
|
-
aria-labelledby={publicSigningKeyTitleId}
|
|
372
|
-
>
|
|
373
|
-
<div className={styles.header}>
|
|
374
|
-
<h3 id={publicSigningKeyTitleId} className={styles.title}>
|
|
375
|
-
Striae Verification Utility
|
|
376
|
-
</h3>
|
|
377
|
-
<button
|
|
378
|
-
className={styles.closeButton}
|
|
379
|
-
{...getCloseButtonProps({ ariaLabel: 'Close public signing key dialog' })}
|
|
380
|
-
>
|
|
381
|
-
×
|
|
382
|
-
</button>
|
|
383
|
-
</div>
|
|
384
|
-
|
|
385
|
-
<div className={styles.content}>
|
|
386
|
-
<p className={styles.description}>
|
|
387
|
-
Drop a public key PEM file and a Striae confirmation JSON/ZIP, standalone audit JSON export, or case export ZIP, then run
|
|
388
|
-
verification directly in the browser.
|
|
389
|
-
</p>
|
|
390
|
-
|
|
391
|
-
{publicSigningKeyId && (
|
|
392
|
-
<p className={styles.meta}>
|
|
393
|
-
Current key ID: <span>{publicSigningKeyId}</span>
|
|
394
|
-
</p>
|
|
395
|
-
)}
|
|
396
|
-
|
|
397
|
-
<div className={styles.verifierLayout}>
|
|
398
|
-
<VerificationDropZone
|
|
399
|
-
inputId={publicKeyInputId}
|
|
400
|
-
label="1. Public Key PEM"
|
|
401
|
-
accept=".pem"
|
|
402
|
-
emptyText="Drop a public key PEM file here"
|
|
403
|
-
helperText="Use a .pem file containing the Striae public signing key."
|
|
404
|
-
selectedFileName={selectedPublicKey?.name}
|
|
405
|
-
selectedDescription={selectedKeyDescription}
|
|
406
|
-
errorMessage={keyError}
|
|
407
|
-
onFileSelected={handlePublicKeySelected}
|
|
408
|
-
onClear={() => {
|
|
409
|
-
setSelectedPublicKey(null);
|
|
410
|
-
setKeyError('');
|
|
411
|
-
resetVerificationState();
|
|
412
|
-
}}
|
|
413
|
-
actionButton={{
|
|
414
|
-
label: 'Download Current Public Key',
|
|
415
|
-
onClick: handleDownloadCurrentPublicKey,
|
|
416
|
-
disabled: !publicKeyPem
|
|
417
|
-
}}
|
|
418
|
-
/>
|
|
419
|
-
|
|
420
|
-
<VerificationDropZone
|
|
421
|
-
inputId={exportFileInputId}
|
|
422
|
-
label="2. Confirmation File or Export ZIP"
|
|
423
|
-
accept=".json,.zip"
|
|
424
|
-
emptyText="Drop a confirmation JSON/ZIP, audit JSON, or case export ZIP here"
|
|
425
|
-
helperText="Case exports use .zip. Confirmation exports can be .json or .zip. Audit exports are supported as standalone .json files."
|
|
426
|
-
selectedFileName={selectedExportFile?.name}
|
|
427
|
-
selectedDescription={selectedExportDescription}
|
|
428
|
-
errorMessage={exportFileError}
|
|
429
|
-
onFileSelected={handleExportFileSelected}
|
|
430
|
-
onClear={() => {
|
|
431
|
-
setSelectedExportFile(null);
|
|
432
|
-
setExportFileError('');
|
|
433
|
-
resetVerificationState();
|
|
434
|
-
}}
|
|
435
|
-
/>
|
|
436
|
-
</div>
|
|
437
|
-
|
|
438
|
-
{verificationOutcome && (
|
|
439
|
-
<div
|
|
440
|
-
className={`${styles.resultCard} ${verificationOutcome.state === 'pass' ? styles.resultPass : styles.resultFail}`}
|
|
441
|
-
role="status"
|
|
442
|
-
aria-live="polite"
|
|
443
|
-
>
|
|
444
|
-
<p className={styles.resultTitle}>{verificationOutcome.state === 'pass' ? 'PASS' : 'FAIL'}</p>
|
|
445
|
-
<p className={styles.resultMessage}>{verificationOutcome.message}</p>
|
|
446
|
-
</div>
|
|
447
|
-
)}
|
|
448
|
-
|
|
449
|
-
<div className={styles.actions}>
|
|
450
|
-
<button
|
|
451
|
-
type="button"
|
|
452
|
-
className={styles.primaryButton}
|
|
453
|
-
onClick={handleVerify}
|
|
454
|
-
disabled={isVerifying || !selectedPublicKey || !selectedExportFile}
|
|
455
|
-
>
|
|
456
|
-
{isVerifying ? 'Verifying...' : 'Verify File'}
|
|
457
|
-
</button>
|
|
458
|
-
<button
|
|
459
|
-
type="button"
|
|
460
|
-
className={styles.secondaryButton}
|
|
461
|
-
onClick={onClose}
|
|
462
|
-
>
|
|
463
|
-
Close
|
|
464
|
-
</button>
|
|
465
|
-
</div>
|
|
466
|
-
</div>
|
|
467
|
-
</div>
|
|
468
|
-
</div>
|
|
469
|
-
);
|
|
470
|
-
};
|