@striae-org/striae 5.3.2 → 5.4.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 (112) hide show
  1. package/app/components/auth/auth.module.css +531 -0
  2. package/app/components/auth/mfa-enrollment.tsx +132 -79
  3. package/app/components/auth/mfa-totp-enrollment.tsx +231 -0
  4. package/app/components/auth/mfa-verification.tsx +162 -33
  5. package/app/components/{sidebar/cases/cases-modal.tsx → navbar/case-modals/all-cases-modal.tsx} +4 -4
  6. package/app/components/navbar/case-modals/archive-case-modal.tsx +9 -10
  7. package/app/components/navbar/case-modals/case-modal-shared.module.css +88 -0
  8. package/app/components/navbar/case-modals/delete-case-modal.tsx +9 -10
  9. package/app/components/navbar/case-modals/export-case-modal.tsx +9 -10
  10. package/app/components/navbar/case-modals/export-confirmations-modal.tsx +9 -10
  11. package/app/components/navbar/case-modals/open-case-modal.tsx +4 -4
  12. package/app/components/navbar/case-modals/rename-case-modal.tsx +9 -10
  13. package/app/components/navbar/navbar.tsx +1 -1
  14. package/app/components/sidebar/files/delete-files-modal.tsx +3 -3
  15. package/app/components/sidebar/files/files-modal.module.css +29 -0
  16. package/app/components/sidebar/notes/{class-details-fields.tsx → class-details/class-details-fields.tsx} +1 -1
  17. package/app/components/sidebar/notes/{class-details-modal.tsx → class-details/class-details-modal.tsx} +1 -1
  18. package/app/components/sidebar/notes/{class-details-sections.tsx → class-details/class-details-sections.tsx} +1 -1
  19. package/app/components/sidebar/notes/notes-editor-form.tsx +2 -2
  20. package/app/components/sidebar/notes/notes-editor-modal.tsx +6 -6
  21. package/app/components/sidebar/notes/notes.module.css +52 -0
  22. package/app/components/toolbar/toolbar-color-selector.tsx +8 -8
  23. package/app/components/toolbar/toolbar.module.css +181 -2
  24. package/app/components/user/delete-account.tsx +7 -7
  25. package/app/components/user/inactivity-warning.tsx +6 -6
  26. package/app/components/user/manage-profile.tsx +18 -1
  27. package/app/components/user/mfa-enrolled-factors.tsx +117 -0
  28. package/app/components/user/mfa-phone-update.tsx +8 -4
  29. package/app/components/user/mfa-totp-section.tsx +446 -0
  30. package/app/components/user/user.module.css +665 -0
  31. package/app/routes/striae/striae.tsx +1 -1
  32. package/app/services/audit/audit.service.ts +1 -1
  33. package/app/services/audit/builders/audit-event-builders-user-security.ts +4 -2
  34. package/app/services/firebase/errors.ts +2 -0
  35. package/app/utils/auth/mfa.ts +35 -1
  36. package/package.json +23 -28
  37. package/scripts/deploy-all.sh +166 -0
  38. package/scripts/deploy-config/modules/env-utils.sh +322 -0
  39. package/scripts/deploy-config/modules/keys.sh +404 -0
  40. package/scripts/deploy-config/modules/prompt.sh +375 -0
  41. package/scripts/deploy-config/modules/scaffolding.sh +310 -0
  42. package/scripts/deploy-config/modules/validation.sh +354 -0
  43. package/scripts/deploy-config.sh +236 -0
  44. package/scripts/deploy-pages-secrets.sh +231 -0
  45. package/scripts/deploy-pages.sh +34 -0
  46. package/scripts/deploy-primershear-emails.sh +167 -0
  47. package/scripts/deploy-worker-secrets.sh +385 -0
  48. package/scripts/dev.cjs +23 -0
  49. package/scripts/enable-totp-mfa.mjs +57 -0
  50. package/scripts/install-workers.sh +87 -0
  51. package/scripts/run-eslint.cjs +43 -0
  52. package/scripts/unenroll-totp-mfa.mjs +82 -0
  53. package/scripts/update-compatibility-dates.cjs +124 -0
  54. package/scripts/update-markdown-versions.cjs +43 -0
  55. package/workers/audit-worker/.editorconfig +12 -0
  56. package/workers/audit-worker/.prettierrc +6 -0
  57. package/workers/audit-worker/package.json +1 -1
  58. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  59. package/workers/data-worker/.editorconfig +12 -0
  60. package/workers/data-worker/.prettierrc +6 -0
  61. package/workers/data-worker/package.json +1 -1
  62. package/workers/data-worker/wrangler.jsonc.example +1 -1
  63. package/workers/image-worker/.editorconfig +12 -0
  64. package/workers/image-worker/.prettierrc +6 -0
  65. package/workers/image-worker/package.json +1 -1
  66. package/workers/image-worker/wrangler.jsonc.example +1 -1
  67. package/workers/pdf-worker/.editorconfig +12 -0
  68. package/workers/pdf-worker/.prettierrc +6 -0
  69. package/workers/pdf-worker/package.json +1 -1
  70. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  71. package/workers/user-worker/.editorconfig +12 -0
  72. package/workers/user-worker/.prettierrc +6 -0
  73. package/workers/user-worker/package.json +1 -1
  74. package/workers/user-worker/wrangler.jsonc.example +1 -1
  75. package/wrangler.toml.example +1 -1
  76. package/app/components/auth/mfa-enrollment.module.css +0 -276
  77. package/app/components/auth/mfa-verification.module.css +0 -259
  78. package/app/components/navbar/case-modals/archive-case-modal.module.css +0 -34
  79. package/app/components/navbar/case-modals/delete-case-modal.module.css +0 -9
  80. package/app/components/navbar/case-modals/export-case-modal.module.css +0 -27
  81. package/app/components/navbar/case-modals/export-confirmations-modal.module.css +0 -24
  82. package/app/components/navbar/case-modals/open-case-modal.module.css +0 -82
  83. package/app/components/navbar/case-modals/rename-case-modal.module.css +0 -9
  84. package/app/components/sidebar/files/delete-files-modal.module.css +0 -26
  85. package/app/components/sidebar/notes/notes-editor-modal.module.css +0 -49
  86. package/app/components/toolbar/toolbar-color-selector.module.css +0 -171
  87. package/app/components/user/delete-account.module.css +0 -277
  88. package/app/components/user/inactivity-warning.module.css +0 -148
  89. package/app/components/user/manage-profile.module.css +0 -192
  90. package/app/routes/auth/login.module.css +0 -523
  91. package/app/routes/auth/login.tsx +0 -705
  92. package/workers/audit-worker/worker-configuration.d.ts +0 -7448
  93. package/workers/data-worker/worker-configuration.d.ts +0 -7448
  94. package/workers/image-worker/worker-configuration.d.ts +0 -7448
  95. package/workers/pdf-worker/worker-configuration.d.ts +0 -7447
  96. package/workers/user-worker/worker-configuration.d.ts +0 -7450
  97. /package/app/components/{sidebar → navbar}/case-import/case-import.module.css +0 -0
  98. /package/app/components/{sidebar → navbar}/case-import/case-import.tsx +0 -0
  99. /package/app/components/{sidebar → navbar}/case-import/components/CasePreviewSection.tsx +0 -0
  100. /package/app/components/{sidebar → navbar}/case-import/components/ConfirmationDialog.tsx +0 -0
  101. /package/app/components/{sidebar → navbar}/case-import/components/ConfirmationPreviewSection.tsx +0 -0
  102. /package/app/components/{sidebar → navbar}/case-import/components/ExistingCaseSection.tsx +0 -0
  103. /package/app/components/{sidebar → navbar}/case-import/components/FileSelector.tsx +0 -0
  104. /package/app/components/{sidebar → navbar}/case-import/components/ProgressSection.tsx +0 -0
  105. /package/app/components/{sidebar → navbar}/case-import/hooks/useFilePreview.ts +0 -0
  106. /package/app/components/{sidebar → navbar}/case-import/hooks/useImportExecution.ts +0 -0
  107. /package/app/components/{sidebar → navbar}/case-import/hooks/useImportState.ts +0 -0
  108. /package/app/components/{sidebar → navbar}/case-import/index.ts +0 -0
  109. /package/app/components/{sidebar → navbar}/case-import/utils/file-validation.ts +0 -0
  110. /package/app/components/{sidebar/cases/cases-modal.module.css → navbar/case-modals/all-cases-modal.module.css} +0 -0
  111. /package/app/components/sidebar/notes/{class-details-shared.ts → class-details/class-details-shared.ts} +0 -0
  112. /package/app/components/sidebar/notes/{use-class-details-state.ts → class-details/use-class-details-state.ts} +0 -0
