@striae-org/striae 4.3.4 → 5.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.
Files changed (61) hide show
  1. package/.env.example +9 -2
  2. package/app/components/actions/case-export/download-handlers.ts +66 -11
  3. package/app/components/actions/case-import/confirmation-import.ts +50 -7
  4. package/app/components/actions/case-import/confirmation-package.ts +99 -22
  5. package/app/components/actions/case-import/orchestrator.ts +116 -13
  6. package/app/components/actions/case-import/validation.ts +171 -7
  7. package/app/components/actions/case-import/zip-processing.ts +224 -127
  8. package/app/components/actions/case-manage.ts +74 -15
  9. package/app/components/actions/confirm-export.ts +32 -3
  10. package/app/components/actions/generate-pdf.ts +43 -1
  11. package/app/components/actions/image-manage.ts +13 -45
  12. package/app/components/navbar/navbar.module.css +0 -10
  13. package/app/components/navbar/navbar.tsx +0 -22
  14. package/app/components/sidebar/case-import/case-import.module.css +7 -131
  15. package/app/components/sidebar/case-import/case-import.tsx +7 -14
  16. package/app/components/sidebar/case-import/components/CasePreviewSection.tsx +17 -60
  17. package/app/components/sidebar/case-import/components/ConfirmationDialog.tsx +23 -39
  18. package/app/components/sidebar/case-import/components/ConfirmationPreviewSection.tsx +5 -45
  19. package/app/components/sidebar/case-import/components/FileSelector.tsx +5 -6
  20. package/app/components/sidebar/case-import/hooks/useFilePreview.ts +2 -48
  21. package/app/components/sidebar/case-import/utils/file-validation.ts +9 -21
  22. package/app/config-example/config.json +5 -0
  23. package/app/routes/auth/login.tsx +1 -1
  24. package/app/routes/striae/hooks/use-striae-reset-helpers.ts +4 -0
  25. package/app/routes/striae/striae.tsx +15 -4
  26. package/app/utils/data/operations/case-operations.ts +13 -1
  27. package/app/utils/data/operations/confirmation-summary-operations.ts +38 -1
  28. package/app/utils/data/operations/file-annotation-operations.ts +13 -1
  29. package/app/utils/data/operations/signing-operations.ts +93 -0
  30. package/app/utils/data/operations/types.ts +6 -0
  31. package/app/utils/forensics/export-encryption.ts +316 -0
  32. package/app/utils/forensics/export-verification.ts +1 -409
  33. package/app/utils/forensics/index.ts +1 -0
  34. package/app/utils/ui/case-messages.ts +5 -2
  35. package/package.json +2 -2
  36. package/scripts/deploy-config.sh +244 -7
  37. package/scripts/deploy-pages-secrets.sh +0 -6
  38. package/scripts/deploy-worker-secrets.sh +66 -5
  39. package/scripts/encrypt-r2-backfill.mjs +376 -0
  40. package/worker-configuration.d.ts +13 -7
  41. package/workers/audit-worker/package.json +1 -4
  42. package/workers/audit-worker/src/audit-worker.example.ts +522 -61
  43. package/workers/audit-worker/wrangler.jsonc.example +6 -1
  44. package/workers/data-worker/package.json +1 -4
  45. package/workers/data-worker/src/data-worker.example.ts +409 -1
  46. package/workers/data-worker/src/encryption-utils.ts +269 -0
  47. package/workers/data-worker/worker-configuration.d.ts +1 -1
  48. package/workers/data-worker/wrangler.jsonc.example +6 -2
  49. package/workers/image-worker/package.json +1 -4
  50. package/workers/image-worker/src/encryption-utils.ts +217 -0
  51. package/workers/image-worker/src/image-worker.example.ts +196 -127
  52. package/workers/image-worker/wrangler.jsonc.example +8 -1
  53. package/workers/keys-worker/package.json +1 -4
  54. package/workers/keys-worker/wrangler.jsonc.example +1 -1
  55. package/workers/pdf-worker/package.json +1 -4
  56. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  57. package/workers/user-worker/package.json +1 -4
  58. package/workers/user-worker/wrangler.jsonc.example +1 -1
  59. package/wrangler.toml.example +1 -1
  60. package/app/components/public-signing-key-modal/public-signing-key-modal.module.css +0 -287
  61. package/app/components/public-signing-key-modal/public-signing-key-modal.tsx +0 -470
