@striae-org/striae 7.0.1 → 7.1.1
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/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 +14 -11
- package/scripts/delete-account.mjs +350 -0
- 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/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/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
|
@@ -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.1",
|
|
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
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "data-worker",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
6
|
"deploy": "wrangler deploy",
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
},
|
|
10
10
|
"devDependencies": {
|
|
11
11
|
"@cloudflare/vitest-pool-workers": "^0.14.9",
|
|
12
|
-
"wrangler": "^4.
|
|
12
|
+
"wrangler": "^4.85.0"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "image-worker",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.1",
|
|
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
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { Env } from './types';
|
|
2
|
+
|
|
3
|
+
const JSON_HEADERS: HeadersInit = {
|
|
4
|
+
'Content-Type': 'application/json',
|
|
5
|
+
'Cache-Control': 'no-store',
|
|
6
|
+
'Pragma': 'no-cache',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/** Routes map URL path segment to the KV key used in STRIAE_LISTS. */
|
|
10
|
+
const ROUTE_TO_KV_KEY: Record<string, string> = {
|
|
11
|
+
members: 'allow',
|
|
12
|
+
primershear: 'primershear',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function jsonResponse(data: Record<string, unknown>, status = 200): Response {
|
|
16
|
+
return new Response(JSON.stringify(data), { status, headers: JSON_HEADERS });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Constant-time string comparison to mitigate timing side-channels on auth checks.
|
|
21
|
+
* Both strings are encoded to bytes and compared with a full XOR pass.
|
|
22
|
+
*/
|
|
23
|
+
function timingSafeEqual(a: string, b: string): boolean {
|
|
24
|
+
const encoder = new TextEncoder();
|
|
25
|
+
const aBytes = encoder.encode(a);
|
|
26
|
+
const bBytes = encoder.encode(b);
|
|
27
|
+
if (aBytes.length !== bBytes.length) return false;
|
|
28
|
+
let diff = 0;
|
|
29
|
+
for (let i = 0; i < aBytes.length; i++) {
|
|
30
|
+
diff |= aBytes[i] ^ bBytes[i];
|
|
31
|
+
}
|
|
32
|
+
return diff === 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isAuthorized(request: Request, secret: string): boolean {
|
|
36
|
+
if (!secret) return false;
|
|
37
|
+
const auth = request.headers.get('Authorization');
|
|
38
|
+
if (!auth || !auth.startsWith('Bearer ')) return false;
|
|
39
|
+
return timingSafeEqual(auth.slice(7), secret);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default {
|
|
43
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
44
|
+
const url = new URL(request.url);
|
|
45
|
+
const segment = url.pathname.replace(/^\/+|\/+$/g, '');
|
|
46
|
+
const kvKey = ROUTE_TO_KV_KEY[segment];
|
|
47
|
+
|
|
48
|
+
if (!kvKey) {
|
|
49
|
+
return jsonResponse({ error: 'Not found' }, 404);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (request.method === 'GET') {
|
|
53
|
+
if (!isAuthorized(request, env.LISTS_ADMIN_SECRET)) {
|
|
54
|
+
return jsonResponse({ error: 'Unauthorized' }, 401);
|
|
55
|
+
}
|
|
56
|
+
const raw = (await env.STRIAE_LISTS.get(kvKey)) ?? '';
|
|
57
|
+
const list = raw ? raw.split(',').map(e => e.trim().toLowerCase()).filter(Boolean).join(',') : '';
|
|
58
|
+
return jsonResponse({ list });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (request.method === 'POST' || request.method === 'DELETE') {
|
|
62
|
+
if (!isAuthorized(request, env.LISTS_ADMIN_SECRET)) {
|
|
63
|
+
return jsonResponse({ error: 'Unauthorized' }, 401);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let body: { entry?: unknown };
|
|
67
|
+
try {
|
|
68
|
+
body = await request.json() as { entry?: unknown };
|
|
69
|
+
} catch {
|
|
70
|
+
return jsonResponse({ error: 'Invalid JSON body' }, 400);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const entry = typeof body.entry === 'string' ? body.entry.trim().toLowerCase() : '';
|
|
74
|
+
if (!entry) {
|
|
75
|
+
return jsonResponse({ error: 'Missing or empty entry' }, 400);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const current = (await env.STRIAE_LISTS.get(kvKey)) ?? '';
|
|
79
|
+
const entries = current ? current.split(',').map(e => e.trim().toLowerCase()).filter(Boolean) : [];
|
|
80
|
+
|
|
81
|
+
if (request.method === 'POST') {
|
|
82
|
+
if (!entries.includes(entry)) {
|
|
83
|
+
entries.push(entry);
|
|
84
|
+
}
|
|
85
|
+
await env.STRIAE_LISTS.put(kvKey, entries.join(','));
|
|
86
|
+
return jsonResponse({ ok: true });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// DELETE
|
|
90
|
+
const filtered = entries.filter(e => e !== entry);
|
|
91
|
+
await env.STRIAE_LISTS.put(kvKey, filtered.join(','));
|
|
92
|
+
return jsonResponse({ ok: true });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return jsonResponse({ error: 'Method not allowed' }, 405);
|
|
96
|
+
},
|
|
97
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "LISTS_WORKER_NAME",
|
|
3
|
+
"account_id": "ACCOUNT_ID",
|
|
4
|
+
"main": "src/lists-worker.ts",
|
|
5
|
+
"workers_dev": false,
|
|
6
|
+
"compatibility_date": "2026-04-27",
|
|
7
|
+
"compatibility_flags": [
|
|
8
|
+
"nodejs_compat"
|
|
9
|
+
],
|
|
10
|
+
|
|
11
|
+
"observability": {
|
|
12
|
+
"enabled": true
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
"kv_namespaces": [
|
|
16
|
+
{
|
|
17
|
+
"binding": "STRIAE_LISTS",
|
|
18
|
+
"id": "STRIAE_LISTS_KV_ID"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
|
|
22
|
+
"placement": { "mode": "smart" }
|
|
23
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pdf-worker",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
6
|
"generate:assets": "node scripts/generate-assets.js",
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
"start": "wrangler dev"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"wrangler": "^4.
|
|
12
|
+
"wrangler": "^4.85.0"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "user-worker",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.1",
|
|
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
|
}
|
package/wrangler.toml.example
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#:schema node_modules/wrangler/config-schema.json
|
|
2
2
|
name = "PAGES_PROJECT_NAME"
|
|
3
|
-
compatibility_date = "2026-04-
|
|
3
|
+
compatibility_date = "2026-04-27"
|
|
4
4
|
compatibility_flags = ["nodejs_compat"]
|
|
5
5
|
pages_build_output_dir = "./build/client"
|
|
6
6
|
|
|
@@ -25,4 +25,8 @@ service = "IMAGES_WORKER_NAME"
|
|
|
25
25
|
|
|
26
26
|
[[services]]
|
|
27
27
|
binding = "PDF_WORKER"
|
|
28
|
-
service = "PDF_WORKER_NAME"
|
|
28
|
+
service = "PDF_WORKER_NAME"
|
|
29
|
+
|
|
30
|
+
[[services]]
|
|
31
|
+
binding = "LISTS_WORKER"
|
|
32
|
+
service = "LISTS_WORKER_NAME"
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# Registration gateway - authorized email addresses
|
|
2
|
-
# One entry per line. Lines starting with # are ignored.
|
|
3
|
-
# This file is untracked. Run: npm run deploy-members to push changes.
|
|
4
|
-
#
|
|
5
|
-
# Supported formats:
|
|
6
|
-
# Exact email: analyst@organization.com
|
|
7
|
-
# Domain wildcard: @organization.com (allows all emails from that domain)
|
|
8
|
-
#
|
|
9
|
-
# Examples:
|
|
10
|
-
# analyst@organization.com
|
|
11
|
-
# @striae.org
|