@@ -0,0 +1,385 @@
1
+ #!/bin/bash
2
+
3
+ # ======================================
4
+ # STRIAE WORKER SECRETS DEPLOYMENT SCRIPT
5
+ # ======================================
6
+ # This script deploys environment variables/secrets to Cloudflare Workers
7
+ # Run this AFTER workers are deployed to avoid deployment errors
8
+
9
+ set -e
10
+
11
+ # Colors for output
12
+ RED='\033[0;31m'
13
+ GREEN='\033[0;32m'
14
+ YELLOW='\033[1;33m'
15
+ BLUE='\033[0;34m'
16
+ NC='\033[0m' # No Color
17
+
18
+ echo -e "${BLUE}🔐 Striae Worker Secrets Deployment Script${NC}"
19
+ echo "=========================================="
20
+
21
+ # Check if .env file exists
22
+ if [ ! -f ".env" ]; then
23
+ echo -e "${RED}❌ Error: .env file not found!${NC}"
24
+ echo "Please copy .env.example to .env and fill in your values."
25
+ exit 1
26
+ fi
27
+
28
+ # Source the .env file
29
+ echo -e "${YELLOW}📖 Loading environment variables from .env...${NC}"
30
+ source .env
31
+
32
+ is_admin_service_placeholder() {
33
+ local value="$1"
34
+ local normalized=$(echo "$value" | tr '[:upper:]' '[:lower:]')
35
+
36
+ [[ -z "$normalized" || "$normalized" == your-* || "$normalized" == *"your_private_key"* ]]
37
+ }
38
+
39
+ load_required_admin_service_credentials() {
40
+ local admin_service_path="app/config/admin-service.json"
41
+
42
+ if [ ! -f "$admin_service_path" ]; then
43
+ echo -e "${RED}❌ Error: Required Firebase admin service file not found: $admin_service_path${NC}"
44
+ echo -e "${YELLOW} Create app/config/admin-service.json before deploying worker secrets.${NC}"
45
+ exit 1
46
+ fi
47
+
48
+ local service_project_id
49
+ local service_client_email
50
+ local service_private_key
51
+
52
+ if ! service_project_id=$(node -e "const fs=require('fs'); const data=JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); process.stdout.write(data.project_id || '');" "$admin_service_path"); then
53
+ echo -e "${RED}❌ Error: Could not parse project_id from $admin_service_path${NC}"
54
+ exit 1
55
+ fi
56
+
57
+ if ! service_client_email=$(node -e "const fs=require('fs'); const data=JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); process.stdout.write(data.client_email || '');" "$admin_service_path"); then
58
+ echo -e "${RED}❌ Error: Could not parse client_email from $admin_service_path${NC}"
59
+ exit 1
60
+ fi
61
+
62
+ if ! service_private_key=$(node -e "const fs=require('fs'); const data=JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); process.stdout.write(data.private_key || '');" "$admin_service_path"); then
63
+ echo -e "${RED}❌ Error: Could not parse private_key from $admin_service_path${NC}"
64
+ exit 1
65
+ fi
66
+
67
+ local normalized_private_key="${service_private_key//$'\r'/}"
68
+ normalized_private_key="${normalized_private_key//$'\n'/\\n}"
69
+
70
+ if is_admin_service_placeholder "$service_project_id"; then
71
+ echo -e "${RED}❌ Error: project_id in $admin_service_path is missing or placeholder${NC}"
72
+ exit 1
73
+ fi
74
+
75
+ if is_admin_service_placeholder "$service_client_email" || [[ "$service_client_email" != *".gserviceaccount.com"* ]]; then
76
+ echo -e "${RED}❌ Error: client_email in $admin_service_path is invalid${NC}"
77
+ exit 1
78
+ fi
79
+
80
+ if is_admin_service_placeholder "$normalized_private_key" || [[ "$normalized_private_key" != *"-----BEGIN PRIVATE KEY-----"* ]] || [[ "$normalized_private_key" != *"-----END PRIVATE KEY-----"* ]]; then
81
+ echo -e "${RED}❌ Error: private_key in $admin_service_path is invalid${NC}"
82
+ exit 1
83
+ fi
84
+
85
+ PROJECT_ID="$service_project_id"
86
+ FIREBASE_SERVICE_ACCOUNT_EMAIL="$service_client_email"
87
+ FIREBASE_SERVICE_ACCOUNT_PRIVATE_KEY="$normalized_private_key"
88
+
89
+ export PROJECT_ID
90
+ export FIREBASE_SERVICE_ACCOUNT_EMAIL
91
+ export FIREBASE_SERVICE_ACCOUNT_PRIVATE_KEY
92
+
93
+ echo -e "${GREEN}✅ Loaded Firebase service account credentials from $admin_service_path${NC}"
94
+ }
95
+
96
+ load_required_admin_service_credentials
97
+
98
+ build_user_worker_secret_list() {
99
+ local secrets=(
100
+ "USER_DB_AUTH"
101
+ "R2_KEY_SECRET"
102
+ "PROJECT_ID"
103
+ "FIREBASE_SERVICE_ACCOUNT_EMAIL"
104
+ "FIREBASE_SERVICE_ACCOUNT_PRIVATE_KEY"
105
+ )
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_KEY_ID:-}" ]; then
112
+ secrets+=("DATA_AT_REST_ENCRYPTION_KEY_ID")
113
+ fi
114
+
115
+ if [ -n "${DATA_AT_REST_ENCRYPTION_KEYS_JSON:-}" ]; then
116
+ secrets+=("DATA_AT_REST_ENCRYPTION_KEYS_JSON")
117
+ fi
118
+
119
+ if [ -n "${DATA_AT_REST_ENCRYPTION_ACTIVE_KEY_ID:-}" ]; then
120
+ secrets+=("DATA_AT_REST_ENCRYPTION_ACTIVE_KEY_ID")
121
+ fi
122
+
123
+ if [ -n "${USER_KV_ENCRYPTION_PRIVATE_KEY:-}" ]; then
124
+ secrets+=("USER_KV_ENCRYPTION_PRIVATE_KEY")
125
+ fi
126
+
127
+ if [ -n "${USER_KV_ENCRYPTION_KEYS_JSON:-}" ]; then
128
+ secrets+=("USER_KV_ENCRYPTION_KEYS_JSON")
129
+ fi
130
+
131
+ if [ -n "${USER_KV_ENCRYPTION_ACTIVE_KEY_ID:-}" ]; then
132
+ secrets+=("USER_KV_ENCRYPTION_ACTIVE_KEY_ID")
133
+ fi
134
+
135
+ local write_endpoints_enabled_normalized
136
+ write_endpoints_enabled_normalized=$(printf '%s' "${USER_KV_WRITE_ENDPOINTS_ENABLED:-true}" | tr '[:upper:]' '[:lower:]')
137
+
138
+ if [ "$write_endpoints_enabled_normalized" = "1" ] || [ "$write_endpoints_enabled_normalized" = "true" ] || [ "$write_endpoints_enabled_normalized" = "yes" ] || [ "$write_endpoints_enabled_normalized" = "on" ]; then
139
+ if [ -n "${USER_KV_ENCRYPTION_KEY_ID:-}" ]; then
140
+ secrets+=("USER_KV_ENCRYPTION_KEY_ID")
141
+ fi
142
+
143
+ if [ -n "${USER_KV_ENCRYPTION_PUBLIC_KEY:-}" ]; then
144
+ secrets+=("USER_KV_ENCRYPTION_PUBLIC_KEY")
145
+ fi
146
+ fi
147
+
148
+ printf '%s\n' "${secrets[@]}"
149
+ }
150
+
151
+ build_audit_worker_secret_list() {
152
+ local secrets=(
153
+ "R2_KEY_SECRET"
154
+ )
155
+
156
+ if [ -n "${DATA_AT_REST_ENCRYPTION_ENABLED:-}" ]; then
157
+ secrets+=("DATA_AT_REST_ENCRYPTION_ENABLED")
158
+ fi
159
+
160
+ if [ -n "${DATA_AT_REST_ENCRYPTION_PRIVATE_KEY:-}" ]; then
161
+ secrets+=("DATA_AT_REST_ENCRYPTION_PRIVATE_KEY")
162
+ fi
163
+
164
+ if [ -n "${DATA_AT_REST_ENCRYPTION_PUBLIC_KEY:-}" ]; then
165
+ secrets+=("DATA_AT_REST_ENCRYPTION_PUBLIC_KEY")
166
+ fi
167
+
168
+ if [ -n "${DATA_AT_REST_ENCRYPTION_KEY_ID:-}" ]; then
169
+ secrets+=("DATA_AT_REST_ENCRYPTION_KEY_ID")
170
+ fi
171
+
172
+ if [ -n "${DATA_AT_REST_ENCRYPTION_KEYS_JSON:-}" ]; then
173
+ secrets+=("DATA_AT_REST_ENCRYPTION_KEYS_JSON")
174
+ fi
175
+
176
+ if [ -n "${DATA_AT_REST_ENCRYPTION_ACTIVE_KEY_ID:-}" ]; then
177
+ secrets+=("DATA_AT_REST_ENCRYPTION_ACTIVE_KEY_ID")
178
+ fi
179
+
180
+ printf '%s\n' "${secrets[@]}"
181
+ }
182
+
183
+ # Function to set worker secrets
184
+ set_worker_secrets() {
185
+ local worker_name=$1
186
+ local worker_path=$2
187
+ shift 2
188
+ local secrets=("$@")
189
+
190
+ echo -e "\n${BLUE}🔧 Setting secrets for $worker_name...${NC}"
191
+
192
+ # Check if worker has a wrangler configuration file
193
+ if [ ! -f "$worker_path/wrangler.jsonc" ] && [ ! -f "$worker_path/wrangler.toml" ]; then
194
+ echo -e "${RED}❌ Error: No wrangler configuration found for $worker_name${NC}"
195
+ echo -e "${YELLOW} Please copy wrangler.jsonc.example to wrangler.jsonc and configure it first.${NC}"
196
+ return 1
197
+ fi
198
+
199
+ # Change to worker directory
200
+ pushd "$worker_path" > /dev/null
201
+
202
+ # Get the worker name from the configuration file
203
+ local config_worker_name
204
+ if [ -f "wrangler.jsonc" ]; then
205
+ config_worker_name=$(grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' wrangler.jsonc | sed 's/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
206
+ elif [ -f "wrangler.toml" ]; then
207
+ config_worker_name=$(grep '^name[[:space:]]*=' wrangler.toml | sed 's/.*=[[:space:]]*["\x27]\([^"\x27]*\)["\x27].*/\1/')
208
+ fi
209
+
210
+ if [ -z "$config_worker_name" ]; then
211
+ echo -e "${RED}❌ Error: Could not determine worker name from configuration${NC}"
212
+ popd > /dev/null
213
+ return 1
214
+ fi
215
+
216
+ echo -e "${YELLOW} Using worker name: $config_worker_name${NC}"
217
+
218
+ for secret in "${secrets[@]}"; do
219
+ echo -e "${YELLOW} Setting $secret...${NC}"
220
+ if ! echo "${!secret}" | wrangler secret put "$secret" --name "$config_worker_name"; then
221
+ echo -e "${RED}❌ Failed to set $secret for $worker_name${NC}"
222
+ popd > /dev/null
223
+ return 1
224
+ fi
225
+ done
226
+
227
+ echo -e "${GREEN}✅ $worker_name secrets configured${NC}"
228
+ popd > /dev/null
229
+ }
230
+
231
+ build_data_worker_secret_list() {
232
+ local secrets=(
233
+ "R2_KEY_SECRET"
234
+ "MANIFEST_SIGNING_PRIVATE_KEY"
235
+ "MANIFEST_SIGNING_KEY_ID"
236
+ "EXPORT_ENCRYPTION_PRIVATE_KEY"
237
+ "EXPORT_ENCRYPTION_KEY_ID"
238
+ )
239
+
240
+ if [ -n "${DATA_AT_REST_ENCRYPTION_ENABLED:-}" ]; then
241
+ secrets+=("DATA_AT_REST_ENCRYPTION_ENABLED")
242
+ fi
243
+
244
+ if [ -n "${DATA_AT_REST_ENCRYPTION_PRIVATE_KEY:-}" ]; then
245
+ secrets+=("DATA_AT_REST_ENCRYPTION_PRIVATE_KEY")
246
+ fi
247
+
248
+ if [ -n "${DATA_AT_REST_ENCRYPTION_PUBLIC_KEY:-}" ]; then
249
+ secrets+=("DATA_AT_REST_ENCRYPTION_PUBLIC_KEY")
250
+ fi
251
+
252
+ if [ -n "${DATA_AT_REST_ENCRYPTION_KEY_ID:-}" ]; then
253
+ secrets+=("DATA_AT_REST_ENCRYPTION_KEY_ID")
254
+ fi
255
+
256
+ if [ -n "${DATA_AT_REST_ENCRYPTION_KEYS_JSON:-}" ]; then
257
+ secrets+=("DATA_AT_REST_ENCRYPTION_KEYS_JSON")
258
+ fi
259
+
260
+ if [ -n "${DATA_AT_REST_ENCRYPTION_ACTIVE_KEY_ID:-}" ]; then
261
+ secrets+=("DATA_AT_REST_ENCRYPTION_ACTIVE_KEY_ID")
262
+ fi
263
+
264
+ if [ -n "${EXPORT_ENCRYPTION_KEYS_JSON:-}" ]; then
265
+ secrets+=("EXPORT_ENCRYPTION_KEYS_JSON")
266
+ fi
267
+
268
+ if [ -n "${EXPORT_ENCRYPTION_ACTIVE_KEY_ID:-}" ]; then
269
+ secrets+=("EXPORT_ENCRYPTION_ACTIVE_KEY_ID")
270
+ fi
271
+
272
+ printf '%s\n' "${secrets[@]}"
273
+ }
274
+
275
+ build_images_worker_secret_list() {
276
+ local secrets=(
277
+ "IMAGES_API_TOKEN"
278
+ "DATA_AT_REST_ENCRYPTION_PUBLIC_KEY"
279
+ "DATA_AT_REST_ENCRYPTION_KEY_ID"
280
+ "IMAGE_SIGNED_URL_SECRET"
281
+ )
282
+
283
+ if [ -n "${DATA_AT_REST_ENCRYPTION_PRIVATE_KEY:-}" ]; then
284
+ secrets+=("DATA_AT_REST_ENCRYPTION_PRIVATE_KEY")
285
+ fi
286
+
287
+ if [ -n "${DATA_AT_REST_ENCRYPTION_KEYS_JSON:-}" ]; then
288
+ secrets+=("DATA_AT_REST_ENCRYPTION_KEYS_JSON")
289
+ fi
290
+
291
+ if [ -n "${DATA_AT_REST_ENCRYPTION_ACTIVE_KEY_ID:-}" ]; then
292
+ secrets+=("DATA_AT_REST_ENCRYPTION_ACTIVE_KEY_ID")
293
+ fi
294
+
295
+ if [ -n "${IMAGE_SIGNED_URL_BASE_URL:-}" ]; then
296
+ secrets+=("IMAGE_SIGNED_URL_BASE_URL")
297
+ fi
298
+
299
+ printf '%s\n' "${secrets[@]}"
300
+ }
301
+
302
+ # Deploy secrets to each worker
303
+ echo -e "\n${BLUE}🔐 Deploying secrets to workers...${NC}"
304
+
305
+ # Check if workers are configured
306
+ echo -e "${YELLOW}🔍 Checking worker configurations...${NC}"
307
+ workers_configured=0
308
+ total_workers=5
309
+
310
+ for worker_dir in workers/*/; do
311
+ if [ -f "$worker_dir/wrangler.jsonc" ] || [ -f "$worker_dir/wrangler.toml" ]; then
312
+ workers_configured=$((workers_configured + 1))
313
+ fi
314
+ done
315
+
316
+ if [ $workers_configured -eq 0 ]; then
317
+ echo -e "${RED}❌ No workers are configured!${NC}"
318
+ echo -e "${YELLOW} Please copy wrangler.jsonc.example to wrangler.jsonc in each worker directory and configure them.${NC}"
319
+ echo -e "${YELLOW} Then run this script again.${NC}"
320
+ exit 1
321
+ elif [ $workers_configured -lt $total_workers ]; then
322
+ echo -e "${YELLOW}⚠️ Warning: Only $workers_configured of $total_workers workers are configured.${NC}"
323
+ echo -e "${YELLOW} Some workers may not have their secrets deployed.${NC}"
324
+ fi
325
+
326
+ # Audit Worker
327
+ audit_worker_secrets=()
328
+ while IFS= read -r secret; do
329
+ audit_worker_secrets+=("$secret")
330
+ done < <(build_audit_worker_secret_list)
331
+
332
+ if ! set_worker_secrets "Audit Worker" "workers/audit-worker" "${audit_worker_secrets[@]}"; then
333
+ echo -e "${YELLOW}⚠️ Skipping Audit Worker (not configured)${NC}"
334
+ fi
335
+
336
+ # User Worker
337
+ user_worker_secrets=()
338
+ while IFS= read -r secret; do
339
+ user_worker_secrets+=("$secret")
340
+ done < <(build_user_worker_secret_list)
341
+
342
+ if ! set_worker_secrets "User Worker" "workers/user-worker" "${user_worker_secrets[@]}"; then
343
+ echo -e "${YELLOW}⚠️ Skipping User Worker (not configured)${NC}"
344
+ fi
345
+
346
+ # Data Worker
347
+ data_worker_secrets=()
348
+ while IFS= read -r secret; do
349
+ data_worker_secrets+=("$secret")
350
+ done < <(build_data_worker_secret_list)
351
+
352
+ if ! set_worker_secrets "Data Worker" "workers/data-worker" "${data_worker_secrets[@]}"; then
353
+ echo -e "${YELLOW}⚠️ Skipping Data Worker (not configured)${NC}"
354
+ fi
355
+
356
+ # Images Worker
357
+ images_worker_secrets=()
358
+ while IFS= read -r secret; do
359
+ images_worker_secrets+=("$secret")
360
+ done < <(build_images_worker_secret_list)
361
+
362
+ if ! set_worker_secrets "Images Worker" "workers/image-worker" "${images_worker_secrets[@]}"; then
363
+ echo -e "${YELLOW}⚠️ Skipping Images Worker (not configured)${NC}"
364
+ fi
365
+
366
+ # PDF Worker
367
+ if ! set_worker_secrets "PDF Worker" "workers/pdf-worker" \
368
+ "PDF_WORKER_AUTH" "ACCOUNT_ID" "BROWSER_API_TOKEN"; then
369
+ echo -e "${YELLOW}⚠️ Skipping PDF Worker (not configured)${NC}"
370
+ fi
371
+
372
+ echo -e "\n${GREEN}🎉 Worker secrets deployment completed!${NC}"
373
+
374
+ echo -e "\n${YELLOW}⚠️ WORKER CONFIGURATION REMINDERS:${NC}"
375
+ echo " - Copy wrangler.jsonc.example to wrangler.jsonc in each worker directory"
376
+ echo " - Configure KV namespace ID in workers/user-worker/wrangler.jsonc"
377
+ echo " - Configure R2 bucket name in workers/data-worker/wrangler.jsonc"
378
+ echo " - Configure R2 bucket name in workers/audit-worker/wrangler.jsonc"
379
+ echo " - Configure R2 bucket name in workers/image-worker/wrangler.jsonc"
380
+ echo " - Update ACCOUNT_ID and custom domains in all worker configurations"
381
+
382
+ echo -e "\n${BLUE}📝 For manual deployment, use these commands:${NC}"
383
+ echo " cd workers/[worker-name]"
384
+ echo " wrangler secret put VARIABLE_NAME --name [worker-name]"
385
+ echo -e "\n${GREEN}✨ Worker secrets deployment complete!${NC}"
@@ -0,0 +1,23 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { updateMarkdownVersions } = require('./update-markdown-versions.cjs');
4
+ const { updateCompatibilityDates } = require('./update-compatibility-dates.cjs');
5
+
6
+ // Read the ASCII art file from the filesystem
7
+ const asciiArtPath = path.join(__dirname, '..', 'public', 'striae-ascii.txt');
8
+ let asciiArt;
9
+ try {
10
+ asciiArt = fs.readFileSync(asciiArtPath, 'utf8');
11
+ } catch (err) {
12
+ console.warn(`Warning: Unable to read ASCII art file at ${asciiArtPath}.\n${err.message}`);
13
+ asciiArt = "(ASCII art unavailable)\n";
14
+ }
15
+
16
+ // Pop a lil' logo in the terminal
17
+ console.info(asciiArt);
18
+
19
+ // Update markdown files with current version
20
+ updateMarkdownVersions();
21
+
22
+ // Update compatibility dates to current date
23
+ updateCompatibilityDates();
@@ -0,0 +1,57 @@
1
+ /**
2
+ * One-time script to enable TOTP MFA provider via Firebase Admin SDK.
3
+ * Run with: npm run enable-totp-mfa
4
+ *
5
+ * Requires app/config/admin-service.json (gitignored service account key).
6
+ * Docs: https://firebase.google.com/docs/auth/web/totp-mfa#enable_totp_mfa
7
+ */
8
+
9
+ import { createRequire } from 'module';
10
+ import { fileURLToPath } from 'url';
11
+ import { dirname, resolve } from 'path';
12
+ import { initializeApp, cert, getApps } from 'firebase-admin/app';
13
+ import { getAuth } from 'firebase-admin/auth';
14
+
15
+ const require = createRequire(import.meta.url);
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+
18
+ const serviceAccountPath = resolve(__dirname, '../app/config/admin-service.json');
19
+
20
+ let serviceAccount;
21
+ try {
22
+ serviceAccount = require(serviceAccountPath);
23
+ } catch {
24
+ console.error(`\n❌ Could not load service account key from:\n ${serviceAccountPath}`);
25
+ console.error('\nMake sure app/config/admin-service.json exists (it is gitignored).');
26
+ process.exit(1);
27
+ }
28
+
29
+ if (getApps().length === 0) {
30
+ initializeApp({ credential: cert(serviceAccount) });
31
+ }
32
+
33
+ const auth = getAuth();
34
+
35
+ console.log('\n🔑 Enabling TOTP MFA provider...');
36
+
37
+ try {
38
+ await auth.projectConfigManager().updateProjectConfig({
39
+ multiFactorConfig: {
40
+ providerConfigs: [
41
+ {
42
+ state: 'ENABLED',
43
+ totpProviderConfig: {
44
+ adjacentIntervals: 5,
45
+ },
46
+ },
47
+ ],
48
+ },
49
+ });
50
+
51
+ console.log('✅ TOTP MFA provider enabled successfully.');
52
+ console.log(' adjacentIntervals: 5 (allows ±2.5 minutes clock skew)');
53
+ } catch (err) {
54
+ console.error('\n❌ Failed to enable TOTP MFA provider:');
55
+ console.error(err?.message ?? err);
56
+ process.exit(1);
57
+ }
@@ -0,0 +1,87 @@
1
+ #!/bin/bash
2
+
3
+ # ======================================
4
+ # STRIAE WORKERS NPM INSTALL SCRIPT
5
+ # ======================================
6
+ # This script installs npm dependencies for all Striae workers:
7
+ # 1. audit-worker
8
+ # 2. data-worker
9
+ # 3. image-worker
10
+ # 4. pdf-worker
11
+ # 5. user-worker
12
+
13
+ # Colors for output
14
+ RED='\033[0;31m'
15
+ GREEN='\033[0;32m'
16
+ YELLOW='\033[1;33m'
17
+ BLUE='\033[0;34m'
18
+ PURPLE='\033[0;35m'
19
+ NC='\033[0m' # No Color
20
+
21
+ echo -e "${BLUE}📦 Striae Workers NPM Install Script${NC}"
22
+ echo "========================================"
23
+ echo ""
24
+
25
+ # Get the script directory and project root
26
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
+ PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
28
+ WORKERS_DIR="$PROJECT_ROOT/workers"
29
+
30
+ # Check if workers directory exists
31
+ if [ ! -d "$WORKERS_DIR" ]; then
32
+ echo -e "${RED}❌ Error: Workers directory not found at $WORKERS_DIR${NC}"
33
+ exit 1
34
+ fi
35
+
36
+ # List of workers
37
+ WORKERS=("audit-worker" "data-worker" "image-worker" "pdf-worker" "user-worker")
38
+
39
+ echo -e "${PURPLE}Installing npm dependencies for all workers...${NC}"
40
+ echo ""
41
+
42
+ # Counter for progress
43
+ total=${#WORKERS[@]}
44
+ current=0
45
+
46
+ # Install dependencies for each worker
47
+ for worker in "${WORKERS[@]}"; do
48
+ current=$((current + 1))
49
+ worker_path="$WORKERS_DIR/$worker"
50
+
51
+ echo -e "${YELLOW}[$current/$total] Installing dependencies for $worker...${NC}"
52
+
53
+ # Check if worker directory exists
54
+ if [ ! -d "$worker_path" ]; then
55
+ echo -e "${RED}❌ Warning: Worker directory not found: $worker_path${NC}"
56
+ continue
57
+ fi
58
+
59
+ # Check if package.json exists
60
+ if [ ! -f "$worker_path/package.json" ]; then
61
+ echo -e "${RED}❌ Warning: package.json not found in $worker_path${NC}"
62
+ continue
63
+ fi
64
+
65
+ # Change to worker directory and install dependencies
66
+ cd "$worker_path"
67
+
68
+ echo " Running npm install in $worker_path..."
69
+ if npm install; then
70
+ echo -e "${GREEN}✅ Successfully installed dependencies for $worker${NC}"
71
+ else
72
+ echo -e "${RED}❌ Failed to install dependencies for $worker${NC}"
73
+ exit 1
74
+ fi
75
+
76
+ echo ""
77
+ done
78
+
79
+ # Return to original directory
80
+ cd "$PROJECT_ROOT"
81
+
82
+ echo -e "${GREEN}🎉 All worker dependencies installed successfully!${NC}"
83
+ echo ""
84
+ echo -e "${BLUE}Summary:${NC}"
85
+ echo "- Installed dependencies for $total workers"
86
+ echo "- All workers are ready for development/deployment"
87
+ echo ""
@@ -0,0 +1,43 @@
1
+ const { spawnSync } = require('node:child_process');
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+
5
+ const eslintApiPath = require.resolve('eslint');
6
+ const eslintCliPath = path.resolve(path.dirname(eslintApiPath), '..', 'bin', 'eslint.js');
7
+
8
+ const gitignorePath = path.resolve(process.cwd(), '.gitignore');
9
+ const ignorePatterns = fs.existsSync(gitignorePath)
10
+ ? fs
11
+ .readFileSync(gitignorePath, 'utf8')
12
+ .split(/\r?\n/)
13
+ .map((line) => line.trim())
14
+ .filter((line) => line && !line.startsWith('#'))
15
+ : [];
16
+
17
+ const ignoreArgs = ignorePatterns.flatMap((pattern) => ['--ignore-pattern', pattern]);
18
+
19
+ const defaultArgs = [
20
+ ...ignoreArgs,
21
+ '--cache',
22
+ '--cache-location',
23
+ './node_modules/.cache/eslint',
24
+ '.',
25
+ ];
26
+
27
+ const passthroughArgs = process.argv.slice(2);
28
+ const eslintArgs = passthroughArgs.length > 0 ? passthroughArgs : defaultArgs;
29
+
30
+ const result = spawnSync(process.execPath, [eslintCliPath, ...eslintArgs], {
31
+ stdio: 'inherit',
32
+ env: process.env,
33
+ });
34
+
35
+ if (typeof result.status === 'number') {
36
+ process.exit(result.status);
37
+ }
38
+
39
+ if (result.error) {
40
+ console.error(result.error);
41
+ }
42
+
43
+ process.exit(1);
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Admin script to unenroll a user's TOTP MFA factor via Firebase Admin SDK.
3
+ * Run with: npm run unenroll-totp-mfa -- <uid>
4
+ *
5
+ * Requires app/config/admin-service.json (gitignored service account key).
6
+ * Docs: https://firebase.google.com/docs/auth/admin/manage-users#unenroll_a_user_from_mfa
7
+ */
8
+
9
+ import { createRequire } from 'module';
10
+ import { fileURLToPath } from 'url';
11
+ import { dirname, resolve } from 'path';
12
+ import { initializeApp, cert, getApps } from 'firebase-admin/app';
13
+ import { getAuth } from 'firebase-admin/auth';
14
+
15
+ const require = createRequire(import.meta.url);
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+
18
+ const uid = process.argv[2];
19
+
20
+ if (!uid) {
21
+ console.error('\n❌ No UID provided.');
22
+ console.error('\nUsage: npm run unenroll-totp-mfa -- <uid>');
23
+ process.exit(1);
24
+ }
25
+
26
+ const serviceAccountPath = resolve(__dirname, '../app/config/admin-service.json');
27
+
28
+ let serviceAccount;
29
+ try {
30
+ serviceAccount = require(serviceAccountPath);
31
+ } catch {
32
+ console.error(`\n❌ Could not load service account key from:\n ${serviceAccountPath}`);
33
+ console.error('\nMake sure app/config/admin-service.json exists (it is gitignored).');
34
+ process.exit(1);
35
+ }
36
+
37
+ if (getApps().length === 0) {
38
+ initializeApp({ credential: cert(serviceAccount) });
39
+ }
40
+
41
+ const auth = getAuth();
42
+
43
+ console.log(`\n🔍 Fetching MFA factors for UID: ${uid}...`);
44
+
45
+ let userRecord;
46
+ try {
47
+ userRecord = await auth.getUser(uid);
48
+ } catch (err) {
49
+ console.error(`\n❌ Could not fetch user record for UID: ${uid}`);
50
+ console.error(err?.message ?? err);
51
+ process.exit(1);
52
+ }
53
+
54
+ const enrolledFactors = userRecord.multiFactor?.enrolledFactors ?? [];
55
+ const totpFactors = enrolledFactors.filter((f) => f.factorId === 'totp');
56
+ const remainingFactors = enrolledFactors.filter((f) => f.factorId !== 'totp');
57
+
58
+ if (totpFactors.length === 0) {
59
+ console.log(`\nℹ️ No TOTP MFA factors found for UID: ${uid}`);
60
+ console.log(' Nothing to unenroll.');
61
+ process.exit(0);
62
+ }
63
+
64
+ console.log(`\n Found ${totpFactors.length} TOTP factor(s):`);
65
+ for (const factor of totpFactors) {
66
+ console.log(` - ${factor.uid} (displayName: ${factor.displayName ?? 'n/a'}, enrolled: ${factor.enrollmentTime})`);
67
+ }
68
+
69
+ try {
70
+ await auth.updateUser(uid, {
71
+ multiFactor: {
72
+ enrolledFactors: remainingFactors,
73
+ },
74
+ });
75
+
76
+ console.log(`\n✅ Successfully unenrolled ${totpFactors.length} TOTP factor(s) for UID: ${uid}`);
77
+ console.log(' The user will need to re-enroll TOTP on their next login.');
78
+ } catch (err) {
79
+ console.error(`\n❌ Failed to unenroll TOTP factor(s) for UID: ${uid}`);
80
+ console.error(err?.message ?? err);
81
+ process.exit(1);
82
+ }