@striae-org/striae 5.1.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +22 -2
- package/app/components/actions/case-export/download-handlers.ts +18 -1
- package/app/components/actions/case-manage.ts +17 -1
- package/app/components/actions/generate-pdf.ts +9 -3
- package/app/components/actions/image-manage.ts +43 -11
- package/app/routes/striae/striae.tsx +1 -0
- package/app/types/file.ts +18 -2
- package/app/utils/api/image-api-client.ts +49 -1
- package/app/utils/data/permissions.ts +4 -2
- package/functions/api/image/[[path]].ts +2 -1
- package/package.json +4 -4
- package/scripts/deploy-config/modules/env-utils.sh +322 -0
- package/scripts/deploy-config/modules/keys.sh +404 -0
- package/scripts/deploy-config/modules/prompt.sh +372 -0
- package/scripts/deploy-config/modules/scaffolding.sh +336 -0
- package/scripts/deploy-config/modules/validation.sh +365 -0
- package/scripts/deploy-config.sh +59 -1556
- package/scripts/deploy-worker-secrets.sh +101 -6
- package/worker-configuration.d.ts +9 -4
- package/workers/audit-worker/package.json +1 -1
- package/workers/audit-worker/src/audit-worker.example.ts +188 -6
- package/workers/audit-worker/wrangler.jsonc.example +1 -1
- package/workers/data-worker/package.json +1 -1
- package/workers/data-worker/src/data-worker.example.ts +344 -32
- package/workers/data-worker/wrangler.jsonc.example +2 -4
- package/workers/image-worker/package.json +1 -1
- package/workers/image-worker/src/image-worker.example.ts +456 -20
- package/workers/image-worker/worker-configuration.d.ts +3 -2
- package/workers/image-worker/wrangler.jsonc.example +1 -1
- package/workers/keys-worker/package.json +1 -1
- package/workers/keys-worker/wrangler.jsonc.example +1 -1
- package/workers/pdf-worker/package.json +1 -1
- package/workers/pdf-worker/src/pdf-worker.example.ts +0 -1
- package/workers/pdf-worker/wrangler.jsonc.example +1 -5
- package/workers/user-worker/package.json +17 -17
- package/workers/user-worker/src/encryption-utils.ts +244 -0
- package/workers/user-worker/src/user-worker.example.ts +333 -31
- package/workers/user-worker/wrangler.jsonc.example +1 -1
- package/wrangler.toml.example +1 -1
- package/scripts/encrypt-r2-backfill.mjs +0 -376
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
escape_for_sed_pattern() {
|
|
4
|
+
printf '%s' "$1" | sed -e 's/[][\\.^$*+?{}|()]/\\&/g'
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
dedupe_env_var_entries() {
|
|
8
|
+
local var_name=$1
|
|
9
|
+
local expected_count=1
|
|
10
|
+
local escaped_var_name
|
|
11
|
+
|
|
12
|
+
escaped_var_name=$(escape_for_sed_pattern "$var_name")
|
|
13
|
+
|
|
14
|
+
if [ -f ".env.example" ]; then
|
|
15
|
+
expected_count=$(grep -c "^$escaped_var_name=" .env.example || true)
|
|
16
|
+
|
|
17
|
+
if [ "$expected_count" -lt 1 ]; then
|
|
18
|
+
expected_count=1
|
|
19
|
+
fi
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
awk -v key="$var_name" -v keep="$expected_count" '
|
|
23
|
+
BEGIN { seen = 0 }
|
|
24
|
+
{
|
|
25
|
+
if (index($0, key "=") == 1) {
|
|
26
|
+
seen++
|
|
27
|
+
|
|
28
|
+
if (seen > keep) {
|
|
29
|
+
next
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
print
|
|
33
|
+
}
|
|
34
|
+
' .env > .env.tmp && mv .env.tmp .env
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
normalize_domain_value() {
|
|
38
|
+
local domain="$1"
|
|
39
|
+
|
|
40
|
+
domain=$(printf '%s' "$domain" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
|
41
|
+
domain="${domain#http://}"
|
|
42
|
+
domain="${domain#https://}"
|
|
43
|
+
domain="${domain%/}"
|
|
44
|
+
|
|
45
|
+
printf '%s' "$domain"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
normalize_worker_label_value() {
|
|
49
|
+
local label="$1"
|
|
50
|
+
|
|
51
|
+
label=$(normalize_domain_value "$label")
|
|
52
|
+
label="${label#.}"
|
|
53
|
+
label="${label%.}"
|
|
54
|
+
label=$(printf '%s' "$label" | tr '[:upper:]' '[:lower:]')
|
|
55
|
+
|
|
56
|
+
printf '%s' "$label"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
normalize_worker_subdomain_value() {
|
|
60
|
+
local subdomain="$1"
|
|
61
|
+
|
|
62
|
+
subdomain=$(normalize_domain_value "$subdomain")
|
|
63
|
+
subdomain="${subdomain#.}"
|
|
64
|
+
subdomain="${subdomain%.}"
|
|
65
|
+
subdomain=$(printf '%s' "$subdomain" | tr '[:upper:]' '[:lower:]')
|
|
66
|
+
|
|
67
|
+
printf '%s' "$subdomain"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
is_valid_worker_label() {
|
|
71
|
+
local label="$1"
|
|
72
|
+
|
|
73
|
+
[[ "$label" =~ ^[a-z0-9-]+$ ]]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
is_valid_worker_subdomain() {
|
|
77
|
+
local subdomain="$1"
|
|
78
|
+
|
|
79
|
+
[[ "$subdomain" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$ ]]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
strip_carriage_returns() {
|
|
83
|
+
printf '%s' "$1" | tr -d '\r'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
read_env_var_from_file() {
|
|
87
|
+
local env_file=$1
|
|
88
|
+
local var_name=$2
|
|
89
|
+
|
|
90
|
+
if [ ! -f "$env_file" ]; then
|
|
91
|
+
return 0
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
awk -v key="$var_name" '
|
|
95
|
+
index($0, key "=") == 1 {
|
|
96
|
+
value = substr($0, length(key) + 2)
|
|
97
|
+
}
|
|
98
|
+
END {
|
|
99
|
+
if (value != "") {
|
|
100
|
+
gsub(/\r/, "", value)
|
|
101
|
+
gsub(/^"/, "", value)
|
|
102
|
+
gsub(/"$/, "", value)
|
|
103
|
+
print value
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
' "$env_file"
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
resolve_existing_domain_value() {
|
|
110
|
+
local var_name=$1
|
|
111
|
+
local current_value=$2
|
|
112
|
+
local preserved_value=""
|
|
113
|
+
|
|
114
|
+
current_value=$(normalize_domain_value "$current_value")
|
|
115
|
+
|
|
116
|
+
if [ "$current_value" = "$var_name" ]; then
|
|
117
|
+
current_value=""
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
if [ -n "$current_value" ] && ! is_placeholder "$current_value"; then
|
|
121
|
+
printf '%s' "$current_value"
|
|
122
|
+
return 0
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
if [ -n "$preserved_domain_env_file" ] && [ -f "$preserved_domain_env_file" ]; then
|
|
126
|
+
preserved_value=$(read_env_var_from_file "$preserved_domain_env_file" "$var_name")
|
|
127
|
+
preserved_value=$(normalize_domain_value "$preserved_value")
|
|
128
|
+
|
|
129
|
+
if [ "$preserved_value" = "$var_name" ]; then
|
|
130
|
+
preserved_value=""
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
if [ -n "$preserved_value" ] && ! is_placeholder "$preserved_value"; then
|
|
134
|
+
printf '%s' "$preserved_value"
|
|
135
|
+
return 0
|
|
136
|
+
fi
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
printf '%s' "$current_value"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
restore_env_var_from_backup_if_missing() {
|
|
143
|
+
local var_name=$1
|
|
144
|
+
local current_value="${!var_name}"
|
|
145
|
+
local preserved_value=""
|
|
146
|
+
|
|
147
|
+
if [ "$update_env" != "true" ]; then
|
|
148
|
+
return 0
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
if [ -z "$preserved_domain_env_file" ] || [ ! -f "$preserved_domain_env_file" ]; then
|
|
152
|
+
return 0
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
current_value=$(strip_carriage_returns "$current_value")
|
|
156
|
+
|
|
157
|
+
if [ -n "$current_value" ] && ! is_placeholder "$current_value"; then
|
|
158
|
+
return 0
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
preserved_value=$(read_env_var_from_file "$preserved_domain_env_file" "$var_name")
|
|
162
|
+
preserved_value=$(strip_carriage_returns "$preserved_value")
|
|
163
|
+
|
|
164
|
+
if [ -z "$preserved_value" ] || is_placeholder "$preserved_value"; then
|
|
165
|
+
return 0
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
printf -v "$var_name" '%s' "$preserved_value"
|
|
169
|
+
export "$var_name"
|
|
170
|
+
write_env_var "$var_name" "$preserved_value"
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
confirm_key_pair_regeneration() {
|
|
174
|
+
local key_pair_label=$1
|
|
175
|
+
local impact_warning=$2
|
|
176
|
+
local regenerate_choice=""
|
|
177
|
+
|
|
178
|
+
if [ "$force_rotate_keys" = "true" ]; then
|
|
179
|
+
echo -e "${YELLOW}⚠️ Auto-confirmed regeneration for $key_pair_label key pair (--force-rotate-keys).${NC}"
|
|
180
|
+
return 0
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
echo -e "${GREEN}Current $key_pair_label key pair: [HIDDEN]${NC}"
|
|
184
|
+
|
|
185
|
+
if [ -n "$impact_warning" ]; then
|
|
186
|
+
echo -e "${YELLOW}⚠️ $impact_warning${NC}"
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
read -p "Regenerate $key_pair_label key pair? (press Enter to keep current, or type 'y' to regenerate): " regenerate_choice
|
|
190
|
+
regenerate_choice=$(strip_carriage_returns "$regenerate_choice")
|
|
191
|
+
|
|
192
|
+
if [ "$regenerate_choice" = "y" ] || [ "$regenerate_choice" = "Y" ]; then
|
|
193
|
+
return 0
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
return 1
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
generate_worker_subdomain_label() {
|
|
200
|
+
node -e "const { randomInt } = require('crypto'); const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'; let value = ''; for (let index = 0; index < 10; index += 1) { value += alphabet[randomInt(alphabet.length)]; } process.stdout.write(value);" 2>/dev/null
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
compose_worker_domain() {
|
|
204
|
+
local worker_name=$1
|
|
205
|
+
local worker_subdomain=$2
|
|
206
|
+
|
|
207
|
+
worker_name=$(normalize_worker_label_value "$worker_name")
|
|
208
|
+
worker_subdomain=$(normalize_worker_subdomain_value "$worker_subdomain")
|
|
209
|
+
|
|
210
|
+
if [ -z "$worker_name" ] || [ -z "$worker_subdomain" ]; then
|
|
211
|
+
return 1
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
if ! is_valid_worker_label "$worker_name" || ! is_valid_worker_subdomain "$worker_subdomain"; then
|
|
215
|
+
return 1
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
printf '%s.%s' "$worker_name" "$worker_subdomain"
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
infer_worker_subdomain_from_domain() {
|
|
222
|
+
local worker_name=$1
|
|
223
|
+
local worker_domain=$2
|
|
224
|
+
local worker_subdomain=""
|
|
225
|
+
|
|
226
|
+
worker_name=$(normalize_worker_label_value "$worker_name")
|
|
227
|
+
worker_domain=$(normalize_domain_value "$worker_domain")
|
|
228
|
+
worker_domain=$(printf '%s' "$worker_domain" | tr '[:upper:]' '[:lower:]')
|
|
229
|
+
|
|
230
|
+
if [ -z "$worker_name" ] || [ -z "$worker_domain" ] || is_placeholder "$worker_name" || is_placeholder "$worker_domain"; then
|
|
231
|
+
printf '%s' ""
|
|
232
|
+
return 0
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
case "$worker_domain" in
|
|
236
|
+
"$worker_name".*)
|
|
237
|
+
worker_subdomain="${worker_domain#${worker_name}.}"
|
|
238
|
+
worker_subdomain=$(normalize_worker_subdomain_value "$worker_subdomain")
|
|
239
|
+
|
|
240
|
+
if is_valid_worker_subdomain "$worker_subdomain"; then
|
|
241
|
+
printf '%s' "$worker_subdomain"
|
|
242
|
+
return 0
|
|
243
|
+
fi
|
|
244
|
+
;;
|
|
245
|
+
esac
|
|
246
|
+
|
|
247
|
+
printf '%s' ""
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
write_env_var() {
|
|
251
|
+
local var_name=$1
|
|
252
|
+
local var_value=$2
|
|
253
|
+
local env_file_value="$var_value"
|
|
254
|
+
|
|
255
|
+
var_value=$(strip_carriage_returns "$var_value")
|
|
256
|
+
env_file_value="$var_value"
|
|
257
|
+
|
|
258
|
+
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" ] || [ "$var_name" = "USER_KV_ENCRYPTION_PRIVATE_KEY" ] || [ "$var_name" = "USER_KV_ENCRYPTION_PUBLIC_KEY" ] || [ "$var_name" = "EXPORT_ENCRYPTION_KEYS_JSON" ] || [ "$var_name" = "DATA_AT_REST_ENCRYPTION_KEYS_JSON" ] || [ "$var_name" = "USER_KV_ENCRYPTION_KEYS_JSON" ]; then
|
|
259
|
+
# Store as a quoted string so sourced .env preserves escaped newline markers (\n)
|
|
260
|
+
env_file_value=${env_file_value//\"/\\\"}
|
|
261
|
+
env_file_value="\"$env_file_value\""
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
local escaped_var_name
|
|
265
|
+
local replacement_line
|
|
266
|
+
escaped_var_name=$(escape_for_sed_pattern "$var_name")
|
|
267
|
+
replacement_line=$(escape_for_sed_replacement "$var_name=$env_file_value")
|
|
268
|
+
|
|
269
|
+
if grep -q "^$escaped_var_name=" .env; then
|
|
270
|
+
# Replace all occurrences so intentional duplicates in .env.example stay in sync.
|
|
271
|
+
sed -i "s|^$escaped_var_name=.*|$replacement_line|g" .env
|
|
272
|
+
dedupe_env_var_entries "$var_name"
|
|
273
|
+
else
|
|
274
|
+
echo "$var_name=$env_file_value" >> .env
|
|
275
|
+
fi
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
update_private_key_registry() {
|
|
279
|
+
local registry_var_name=$1
|
|
280
|
+
local active_key_var_name=$2
|
|
281
|
+
local current_key_id=$3
|
|
282
|
+
local private_key_value=$4
|
|
283
|
+
local registry_label=$5
|
|
284
|
+
local existing_registry_json=""
|
|
285
|
+
local updated_registry_json=""
|
|
286
|
+
local registry_entry_count=""
|
|
287
|
+
|
|
288
|
+
if [ -z "$registry_var_name" ] || [ -z "$active_key_var_name" ] || [ -z "$current_key_id" ] || [ -z "$private_key_value" ]; then
|
|
289
|
+
echo -e "${YELLOW}⚠️ Skipping $registry_label key registry update due to missing inputs${NC}"
|
|
290
|
+
return 0
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
existing_registry_json="${!registry_var_name}"
|
|
294
|
+
existing_registry_json=$(strip_carriage_returns "$existing_registry_json")
|
|
295
|
+
|
|
296
|
+
if [ -z "$existing_registry_json" ] || is_placeholder "$existing_registry_json"; then
|
|
297
|
+
existing_registry_json="{}"
|
|
298
|
+
fi
|
|
299
|
+
|
|
300
|
+
updated_registry_json=$(node -e "const raw = process.argv[1] || '{}'; const keyId = process.argv[2] || ''; const privateKey = process.argv[3] || ''; if (!keyId || !privateKey) process.exit(1); const normalized = { activeKeyId: null, keys: {} }; try { const parsed = JSON.parse(raw); if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { if (parsed.keys && typeof parsed.keys === 'object' && !Array.isArray(parsed.keys)) { normalized.keys = Object.fromEntries(Object.entries(parsed.keys).filter(([id, pem]) => typeof id === 'string' && id.trim().length > 0 && typeof pem === 'string' && pem.trim().length > 0)); if (typeof parsed.activeKeyId === 'string' && parsed.activeKeyId.trim().length > 0) normalized.activeKeyId = parsed.activeKeyId.trim(); } else { normalized.keys = Object.fromEntries(Object.entries(parsed).filter(([id, pem]) => id !== 'activeKeyId' && id !== 'keys' && typeof id === 'string' && id.trim().length > 0 && typeof pem === 'string' && pem.trim().length > 0)); if (typeof parsed.activeKeyId === 'string' && parsed.activeKeyId.trim().length > 0) normalized.activeKeyId = parsed.activeKeyId.trim(); } } } catch (_) {} normalized.keys[keyId] = privateKey; normalized.activeKeyId = keyId; process.stdout.write(JSON.stringify(normalized));" "$existing_registry_json" "$current_key_id" "$private_key_value" 2>/dev/null || true)
|
|
301
|
+
|
|
302
|
+
if [ -z "$updated_registry_json" ]; then
|
|
303
|
+
echo -e "${RED}❌ Error: Failed to update $registry_label key registry JSON${NC}"
|
|
304
|
+
exit 1
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
printf -v "$registry_var_name" '%s' "$updated_registry_json"
|
|
308
|
+
export "$registry_var_name"
|
|
309
|
+
write_env_var "$registry_var_name" "$updated_registry_json"
|
|
310
|
+
|
|
311
|
+
printf -v "$active_key_var_name" '%s' "$current_key_id"
|
|
312
|
+
export "$active_key_var_name"
|
|
313
|
+
write_env_var "$active_key_var_name" "$current_key_id"
|
|
314
|
+
|
|
315
|
+
registry_entry_count=$(node -e "const raw = process.argv[1] || '{}'; try { const parsed = JSON.parse(raw); if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { process.stdout.write('0'); process.exit(0); } const keys = parsed.keys && typeof parsed.keys === 'object' && !Array.isArray(parsed.keys) ? parsed.keys : parsed; const count = Object.entries(keys).filter(([id, pem]) => id !== 'activeKeyId' && id !== 'keys' && typeof id === 'string' && id.trim().length > 0 && typeof pem === 'string' && pem.trim().length > 0).length; process.stdout.write(String(count)); } catch (_) { process.stdout.write('0'); }" "$updated_registry_json")
|
|
316
|
+
echo -e "${GREEN}✅ Updated $registry_label key registry ($registry_entry_count key IDs tracked)${NC}"
|
|
317
|
+
echo -e "${GREEN}✅ $active_key_var_name: $current_key_id${NC}"
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
escape_for_sed_replacement() {
|
|
321
|
+
printf '%s' "$1" | sed -e 's/[&|\\]/\\&/g'
|
|
322
|
+
}
|