@striae-org/striae 5.0.0 → 5.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.
Files changed (33) hide show
  1. package/.env.example +7 -3
  2. package/app/components/actions/case-export/download-handlers.ts +23 -7
  3. package/app/components/actions/case-manage.ts +24 -9
  4. package/app/components/actions/generate-pdf.ts +52 -4
  5. package/app/components/actions/image-manage.ts +48 -48
  6. package/app/routes/striae/hooks/use-striae-reset-helpers.ts +4 -0
  7. package/app/routes/striae/striae.tsx +16 -4
  8. package/app/types/file.ts +18 -2
  9. package/app/utils/api/image-api-client.ts +49 -1
  10. package/app/utils/data/operations/case-operations.ts +13 -1
  11. package/app/utils/data/operations/confirmation-summary-operations.ts +38 -1
  12. package/app/utils/data/operations/file-annotation-operations.ts +13 -1
  13. package/functions/api/image/[[path]].ts +2 -1
  14. package/package.json +2 -2
  15. package/scripts/deploy-config.sh +191 -20
  16. package/scripts/deploy-pages-secrets.sh +0 -6
  17. package/scripts/deploy-worker-secrets.sh +67 -6
  18. package/worker-configuration.d.ts +15 -7
  19. package/workers/audit-worker/package.json +1 -4
  20. package/workers/audit-worker/src/audit-worker.example.ts +522 -61
  21. package/workers/audit-worker/wrangler.jsonc.example +5 -0
  22. package/workers/data-worker/package.json +1 -4
  23. package/workers/data-worker/src/data-worker.example.ts +280 -2
  24. package/workers/data-worker/src/encryption-utils.ts +145 -1
  25. package/workers/data-worker/wrangler.jsonc.example +3 -1
  26. package/workers/image-worker/package.json +1 -4
  27. package/workers/image-worker/src/encryption-utils.ts +217 -0
  28. package/workers/image-worker/src/image-worker.example.ts +449 -129
  29. package/workers/image-worker/worker-configuration.d.ts +3 -2
  30. package/workers/image-worker/wrangler.jsonc.example +7 -0
  31. package/workers/keys-worker/package.json +1 -4
  32. package/workers/pdf-worker/package.json +1 -4
  33. package/workers/user-worker/package.json +1 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@striae-org/striae",
3
- "version": "5.0.0",
3
+ "version": "5.1.1",
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",
@@ -106,7 +106,7 @@
106
106
  "deploy-workers:image": "cd workers/image-worker && npm run deploy",
107
107
  "deploy-workers:keys": "cd workers/keys-worker && npm run deploy",
108
108
  "deploy-workers:pdf": "cd workers/pdf-worker && npm run deploy",
109
- "deploy-workers:user": "cd workers/user-worker && npm run deploy"
109
+ "deploy-workers:user": "cd workers/user-worker && npm run deploy"
110
110
  },
