@striae-org/striae 7.0.0 โ 7.1.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 +8 -14
- package/app/components/canvas/canvas.module.css +12 -0
- package/app/components/canvas/canvas.tsx +26 -5
- package/functions/api/_shared/lists-client.ts +39 -0
- package/functions/api/_shared/registration-allowlist.ts +5 -4
- package/functions/api/auth/can-register.ts +7 -2
- package/functions/api/pdf/[[path]].ts +4 -1
- package/functions/api/user/[[path]].ts +11 -5
- package/package.json +10 -8
- package/scripts/deploy-all.sh +3 -3
- package/scripts/deploy-config/modules/prompt.sh +43 -7
- package/scripts/deploy-config/modules/scaffolding.sh +19 -0
- package/scripts/deploy-config/modules/validation.sh +3 -0
- package/scripts/deploy-config.sh +0 -33
- package/scripts/deploy-pages-secrets.sh +1 -10
- package/scripts/deploy-worker-secrets.sh +19 -1
- package/scripts/install-workers.sh +4 -3
- package/scripts/update-markdown-versions.cjs +1 -0
- package/workers/audit-worker/package.json +2 -2
- package/workers/audit-worker/wrangler.jsonc.example +1 -1
- package/workers/data-worker/package.json +2 -2
- package/workers/data-worker/wrangler.jsonc.example +1 -1
- package/workers/image-worker/package.json +2 -2
- package/workers/image-worker/src/handlers/delete-image.ts +5 -5
- package/workers/image-worker/src/handlers/mint-signed-url.ts +5 -5
- package/workers/image-worker/src/handlers/serve-image.ts +7 -7
- package/workers/image-worker/src/handlers/upload-image.ts +4 -4
- package/workers/image-worker/src/image-worker.ts +4 -4
- package/workers/image-worker/src/router.ts +11 -11
- package/workers/image-worker/src/types.ts +1 -1
- package/workers/image-worker/wrangler.jsonc.example +1 -1
- package/workers/lists-worker/package.json +13 -0
- package/workers/lists-worker/src/lists-worker.ts +97 -0
- package/workers/lists-worker/src/types.ts +4 -0
- package/workers/lists-worker/wrangler.jsonc.example +23 -0
- package/workers/pdf-worker/package.json +2 -2
- package/workers/pdf-worker/wrangler.jsonc.example +1 -1
- package/workers/user-worker/package.json +2 -2
- package/workers/user-worker/src/handlers/user-routes.ts +26 -34
- package/workers/user-worker/src/types.ts +13 -0
- package/workers/user-worker/src/user-worker.ts +18 -24
- package/workers/user-worker/wrangler.jsonc.example +1 -1
- package/wrangler.toml.example +6 -2
- package/app/config-example/members.emails +0 -11
- package/app/config-example/primershear.emails +0 -6
- package/scripts/deploy-members-emails.sh +0 -102
- package/scripts/deploy-primershear-emails.sh +0 -101
package/.env.example
CHANGED
|
@@ -110,17 +110,11 @@ PDF_WORKER_NAME=your_pdf_worker_name_here
|
|
|
110
110
|
BROWSER_API_TOKEN=your_cloudflare_browser_rendering_api_token_here
|
|
111
111
|
|
|
112
112
|
# ================================
|
|
113
|
-
#
|
|
114
|
-
# ================================
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
# REGISTRATION EMAIL ALLOWLIST CONFIGURATION
|
|
122
|
-
# ================================
|
|
123
|
-
# Comma-separated list of email addresses that may register an account.
|
|
124
|
-
# Leave empty to disable the feature. Never commit this value to source control.
|
|
125
|
-
# Example: REGISTRATION_EMAILS=analyst@org.com,user2@org.com
|
|
126
|
-
REGISTRATION_EMAILS=
|
|
113
|
+
# LISTS WORKER ENVIRONMENT VARIABLES
|
|
114
|
+
# ================================
|
|
115
|
+
# The lists-worker manages registration and PDF format allowlists via KV.
|
|
116
|
+
# STRIAE_LISTS_KV_ID is the KV namespace ID backing both lists.
|
|
117
|
+
# LISTS_ADMIN_SECRET guards write endpoints (POST/DELETE); use a strong random value.
|
|
118
|
+
LISTS_WORKER_NAME=your_lists_worker_name_here
|
|
119
|
+
STRIAE_LISTS_KV_ID=your_striae_lists_kv_id_here
|
|
120
|
+
LISTS_ADMIN_SECRET=your_lists_admin_secret_here
|
|
@@ -309,6 +309,18 @@
|
|
|
309
309
|
font-family: "Inter", sans-serif;
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
.noteSection {
|
|
313
|
+
flex: 1;
|
|
314
|
+
display: flex;
|
|
315
|
+
flex-direction: column;
|
|
316
|
+
overflow: hidden;
|
|
317
|
+
min-height: 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.noteSection + .noteSection {
|
|
321
|
+
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
|
322
|
+
}
|
|
323
|
+
|
|
312
324
|
.additionalNotesBox {
|
|
313
325
|
flex: 1;
|
|
314
326
|
overflow-y: auto;
|
|
@@ -424,12 +424,33 @@ export const Canvas = ({
|
|
|
424
424
|
</div>{/* end imageContainer */}
|
|
425
425
|
|
|
426
426
|
{/* Additional Notes - Right Panel */}
|
|
427
|
-
{activeAnnotations?.has('notes') &&
|
|
427
|
+
{activeAnnotations?.has('notes') &&
|
|
428
|
+
(annotationData?.leftAdditionalNotes || annotationData?.rightAdditionalNotes || annotationData?.additionalNotes) && (
|
|
428
429
|
<aside className={styles.notesPanel} aria-label="Additional notes">
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
430
|
+
{annotationData?.leftAdditionalNotes && (
|
|
431
|
+
<div className={styles.noteSection}>
|
|
432
|
+
<div className={styles.notesPanelHeader}>Left Item</div>
|
|
433
|
+
<div className={styles.additionalNotesBox}>
|
|
434
|
+
{annotationData.leftAdditionalNotes}
|
|
435
|
+
</div>
|
|
436
|
+
</div>
|
|
437
|
+
)}
|
|
438
|
+
{annotationData?.rightAdditionalNotes && (
|
|
439
|
+
<div className={styles.noteSection}>
|
|
440
|
+
<div className={styles.notesPanelHeader}>Right Item</div>
|
|
441
|
+
<div className={styles.additionalNotesBox}>
|
|
442
|
+
{annotationData.rightAdditionalNotes}
|
|
443
|
+
</div>
|
|
444
|
+
</div>
|
|
445
|
+
)}
|
|
446
|
+
{annotationData?.additionalNotes && (
|
|
447
|
+
<div className={styles.noteSection}>
|
|
448
|
+
<div className={styles.notesPanelHeader}>Notes</div>
|
|
449
|
+
<div className={styles.additionalNotesBox}>
|
|
450
|
+
{annotationData.additionalNotes}
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
)}
|
|
433
454
|
</aside>
|
|
434
455
|
)}
|
|
435
456
|
</div>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type ListResult =
|
|
2
|
+
| { ok: true; list: string }
|
|
3
|
+
| { ok: false; error: string };
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Client helper for reading email lists from the lists-worker via service binding.
|
|
7
|
+
* Returns a ListResult so callers can apply fail-open or fail-closed logic
|
|
8
|
+
* appropriate to their security context.
|
|
9
|
+
*
|
|
10
|
+
* - ok: true โ list fetched successfully (may be empty string if list is empty)
|
|
11
|
+
* - ok: false โ worker unreachable, auth failure, or unexpected response shape
|
|
12
|
+
*/
|
|
13
|
+
export async function fetchListFromWorker(
|
|
14
|
+
binding: Fetcher,
|
|
15
|
+
list: 'members' | 'primershear',
|
|
16
|
+
secret: string
|
|
17
|
+
): Promise<ListResult> {
|
|
18
|
+
try {
|
|
19
|
+
const response = await binding.fetch(`https://worker/${list}`, {
|
|
20
|
+
headers: { 'Authorization': `Bearer ${secret}` },
|
|
21
|
+
});
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
const msg = `lists-client: GET /${list} returned ${response.status}`;
|
|
24
|
+
console.error(msg);
|
|
25
|
+
return { ok: false, error: msg };
|
|
26
|
+
}
|
|
27
|
+
const data = await response.json() as { list?: unknown };
|
|
28
|
+
if (typeof data.list !== 'string') {
|
|
29
|
+
const msg = `lists-client: unexpected response shape for /${list}`;
|
|
30
|
+
console.error(msg);
|
|
31
|
+
return { ok: false, error: msg };
|
|
32
|
+
}
|
|
33
|
+
return { ok: true, list: data.list };
|
|
34
|
+
} catch (err) {
|
|
35
|
+
const msg = `lists-client: failed to fetch /${list}`;
|
|
36
|
+
console.error(msg, err);
|
|
37
|
+
return { ok: false, error: msg };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Checks whether the given email is permitted to register based on the
|
|
3
|
-
*
|
|
3
|
+
* registration allowlist (a comma-separated list of allowed entries) sourced
|
|
4
|
+
* from the lists-worker KV (key: "allow").
|
|
4
5
|
*
|
|
5
6
|
* Each entry may be:
|
|
6
7
|
* - An exact email address: user@example.com
|
|
7
8
|
* - A domain wildcard: @example.com (matches any email from that domain)
|
|
8
9
|
*
|
|
9
|
-
* If registrationEmails is empty or unset,
|
|
10
|
-
*
|
|
10
|
+
* If registrationEmails is empty or unset, registration is denied (fail closed).
|
|
11
|
+
* An empty list indicates the allowlist has not been populated, not that all are allowed.
|
|
11
12
|
*/
|
|
12
13
|
export function isEmailAllowed(email: string, registrationEmails: string): boolean {
|
|
13
14
|
if (!registrationEmails || registrationEmails.trim().length === 0) {
|
|
14
|
-
return
|
|
15
|
+
return false;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
const normalizedEmail = email.toLowerCase().trim();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isEmailAllowed } from '../_shared/registration-allowlist';
|
|
2
|
+
import { fetchListFromWorker } from '../_shared/lists-client';
|
|
2
3
|
|
|
3
4
|
interface CanRegisterContext {
|
|
4
5
|
request: Request;
|
|
@@ -49,9 +50,13 @@ export const onRequest = async ({ request, env }: CanRegisterContext): Promise<R
|
|
|
49
50
|
return textResponse('Missing required parameter: email', 400);
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
const
|
|
53
|
+
const listResult = await fetchListFromWorker(env.LISTS_WORKER, 'members', env.LISTS_ADMIN_SECRET);
|
|
54
|
+
if (!listResult.ok) {
|
|
55
|
+
// Fail closed: cannot verify allowlist, deny to prevent bypass.
|
|
56
|
+
return textResponse('Unable to verify registration eligibility', 503);
|
|
57
|
+
}
|
|
53
58
|
|
|
54
|
-
if (isEmailAllowed(email,
|
|
59
|
+
if (isEmailAllowed(email, listResult.list)) {
|
|
55
60
|
return jsonResponse({ allowed: true });
|
|
56
61
|
}
|
|
57
62
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { verifyFirebaseIdentityFromRequest } from '../_shared/firebase-auth';
|
|
2
|
+
import { fetchListFromWorker } from '../_shared/lists-client';
|
|
2
3
|
|
|
3
4
|
interface PdfProxyContext {
|
|
4
5
|
request: Request;
|
|
@@ -97,9 +98,11 @@ export const onRequest = async ({ request, env }: PdfProxyContext): Promise<Resp
|
|
|
97
98
|
|
|
98
99
|
// Resolve the report format server-side based on the verified user email.
|
|
99
100
|
// This prevents email lists from ever being exposed in the client bundle.
|
|
101
|
+
// Fail-open: if the lists-worker is unavailable, fall back to the default format.
|
|
102
|
+
const primershearResult = await fetchListFromWorker(env.LISTS_WORKER, 'primershear', env.LISTS_ADMIN_SECRET);
|
|
100
103
|
const reportFormat = resolveReportFormat(
|
|
101
104
|
identity.email,
|
|
102
|
-
|
|
105
|
+
primershearResult.ok ? primershearResult.list : ''
|
|
103
106
|
);
|
|
104
107
|
|
|
105
108
|
let upstreamBody: BodyInit;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { verifyFirebaseIdentityFromRequest } from '../_shared/firebase-auth';
|
|
2
2
|
import { isEmailAllowed } from '../_shared/registration-allowlist';
|
|
3
|
+
import { fetchListFromWorker } from '../_shared/lists-client';
|
|
3
4
|
|
|
4
5
|
interface UserProxyContext {
|
|
5
6
|
request: Request;
|
|
@@ -142,9 +143,14 @@ export const onRequest = async ({ request, env }: UserProxyContext): Promise<Res
|
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
// Registration gateway: for PUT requests, check if this is a new user creation.
|
|
145
|
-
//
|
|
146
|
+
// Always enforce the allowlist for new users โ isEmailAllowed fails closed for empty lists.
|
|
146
147
|
// This is defense-in-depth โ the primary check runs client-side in the login flow.
|
|
147
|
-
if (request.method === 'PUT'
|
|
148
|
+
if (request.method === 'PUT') {
|
|
149
|
+
const listResult = await fetchListFromWorker(env.LISTS_WORKER, 'members', env.LISTS_ADMIN_SECRET);
|
|
150
|
+
if (!listResult.ok) {
|
|
151
|
+
// Fail closed: cannot verify allowlist, reject to prevent bypass.
|
|
152
|
+
return textResponse('Unable to verify registration eligibility', 503);
|
|
153
|
+
}
|
|
148
154
|
try {
|
|
149
155
|
const existenceResponse = await env.USER_WORKER.fetch(
|
|
150
156
|
`https://worker/${encodeURIComponent(requestedUserId)}`,
|
|
@@ -158,8 +164,8 @@ export const onRequest = async ({ request, env }: UserProxyContext): Promise<Res
|
|
|
158
164
|
|
|
159
165
|
if (existenceResponse.status === 404) {
|
|
160
166
|
// User does not exist yet โ this is a registration PUT.
|
|
161
|
-
// Enforce the email allowlist.
|
|
162
|
-
if (!isEmailAllowed(identity.email ?? '',
|
|
167
|
+
// Enforce the email allowlist (isEmailAllowed returns false for empty list).
|
|
168
|
+
if (!isEmailAllowed(identity.email ?? '', listResult.list)) {
|
|
163
169
|
return textResponse('Registration is not permitted for this email address', 403);
|
|
164
170
|
}
|
|
165
171
|
} else if (!existenceResponse.ok) {
|
|
@@ -169,7 +175,7 @@ export const onRequest = async ({ request, env }: UserProxyContext): Promise<Res
|
|
|
169
175
|
}
|
|
170
176
|
// If user already exists (200), proceed normally.
|
|
171
177
|
} catch {
|
|
172
|
-
// Fail closed: on network error
|
|
178
|
+
// Fail closed: on network error, reject the request.
|
|
173
179
|
return textResponse('Unable to verify registration eligibility', 502);
|
|
174
180
|
}
|
|
175
181
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@striae-org/striae",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.0",
|
|
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",
|
|
@@ -89,16 +89,15 @@
|
|
|
89
89
|
"deploy-config": "bash ./scripts/deploy-config.sh",
|
|
90
90
|
"update-env": "bash ./scripts/deploy-config.sh --update-env",
|
|
91
91
|
"install-workers": "bash ./scripts/install-workers.sh",
|
|
92
|
-
"deploy-workers": "npm run deploy-workers:audit && npm run deploy-workers:data && npm run deploy-workers:image && npm run deploy-workers:pdf && npm run deploy-workers:user",
|
|
92
|
+
"deploy-workers": "npm run deploy-workers:audit && npm run deploy-workers:data && npm run deploy-workers:image && npm run deploy-workers:lists && npm run deploy-workers:pdf && npm run deploy-workers:user",
|
|
93
93
|
"deploy-workers:secrets": "bash ./scripts/deploy-worker-secrets.sh",
|
|
94
94
|
"deploy-pages:secrets": "bash ./scripts/deploy-pages-secrets.sh",
|
|
95
95
|
"deploy-pages": "bash ./scripts/deploy-pages.sh",
|
|
96
|
-
"deploy-primershear": "bash ./scripts/deploy-primershear-emails.sh",
|
|
97
|
-
"deploy-members": "bash ./scripts/deploy-members-emails.sh",
|
|
98
96
|
"deploy-workers:audit": "cd workers/audit-worker && npm run deploy",
|
|
99
97
|
"deploy-workers:data": "cd workers/data-worker && npm run deploy",
|
|
100
98
|
"deploy-workers:image": "cd workers/image-worker && npm run deploy",
|
|
101
99
|
"deploy-workers:pdf": "cd workers/pdf-worker && npm run deploy",
|
|
100
|
+
"deploy-workers:lists": "cd workers/lists-worker && npm run deploy",
|
|
102
101
|
"deploy-workers:user": "cd workers/user-worker && npm run deploy"
|
|
103
102
|
},
|
|
104
103
|
"dependencies": {
|
|
@@ -112,7 +111,7 @@
|
|
|
112
111
|
"react-router": "^7.14.2"
|
|
113
112
|
},
|
|
114
113
|
"devDependencies": {
|
|
115
|
-
"@cloudflare/vitest-pool-workers": "^0.
|
|
114
|
+
"@cloudflare/vitest-pool-workers": "^0.15.0",
|
|
116
115
|
"@react-router/dev": "^7.14.2",
|
|
117
116
|
"@react-router/fs-routes": "^7.14.2",
|
|
118
117
|
"@types/qrcode": "^1.5.6",
|
|
@@ -130,12 +129,15 @@
|
|
|
130
129
|
"firebase-admin": "^13.8.0",
|
|
131
130
|
"modern-normalize": "^3.0.1",
|
|
132
131
|
"typescript": "^6.0.3",
|
|
133
|
-
"vite": "^8.0.
|
|
132
|
+
"vite": "^8.0.10",
|
|
134
133
|
"vitest": "^4.1.5",
|
|
135
|
-
"wrangler": "^4.
|
|
134
|
+
"wrangler": "^4.85.0"
|
|
136
135
|
},
|
|
137
136
|
"overrides": {
|
|
138
|
-
"@tootallnate/once": "3.0.1"
|
|
137
|
+
"@tootallnate/once": "3.0.1",
|
|
138
|
+
"firebase-admin": {
|
|
139
|
+
"uuid": "^14.0.0"
|
|
140
|
+
}
|
|
139
141
|
},
|
|
140
142
|
"engines": {
|
|
141
143
|
"node": ">=20.19.0"
|
package/scripts/deploy-all.sh
CHANGED
|
@@ -110,7 +110,7 @@ if ! npx wrangler types; then
|
|
|
110
110
|
echo -e "${RED}โ Root wrangler types generation failed!${NC}"
|
|
111
111
|
exit 1
|
|
112
112
|
fi
|
|
113
|
-
for WORKER in audit-worker data-worker image-worker pdf-worker user-worker; do
|
|
113
|
+
for WORKER in audit-worker data-worker image-worker lists-worker pdf-worker user-worker; do
|
|
114
114
|
echo -e "${YELLOW} โ Generating types for ${WORKER}...${NC}"
|
|
115
115
|
if ! (cd "workers/$WORKER" && npx wrangler types); then
|
|
116
116
|
echo -e "${RED}โ wrangler types failed for ${WORKER}!${NC}"
|
|
@@ -123,7 +123,7 @@ echo ""
|
|
|
123
123
|
# Step 4: Deploy Workers
|
|
124
124
|
echo -e "${PURPLE}Step 4/7: Deploying Workers${NC}"
|
|
125
125
|
echo "----------------------------"
|
|
126
|
-
echo -e "${YELLOW}๐ง Deploying all
|
|
126
|
+
echo -e "${YELLOW}๐ง Deploying all 6 Cloudflare Workers...${NC}"
|
|
127
127
|
if ! npm run deploy-workers; then
|
|
128
128
|
echo -e "${RED}โ Worker deployment failed!${NC}"
|
|
129
129
|
exit 1
|
|
@@ -172,7 +172,7 @@ echo ""
|
|
|
172
172
|
echo -e "${BLUE}Deployed Components:${NC}"
|
|
173
173
|
echo " โ
Worker dependencies (npm install)"
|
|
174
174
|
echo " โ
Wrangler types (root + all workers)"
|
|
175
|
-
echo " โ
|
|
175
|
+
echo " โ
6 Cloudflare Workers"
|
|
176
176
|
echo " โ
Worker environment variables"
|
|
177
177
|
echo " โ
Pages environment variables"
|
|
178
178
|
echo " โ
Cloudflare Pages frontend"
|
|
@@ -23,7 +23,7 @@ prompt_for_secrets() {
|
|
|
23
23
|
is_auto_generated_secret_var() {
|
|
24
24
|
local var_name=$1
|
|
25
25
|
case "$var_name" in
|
|
26
|
-
IMAGE_SIGNED_URL_SECRET)
|
|
26
|
+
IMAGE_SIGNED_URL_SECRET|LISTS_ADMIN_SECRET)
|
|
27
27
|
return 0
|
|
28
28
|
;;
|
|
29
29
|
*)
|
|
@@ -39,6 +39,9 @@ prompt_for_secrets() {
|
|
|
39
39
|
IMAGE_SIGNED_URL_SECRET)
|
|
40
40
|
[ "$value" = "your_image_signed_url_secret_here" ]
|
|
41
41
|
;;
|
|
42
|
+
LISTS_ADMIN_SECRET)
|
|
43
|
+
[ "$value" = "your_lists_admin_secret_here" ]
|
|
44
|
+
;;
|
|
42
45
|
*)
|
|
43
46
|
return 1
|
|
44
47
|
;;
|
|
@@ -223,13 +226,44 @@ prompt_for_secrets() {
|
|
|
223
226
|
|
|
224
227
|
echo -e "${BLUE}๐ WORKER NAMES${NC}"
|
|
225
228
|
echo "==============="
|
|
226
|
-
echo -e "${YELLOW}Worker names are lowercased
|
|
229
|
+
echo -e "${YELLOW}Worker names are auto-generated and lowercased. Press Enter to keep the generated value or type a custom name.${NC}"
|
|
230
|
+
|
|
231
|
+
# Auto-generate each worker name if not yet set or still a placeholder.
|
|
232
|
+
_gen_worker_name() {
|
|
233
|
+
local var_name=$1
|
|
234
|
+
local current="${!var_name:-}"
|
|
235
|
+
if is_placeholder "$current" || [ -z "$current" ]; then
|
|
236
|
+
local suffix
|
|
237
|
+
suffix=$(openssl rand -base64 16 2>/dev/null | tr -dc 'a-z0-9' | head -c 10 || true)
|
|
238
|
+
if [ -n "$suffix" ]; then
|
|
239
|
+
printf '%s' "striae-dev-${suffix}"
|
|
240
|
+
fi
|
|
241
|
+
fi
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
_new=$( _gen_worker_name "USER_WORKER_NAME")
|
|
245
|
+
[ -n "$_new" ] && { USER_WORKER_NAME="$_new"; export USER_WORKER_NAME; }
|
|
246
|
+
prompt_for_var "USER_WORKER_NAME" "User worker name (auto-generated; change only if using an existing worker)"
|
|
247
|
+
|
|
248
|
+
_new=$(_gen_worker_name "DATA_WORKER_NAME")
|
|
249
|
+
[ -n "$_new" ] && { DATA_WORKER_NAME="$_new"; export DATA_WORKER_NAME; }
|
|
250
|
+
prompt_for_var "DATA_WORKER_NAME" "Data worker name (auto-generated; change only if using an existing worker)"
|
|
251
|
+
|
|
252
|
+
_new=$(_gen_worker_name "AUDIT_WORKER_NAME")
|
|
253
|
+
[ -n "$_new" ] && { AUDIT_WORKER_NAME="$_new"; export AUDIT_WORKER_NAME; }
|
|
254
|
+
prompt_for_var "AUDIT_WORKER_NAME" "Audit worker name (auto-generated; change only if using an existing worker)"
|
|
255
|
+
|
|
256
|
+
_new=$(_gen_worker_name "IMAGES_WORKER_NAME")
|
|
257
|
+
[ -n "$_new" ] && { IMAGES_WORKER_NAME="$_new"; export IMAGES_WORKER_NAME; }
|
|
258
|
+
prompt_for_var "IMAGES_WORKER_NAME" "Images worker name (auto-generated; change only if using an existing worker)"
|
|
259
|
+
|
|
260
|
+
_new=$(_gen_worker_name "PDF_WORKER_NAME")
|
|
261
|
+
[ -n "$_new" ] && { PDF_WORKER_NAME="$_new"; export PDF_WORKER_NAME; }
|
|
262
|
+
prompt_for_var "PDF_WORKER_NAME" "PDF worker name (auto-generated; change only if using an existing worker)"
|
|
227
263
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
prompt_for_var "
|
|
231
|
-
prompt_for_var "IMAGES_WORKER_NAME" "Images worker name"
|
|
232
|
-
prompt_for_var "PDF_WORKER_NAME" "PDF worker name"
|
|
264
|
+
_new=$(_gen_worker_name "LISTS_WORKER_NAME")
|
|
265
|
+
[ -n "$_new" ] && { LISTS_WORKER_NAME="$_new"; export LISTS_WORKER_NAME; }
|
|
266
|
+
prompt_for_var "LISTS_WORKER_NAME" "Lists worker name (auto-generated; change only if using an existing worker)"
|
|
233
267
|
echo ""
|
|
234
268
|
|
|
235
269
|
echo -e "${BLUE}๐๏ธ STORAGE CONFIGURATION${NC}"
|
|
@@ -238,6 +272,7 @@ prompt_for_secrets() {
|
|
|
238
272
|
prompt_for_var "AUDIT_BUCKET_NAME" "Your R2 bucket name for audit logs (separate from data bucket)"
|
|
239
273
|
prompt_for_var "FILES_BUCKET_NAME" "Your R2 bucket name for encrypted files storage"
|
|
240
274
|
prompt_for_var "KV_STORE_ID" "Your KV namespace ID (UUID format)"
|
|
275
|
+
prompt_for_var "STRIAE_LISTS_KV_ID" "KV namespace ID for the lists-worker (UUID format; backs registration and primershear allowlists)"
|
|
241
276
|
|
|
242
277
|
echo -e "${BLUE}๐ SERVICE-SPECIFIC SECRETS${NC}"
|
|
243
278
|
echo "============================"
|
|
@@ -255,6 +290,7 @@ prompt_for_secrets() {
|
|
|
255
290
|
prompt_for_var "IMAGE_SIGNED_URL_BASE_URL" "Signed URL delivery base URL โ routes signed image delivery through the Pages proxy (leave as-is unless using a non-standard domain)"
|
|
256
291
|
|
|
257
292
|
prompt_for_var "BROWSER_API_TOKEN" "Cloudflare Browser Rendering API token (for PDF Worker)"
|
|
293
|
+
prompt_for_var "LISTS_ADMIN_SECRET" "Lists worker admin secret โ guards write endpoints (auto-generated; guards POST/DELETE on the lists-worker)"
|
|
258
294
|
|
|
259
295
|
configure_manifest_signing_credentials
|
|
260
296
|
configure_export_encryption_credentials
|
|
@@ -106,6 +106,14 @@ copy_example_configs() {
|
|
|
106
106
|
echo -e "${YELLOW} โ ๏ธ pdf-worker: wrangler.jsonc already exists, skipping copy${NC}"
|
|
107
107
|
fi
|
|
108
108
|
|
|
109
|
+
cd ../lists-worker
|
|
110
|
+
if [ -f "wrangler.jsonc.example" ] && { [ "$update_env" = "true" ] || [ ! -f "wrangler.jsonc" ]; }; then
|
|
111
|
+
cp wrangler.jsonc.example wrangler.jsonc
|
|
112
|
+
echo -e "${GREEN} โ
lists-worker: wrangler.jsonc created from example${NC}"
|
|
113
|
+
elif [ -f "wrangler.jsonc" ]; then
|
|
114
|
+
echo -e "${YELLOW} โ ๏ธ lists-worker: wrangler.jsonc already exists, skipping copy${NC}"
|
|
115
|
+
fi
|
|
116
|
+
|
|
109
117
|
# Return to project root
|
|
110
118
|
cd ../..
|
|
111
119
|
|
|
@@ -172,6 +180,16 @@ update_wrangler_configs() {
|
|
|
172
180
|
echo -e "${GREEN} โ
pdf-worker configuration updated${NC}"
|
|
173
181
|
fi
|
|
174
182
|
|
|
183
|
+
if [ -f "workers/lists-worker/wrangler.jsonc" ]; then
|
|
184
|
+
echo -e "${YELLOW} Updating lists-worker/wrangler.jsonc...${NC}"
|
|
185
|
+
local escaped_striae_lists_kv_id
|
|
186
|
+
escaped_striae_lists_kv_id=$(escape_for_sed_replacement "$STRIAE_LISTS_KV_ID")
|
|
187
|
+
sed -i "s/\"LISTS_WORKER_NAME\"/\"$LISTS_WORKER_NAME\"/g" workers/lists-worker/wrangler.jsonc
|
|
188
|
+
sed -i "s/\"ACCOUNT_ID\"/\"$escaped_account_id\"/g" workers/lists-worker/wrangler.jsonc
|
|
189
|
+
sed -i "s/\"STRIAE_LISTS_KV_ID\"/\"$escaped_striae_lists_kv_id\"/g" workers/lists-worker/wrangler.jsonc
|
|
190
|
+
echo -e "${GREEN} โ
lists-worker configuration updated${NC}"
|
|
191
|
+
fi
|
|
192
|
+
|
|
175
193
|
if [ -f "workers/user-worker/wrangler.jsonc" ]; then
|
|
176
194
|
echo -e "${YELLOW} Updating user-worker/wrangler.jsonc...${NC}"
|
|
177
195
|
sed -i "s/\"USER_WORKER_NAME\"/\"$USER_WORKER_NAME\"/g" workers/user-worker/wrangler.jsonc
|
|
@@ -190,6 +208,7 @@ update_wrangler_configs() {
|
|
|
190
208
|
sed -i "s/AUDIT_WORKER_NAME/$AUDIT_WORKER_NAME/g" wrangler.toml
|
|
191
209
|
sed -i "s/IMAGES_WORKER_NAME/$IMAGES_WORKER_NAME/g" wrangler.toml
|
|
192
210
|
sed -i "s/PDF_WORKER_NAME/$PDF_WORKER_NAME/g" wrangler.toml
|
|
211
|
+
sed -i "s/LISTS_WORKER_NAME/$LISTS_WORKER_NAME/g" wrangler.toml
|
|
193
212
|
echo -e "${GREEN} โ
main wrangler.toml configuration updated${NC}"
|
|
194
213
|
fi
|
|
195
214
|
|
|
@@ -96,12 +96,14 @@ required_vars=(
|
|
|
96
96
|
"AUDIT_WORKER_NAME"
|
|
97
97
|
"IMAGES_WORKER_NAME"
|
|
98
98
|
"PDF_WORKER_NAME"
|
|
99
|
+
"LISTS_WORKER_NAME"
|
|
99
100
|
|
|
100
101
|
# Storage Configuration (required for config replacement)
|
|
101
102
|
"DATA_BUCKET_NAME"
|
|
102
103
|
"AUDIT_BUCKET_NAME"
|
|
103
104
|
"FILES_BUCKET_NAME"
|
|
104
105
|
"KV_STORE_ID"
|
|
106
|
+
"STRIAE_LISTS_KV_ID"
|
|
105
107
|
|
|
106
108
|
# Worker-Specific Secrets (required for deployment)
|
|
107
109
|
"IMAGE_SIGNED_URL_SECRET"
|
|
@@ -112,6 +114,7 @@ required_vars=(
|
|
|
112
114
|
"EXPORT_ENCRYPTION_PRIVATE_KEY"
|
|
113
115
|
"EXPORT_ENCRYPTION_KEY_ID"
|
|
114
116
|
"EXPORT_ENCRYPTION_PUBLIC_KEY"
|
|
117
|
+
"LISTS_ADMIN_SECRET"
|
|
115
118
|
)
|
|
116
119
|
|
|
117
120
|
validate_required_vars() {
|
package/scripts/deploy-config.sh
CHANGED
|
@@ -200,36 +200,6 @@ source "$DEPLOY_CONFIG_VALIDATION_MODULE"
|
|
|
200
200
|
source "$DEPLOY_CONFIG_SCAFFOLDING_MODULE"
|
|
201
201
|
source "$DEPLOY_CONFIG_PROMPT_MODULE"
|
|
202
202
|
|
|
203
|
-
EMAIL_LIST_CONFIG_DIR="app/config"
|
|
204
|
-
MEMBERS_EMAILS_FILE="$EMAIL_LIST_CONFIG_DIR/members.emails"
|
|
205
|
-
PRIMERSHEAR_EMAILS_FILE="$EMAIL_LIST_CONFIG_DIR/primershear.emails"
|
|
206
|
-
|
|
207
|
-
sync_env_var_from_email_list_file() {
|
|
208
|
-
local env_var_name=$1
|
|
209
|
-
local file_path=$2
|
|
210
|
-
local loaded_values=""
|
|
211
|
-
|
|
212
|
-
if [ ! -f "$file_path" ]; then
|
|
213
|
-
echo -e "${YELLOW}โ ๏ธ $file_path not found; keeping existing $env_var_name value in .env${NC}"
|
|
214
|
-
return 0
|
|
215
|
-
fi
|
|
216
|
-
|
|
217
|
-
loaded_values=$(grep -v '^[[:space:]]*#' "$file_path" | grep -v '^[[:space:]]*$' | sed -e 's/\r$//' -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | paste -sd ',' - || true)
|
|
218
|
-
|
|
219
|
-
write_env_var "$env_var_name" "$loaded_values"
|
|
220
|
-
export "$env_var_name=$loaded_values"
|
|
221
|
-
|
|
222
|
-
local loaded_count
|
|
223
|
-
loaded_count=$(echo "$loaded_values" | tr ',' '\n' | grep -c '[^[:space:]]' || true)
|
|
224
|
-
echo -e "${GREEN}โ
Synced $env_var_name from $file_path ($loaded_count entry/entries)${NC}"
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
sync_email_list_env_vars_from_config() {
|
|
228
|
-
echo -e "${YELLOW}๐ง Syncing optional email list env vars from app/config...${NC}"
|
|
229
|
-
sync_env_var_from_email_list_file "REGISTRATION_EMAILS" "$MEMBERS_EMAILS_FILE"
|
|
230
|
-
sync_env_var_from_email_list_file "PRIMERSHEAR_EMAILS" "$PRIMERSHEAR_EMAILS_FILE"
|
|
231
|
-
}
|
|
232
|
-
|
|
233
203
|
if [ "$validate_only" = "true" ]; then
|
|
234
204
|
echo -e "\n${BLUE}๐งช Validate-only mode enabled${NC}"
|
|
235
205
|
run_validation_checkpoint
|
|
@@ -247,9 +217,6 @@ load_admin_service_credentials
|
|
|
247
217
|
# Always prompt for secrets to ensure configuration
|
|
248
218
|
prompt_for_secrets
|
|
249
219
|
|
|
250
|
-
# Keep optional email list env vars aligned with app/config source files.
|
|
251
|
-
sync_email_list_env_vars_from_config
|
|
252
|
-
|
|
253
220
|
# Validate after secrets have been configured
|
|
254
221
|
validate_required_vars
|
|
255
222
|
|
|
@@ -117,16 +117,6 @@ deploy_pages_secrets() {
|
|
|
117
117
|
printf '%s' "$secret_value" | wrangler pages secret put "$secret" --project-name "$PAGES_PROJECT_NAME"
|
|
118
118
|
done
|
|
119
119
|
|
|
120
|
-
local optional_primershear_emails
|
|
121
|
-
optional_primershear_emails=$(get_optional_value "PRIMERSHEAR_EMAILS")
|
|
122
|
-
echo -e "${YELLOW} Setting PRIMERSHEAR_EMAILS...${NC}"
|
|
123
|
-
printf '%s' "$optional_primershear_emails" | wrangler pages secret put "PRIMERSHEAR_EMAILS" --project-name "$PAGES_PROJECT_NAME"
|
|
124
|
-
|
|
125
|
-
local optional_registration_emails
|
|
126
|
-
optional_registration_emails=$(get_optional_value "REGISTRATION_EMAILS")
|
|
127
|
-
echo -e "${YELLOW} Setting REGISTRATION_EMAILS...${NC}"
|
|
128
|
-
printf '%s' "$optional_registration_emails" | wrangler pages secret put "REGISTRATION_EMAILS" --project-name "$PAGES_PROJECT_NAME"
|
|
129
|
-
|
|
130
120
|
echo -e "${GREEN}โ
Pages secrets deployed to production${NC}"
|
|
131
121
|
}
|
|
132
122
|
|
|
@@ -152,6 +142,7 @@ fi
|
|
|
152
142
|
|
|
153
143
|
required_pages_secrets=(
|
|
154
144
|
"PROJECT_ID"
|
|
145
|
+
"LISTS_ADMIN_SECRET"
|
|
155
146
|
)
|
|
156
147
|
|
|
157
148
|
echo -e "${YELLOW}๐ Validating required Pages secret values...${NC}"
|
|
@@ -267,6 +267,13 @@ build_data_worker_secret_list() {
|
|
|
267
267
|
printf '%s\n' "${secrets[@]}"
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
build_lists_worker_secret_list() {
|
|
271
|
+
local secrets=(
|
|
272
|
+
"LISTS_ADMIN_SECRET"
|
|
273
|
+
)
|
|
274
|
+
printf '%s\n' "${secrets[@]}"
|
|
275
|
+
}
|
|
276
|
+
|
|
270
277
|
build_images_worker_secret_list() {
|
|
271
278
|
local secrets=(
|
|
272
279
|
"DATA_AT_REST_ENCRYPTION_PUBLIC_KEY"
|
|
@@ -299,7 +306,7 @@ echo -e "\n${BLUE}๐ Deploying secrets to workers...${NC}"
|
|
|
299
306
|
# Check if workers are configured
|
|
300
307
|
echo -e "${YELLOW}๐ Checking worker configurations...${NC}"
|
|
301
308
|
workers_configured=0
|
|
302
|
-
total_workers=
|
|
309
|
+
total_workers=6
|
|
303
310
|
|
|
304
311
|
for worker_dir in workers/*/; do
|
|
305
312
|
if [ -f "$worker_dir/wrangler.jsonc" ] || [ -f "$worker_dir/wrangler.toml" ]; then
|
|
@@ -363,6 +370,16 @@ if ! set_worker_secrets "PDF Worker" "workers/pdf-worker" \
|
|
|
363
370
|
echo -e "${YELLOW}โ ๏ธ Skipping PDF Worker (not configured)${NC}"
|
|
364
371
|
fi
|
|
365
372
|
|
|
373
|
+
# Lists Worker
|
|
374
|
+
lists_worker_secrets=()
|
|
375
|
+
while IFS= read -r secret; do
|
|
376
|
+
lists_worker_secrets+=("$secret")
|
|
377
|
+
done < <(build_lists_worker_secret_list)
|
|
378
|
+
|
|
379
|
+
if ! set_worker_secrets "Lists Worker" "workers/lists-worker" "${lists_worker_secrets[@]}"; then
|
|
380
|
+
echo -e "${YELLOW}โ ๏ธ Skipping Lists Worker (not configured)${NC}"
|
|
381
|
+
fi
|
|
382
|
+
|
|
366
383
|
echo -e "\n${GREEN}๐ Worker secrets deployment completed!${NC}"
|
|
367
384
|
|
|
368
385
|
echo -e "\n${YELLOW}โ ๏ธ WORKER CONFIGURATION REMINDERS:${NC}"
|
|
@@ -371,6 +388,7 @@ echo " - Configure KV namespace ID in workers/user-worker/wrangler.jsonc"
|
|
|
371
388
|
echo " - Configure R2 bucket name in workers/data-worker/wrangler.jsonc"
|
|
372
389
|
echo " - Configure R2 bucket name in workers/audit-worker/wrangler.jsonc"
|
|
373
390
|
echo " - Configure R2 bucket name in workers/image-worker/wrangler.jsonc"
|
|
391
|
+
echo " - Configure KV namespace ID in workers/lists-worker/wrangler.jsonc"
|
|
374
392
|
echo " - Update ACCOUNT_ID and custom domains in all worker configurations"
|
|
375
393
|
|
|
376
394
|
echo -e "\n${BLUE}๐ For manual deployment, use these commands:${NC}"
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
# 1. audit-worker
|
|
8
8
|
# 2. data-worker
|
|
9
9
|
# 3. image-worker
|
|
10
|
-
# 4.
|
|
11
|
-
# 5.
|
|
10
|
+
# 4. lists-worker
|
|
11
|
+
# 5. pdf-worker
|
|
12
|
+
# 6. user-worker
|
|
12
13
|
|
|
13
14
|
# Colors for output
|
|
14
15
|
RED='\033[0;31m'
|
|
@@ -34,7 +35,7 @@ if [ ! -d "$WORKERS_DIR" ]; then
|
|
|
34
35
|
fi
|
|
35
36
|
|
|
36
37
|
# List of workers
|
|
37
|
-
WORKERS=("audit-worker" "data-worker" "image-worker" "pdf-worker" "user-worker")
|
|
38
|
+
WORKERS=("audit-worker" "data-worker" "image-worker" "lists-worker" "pdf-worker" "user-worker")
|
|
38
39
|
|
|
39
40
|
echo -e "${PURPLE}Installing npm dependencies for all workers...${NC}"
|
|
40
41
|
echo ""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "audit-worker",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
6
|
"deploy": "wrangler deploy",
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
"start": "wrangler dev"
|
|
9
9
|
},
|
|
10
10
|
"devDependencies": {
|
|
11
|
-
"wrangler": "^4.
|
|
11
|
+
"wrangler": "^4.85.0"
|
|
12
12
|
}
|
|
13
13
|
}
|