@@ -334,7 +334,7 @@ write_env_var() {
334
334
  var_value=$(strip_carriage_returns "$var_value")
335
335
  env_file_value="$var_value"
336
336
 
337
- if [ "$var_name" = "FIREBASE_SERVICE_ACCOUNT_PRIVATE_KEY" ] || [ "$var_name" = "MANIFEST_SIGNING_PRIVATE_KEY" ] || [ "$var_name" = "MANIFEST_SIGNING_PUBLIC_KEY" ]; then
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" ] || [ "$var_name" = "DATA_AT_REST_ENCRYPTION_PRIVATE_KEY" ] || [ "$var_name" = "DATA_AT_REST_ENCRYPTION_PUBLIC_KEY" ]; then
338
338
  # Store as a quoted string so sourced .env preserves escaped newline markers (\n)
339
339
  env_file_value=${env_file_value//\"/\\\"}
340
340
  env_file_value="\"$env_file_value\""
@@ -508,6 +508,229 @@ configure_manifest_signing_credentials() {
508
508
  echo ""
509
509
  }
510
510
 
511
+ generate_export_encryption_key_pair() {
512
+ local private_key_file
513
+ local public_key_file
514
+ private_key_file=$(mktemp)
515
+ public_key_file=$(mktemp)
516
+
517
+ 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
518
+ rm -f "$private_key_file" "$public_key_file"
519
+ return 1
520
+ fi
521
+
522
+ local private_key_pem
523
+ local public_key_pem
524
+ private_key_pem=$(cat "$private_key_file")
525
+ public_key_pem=$(cat "$public_key_file")
526
+ rm -f "$private_key_file" "$public_key_file"
527
+
528
+ private_key_pem="${private_key_pem//$'\r'/}"
529
+ public_key_pem="${public_key_pem//$'\r'/}"
530
+
531
+ EXPORT_ENCRYPTION_PRIVATE_KEY="${private_key_pem//$'\n'/\\n}"
532
+ EXPORT_ENCRYPTION_PUBLIC_KEY="${public_key_pem//$'\n'/\\n}"
533
+
534
+ export EXPORT_ENCRYPTION_PRIVATE_KEY
535
+ export EXPORT_ENCRYPTION_PUBLIC_KEY
536
+
537
+ write_env_var "EXPORT_ENCRYPTION_PRIVATE_KEY" "$EXPORT_ENCRYPTION_PRIVATE_KEY"
538
+ write_env_var "EXPORT_ENCRYPTION_PUBLIC_KEY" "$EXPORT_ENCRYPTION_PUBLIC_KEY"
539
+
540
+ return 0
541
+ }
542
+
543
+ configure_export_encryption_credentials() {
544
+ echo -e "${BLUE}🔐 EXPORT ENCRYPTION CONFIGURATION${NC}"
545
+ echo "================================="
546
+
547
+ local should_generate="false"
548
+ local regenerate_choice=""
549
+
550
+ if [ "$update_env" = "true" ]; then
551
+ should_generate="true"
552
+ elif [ -z "$EXPORT_ENCRYPTION_PRIVATE_KEY" ] || is_placeholder "$EXPORT_ENCRYPTION_PRIVATE_KEY" || [ -z "$EXPORT_ENCRYPTION_PUBLIC_KEY" ] || is_placeholder "$EXPORT_ENCRYPTION_PUBLIC_KEY"; then
553
+ should_generate="true"
554
+ else
555
+ echo -e "${GREEN}Current export encryption key pair: [HIDDEN]${NC}"
556
+ read -p "Generate new export encryption key pair? (press Enter to keep current, or type 'y' to regenerate): " regenerate_choice
557
+ regenerate_choice=$(strip_carriage_returns "$regenerate_choice")
558
+ if [ "$regenerate_choice" = "y" ] || [ "$regenerate_choice" = "Y" ]; then
559
+ should_generate="true"
560
+ fi
561
+ fi
562
+
563
+ if [ "$should_generate" = "true" ]; then
564
+ echo -e "${YELLOW}Generating export encryption RSA key pair...${NC}"
565
+ if generate_export_encryption_key_pair; then
566
+ echo -e "${GREEN}✅ Export encryption key pair generated${NC}"
567
+ else
568
+ echo -e "${RED}❌ Error: Failed to generate export encryption key pair${NC}"
569
+ exit 1
570
+ fi
571
+ else
572
+ echo -e "${GREEN}✅ Keeping current export encryption key pair${NC}"
573
+ fi
574
+
575
+ if [ -z "$EXPORT_ENCRYPTION_KEY_ID" ] || is_placeholder "$EXPORT_ENCRYPTION_KEY_ID" || [ "$should_generate" = "true" ]; then
576
+ local generated_key_id
577
+ generated_key_id=$(generate_worker_subdomain_label)
578
+ if [ -z "$generated_key_id" ] || [ ${#generated_key_id} -ne 10 ]; then
579
+ echo -e "${RED}❌ Error: Failed to generate EXPORT_ENCRYPTION_KEY_ID${NC}"
580
+ exit 1
581
+ fi
582
+ EXPORT_ENCRYPTION_KEY_ID="$generated_key_id"
583
+ export EXPORT_ENCRYPTION_KEY_ID
584
+ write_env_var "EXPORT_ENCRYPTION_KEY_ID" "$EXPORT_ENCRYPTION_KEY_ID"
585
+ echo -e "${GREEN}✅ EXPORT_ENCRYPTION_KEY_ID generated: $EXPORT_ENCRYPTION_KEY_ID${NC}"
586
+ else
587
+ echo -e "${GREEN}✅ EXPORT_ENCRYPTION_KEY_ID: $EXPORT_ENCRYPTION_KEY_ID${NC}"
588
+ fi
589
+
590
+ echo ""
591
+ }
592
+
593
+ generate_data_at_rest_encryption_key_pair() {
594
+ local private_key_file
595
+ local public_key_file
596
+ private_key_file=$(mktemp)
597
+ public_key_file=$(mktemp)
598
+
599
+ 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
600
+ rm -f "$private_key_file" "$public_key_file"
601
+ return 1
602
+ fi
603
+
604
+ local private_key_pem
605
+ local public_key_pem
606
+ private_key_pem=$(cat "$private_key_file")
607
+ public_key_pem=$(cat "$public_key_file")
608
+ rm -f "$private_key_file" "$public_key_file"
609
+
610
+ private_key_pem="${private_key_pem//$'\r'/}"
611
+ public_key_pem="${public_key_pem//$'\r'/}"
612
+
613
+ DATA_AT_REST_ENCRYPTION_PRIVATE_KEY="${private_key_pem//$'\n'/\\n}"
614
+ DATA_AT_REST_ENCRYPTION_PUBLIC_KEY="${public_key_pem//$'\n'/\\n}"
615
+
616
+ export DATA_AT_REST_ENCRYPTION_PRIVATE_KEY
617
+ export DATA_AT_REST_ENCRYPTION_PUBLIC_KEY
618
+
619
+ write_env_var "DATA_AT_REST_ENCRYPTION_PRIVATE_KEY" "$DATA_AT_REST_ENCRYPTION_PRIVATE_KEY"
620
+ write_env_var "DATA_AT_REST_ENCRYPTION_PUBLIC_KEY" "$DATA_AT_REST_ENCRYPTION_PUBLIC_KEY"
621
+
622
+ return 0
623
+ }
624
+
625
+ configure_data_at_rest_encryption_credentials() {
626
+ echo -e "${BLUE}🗃️ DATA-AT-REST ENCRYPTION CONFIGURATION${NC}"
627
+ echo "========================================"
628
+
629
+ local current_enabled="${DATA_AT_REST_ENCRYPTION_ENABLED:-}"
630
+ local normalized_enabled=""
631
+ local enable_choice=""
632
+
633
+ current_enabled=$(strip_carriage_returns "$current_enabled")
634
+ normalized_enabled=$(printf '%s' "$current_enabled" | tr '[:upper:]' '[:lower:]')
635
+
636
+ if [ -z "$normalized_enabled" ] || is_placeholder "$normalized_enabled"; then
637
+ DATA_AT_REST_ENCRYPTION_ENABLED="false"
638
+ elif [ "$normalized_enabled" = "1" ] || [ "$normalized_enabled" = "true" ] || [ "$normalized_enabled" = "yes" ] || [ "$normalized_enabled" = "on" ]; then
639
+ DATA_AT_REST_ENCRYPTION_ENABLED="true"
640
+ else
641
+ DATA_AT_REST_ENCRYPTION_ENABLED="false"
642
+ fi
643
+
644
+ if [ "$update_env" != "true" ]; then
645
+ read -p "Enable data-at-rest encryption for new writes? (y/N, Enter keeps current): " enable_choice
646
+ enable_choice=$(strip_carriage_returns "$enable_choice")
647
+ case "$enable_choice" in
648
+ y|Y|yes|YES)
649
+ DATA_AT_REST_ENCRYPTION_ENABLED="true"
650
+ ;;
651
+ n|N|no|NO)
652
+ DATA_AT_REST_ENCRYPTION_ENABLED="false"
653
+ ;;
654
+ "")
655
+ ;;
656
+ *)
657
+ echo -e "${YELLOW}⚠️ Unrecognized choice '$enable_choice'; keeping current setting${NC}"
658
+ ;;
659
+ esac
660
+ fi
661
+
662
+ export DATA_AT_REST_ENCRYPTION_ENABLED
663
+ write_env_var "DATA_AT_REST_ENCRYPTION_ENABLED" "$DATA_AT_REST_ENCRYPTION_ENABLED"
664
+ echo -e "${GREEN}✅ DATA_AT_REST_ENCRYPTION_ENABLED: $DATA_AT_REST_ENCRYPTION_ENABLED${NC}"
665
+
666
+ local should_generate="false"
667
+ local regenerate_choice=""
668
+
669
+ if [ "$update_env" = "true" ]; then
670
+ should_generate="true"
671
+ 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
672
+ should_generate="true"
673
+ else
674
+ echo -e "${GREEN}Current data-at-rest encryption key pair: [HIDDEN]${NC}"
675
+ read -p "Generate new data-at-rest encryption key pair? (press Enter to keep current, or type 'y' to regenerate): " regenerate_choice
676
+ regenerate_choice=$(strip_carriage_returns "$regenerate_choice")
677
+ if [ "$regenerate_choice" = "y" ] || [ "$regenerate_choice" = "Y" ]; then
678
+ should_generate="true"
679
+ fi
680
+ fi
681
+
682
+ if [ "$should_generate" = "true" ]; then
683
+ echo -e "${YELLOW}Generating data-at-rest encryption RSA key pair...${NC}"
684
+ if generate_data_at_rest_encryption_key_pair; then
685
+ echo -e "${GREEN}✅ Data-at-rest encryption key pair generated${NC}"
686
+ else
687
+ echo -e "${RED}❌ Error: Failed to generate data-at-rest encryption key pair${NC}"
688
+ exit 1
689
+ fi
690
+ else
691
+ echo -e "${GREEN}✅ Keeping current data-at-rest encryption key pair${NC}"
692
+ fi
693
+
694
+ if [ -z "$DATA_AT_REST_ENCRYPTION_KEY_ID" ] || is_placeholder "$DATA_AT_REST_ENCRYPTION_KEY_ID" || [ "$should_generate" = "true" ]; then
695
+ local generated_key_id
696
+ generated_key_id=$(generate_worker_subdomain_label)
697
+ if [ -z "$generated_key_id" ] || [ ${#generated_key_id} -ne 10 ]; then
698
+ echo -e "${RED}❌ Error: Failed to generate DATA_AT_REST_ENCRYPTION_KEY_ID${NC}"
699
+ exit 1
700
+ fi
701
+ DATA_AT_REST_ENCRYPTION_KEY_ID="$generated_key_id"
702
+ export DATA_AT_REST_ENCRYPTION_KEY_ID
703
+ write_env_var "DATA_AT_REST_ENCRYPTION_KEY_ID" "$DATA_AT_REST_ENCRYPTION_KEY_ID"
704
+ echo -e "${GREEN}✅ DATA_AT_REST_ENCRYPTION_KEY_ID generated: $DATA_AT_REST_ENCRYPTION_KEY_ID${NC}"
705
+ else
706
+ echo -e "${GREEN}✅ DATA_AT_REST_ENCRYPTION_KEY_ID: $DATA_AT_REST_ENCRYPTION_KEY_ID${NC}"
707
+ fi
708
+
709
+ echo ""
710
+ }
711
+
712
+ validate_data_at_rest_encryption_settings() {
713
+ local enabled_normalized
714
+ enabled_normalized=$(printf '%s' "${DATA_AT_REST_ENCRYPTION_ENABLED:-false}" | tr '[:upper:]' '[:lower:]')
715
+
716
+ if [ "$enabled_normalized" = "1" ] || [ "$enabled_normalized" = "true" ] || [ "$enabled_normalized" = "yes" ] || [ "$enabled_normalized" = "on" ]; then
717
+ if [ -z "$DATA_AT_REST_ENCRYPTION_PRIVATE_KEY" ] || is_placeholder "$DATA_AT_REST_ENCRYPTION_PRIVATE_KEY"; then
718
+ echo -e "${RED}❌ Error: DATA_AT_REST_ENCRYPTION_PRIVATE_KEY is required when DATA_AT_REST_ENCRYPTION_ENABLED is true${NC}"
719
+ exit 1
720
+ fi
721
+
722
+ if [ -z "$DATA_AT_REST_ENCRYPTION_PUBLIC_KEY" ] || is_placeholder "$DATA_AT_REST_ENCRYPTION_PUBLIC_KEY"; then
723
+ echo -e "${RED}❌ Error: DATA_AT_REST_ENCRYPTION_PUBLIC_KEY is required when DATA_AT_REST_ENCRYPTION_ENABLED is true${NC}"
724
+ exit 1
725
+ fi
726
+
727
+ if [ -z "$DATA_AT_REST_ENCRYPTION_KEY_ID" ] || is_placeholder "$DATA_AT_REST_ENCRYPTION_KEY_ID"; then
728
+ echo -e "${RED}❌ Error: DATA_AT_REST_ENCRYPTION_KEY_ID is required when DATA_AT_REST_ENCRYPTION_ENABLED is true${NC}"
729
+ exit 1
730
+ fi
731
+ fi
732
+ }
733
+
511
734
  # Validate required variables
512
735
  required_vars=(
513
736
  # Core Cloudflare Configuration
@@ -552,18 +775,20 @@ required_vars=(
552
775
  # Storage Configuration (required for config replacement)
553
776
  "DATA_BUCKET_NAME"
554
777
  "AUDIT_BUCKET_NAME"
778
+ "FILES_BUCKET_NAME"
555
779
  "KV_STORE_ID"
556
780
 
557
781
  # Worker-Specific Secrets (required for deployment)
558
782
  "KEYS_AUTH"
559
783
  "PDF_WORKER_AUTH"
560
784
  "ACCOUNT_HASH"
561
- "API_TOKEN"
562
785
  "BROWSER_API_TOKEN"
563
- "HMAC_KEY"
564
786
  "MANIFEST_SIGNING_PRIVATE_KEY"
565
787
  "MANIFEST_SIGNING_KEY_ID"
566
788
  "MANIFEST_SIGNING_PUBLIC_KEY"
789
+ "EXPORT_ENCRYPTION_PRIVATE_KEY"
790
+ "EXPORT_ENCRYPTION_KEY_ID"
791
+ "EXPORT_ENCRYPTION_PUBLIC_KEY"
567
792
  )
568
793
 
569
794
  validate_required_vars() {
@@ -591,7 +816,7 @@ assert_contains_literal() {
591
816
  local literal=$2
592
817
  local description=$3
593
818
 
594
- if ! grep -Fq "$literal" "$file_path"; then
819
+ if ! grep -Fq -- "$literal" "$file_path"; then
595
820
  echo -e "${RED}❌ Error: ${description}${NC}"
596
821
  echo -e "${YELLOW} Expected to find '$literal' in $file_path${NC}"
597
822
  exit 1
@@ -739,10 +964,13 @@ validate_generated_configs() {
739
964
 
740
965
  assert_contains_literal "workers/data-worker/wrangler.jsonc" "$DATA_BUCKET_NAME" "DATA_BUCKET_NAME missing in data worker config"
741
966
  assert_contains_literal "workers/audit-worker/wrangler.jsonc" "$AUDIT_BUCKET_NAME" "AUDIT_BUCKET_NAME missing in audit worker config"
967
+ assert_contains_literal "workers/image-worker/wrangler.jsonc" "$FILES_BUCKET_NAME" "FILES_BUCKET_NAME missing in image worker config"
742
968
  assert_contains_literal "workers/user-worker/wrangler.jsonc" "$KV_STORE_ID" "KV_STORE_ID missing in user worker config"
743
969
 
744
970
  assert_contains_literal "app/config/config.json" "https://$PAGES_CUSTOM_DOMAIN" "PAGES_CUSTOM_DOMAIN missing in app/config/config.json"
745
971
  assert_contains_literal "app/config/config.json" "$ACCOUNT_HASH" "ACCOUNT_HASH missing in app/config/config.json"
972
+ assert_contains_literal "app/config/config.json" "$EXPORT_ENCRYPTION_KEY_ID" "EXPORT_ENCRYPTION_KEY_ID missing in app/config/config.json"
973
+ assert_contains_literal "app/config/config.json" "\"export_encryption_public_key\":" "export_encryption_public_key missing in app/config/config.json"
746
974
  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"
747
975
 
748
976
  assert_contains_literal "app/config/firebase.ts" "$API_KEY" "API_KEY missing in app/config/firebase.ts"
@@ -761,7 +989,7 @@ validate_generated_configs() {
761
989
  assert_contains_literal "workers/user-worker/src/user-worker.ts" "https://$PAGES_CUSTOM_DOMAIN" "PAGES_CUSTOM_DOMAIN missing in user-worker source"
762
990
 
763
991
  local placeholder_pattern
764
- 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|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)')"
992
+ 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|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)')"
765
993
 
766
994
  local files_to_scan=(
767
995
  "wrangler.toml"
@@ -793,6 +1021,7 @@ run_validation_checkpoint() {
793
1021
  validate_required_vars
794
1022
  validate_env_value_formats
795
1023
  validate_env_file_entries
1024
+ validate_data_at_rest_encryption_settings
796
1025
  validate_generated_configs
797
1026
  }
798
1027
 
@@ -1298,6 +1527,7 @@ prompt_for_secrets() {
1298
1527
  echo "========================="
1299
1528
  prompt_for_var "DATA_BUCKET_NAME" "Your R2 bucket name for case data storage"
1300
1529
  prompt_for_var "AUDIT_BUCKET_NAME" "Your R2 bucket name for audit logs (separate from data bucket)"
1530
+ prompt_for_var "FILES_BUCKET_NAME" "Your R2 bucket name for encrypted files storage"
1301
1531
  prompt_for_var "KV_STORE_ID" "Your KV namespace ID (UUID format)"
1302
1532
 
1303
1533
  echo -e "${BLUE}🔐 SERVICE-SPECIFIC SECRETS${NC}"
@@ -1305,11 +1535,11 @@ prompt_for_secrets() {
1305
1535
  prompt_for_var "KEYS_AUTH" "Keys worker authentication token (generate with: openssl rand -hex 16)"
1306
1536
  prompt_for_var "PDF_WORKER_AUTH" "PDF worker authentication token (generate with: openssl rand -hex 16)"
1307
1537
  prompt_for_var "ACCOUNT_HASH" "Cloudflare Images Account Hash"
1308
- prompt_for_var "API_TOKEN" "Cloudflare Images API token (for Images Worker)"
1309
1538
  prompt_for_var "BROWSER_API_TOKEN" "Cloudflare Browser Rendering API token (for PDF Worker)"
1310
- prompt_for_var "HMAC_KEY" "Cloudflare Images HMAC signing key"
1311
1539
 
1312
1540
  configure_manifest_signing_credentials
1541
+ configure_export_encryption_credentials
1542
+ configure_data_at_rest_encryption_credentials
1313
1543
 
1314
1544
  # Reload the updated .env file
1315
1545
  source .env
@@ -1374,6 +1604,7 @@ update_wrangler_configs() {
1374
1604
  echo -e "${YELLOW} Updating image-worker/wrangler.jsonc...${NC}"
1375
1605
  sed -i "s/\"IMAGES_WORKER_NAME\"/\"$IMAGES_WORKER_NAME\"/g" workers/image-worker/wrangler.jsonc
1376
1606
  sed -i "s/\"ACCOUNT_ID\"/\"$ACCOUNT_ID\"/g" workers/image-worker/wrangler.jsonc
1607
+ sed -i "s/\"FILES_BUCKET_NAME\"/\"$FILES_BUCKET_NAME\"/g" workers/image-worker/wrangler.jsonc
1377
1608
  echo -e "${GREEN} ✅ image-worker configuration updated${NC}"
1378
1609
  fi
1379
1610
 
@@ -1447,15 +1678,21 @@ update_wrangler_configs() {
1447
1678
  echo -e "${YELLOW} Updating app/config/config.json...${NC}"
1448
1679
  local escaped_manifest_signing_key_id
1449
1680
  local escaped_manifest_signing_public_key
1681
+ local escaped_export_encryption_key_id
1682
+ local escaped_export_encryption_public_key
1450
1683
  local escaped_account_hash
1451
1684
  escaped_manifest_signing_key_id=$(escape_for_sed_replacement "$MANIFEST_SIGNING_KEY_ID")
1452
1685
  escaped_manifest_signing_public_key=$(escape_for_sed_replacement "$MANIFEST_SIGNING_PUBLIC_KEY")
1686
+ escaped_export_encryption_key_id=$(escape_for_sed_replacement "$EXPORT_ENCRYPTION_KEY_ID")
1687
+ escaped_export_encryption_public_key=$(escape_for_sed_replacement "$EXPORT_ENCRYPTION_PUBLIC_KEY")
1453
1688
  escaped_account_hash=$(escape_for_sed_replacement "$ACCOUNT_HASH")
1454
1689
 
1455
1690
  sed -i "s|\"url\": \"[^\"]*\"|\"url\": \"https://$escaped_pages_custom_domain\"|g" app/config/config.json
1456
1691
  sed -i "s|\"account_hash\": \"[^\"]*\"|\"account_hash\": \"$escaped_account_hash\"|g" app/config/config.json
1457
1692
  sed -i "s|\"MANIFEST_SIGNING_KEY_ID\"|\"$escaped_manifest_signing_key_id\"|g" app/config/config.json
1458
1693
  sed -i "s|\"MANIFEST_SIGNING_PUBLIC_KEY\"|\"$escaped_manifest_signing_public_key\"|g" app/config/config.json
1694
+ sed -i "s|\"EXPORT_ENCRYPTION_KEY_ID\"|\"$escaped_export_encryption_key_id\"|g" app/config/config.json
1695
+ sed -i "s|\"EXPORT_ENCRYPTION_PUBLIC_KEY\"|\"$escaped_export_encryption_public_key\"|g" app/config/config.json
1459
1696
  echo -e "${GREEN} ✅ app config.json updated${NC}"
1460
1697
  fi
1461
1698
 
@@ -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,8 +220,12 @@ 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
 
@@ -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"; 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"; 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}"