111
111
  "dependencies": {
112
112
  "@react-router/cloudflare": "^7.13.2",
@@ -81,13 +81,23 @@ require_command grep
81
81
 
82
82
  is_placeholder() {
83
83
  local value="$1"
84
- local normalized=$(echo "$value" | tr '[:upper:]' '[:lower:]')
84
+ local normalized
85
+
86
+ normalized=$(printf '%s' "$value" | tr -d '\r' | tr '[:upper:]' '[:lower:]')
87
+ normalized=$(printf '%s' "$normalized" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
88
+ normalized=${normalized#\"}
89
+ normalized=${normalized%\"}
85
90
 
86
91
  if [ -z "$normalized" ]; then
87
92
  return 1
88
93
  fi
89
94
 
90
- [[ "$normalized" == your_*_here ]]
95
+ [[ "$normalized" =~ ^your_[a-z0-9_]+_here$ || \
96
+ "$normalized" =~ ^your-[a-z0-9-]+-here$ || \
97
+ "$normalized" == "placeholder" || \
98
+ "$normalized" == "changeme" || \
99
+ "$normalized" == "replace_me" || \
100
+ "$normalized" == "replace-me" ]]
91
101
  }
92
102
 
93
103
  # Check if .env file exists
@@ -334,7 +344,7 @@ write_env_var() {
334
344
  var_value=$(strip_carriage_returns "$var_value")
335
345
  env_file_value="$var_value"
336
346
 
337
- if [ "$var_name" = "FIREBASE_SERVICE_ACCOUNT_PRIVATE_KEY" ] || [ "$var_name" = "MANIFEST_SIGNING_PRIVATE_KEY" ] || [ "$var_name" = "MANIFEST_SIGNING_PUBLIC_KEY" ] || [ "$var_name" = "EXPORT_ENCRYPTION_PRIVATE_KEY" ] || [ "$var_name" = "EXPORT_ENCRYPTION_PUBLIC_KEY" ]; then
347
+ if [ "$var_name" = "FIREBASE_SERVICE_ACCOUNT_PRIVATE_KEY" ] || [ "$var_name" = "MANIFEST_SIGNING_PRIVATE_KEY" ] || [ "$var_name" = "MANIFEST_SIGNING_PUBLIC_KEY" ] || [ "$var_name" = "EXPORT_ENCRYPTION_PRIVATE_KEY" ] || [ "$var_name" = "EXPORT_ENCRYPTION_PUBLIC_KEY" ] || [ "$var_name" = "DATA_AT_REST_ENCRYPTION_PRIVATE_KEY" ] || [ "$var_name" = "DATA_AT_REST_ENCRYPTION_PUBLIC_KEY" ]; then
338
348
  # Store as a quoted string so sourced .env preserves escaped newline markers (\n)
339
349
  env_file_value=${env_file_value//\"/\\\"}
340
350
  env_file_value="\"$env_file_value\""
@@ -590,6 +600,117 @@ configure_export_encryption_credentials() {
590
600
  echo ""
591
601
  }
592
602
 
603
+ generate_data_at_rest_encryption_key_pair() {
604
+ local private_key_file
605
+ local public_key_file
606
+ private_key_file=$(mktemp)
607
+ public_key_file=$(mktemp)
608
+
609
+ if ! node -e "const { generateKeyPairSync } = require('crypto'); const fs = require('fs'); const pair = generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } }); fs.writeFileSync(process.argv[1], pair.privateKey, 'utf8'); fs.writeFileSync(process.argv[2], pair.publicKey, 'utf8');" "$private_key_file" "$public_key_file"; then
610
+ rm -f "$private_key_file" "$public_key_file"
611
+ return 1
612
+ fi
613
+
614
+ local private_key_pem
615
+ local public_key_pem
616
+ private_key_pem=$(cat "$private_key_file")
617
+ public_key_pem=$(cat "$public_key_file")
618
+ rm -f "$private_key_file" "$public_key_file"
619
+
620
+ private_key_pem="${private_key_pem//$'\r'/}"
621
+ public_key_pem="${public_key_pem//$'\r'/}"
622
+
623
+ DATA_AT_REST_ENCRYPTION_PRIVATE_KEY="${private_key_pem//$'\n'/\\n}"
624
+ DATA_AT_REST_ENCRYPTION_PUBLIC_KEY="${public_key_pem//$'\n'/\\n}"
625
+
626
+ export DATA_AT_REST_ENCRYPTION_PRIVATE_KEY
627
+ export DATA_AT_REST_ENCRYPTION_PUBLIC_KEY
628
+
629
+ write_env_var "DATA_AT_REST_ENCRYPTION_PRIVATE_KEY" "$DATA_AT_REST_ENCRYPTION_PRIVATE_KEY"
630
+ write_env_var "DATA_AT_REST_ENCRYPTION_PUBLIC_KEY" "$DATA_AT_REST_ENCRYPTION_PUBLIC_KEY"
631
+
632
+ return 0
633
+ }
634
+
635
+ configure_data_at_rest_encryption_credentials() {
636
+ echo -e "${BLUE}🗃️ DATA-AT-REST ENCRYPTION CONFIGURATION${NC}"
637
+ echo "========================================"
638
+
639
+ # Data-at-rest encryption is mandatory for all environments.
640
+ DATA_AT_REST_ENCRYPTION_ENABLED="true"
641
+
642
+ export DATA_AT_REST_ENCRYPTION_ENABLED
643
+ write_env_var "DATA_AT_REST_ENCRYPTION_ENABLED" "$DATA_AT_REST_ENCRYPTION_ENABLED"
644
+ echo -e "${GREEN}✅ DATA_AT_REST_ENCRYPTION_ENABLED: $DATA_AT_REST_ENCRYPTION_ENABLED${NC}"
645
+
646
+ local should_generate="false"
647
+ local regenerate_choice=""
648
+
649
+ if [ "$update_env" = "true" ]; then
650
+ should_generate="true"
651
+ elif [ -z "$DATA_AT_REST_ENCRYPTION_PRIVATE_KEY" ] || is_placeholder "$DATA_AT_REST_ENCRYPTION_PRIVATE_KEY" || [ -z "$DATA_AT_REST_ENCRYPTION_PUBLIC_KEY" ] || is_placeholder "$DATA_AT_REST_ENCRYPTION_PUBLIC_KEY"; then
652
+ should_generate="true"
653
+ else
654
+ echo -e "${GREEN}Current data-at-rest encryption key pair: [HIDDEN]${NC}"
655
+ read -p "Generate new data-at-rest encryption key pair? (press Enter to keep current, or type 'y' to regenerate): " regenerate_choice
656
+ regenerate_choice=$(strip_carriage_returns "$regenerate_choice")
657
+ if [ "$regenerate_choice" = "y" ] || [ "$regenerate_choice" = "Y" ]; then
658
+ should_generate="true"
659
+ fi
660
+ fi
661
+
662
+ if [ "$should_generate" = "true" ]; then
663
+ echo -e "${YELLOW}Generating data-at-rest encryption RSA key pair...${NC}"
664
+ if generate_data_at_rest_encryption_key_pair; then
665
+ echo -e "${GREEN}✅ Data-at-rest encryption key pair generated${NC}"
666
+ else
667
+ echo -e "${RED}❌ Error: Failed to generate data-at-rest encryption key pair${NC}"
668
+ exit 1
669
+ fi
670
+ else
671
+ echo -e "${GREEN}✅ Keeping current data-at-rest encryption key pair${NC}"
672
+ fi
673
+
674
+ if [ -z "$DATA_AT_REST_ENCRYPTION_KEY_ID" ] || is_placeholder "$DATA_AT_REST_ENCRYPTION_KEY_ID" || [ "$should_generate" = "true" ]; then
675
+ local generated_key_id
676
+ generated_key_id=$(generate_worker_subdomain_label)
677
+ if [ -z "$generated_key_id" ] || [ ${#generated_key_id} -ne 10 ]; then
678
+ echo -e "${RED}❌ Error: Failed to generate DATA_AT_REST_ENCRYPTION_KEY_ID${NC}"
679
+ exit 1
680
+ fi
681
+ DATA_AT_REST_ENCRYPTION_KEY_ID="$generated_key_id"
682
+ export DATA_AT_REST_ENCRYPTION_KEY_ID
683
+ write_env_var "DATA_AT_REST_ENCRYPTION_KEY_ID" "$DATA_AT_REST_ENCRYPTION_KEY_ID"
684
+ echo -e "${GREEN}✅ DATA_AT_REST_ENCRYPTION_KEY_ID generated: $DATA_AT_REST_ENCRYPTION_KEY_ID${NC}"
685
+ else
686
+ echo -e "${GREEN}✅ DATA_AT_REST_ENCRYPTION_KEY_ID: $DATA_AT_REST_ENCRYPTION_KEY_ID${NC}"
687
+ fi
688
+
689
+ echo ""
690
+ }
691
+
692
+ validate_data_at_rest_encryption_settings() {
693
+ local enabled_normalized
694
+ enabled_normalized=$(printf '%s' "${DATA_AT_REST_ENCRYPTION_ENABLED:-false}" | tr '[:upper:]' '[:lower:]')
695
+
696
+ if [ "$enabled_normalized" = "1" ] || [ "$enabled_normalized" = "true" ] || [ "$enabled_normalized" = "yes" ] || [ "$enabled_normalized" = "on" ]; then
697
+ if [ -z "$DATA_AT_REST_ENCRYPTION_PRIVATE_KEY" ] || is_placeholder "$DATA_AT_REST_ENCRYPTION_PRIVATE_KEY"; then
698
+ echo -e "${RED}❌ Error: DATA_AT_REST_ENCRYPTION_PRIVATE_KEY is required when DATA_AT_REST_ENCRYPTION_ENABLED is true${NC}"
699
+ exit 1
700
+ fi
701
+
702
+ if [ -z "$DATA_AT_REST_ENCRYPTION_PUBLIC_KEY" ] || is_placeholder "$DATA_AT_REST_ENCRYPTION_PUBLIC_KEY"; then
703
+ echo -e "${RED}❌ Error: DATA_AT_REST_ENCRYPTION_PUBLIC_KEY is required when DATA_AT_REST_ENCRYPTION_ENABLED is true${NC}"
704
+ exit 1
705
+ fi
706
+
707
+ if [ -z "$DATA_AT_REST_ENCRYPTION_KEY_ID" ] || is_placeholder "$DATA_AT_REST_ENCRYPTION_KEY_ID"; then
708
+ echo -e "${RED}❌ Error: DATA_AT_REST_ENCRYPTION_KEY_ID is required when DATA_AT_REST_ENCRYPTION_ENABLED is true${NC}"
709
+ exit 1
710
+ fi
711
+ fi
712
+ }
713
+
593
714
  # Validate required variables
594
715
  required_vars=(
595
716
  # Core Cloudflare Configuration
@@ -634,15 +755,14 @@ required_vars=(
634
755
  # Storage Configuration (required for config replacement)
635
756
  "DATA_BUCKET_NAME"
636
757
  "AUDIT_BUCKET_NAME"
758
+ "FILES_BUCKET_NAME"
637
759
  "KV_STORE_ID"
638
760
 
639
761
  # Worker-Specific Secrets (required for deployment)
640
762
  "KEYS_AUTH"
641
763
  "PDF_WORKER_AUTH"
642
- "ACCOUNT_HASH"
643
- "API_TOKEN"
764
+ "IMAGE_SIGNED_URL_SECRET"
644
765
  "BROWSER_API_TOKEN"
645
- "HMAC_KEY"
646
766
  "MANIFEST_SIGNING_PRIVATE_KEY"
647
767
  "MANIFEST_SIGNING_KEY_ID"
648
768
  "MANIFEST_SIGNING_PUBLIC_KEY"
@@ -824,10 +944,10 @@ validate_generated_configs() {
824
944
 
825
945
  assert_contains_literal "workers/data-worker/wrangler.jsonc" "$DATA_BUCKET_NAME" "DATA_BUCKET_NAME missing in data worker config"
826
946
  assert_contains_literal "workers/audit-worker/wrangler.jsonc" "$AUDIT_BUCKET_NAME" "AUDIT_BUCKET_NAME missing in audit worker config"
947
+ assert_contains_literal "workers/image-worker/wrangler.jsonc" "$FILES_BUCKET_NAME" "FILES_BUCKET_NAME missing in image worker config"
827
948
  assert_contains_literal "workers/user-worker/wrangler.jsonc" "$KV_STORE_ID" "KV_STORE_ID missing in user worker config"
828
949
 
829
950
  assert_contains_literal "app/config/config.json" "https://$PAGES_CUSTOM_DOMAIN" "PAGES_CUSTOM_DOMAIN missing in app/config/config.json"
830
- assert_contains_literal "app/config/config.json" "$ACCOUNT_HASH" "ACCOUNT_HASH missing in app/config/config.json"
831
951
  assert_contains_literal "app/config/config.json" "$EXPORT_ENCRYPTION_KEY_ID" "EXPORT_ENCRYPTION_KEY_ID missing in app/config/config.json"
832
952
  assert_contains_literal "app/config/config.json" "\"export_encryption_public_key\":" "export_encryption_public_key missing in app/config/config.json"
833
953
  assert_contains_literal "app/routes/auth/login.tsx" "const APP_CANONICAL_ORIGIN = 'https://$PAGES_CUSTOM_DOMAIN';" "PAGES_CUSTOM_DOMAIN missing in app/routes/auth/login.tsx canonical origin"
@@ -848,7 +968,7 @@ validate_generated_configs() {
848
968
  assert_contains_literal "workers/user-worker/src/user-worker.ts" "https://$PAGES_CUSTOM_DOMAIN" "PAGES_CUSTOM_DOMAIN missing in user-worker source"
849
969
 
850
970
  local placeholder_pattern
851
- placeholder_pattern="(\"(ACCOUNT_ID|PAGES_PROJECT_NAME|PAGES_CUSTOM_DOMAIN|KEYS_WORKER_NAME|USER_WORKER_NAME|DATA_WORKER_NAME|AUDIT_WORKER_NAME|IMAGES_WORKER_NAME|PDF_WORKER_NAME|KEYS_WORKER_DOMAIN|USER_WORKER_DOMAIN|DATA_WORKER_DOMAIN|AUDIT_WORKER_DOMAIN|IMAGES_WORKER_DOMAIN|PDF_WORKER_DOMAIN|DATA_BUCKET_NAME|AUDIT_BUCKET_NAME|KV_STORE_ID|ACCOUNT_HASH|MANIFEST_SIGNING_KEY_ID|MANIFEST_SIGNING_PUBLIC_KEY|EXPORT_ENCRYPTION_KEY_ID|EXPORT_ENCRYPTION_PUBLIC_KEY|YOUR_FIREBASE_API_KEY|YOUR_FIREBASE_AUTH_DOMAIN|YOUR_FIREBASE_PROJECT_ID|YOUR_FIREBASE_STORAGE_BUCKET|YOUR_FIREBASE_MESSAGING_SENDER_ID|YOUR_FIREBASE_APP_ID|YOUR_FIREBASE_MEASUREMENT_ID)\"|'(PAGES_CUSTOM_DOMAIN|DATA_WORKER_DOMAIN|IMAGES_WORKER_DOMAIN)')"
971
+ placeholder_pattern="(\"(ACCOUNT_ID|PAGES_PROJECT_NAME|PAGES_CUSTOM_DOMAIN|KEYS_WORKER_NAME|USER_WORKER_NAME|DATA_WORKER_NAME|AUDIT_WORKER_NAME|IMAGES_WORKER_NAME|PDF_WORKER_NAME|KEYS_WORKER_DOMAIN|USER_WORKER_DOMAIN|DATA_WORKER_DOMAIN|AUDIT_WORKER_DOMAIN|IMAGES_WORKER_DOMAIN|PDF_WORKER_DOMAIN|DATA_BUCKET_NAME|AUDIT_BUCKET_NAME|FILES_BUCKET_NAME|KV_STORE_ID|MANIFEST_SIGNING_KEY_ID|MANIFEST_SIGNING_PUBLIC_KEY|EXPORT_ENCRYPTION_KEY_ID|EXPORT_ENCRYPTION_PUBLIC_KEY|YOUR_FIREBASE_API_KEY|YOUR_FIREBASE_AUTH_DOMAIN|YOUR_FIREBASE_PROJECT_ID|YOUR_FIREBASE_STORAGE_BUCKET|YOUR_FIREBASE_MESSAGING_SENDER_ID|YOUR_FIREBASE_APP_ID|YOUR_FIREBASE_MEASUREMENT_ID)\"|'(PAGES_CUSTOM_DOMAIN|DATA_WORKER_DOMAIN|IMAGES_WORKER_DOMAIN)')"
852
972
 
853
973
  local files_to_scan=(
854
974
  "wrangler.toml"
@@ -880,6 +1000,7 @@ run_validation_checkpoint() {
880
1000
  validate_required_vars
881
1001
  validate_env_value_formats
882
1002
  validate_env_file_entries
1003
+ validate_data_at_rest_encryption_settings
883
1004
  validate_generated_configs
884
1005
  }
885
1006
 
@@ -1108,6 +1229,58 @@ prompt_for_secrets() {
1108
1229
  fi
1109
1230
 
1110
1231
  # Function to prompt for a variable
1232
+ is_auto_generated_secret_var() {
1233
+ local var_name=$1
1234
+ case "$var_name" in
1235
+ USER_DB_AUTH|R2_KEY_SECRET|KEYS_AUTH|PDF_WORKER_AUTH|IMAGES_API_TOKEN|IMAGE_SIGNED_URL_SECRET)
1236
+ return 0
1237
+ ;;
1238
+ *)
1239
+ return 1
1240
+ ;;
1241
+ esac
1242
+ }
1243
+
1244
+ is_secret_placeholder_value() {
1245
+ local var_name=$1
1246
+ local value=$2
1247
+ case "$var_name" in
1248
+ USER_DB_AUTH)
1249
+ [ "$value" = "your_custom_user_db_auth_token_here" ]
1250
+ ;;
1251
+ R2_KEY_SECRET)
1252
+ [ "$value" = "your_custom_r2_secret_here" ]
1253
+ ;;
1254
+ KEYS_AUTH)
1255
+ [ "$value" = "your_custom_keys_auth_token_here" ]
1256
+ ;;
1257
+ PDF_WORKER_AUTH)
1258
+ [ "$value" = "your_custom_pdf_worker_auth_token_here" ]
1259
+ ;;
1260
+ IMAGES_API_TOKEN)
1261
+ [ "$value" = "your_cloudflare_images_api_token_here" ]
1262
+ ;;
1263
+ IMAGE_SIGNED_URL_SECRET)
1264
+ [ "$value" = "your_image_signed_url_secret_here" ]
1265
+ ;;
1266
+ *)
1267
+ return 1
1268
+ ;;
1269
+ esac
1270
+ }
1271
+
1272
+ generate_secret_value() {
1273
+ local var_name=$1
1274
+ case "$var_name" in
1275
+ IMAGE_SIGNED_URL_SECRET)
1276
+ openssl rand -base64 48 2>/dev/null | tr '+/' '-_' | tr -d '='
1277
+ ;;
1278
+ *)
1279
+ openssl rand -hex 32 2>/dev/null
1280
+ ;;
1281
+ esac
1282
+ }
1283
+
1111
1284
  prompt_for_var() {
1112
1285
  local var_name=$1
1113
1286
  local description=$2
@@ -1121,19 +1294,19 @@ prompt_for_secrets() {
1121
1294
  current_value=$(resolve_existing_domain_value "$var_name" "$current_value")
1122
1295
  fi
1123
1296
 
1124
- # Auto-generate specific authentication secrets - but allow keeping current
1125
- if [ "$var_name" = "USER_DB_AUTH" ] || [ "$var_name" = "R2_KEY_SECRET" ] || [ "$var_name" = "KEYS_AUTH" ] || [ "$var_name" = "PDF_WORKER_AUTH" ]; then
1297
+ # Auto-generate selected secrets - but allow keeping current.
1298
+ if is_auto_generated_secret_var "$var_name"; then
1126
1299
  echo -e "${BLUE}$var_name${NC}"
1127
1300
  echo -e "${YELLOW}$description${NC}"
1128
1301
 
1129
- if [ "$update_env" != "true" ] && [ -n "$current_value" ] && ! is_placeholder "$current_value" && [ "$current_value" != "your_custom_user_db_auth_token_here" ] && [ "$current_value" != "your_custom_r2_secret_here" ] && [ "$current_value" != "your_custom_keys_auth_token_here" ] && [ "$current_value" != "your_custom_pdf_worker_auth_token_here" ]; then
1302
+ if [ "$update_env" != "true" ] && [ -n "$current_value" ] && ! is_placeholder "$current_value" && ! is_secret_placeholder_value "$var_name" "$current_value"; then
1130
1303
  # Current value exists and is not a placeholder
1131
1304
  echo -e "${GREEN}Current value: [HIDDEN]${NC}"
1132
1305
  read -p "Generate new secret? (press Enter to keep current, or type 'y' to generate): " gen_choice
1133
1306
  gen_choice=$(strip_carriage_returns "$gen_choice")
1134
1307
 
1135
1308
  if [ "$gen_choice" = "y" ] || [ "$gen_choice" = "Y" ]; then
1136
- new_value=$(openssl rand -hex 32 2>/dev/null || echo "")
1309
+ new_value=$(generate_secret_value "$var_name" || echo "")
1137
1310
  if [ -n "$new_value" ]; then
1138
1311
  echo -e "${GREEN}✅ $var_name auto-generated${NC}"
1139
1312
  else
@@ -1160,7 +1333,7 @@ prompt_for_secrets() {
1160
1333
  else
1161
1334
  # No current value or placeholder value - auto-generate
1162
1335
  echo -e "${YELLOW}Auto-generating secret...${NC}"
1163
- new_value=$(openssl rand -hex 32 2>/dev/null || echo "")
1336
+ new_value=$(generate_secret_value "$var_name" || echo "")
1164
1337
  if [ -n "$new_value" ]; then
1165
1338
  echo -e "${GREEN}✅ $var_name auto-generated${NC}"
1166
1339
  else
@@ -1286,7 +1459,7 @@ prompt_for_secrets() {
1286
1459
  echo "==================================="
1287
1460
  prompt_for_var "USER_DB_AUTH" "Custom user database authentication token (generate with: openssl rand -hex 16)"
1288
1461
  prompt_for_var "R2_KEY_SECRET" "Custom R2 storage authentication token (generate with: openssl rand -hex 16)"
1289
- prompt_for_var "IMAGES_API_TOKEN" "Cloudflare Images API token (shared between workers)"
1462
+ prompt_for_var "IMAGES_API_TOKEN" "Image worker API token (shared between workers)"
1290
1463
 
1291
1464
  echo -e "${BLUE}🔥 FIREBASE AUTH CONFIGURATION${NC}"
1292
1465
  echo "==============================="
@@ -1385,19 +1558,19 @@ prompt_for_secrets() {
1385
1558
  echo "========================="
1386
1559
  prompt_for_var "DATA_BUCKET_NAME" "Your R2 bucket name for case data storage"
1387
1560
  prompt_for_var "AUDIT_BUCKET_NAME" "Your R2 bucket name for audit logs (separate from data bucket)"
1561
+ prompt_for_var "FILES_BUCKET_NAME" "Your R2 bucket name for encrypted files storage"
1388
1562
  prompt_for_var "KV_STORE_ID" "Your KV namespace ID (UUID format)"
1389
1563
 
1390
1564
  echo -e "${BLUE}🔐 SERVICE-SPECIFIC SECRETS${NC}"
1391
1565
  echo "============================"
1392
1566
  prompt_for_var "KEYS_AUTH" "Keys worker authentication token (generate with: openssl rand -hex 16)"
1393
1567
  prompt_for_var "PDF_WORKER_AUTH" "PDF worker authentication token (generate with: openssl rand -hex 16)"
1394
- prompt_for_var "ACCOUNT_HASH" "Cloudflare Images Account Hash"
1395
- prompt_for_var "API_TOKEN" "Cloudflare Images API token (for Images Worker)"
1568
+ prompt_for_var "IMAGE_SIGNED_URL_SECRET" "Image signed URL secret (generate with: openssl rand -base64 48 | tr '+/' '-_' | tr -d '=')"
1396
1569
  prompt_for_var "BROWSER_API_TOKEN" "Cloudflare Browser Rendering API token (for PDF Worker)"
1397
- prompt_for_var "HMAC_KEY" "Cloudflare Images HMAC signing key"
1398
1570
 
1399
1571
  configure_manifest_signing_credentials
1400
1572
  configure_export_encryption_credentials
1573
+ configure_data_at_rest_encryption_credentials
1401
1574
 
1402
1575
  # Reload the updated .env file
1403
1576
  source .env
@@ -1462,6 +1635,7 @@ update_wrangler_configs() {
1462
1635
  echo -e "${YELLOW} Updating image-worker/wrangler.jsonc...${NC}"
1463
1636
  sed -i "s/\"IMAGES_WORKER_NAME\"/\"$IMAGES_WORKER_NAME\"/g" workers/image-worker/wrangler.jsonc
1464
1637
  sed -i "s/\"ACCOUNT_ID\"/\"$ACCOUNT_ID\"/g" workers/image-worker/wrangler.jsonc
1638
+ sed -i "s/\"FILES_BUCKET_NAME\"/\"$FILES_BUCKET_NAME\"/g" workers/image-worker/wrangler.jsonc
1465
1639
  echo -e "${GREEN} ✅ image-worker configuration updated${NC}"
1466
1640
  fi
1467
1641
 
@@ -1537,15 +1711,12 @@ update_wrangler_configs() {
1537
1711
  local escaped_manifest_signing_public_key
1538
1712
  local escaped_export_encryption_key_id
1539
1713
  local escaped_export_encryption_public_key
1540
- local escaped_account_hash
1541
1714
  escaped_manifest_signing_key_id=$(escape_for_sed_replacement "$MANIFEST_SIGNING_KEY_ID")
1542
1715
  escaped_manifest_signing_public_key=$(escape_for_sed_replacement "$MANIFEST_SIGNING_PUBLIC_KEY")
1543
1716
  escaped_export_encryption_key_id=$(escape_for_sed_replacement "$EXPORT_ENCRYPTION_KEY_ID")
1544
1717
  escaped_export_encryption_public_key=$(escape_for_sed_replacement "$EXPORT_ENCRYPTION_PUBLIC_KEY")
1545
- escaped_account_hash=$(escape_for_sed_replacement "$ACCOUNT_HASH")
1546
1718
 
1547
1719
  sed -i "s|\"url\": \"[^\"]*\"|\"url\": \"https://$escaped_pages_custom_domain\"|g" app/config/config.json
1548
- sed -i "s|\"account_hash\": \"[^\"]*\"|\"account_hash\": \"$escaped_account_hash\"|g" app/config/config.json
1549
1720
  sed -i "s|\"MANIFEST_SIGNING_KEY_ID\"|\"$escaped_manifest_signing_key_id\"|g" app/config/config.json
1550
1721
  sed -i "s|\"MANIFEST_SIGNING_PUBLIC_KEY\"|\"$escaped_manifest_signing_public_key\"|g" app/config/config.json
1551
1722
  sed -i "s|\"EXPORT_ENCRYPTION_KEY_ID\"|\"$escaped_export_encryption_key_id\"|g" app/config/config.json
@@ -172,12 +172,6 @@ deploy_pages_environment_secrets() {
172
172
  set_pages_secret "$secret" "$secret_value" "$pages_env"
173
173
  done
174
174
 
175
- local optional_api_token
176
- optional_api_token=$(get_optional_value "API_TOKEN")
177
- if [ -n "$optional_api_token" ]; then
178
- set_pages_secret "API_TOKEN" "$optional_api_token" "$pages_env"
179
- fi
180
-
181
175
  local optional_primershear_emails
182
176
  optional_primershear_emails=$(get_optional_value "PRIMERSHEAR_EMAILS")
183
177
  if [ -n "$optional_primershear_emails" ]; then
@@ -95,6 +95,30 @@ load_required_admin_service_credentials() {
95
95
 
96
96
  load_required_admin_service_credentials
97
97
 
98
+ build_audit_worker_secret_list() {
99
+ local secrets=(
100
+ "R2_KEY_SECRET"
101
+ )
102
+
103
+ if [ -n "${DATA_AT_REST_ENCRYPTION_ENABLED:-}" ]; then
104
+ secrets+=("DATA_AT_REST_ENCRYPTION_ENABLED")
105
+ fi
106
+
107
+ if [ -n "${DATA_AT_REST_ENCRYPTION_PRIVATE_KEY:-}" ]; then
108
+ secrets+=("DATA_AT_REST_ENCRYPTION_PRIVATE_KEY")
109
+ fi
110
+
111
+ if [ -n "${DATA_AT_REST_ENCRYPTION_PUBLIC_KEY:-}" ]; then
112
+ secrets+=("DATA_AT_REST_ENCRYPTION_PUBLIC_KEY")
113
+ fi
114
+
115
+ if [ -n "${DATA_AT_REST_ENCRYPTION_KEY_ID:-}" ]; then
116
+ secrets+=("DATA_AT_REST_ENCRYPTION_KEY_ID")
117
+ fi
118
+
119
+ printf '%s\n' "${secrets[@]}"
120
+ }
121
+
98
122
  # Function to set worker secrets
99
123
  set_worker_secrets() {
100
124
  local worker_name=$1
@@ -143,6 +167,34 @@ set_worker_secrets() {
143
167
  popd > /dev/null
144
168
  }
145
169
 
170
+ build_data_worker_secret_list() {
171
+ local secrets=(
172
+ "R2_KEY_SECRET"
173
+ "MANIFEST_SIGNING_PRIVATE_KEY"
174
+ "MANIFEST_SIGNING_KEY_ID"
175
+ "EXPORT_ENCRYPTION_PRIVATE_KEY"
176
+ "EXPORT_ENCRYPTION_KEY_ID"
177
+ )
178
+
179
+ if [ -n "${DATA_AT_REST_ENCRYPTION_ENABLED:-}" ]; then
180
+ secrets+=("DATA_AT_REST_ENCRYPTION_ENABLED")
181
+ fi
182
+
183
+ if [ -n "${DATA_AT_REST_ENCRYPTION_PRIVATE_KEY:-}" ]; then
184
+ secrets+=("DATA_AT_REST_ENCRYPTION_PRIVATE_KEY")
185
+ fi
186
+
187
+ if [ -n "${DATA_AT_REST_ENCRYPTION_PUBLIC_KEY:-}" ]; then
188
+ secrets+=("DATA_AT_REST_ENCRYPTION_PUBLIC_KEY")
189
+ fi
190
+
191
+ if [ -n "${DATA_AT_REST_ENCRYPTION_KEY_ID:-}" ]; then
192
+ secrets+=("DATA_AT_REST_ENCRYPTION_KEY_ID")
193
+ fi
194
+
195
+ printf '%s\n' "${secrets[@]}"
196
+ }
197
+
146
198
  # Deploy secrets to each worker
147
199
  echo -e "\n${BLUE}🔐 Deploying secrets to workers...${NC}"
148
200
 
@@ -168,14 +220,18 @@ elif [ $workers_configured -lt $total_workers ]; then
168
220
  fi
169
221
 
170
222
  # Audit Worker
171
- if ! set_worker_secrets "Audit Worker" "workers/audit-worker" \
172
- "R2_KEY_SECRET"; then
223
+ audit_worker_secrets=()
224
+ while IFS= read -r secret; do
225
+ audit_worker_secrets+=("$secret")
226
+ done < <(build_audit_worker_secret_list)
227
+
228
+ if ! set_worker_secrets "Audit Worker" "workers/audit-worker" "${audit_worker_secrets[@]}"; then
173
229
  echo -e "${YELLOW}⚠️ Skipping Audit Worker (not configured)${NC}"
174
230
  fi
175
231
 
176
232
  # Keys Worker
177
233
  if ! set_worker_secrets "Keys Worker" "workers/keys-worker" \
178
- "KEYS_AUTH" "USER_DB_AUTH" "R2_KEY_SECRET" "ACCOUNT_HASH" "IMAGES_API_TOKEN" "PDF_WORKER_AUTH"; then
234
+ "KEYS_AUTH" "USER_DB_AUTH" "R2_KEY_SECRET" "IMAGES_API_TOKEN" "PDF_WORKER_AUTH"; then
179
235
  echo -e "${YELLOW}⚠️ Skipping Keys Worker (not configured)${NC}"
180
236
  fi
181
237
 
@@ -186,14 +242,18 @@ if ! set_worker_secrets "User Worker" "workers/user-worker" \
186
242
  fi
187
243
 
188
244
  # Data Worker
189
- if ! set_worker_secrets "Data Worker" "workers/data-worker" \
190
- "R2_KEY_SECRET" "MANIFEST_SIGNING_PRIVATE_KEY" "MANIFEST_SIGNING_KEY_ID" "EXPORT_ENCRYPTION_PRIVATE_KEY" "EXPORT_ENCRYPTION_KEY_ID"; then
245
+ data_worker_secrets=()
246
+ while IFS= read -r secret; do
247
+ data_worker_secrets+=("$secret")
248
+ done < <(build_data_worker_secret_list)
249
+
250
+ if ! set_worker_secrets "Data Worker" "workers/data-worker" "${data_worker_secrets[@]}"; then
191
251
  echo -e "${YELLOW}⚠️ Skipping Data Worker (not configured)${NC}"
192
252
  fi
193
253
 
194
254
  # Images Worker
195
255
  if ! set_worker_secrets "Images Worker" "workers/image-worker" \
196
- "ACCOUNT_ID" "API_TOKEN" "HMAC_KEY"; then
256
+ "IMAGES_API_TOKEN" "DATA_AT_REST_ENCRYPTION_PRIVATE_KEY" "DATA_AT_REST_ENCRYPTION_PUBLIC_KEY" "DATA_AT_REST_ENCRYPTION_KEY_ID" "IMAGE_SIGNED_URL_SECRET"; then
197
257
  echo -e "${YELLOW}⚠️ Skipping Images Worker (not configured)${NC}"
198
258
  fi
199
259
 
@@ -210,6 +270,7 @@ echo " - Copy wrangler.jsonc.example to wrangler.jsonc in each worker director
210
270
  echo " - Configure KV namespace ID in workers/user-worker/wrangler.jsonc"
211
271
  echo " - Configure R2 bucket name in workers/data-worker/wrangler.jsonc"
212
272
  echo " - Configure R2 bucket name in workers/audit-worker/wrangler.jsonc"
273
+ echo " - Configure R2 bucket name in workers/image-worker/wrangler.jsonc"
213
274
  echo " - Update ACCOUNT_ID and custom domains in all worker configurations"
214
275
 
215
276
  echo -e "\n${BLUE}📝 For manual deployment, use these commands:${NC}"
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable */
2
- // Generated by Wrangler by running `wrangler types` (hash: 3509cf2c6fcfb4a2a6e6bd16efbe22b3)
3
- // Runtime types generated with workerd@1.20250823.0 2026-03-20 nodejs_compat
2
+ // Generated by Wrangler by running `wrangler types` (hash: abf7efe3b0d9f2d66e012084f057d73b)
3
+ // Runtime types generated with workerd@1.20250823.0 2026-03-24 nodejs_compat
4
4
  declare namespace Cloudflare {
5
5
  interface Env {
6
6
  ACCOUNT_ID: string;
@@ -27,22 +27,30 @@ declare namespace Cloudflare {
27
27
  KV_STORE_ID: string;
28
28
  DATA_WORKER_NAME: string;
29
29
  DATA_BUCKET_NAME: string;
30
+ FILES_BUCKET_NAME: string;
30
31
  DATA_WORKER_DOMAIN: string;
31
32
  MANIFEST_SIGNING_PRIVATE_KEY: string;
32
33
  MANIFEST_SIGNING_KEY_ID: string;
33
34
  MANIFEST_SIGNING_PUBLIC_KEY: string;
35
+ EXPORT_ENCRYPTION_PRIVATE_KEY: string;
36
+ EXPORT_ENCRYPTION_KEY_ID: string;
37
+ EXPORT_ENCRYPTION_PUBLIC_KEY: string;
38
+ DATA_AT_REST_ENCRYPTION_PRIVATE_KEY: string;
39
+ DATA_AT_REST_ENCRYPTION_KEY_ID: string;
40
+ DATA_AT_REST_ENCRYPTION_PUBLIC_KEY: string;
34
41
  AUDIT_WORKER_NAME: string;
35
42
  AUDIT_BUCKET_NAME: string;
36
43
  AUDIT_WORKER_DOMAIN: string;
37
44
  IMAGES_WORKER_NAME: string;
38
45
  IMAGES_WORKER_DOMAIN: string;
39
- API_TOKEN: string;
40
- HMAC_KEY: string;
46
+ IMAGE_SIGNED_URL_SECRET: string;
47
+ IMAGE_SIGNED_URL_TTL_SECONDS: string;
41
48
  PDF_WORKER_NAME: string;
42
49
  PDF_WORKER_DOMAIN: string;
43
50
  PDF_WORKER_AUTH: string;
44
51
  BROWSER_API_TOKEN: string;
45
52
  PRIMERSHEAR_EMAILS: string;
53
+ DATA_AT_REST_ENCRYPTION_ENABLED: string;
46
54
  }
47
55
  }
48
56
  interface Env extends Cloudflare.Env {}
@@ -50,7 +58,7 @@ type StringifyValues<EnvType extends Record<string, unknown>> = {
50
58
  [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string;
51
59
  };
52
60
  declare namespace NodeJS {
53
- interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "ACCOUNT_ID" | "USER_DB_AUTH" | "R2_KEY_SECRET" | "IMAGES_API_TOKEN" | "API_KEY" | "AUTH_DOMAIN" | "PROJECT_ID" | "STORAGE_BUCKET" | "MESSAGING_SENDER_ID" | "APP_ID" | "MEASUREMENT_ID" | "FIREBASE_SERVICE_ACCOUNT_EMAIL" | "FIREBASE_SERVICE_ACCOUNT_PRIVATE_KEY" | "PAGES_PROJECT_NAME" | "PAGES_CUSTOM_DOMAIN" | "KEYS_WORKER_NAME" | "KEYS_WORKER_DOMAIN" | "KEYS_AUTH" | "ACCOUNT_HASH" | "USER_WORKER_NAME" | "USER_WORKER_DOMAIN" | "KV_STORE_ID" | "DATA_WORKER_NAME" | "DATA_BUCKET_NAME" | "DATA_WORKER_DOMAIN" | "MANIFEST_SIGNING_PRIVATE_KEY" | "MANIFEST_SIGNING_KEY_ID" | "MANIFEST_SIGNING_PUBLIC_KEY" | "AUDIT_WORKER_NAME" | "AUDIT_BUCKET_NAME" | "AUDIT_WORKER_DOMAIN" | "IMAGES_WORKER_NAME" | "IMAGES_WORKER_DOMAIN" | "API_TOKEN" | "HMAC_KEY" | "PDF_WORKER_NAME" | "PDF_WORKER_DOMAIN" | "PDF_WORKER_AUTH" | "BROWSER_API_TOKEN" | "PRIMERSHEAR_EMAILS">> {}
61
+ interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "ACCOUNT_ID" | "USER_DB_AUTH" | "R2_KEY_SECRET" | "IMAGES_API_TOKEN" | "API_KEY" | "AUTH_DOMAIN" | "PROJECT_ID" | "STORAGE_BUCKET" | "MESSAGING_SENDER_ID" | "APP_ID" | "MEASUREMENT_ID" | "FIREBASE_SERVICE_ACCOUNT_EMAIL" | "FIREBASE_SERVICE_ACCOUNT_PRIVATE_KEY" | "PAGES_PROJECT_NAME" | "PAGES_CUSTOM_DOMAIN" | "KEYS_WORKER_NAME" | "KEYS_WORKER_DOMAIN" | "KEYS_AUTH" | "ACCOUNT_HASH" | "USER_WORKER_NAME" | "USER_WORKER_DOMAIN" | "KV_STORE_ID" | "DATA_WORKER_NAME" | "DATA_BUCKET_NAME" | "FILES_BUCKET_NAME" | "DATA_WORKER_DOMAIN" | "MANIFEST_SIGNING_PRIVATE_KEY" | "MANIFEST_SIGNING_KEY_ID" | "MANIFEST_SIGNING_PUBLIC_KEY" | "EXPORT_ENCRYPTION_PRIVATE_KEY" | "EXPORT_ENCRYPTION_KEY_ID" | "EXPORT_ENCRYPTION_PUBLIC_KEY" | "DATA_AT_REST_ENCRYPTION_PRIVATE_KEY" | "DATA_AT_REST_ENCRYPTION_KEY_ID" | "DATA_AT_REST_ENCRYPTION_PUBLIC_KEY" | "AUDIT_WORKER_NAME" | "AUDIT_BUCKET_NAME" | "AUDIT_WORKER_DOMAIN" | "IMAGES_WORKER_NAME" | "IMAGES_WORKER_DOMAIN" | "IMAGE_SIGNED_URL_SECRET" | "IMAGE_SIGNED_URL_TTL_SECONDS" | "PDF_WORKER_NAME" | "PDF_WORKER_DOMAIN" | "PDF_WORKER_AUTH" | "BROWSER_API_TOKEN" | "PRIMERSHEAR_EMAILS" | "DATA_AT_REST_ENCRYPTION_ENABLED">> {}
54
62
  }
55
63
 
56
64
  // Begin runtime types
@@ -5485,7 +5493,7 @@ type AIGatewayHeaders = {
5485
5493
  [key: string]: string | number | boolean | object;
5486
5494
  };
5487
5495
  type AIGatewayUniversalRequest = {
5488
- provider: AIGatewayProviders | string;
5496
+ provider: AIGatewayProviders | string; // eslint-disable-line
5489
5497
  endpoint: string;
5490
5498
  headers: Partial<AIGatewayHeaders>;
5491
5499
  query: unknown;
@@ -5501,7 +5509,7 @@ declare abstract class AiGateway {
5501
5509
  gateway?: UniversalGatewayOptions;
5502
5510
  extraHeaders?: object;
5503
5511
  }): Promise<Response>;
5504
- getUrl(provider?: AIGatewayProviders | string): Promise<string>;
5512
+ getUrl(provider?: AIGatewayProviders | string): Promise<string>; // eslint-disable-line
5505
5513
  }
5506
5514
  interface AutoRAGInternalError extends Error {
5507
5515
  }
@@ -5,13 +5,10 @@
5
5
  "scripts": {
6
6
  "deploy": "wrangler deploy",
7
7
  "dev": "wrangler dev",
8
- "start": "wrangler dev",
9
- "test": "vitest"
8
+ "start": "wrangler dev"
10
9
  },
11
10
  "devDependencies": {
12
11
  "@cloudflare/puppeteer": "^1.0.6",
13
- "@cloudflare/vitest-pool-workers": "^0.13.0",
14
- "vitest": "~4.1.0",
15
12
  "wrangler": "^4.76.0"
16
13
  },
17
14
  "overrides": {