@striae-org/striae 4.0.0 → 4.0.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.
- package/.env.example +1 -0
- package/README.md +1 -1
- package/app/components/actions/case-export/data-processing.ts +1 -1
- package/app/components/actions/case-export/download-handlers.ts +5 -4
- package/app/components/actions/case-export/metadata-helpers.ts +1 -1
- package/app/components/actions/case-import/confirmation-import.ts +1 -1
- package/app/components/actions/case-import/image-operations.ts +1 -1
- package/app/components/actions/case-import/orchestrator.ts +1 -1
- package/app/components/actions/case-import/storage-operations.ts +3 -3
- package/app/components/actions/case-import/validation.ts +3 -4
- package/app/components/actions/case-import/zip-processing.ts +1 -1
- package/app/components/actions/case-manage.ts +3 -5
- package/app/components/actions/confirm-export.ts +4 -5
- package/app/components/actions/generate-pdf.ts +1 -1
- package/app/components/actions/image-manage.ts +4 -4
- package/app/components/actions/notes-manage.ts +1 -1
- package/app/components/actions/signout.tsx +1 -1
- package/app/components/audit/user-audit-viewer.tsx +1 -1
- package/app/components/auth/auth-provider.tsx +1 -1
- package/app/components/auth/mfa-verification.tsx +1 -1
- package/app/components/button/button.tsx +1 -1
- package/app/components/canvas/box-annotations/box-annotations.tsx +1 -1
- package/app/components/canvas/confirmation/confirmation.tsx +1 -1
- package/app/components/icon/icon.tsx +1 -1
- package/app/components/public-signing-key-modal/public-signing-key-modal.tsx +1 -1
- package/app/components/sidebar/case-export/case-export.tsx +1 -1
- package/app/components/sidebar/cases/case-sidebar.tsx +3 -3
- package/app/components/sidebar/cases/cases-modal.tsx +1 -1
- package/app/components/sidebar/files/files-modal.tsx +1 -1
- package/app/components/sidebar/notes/notes-sidebar.tsx +1 -1
- package/app/components/sidebar/sidebar-container.tsx +2 -17
- package/app/components/sidebar/sidebar.module.css +0 -29
- package/app/components/theme-provider/theme-provider.tsx +1 -1
- package/app/components/theme-provider/theme.ts +1 -1
- package/app/components/user/delete-account.tsx +1 -1
- package/app/components/user/manage-profile.tsx +1 -1
- package/app/components/user/mfa-phone-update.tsx +1 -1
- package/app/root.tsx +18 -51
- package/app/routes/auth/emailActionHandler.tsx +2 -3
- package/app/routes/auth/emailVerification.tsx +2 -2
- package/app/routes/auth/login.tsx +7 -9
- package/app/routes/auth/passwordReset.tsx +2 -2
- package/app/routes/striae/striae.tsx +2 -2
- package/app/services/audit/audit-export-signing.ts +2 -2
- package/app/services/audit/audit-export.service.ts +1 -2
- package/app/services/audit/audit.service.ts +1 -1
- package/app/services/firebase/index.ts +1 -1
- package/app/utils/api/index.ts +4 -0
- package/app/utils/auth/index.ts +5 -0
- package/app/utils/common/index.ts +3 -0
- package/app/utils/{version.ts → common/version.ts} +1 -1
- package/app/utils/{data-operations.ts → data/data-operations.ts} +4 -4
- package/app/utils/data/index.ts +2 -0
- package/app/utils/{permissions.ts → data/permissions.ts} +1 -1
- package/app/utils/forensics/index.ts +5 -0
- package/app/utils/ui/index.ts +2 -0
- package/functions/api/image/[[path]].ts +17 -1
- package/package.json +18 -20
- package/public/.well-known/keybase.txt +56 -0
- package/public/.well-known/security.txt +3 -4
- package/scripts/deploy-config.sh +178 -142
- package/scripts/deploy-worker-secrets.sh +1 -2
- package/worker-configuration.d.ts +7491 -11363
- package/workers/audit-worker/worker-configuration.d.ts +11323 -7448
- package/workers/audit-worker/wrangler.jsonc.example +1 -1
- package/workers/data-worker/worker-configuration.d.ts +11323 -7448
- package/workers/data-worker/wrangler.jsonc.example +1 -1
- package/workers/image-worker/src/image-worker.example.ts +10 -2
- package/workers/image-worker/worker-configuration.d.ts +11322 -7447
- package/workers/image-worker/wrangler.jsonc.example +1 -1
- package/workers/keys-worker/src/keys.ts +2 -1
- package/workers/keys-worker/worker-configuration.d.ts +11322 -7447
- package/workers/keys-worker/wrangler.jsonc.example +1 -1
- package/workers/pdf-worker/src/pdf-worker.example.ts +144 -39
- package/workers/pdf-worker/worker-configuration.d.ts +11323 -7448
- package/workers/pdf-worker/wrangler.jsonc.example +1 -1
- package/workers/user-worker/worker-configuration.d.ts +11323 -7448
- package/workers/user-worker/wrangler.jsonc.example +1 -1
- package/wrangler.toml.example +1 -1
- package/public/.well-known/publickey.info@striae.org.asc +0 -17
- package/public/oin-badge.png +0 -0
- /package/app/utils/{data-api-client.ts → api/data-api-client.ts} +0 -0
- /package/app/utils/{image-api-client.ts → api/image-api-client.ts} +0 -0
- /package/app/utils/{pdf-api-client.ts → api/pdf-api-client.ts} +0 -0
- /package/app/utils/{user-api-client.ts → api/user-api-client.ts} +0 -0
- /package/app/utils/{auth-action-settings.ts → auth/auth-action-settings.ts} +0 -0
- /package/app/utils/{auth.ts → auth/auth.ts} +0 -0
- /package/app/utils/{mfa-phone.ts → auth/mfa-phone.ts} +0 -0
- /package/app/utils/{mfa.ts → auth/mfa.ts} +0 -0
- /package/app/utils/{password-policy.ts → auth/password-policy.ts} +0 -0
- /package/app/utils/{batch-operations.ts → common/batch-operations.ts} +0 -0
- /package/app/utils/{id-generator.ts → common/id-generator.ts} +0 -0
- /package/app/utils/{SHA256.ts → forensics/SHA256.ts} +0 -0
- /package/app/utils/{audit-export-signature.ts → forensics/audit-export-signature.ts} +0 -0
- /package/app/utils/{confirmation-signature.ts → forensics/confirmation-signature.ts} +0 -0
- /package/app/utils/{export-verification.ts → forensics/export-verification.ts} +0 -0
- /package/app/utils/{signature-utils.ts → forensics/signature-utils.ts} +0 -0
- /package/app/utils/{annotation-timestamp.ts → ui/annotation-timestamp.ts} +0 -0
- /package/app/utils/{style.ts → ui/style.ts} +0 -0
|
@@ -1,12 +1,28 @@
|
|
|
1
|
-
import { launch } from "@cloudflare/puppeteer";
|
|
2
1
|
import type { PDFGenerationData, PDFGenerationRequest, ReportModule } from './report-types';
|
|
3
2
|
|
|
4
3
|
interface Env {
|
|
5
4
|
BROWSER: Fetcher;
|
|
6
5
|
PDF_WORKER_AUTH: string;
|
|
6
|
+
ACCOUNT_ID?: string;
|
|
7
|
+
CLOUDFLARE_ACCOUNT_ID?: string;
|
|
8
|
+
BROWSER_API_TOKEN?: string;
|
|
9
|
+
API_TOKEN?: string;
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
const DEFAULT_REPORT_FORMAT = 'striae';
|
|
13
|
+
const BROWSER_PDF_TIMEOUT_MS = 90_000;
|
|
14
|
+
const BROWSER_RENDERING_API_BASE = 'https://api.cloudflare.com/client/v4/accounts';
|
|
15
|
+
|
|
16
|
+
const DEFAULT_PDF_OPTIONS = {
|
|
17
|
+
printBackground: true,
|
|
18
|
+
format: 'letter',
|
|
19
|
+
margin: {
|
|
20
|
+
top: '0.5in',
|
|
21
|
+
bottom: '0.5in',
|
|
22
|
+
left: '0.5in',
|
|
23
|
+
right: '0.5in',
|
|
24
|
+
},
|
|
25
|
+
};
|
|
10
26
|
|
|
11
27
|
const reportModuleLoaders: Record<string, () => Promise<ReportModule>> = {
|
|
12
28
|
// Default Striae report format module
|
|
@@ -22,6 +38,45 @@ const corsHeaders: Record<string, string> = {
|
|
|
22
38
|
const hasValidHeader = (request: Request, env: Env): boolean =>
|
|
23
39
|
request.headers.get('X-Custom-Auth-Key') === env.PDF_WORKER_AUTH;
|
|
24
40
|
|
|
41
|
+
function isTimeoutError(error: unknown): boolean {
|
|
42
|
+
return error instanceof Error && (
|
|
43
|
+
error.name === 'AbortError' ||
|
|
44
|
+
error.name === 'TimeoutError' ||
|
|
45
|
+
/timed out/i.test(error.message)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function jsonResponse(body: unknown, status: number): Response {
|
|
50
|
+
return new Response(JSON.stringify(body), {
|
|
51
|
+
status,
|
|
52
|
+
headers: { ...corsHeaders, 'content-type': 'application/json' },
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolveBrowserApiToken(env: Env): string {
|
|
57
|
+
const candidates = [env.BROWSER_API_TOKEN, env.API_TOKEN];
|
|
58
|
+
|
|
59
|
+
for (const candidate of candidates) {
|
|
60
|
+
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
|
61
|
+
return candidate.trim();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function resolveAccountId(env: Env): string {
|
|
69
|
+
const candidates = [env.ACCOUNT_ID, env.CLOUDFLARE_ACCOUNT_ID];
|
|
70
|
+
|
|
71
|
+
for (const candidate of candidates) {
|
|
72
|
+
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
|
73
|
+
return candidate.trim();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return '';
|
|
78
|
+
}
|
|
79
|
+
|
|
25
80
|
function normalizeReportFormat(format: unknown): string {
|
|
26
81
|
if (typeof format !== 'string') {
|
|
27
82
|
return DEFAULT_REPORT_FORMAT;
|
|
@@ -69,6 +124,84 @@ async function renderReport(reportFormat: string, data: PDFGenerationData): Prom
|
|
|
69
124
|
return reportModule.renderReport(data);
|
|
70
125
|
}
|
|
71
126
|
|
|
127
|
+
async function renderPdfViaRestEndpoint(env: Env, html: string): Promise<Response> {
|
|
128
|
+
const accountId = resolveAccountId(env);
|
|
129
|
+
const browserApiToken = resolveBrowserApiToken(env);
|
|
130
|
+
|
|
131
|
+
if (!accountId || !browserApiToken) {
|
|
132
|
+
return jsonResponse(
|
|
133
|
+
{
|
|
134
|
+
error: 'Missing required Browser Rendering credentials',
|
|
135
|
+
requiredSecrets: ['ACCOUNT_ID', 'BROWSER_API_TOKEN'],
|
|
136
|
+
note: 'Set ACCOUNT_ID and a Browser Rendering - Edit token (BROWSER_API_TOKEN) on this worker.',
|
|
137
|
+
},
|
|
138
|
+
502
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const endpoint = `${BROWSER_RENDERING_API_BASE}/${accountId}/browser-rendering/pdf`;
|
|
143
|
+
const requestBody = JSON.stringify({
|
|
144
|
+
html,
|
|
145
|
+
pdfOptions: DEFAULT_PDF_OPTIONS,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
let endpointResponse: Response;
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
endpointResponse = await fetch(endpoint, {
|
|
152
|
+
method: 'POST',
|
|
153
|
+
headers: {
|
|
154
|
+
Authorization: `Bearer ${browserApiToken}`,
|
|
155
|
+
'Content-Type': 'application/json',
|
|
156
|
+
},
|
|
157
|
+
body: requestBody,
|
|
158
|
+
signal: AbortSignal.timeout(BROWSER_PDF_TIMEOUT_MS),
|
|
159
|
+
});
|
|
160
|
+
} catch (error) {
|
|
161
|
+
const message = error instanceof Error ? error.message : 'Unknown browser endpoint error';
|
|
162
|
+
return jsonResponse(
|
|
163
|
+
{
|
|
164
|
+
error: 'Unable to reach Browser Rendering endpoint',
|
|
165
|
+
endpoint,
|
|
166
|
+
message,
|
|
167
|
+
},
|
|
168
|
+
isTimeoutError(error) ? 504 : 502
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!endpointResponse.ok) {
|
|
173
|
+
const failureText = await endpointResponse.text().catch(() => '');
|
|
174
|
+
return jsonResponse(
|
|
175
|
+
{
|
|
176
|
+
error: 'Browser Rendering endpoint returned an error',
|
|
177
|
+
endpoint,
|
|
178
|
+
status: endpointResponse.status,
|
|
179
|
+
details: failureText.slice(0, 512) || endpointResponse.statusText || 'Unknown endpoint failure',
|
|
180
|
+
},
|
|
181
|
+
endpointResponse.status === 504 ? 504 : 502
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const responseHeaders = new Headers(endpointResponse.headers);
|
|
186
|
+
if (!responseHeaders.has('content-type')) {
|
|
187
|
+
responseHeaders.set('content-type', 'application/pdf');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!responseHeaders.has('cache-control')) {
|
|
191
|
+
responseHeaders.set('cache-control', 'no-store');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
for (const [headerName, headerValue] of Object.entries(corsHeaders)) {
|
|
195
|
+
responseHeaders.set(headerName, headerValue);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return new Response(endpointResponse.body, {
|
|
199
|
+
status: endpointResponse.status,
|
|
200
|
+
statusText: endpointResponse.statusText,
|
|
201
|
+
headers: responseHeaders,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
72
205
|
export default {
|
|
73
206
|
async fetch(request: Request, env: Env): Promise<Response> {
|
|
74
207
|
if (request.method === 'OPTIONS') {
|
|
@@ -76,55 +209,27 @@ export default {
|
|
|
76
209
|
}
|
|
77
210
|
|
|
78
211
|
if (!hasValidHeader(request, env)) {
|
|
79
|
-
return
|
|
80
|
-
status: 403,
|
|
81
|
-
headers: { ...corsHeaders, 'content-type': 'application/json' },
|
|
82
|
-
});
|
|
212
|
+
return jsonResponse({ error: 'Forbidden' }, 403);
|
|
83
213
|
}
|
|
84
214
|
|
|
85
215
|
if (request.method === 'POST') {
|
|
86
|
-
let browser: Awaited<ReturnType<typeof launch>> | undefined;
|
|
87
|
-
|
|
88
216
|
try {
|
|
89
217
|
const payload = await request.json() as PDFGenerationData | PDFGenerationRequest;
|
|
90
218
|
const { reportFormat, data } = resolveReportRequest(payload);
|
|
91
|
-
|
|
92
|
-
browser = await launch(env.BROWSER);
|
|
93
|
-
const page = await browser.newPage();
|
|
94
|
-
|
|
95
|
-
// Render report from module selected by report format name.
|
|
96
219
|
const document = await renderReport(reportFormat, data);
|
|
97
|
-
await page.setContent(document);
|
|
98
|
-
|
|
99
|
-
const pdfBuffer = await page.pdf({
|
|
100
|
-
printBackground: true,
|
|
101
|
-
format: 'letter',
|
|
102
|
-
margin: { top: '0.5in', bottom: '0.5in', left: '0.5in', right: '0.5in' },
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
return new Response(new Uint8Array(pdfBuffer), {
|
|
106
|
-
headers: {
|
|
107
|
-
...corsHeaders,
|
|
108
|
-
'content-type': 'application/pdf',
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
} catch (error) {
|
|
112
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
113
220
|
|
|
114
|
-
return
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (browser) {
|
|
120
|
-
await browser.close();
|
|
221
|
+
return await renderPdfViaRestEndpoint(env, document);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (isTimeoutError(error)) {
|
|
224
|
+
const timeoutMessage = error instanceof Error ? error.message : 'PDF generation timed out';
|
|
225
|
+
return jsonResponse({ error: timeoutMessage }, 504);
|
|
121
226
|
}
|
|
227
|
+
|
|
228
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
229
|
+
return jsonResponse({ error: errorMessage }, 500);
|
|
122
230
|
}
|
|
123
231
|
}
|
|
124
232
|
|
|
125
|
-
return
|
|
126
|
-
status: 405,
|
|
127
|
-
headers: { ...corsHeaders, 'content-type': 'application/json' },
|
|
128
|
-
});
|
|
233
|
+
return jsonResponse({ error: 'Method not allowed' }, 405);
|
|
129
234
|
},
|
|
130
235
|
};
|