@striae-org/striae 5.5.0 → 5.5.2

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 (32) hide show
  1. package/.env.example +9 -1
  2. package/app/components/actions/export-audit-pdf.ts +2 -3
  3. package/app/components/sidebar/notes/class-details/class-details-modal.tsx +3 -3
  4. package/app/components/sidebar/notes/notes-editor-form.tsx +244 -12
  5. package/app/components/sidebar/notes/notes-editor-modal.tsx +181 -2
  6. package/app/components/sidebar/notes/notes.module.css +77 -0
  7. package/app/components/user/user.module.css +1 -1
  8. package/app/routes/auth/login.example.tsx +17 -5
  9. package/functions/api/_shared/registration-allowlist.ts +38 -0
  10. package/functions/api/auth/can-register.ts +59 -0
  11. package/functions/api/user/[[path]].ts +34 -0
  12. package/members.emails.example +11 -0
  13. package/package.json +12 -12
  14. package/scripts/deploy-all.sh +2 -2
  15. package/scripts/deploy-members-emails.sh +102 -0
  16. package/scripts/deploy-pages-secrets.sh +13 -70
  17. package/scripts/deploy-primershear-emails.sh +7 -73
  18. package/scripts/update-markdown-versions.cjs +58 -1
  19. package/worker-configuration.d.ts +2 -1
  20. package/workers/audit-worker/package.json +13 -18
  21. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  22. package/workers/data-worker/package.json +13 -18
  23. package/workers/data-worker/wrangler.jsonc.example +1 -1
  24. package/workers/image-worker/package.json +13 -18
  25. package/workers/image-worker/wrangler.jsonc.example +1 -1
  26. package/workers/pdf-worker/package.json +14 -19
  27. package/workers/pdf-worker/src/audit-trail-report.ts +28 -6
  28. package/workers/pdf-worker/src/report-types.ts +1 -0
  29. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  30. package/workers/user-worker/package.json +13 -18
  31. package/workers/user-worker/wrangler.jsonc.example +1 -1
  32. package/wrangler.toml.example +1 -1
@@ -688,6 +688,76 @@ textarea:focus {
688
688
  justify-content: flex-end;
689
689
  }
690
690
 
691
+ .modalButtons .saveButton,
692
+ .modalButtons .cancelButton {
693
+ min-height: 42px;
694
+ margin: 0;
695
+ display: inline-flex;
696
+ align-items: center;
697
+ justify-content: center;
698
+ }
699
+
700
+ .classDetailsModalButtons .classDetailsModalAction {
701
+ min-height: 42px;
702
+ margin: 0;
703
+ display: inline-flex;
704
+ align-items: center;
705
+ justify-content: center;
706
+ }
707
+
708
+ .unsavedChangesModal {
709
+ max-width: 520px;
710
+ }
711
+
712
+ .unsavedChangesModal .modalTitle {
713
+ margin-top: 0;
714
+ }
715
+
716
+ .unsavedChangesMessage {
717
+ margin: 0.75rem;
718
+ color: #495057;
719
+ line-height: 1.5;
720
+ }
721
+
722
+ .unsavedChangesActions {
723
+ display: flex;
724
+ flex-direction: column;
725
+ gap: 0.75rem;
726
+ }
727
+
728
+ .unsavedChangesPrimaryRow {
729
+ display: grid;
730
+ grid-template-columns: repeat(2, minmax(0, 1fr));
731
+ gap: 0.75rem;
732
+ }
733
+
734
+ .unsavedChangesPrimaryAction {
735
+ width: 100%;
736
+ margin: 0;
737
+ }
738
+
739
+ .unsavedChangesPrimaryRow .cancelButton,
740
+ .unsavedChangesPrimaryRow .saveButton {
741
+ width: 100%;
742
+ margin: 0;
743
+ }
744
+
745
+ .secondaryButton {
746
+ width: 100%;
747
+ padding: 0.75rem;
748
+ border: 1.5px solid #ced4da;
749
+ border-radius: 6px;
750
+ background: white;
751
+ color: #495057;
752
+ font-weight: 500;
753
+ cursor: pointer;
754
+ transition: all 0.2s;
755
+ }
756
+
757
+ .secondaryButton:hover {
758
+ background-color: #f8f9fa;
759
+ }
760
+
691
761
  .cancelButton {
692
762
  width: 30%;
693
763
  padding: 0.75rem;
@@ -705,6 +775,13 @@ textarea:focus {
705
775
  background-color: #bd2130;
706
776
  }
707
777
 
778
+ .cancelButton:disabled,
779
+ .saveButton:disabled,
780
+ .secondaryButton:disabled {
781
+ opacity: 0.65;
782
+ cursor: not-allowed;
783
+ }
784
+
708
785
  .saveButton {
709
786
  width: 100%;
710
787
  padding: 0.75rem;
@@ -156,7 +156,7 @@
156
156
  .sectionLabel {
157
157
  display: block;
158
158
  font-weight: 600;
159
- color: var(--textBody);
159
+ color: var(--textTitle);
160
160
  margin-bottom: var(--spaceS);
161
161
  }
162
162
 
@@ -94,14 +94,26 @@ export const Login = () => {
94
94
  setIsClient(true);
95
95
  }, []);
96
96
 
97
- // Email validation with regex
98
- const validateRegistrationEmail = (email: string): { valid: boolean } => {
97
+ // Email validation with regex and registration gateway allowlist check
98
+ const validateRegistrationEmail = async (email: string): Promise<{ valid: boolean; message?: string }> => {
99
99
  const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
100
-
100
+
101
101
  if (!emailRegex.test(email)) {
102
102
  return { valid: false };
103
103
  }
104
104
 
105
+ try {
106
+ const response = await fetch(`/api/auth/can-register?email=${encodeURIComponent(email)}`);
107
+ if (response.status === 403) {
108
+ return {
109
+ valid: false,
110
+ message: 'Registration is limited to Striae membership. You may join at https://join.striae.org.'
111
+ };
112
+ }
113
+ } catch {
114
+ // Fail open on network error — server-side PUT guard provides defense-in-depth
115
+ }
116
+
105
117
  return { valid: true };
106
118
  };
107
119
 
@@ -282,9 +294,9 @@ export const Login = () => {
282
294
 
283
295
  try {
284
296
  if (!isLogin) {
285
- const emailValidation = validateRegistrationEmail(email);
297
+ const emailValidation = await validateRegistrationEmail(email);
286
298
  if (!emailValidation.valid) {
287
- setError('Please enter a valid email address');
299
+ setError(emailValidation.message ?? 'Please enter a valid email address');
288
300
  setIsLoading(false);
289
301
  return;
290
302
  }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Checks whether the given email is permitted to register based on the
3
+ * REGISTRATION_EMAILS secret (a comma-separated list of allowed entries).
4
+ *
5
+ * Each entry may be:
6
+ * - An exact email address: user@example.com
7
+ * - A domain wildcard: @example.com (matches any email from that domain)
8
+ *
9
+ * If registrationEmails is empty or unset, all registrations are allowed
10
+ * (backward-compatible — deploys without a members.emails file are unrestricted).
11
+ */
12
+ export function isEmailAllowed(email: string, registrationEmails: string): boolean {
13
+ if (!registrationEmails || registrationEmails.trim().length === 0) {
14
+ return true;
15
+ }
16
+
17
+ const normalizedEmail = email.toLowerCase().trim();
18
+ const entries = registrationEmails
19
+ .split(',')
20
+ .map(e => e.trim().toLowerCase())
21
+ .filter(Boolean);
22
+
23
+ for (const entry of entries) {
24
+ if (entry.startsWith('@')) {
25
+ // Domain wildcard: @example.com matches user@example.com
26
+ if (normalizedEmail.endsWith(entry)) {
27
+ return true;
28
+ }
29
+ } else {
30
+ // Exact email match
31
+ if (normalizedEmail === entry) {
32
+ return true;
33
+ }
34
+ }
35
+ }
36
+
37
+ return false;
38
+ }
@@ -0,0 +1,59 @@
1
+ import { isEmailAllowed } from '../_shared/registration-allowlist';
2
+
3
+ interface CanRegisterContext {
4
+ request: Request;
5
+ env: Env;
6
+ }
7
+
8
+ const SUPPORTED_METHODS = new Set(['GET', 'OPTIONS']);
9
+
10
+ function jsonResponse(payload: Record<string, unknown>, status: number = 200): Response {
11
+ return new Response(JSON.stringify(payload), {
12
+ status,
13
+ headers: {
14
+ 'Cache-Control': 'no-store',
15
+ 'Content-Type': 'application/json; charset=utf-8'
16
+ }
17
+ });
18
+ }
19
+
20
+ function textResponse(message: string, status: number): Response {
21
+ return new Response(message, {
22
+ status,
23
+ headers: {
24
+ 'Cache-Control': 'no-store',
25
+ 'Content-Type': 'text/plain; charset=utf-8'
26
+ }
27
+ });
28
+ }
29
+
30
+ export const onRequest = async ({ request, env }: CanRegisterContext): Promise<Response> => {
31
+ if (!SUPPORTED_METHODS.has(request.method)) {
32
+ return textResponse('Method not allowed', 405);
33
+ }
34
+
35
+ if (request.method === 'OPTIONS') {
36
+ return new Response(null, {
37
+ status: 204,
38
+ headers: {
39
+ 'Allow': 'GET, OPTIONS',
40
+ 'Cache-Control': 'no-store'
41
+ }
42
+ });
43
+ }
44
+
45
+ const url = new URL(request.url);
46
+ const email = url.searchParams.get('email');
47
+
48
+ if (!email || email.trim().length === 0) {
49
+ return textResponse('Missing required parameter: email', 400);
50
+ }
51
+
52
+ const registrationEmails = env.REGISTRATION_EMAILS ?? '';
53
+
54
+ if (isEmailAllowed(email, registrationEmails)) {
55
+ return jsonResponse({ allowed: true });
56
+ }
57
+
58
+ return jsonResponse({ allowed: false }, 403);
59
+ };
@@ -1,4 +1,5 @@
1
1
  import { verifyFirebaseIdentityFromRequest } from '../_shared/firebase-auth';
2
+ import { isEmailAllowed } from '../_shared/registration-allowlist';
2
3
 
3
4
  interface UserProxyContext {
4
5
  request: Request;
@@ -155,6 +156,39 @@ export const onRequest = async ({ request, env }: UserProxyContext): Promise<Res
155
156
  if (requestedUserId !== identity.uid) {
156
157
  return textResponse('Forbidden', 403);
157
158
  }
159
+
160
+ // Registration gateway: for PUT requests, check if this is a new user creation.
161
+ // If REGISTRATION_EMAILS is set and the user record does not yet exist, enforce the allowlist.
162
+ // This is defense-in-depth — the primary check runs client-side in the login flow.
163
+ if (request.method === 'PUT' && env.REGISTRATION_EMAILS && env.REGISTRATION_EMAILS.trim().length > 0) {
164
+ try {
165
+ const existenceCheckUrl = `${userWorkerBaseUrl}/${encodeURIComponent(requestedUserId)}`;
166
+ const existenceResponse = await fetch(existenceCheckUrl, {
167
+ method: 'GET',
168
+ headers: {
169
+ 'Accept': 'application/json',
170
+ 'X-Custom-Auth-Key': env.USER_DB_AUTH
171
+ }
172
+ });
173
+
174
+ if (existenceResponse.status === 404) {
175
+ // User does not exist yet — this is a registration PUT.
176
+ // Enforce the email allowlist.
177
+ if (!isEmailAllowed(identity.email ?? '', env.REGISTRATION_EMAILS)) {
178
+ return textResponse('Registration is not permitted for this email address', 403);
179
+ }
180
+ } else if (!existenceResponse.ok) {
181
+ // Existence check failed (non-404, non-2xx response).
182
+ // Fail closed: reject the registration to prevent allowlist bypass during errors.
183
+ return textResponse('Unable to verify registration eligibility', 502);
184
+ }
185
+ // If user already exists (200), proceed normally.
186
+ } catch {
187
+ // Fail closed: on network error with allowlist active, reject the request.
188
+ return textResponse('Unable to verify registration eligibility', 502);
189
+ }
190
+ }
191
+
158
192
  const upstreamUrl = `${userWorkerBaseUrl}${proxyPath}${requestUrl.search}`;
159
193
 
160
194
  const upstreamHeaders = new Headers();
@@ -0,0 +1,11 @@
1
+ # Registration gateway - authorized email addresses
2
+ # One entry per line. Lines starting with # are ignored.
3
+ # This file is untracked. Run: npm run deploy-members to push changes.
4
+ #
5
+ # Supported formats:
6
+ # Exact email: analyst@organization.com
7
+ # Domain wildcard: @organization.com (allows all emails from that domain)
8
+ #
9
+ # Examples:
10
+ # analyst@organization.com
11
+ # @striae.org
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@striae-org/striae",
3
- "version": "5.5.0",
3
+ "version": "5.5.2",
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",
@@ -52,11 +52,12 @@
52
52
  "workers/pdf-worker/src/formats/format-striae.ts",
53
53
  ".env.example",
54
54
  "primershear.emails.example",
55
+ "members.emails.example",
55
56
  "firebase.json",
56
57
  "tsconfig.json",
57
58
  "vite.config.ts",
58
59
  "/worker-configuration.d.ts",
59
- "wrangler.toml.example",
60
+ "wrangler.toml.example",
60
61
  "LICENSE"
61
62
  ],
62
63
  "sideEffects": false,
@@ -90,9 +91,10 @@
90
91
  "install-workers": "bash ./scripts/install-workers.sh",
91
92
  "deploy-workers": "npm run deploy-workers:audit && npm run deploy-workers:data && npm run deploy-workers:image && npm run deploy-workers:pdf && npm run deploy-workers:user",
92
93
  "deploy-workers:secrets": "bash ./scripts/deploy-worker-secrets.sh",
93
- "deploy-pages:secrets": "bash ./scripts/deploy-pages-secrets.sh --production-only",
94
+ "deploy-pages:secrets": "bash ./scripts/deploy-pages-secrets.sh",
94
95
  "deploy-pages": "bash ./scripts/deploy-pages.sh",
95
- "deploy-primershear": "bash ./scripts/deploy-primershear-emails.sh --production-only",
96
+ "deploy-primershear": "bash ./scripts/deploy-primershear-emails.sh",
97
+ "deploy-members": "bash ./scripts/deploy-members-emails.sh",
96
98
  "deploy-workers:audit": "cd workers/audit-worker && npm run deploy",
97
99
  "deploy-workers:data": "cd workers/data-worker && npm run deploy",
98
100
  "deploy-workers:image": "cd workers/image-worker && npm run deploy",
@@ -101,12 +103,12 @@
101
103
  },
102
104
  "dependencies": {
103
105
  "@react-router/cloudflare": "^7.14.0",
104
- "firebase": "^12.11.0",
106
+ "firebase": "^12.12.0",
105
107
  "isbot": "^5.1.37",
106
108
  "jszip": "^3.10.1",
107
109
  "qrcode": "^1.5.4",
108
- "react": "^19.2.4",
109
- "react-dom": "^19.2.4",
110
+ "react": "^19.2.5",
111
+ "react-dom": "^19.2.5",
110
112
  "react-router": "^7.14.0"
111
113
  },
112
114
  "devDependencies": {
@@ -123,17 +125,15 @@
123
125
  "eslint-plugin-jsx-a11y": "^6.10.2",
124
126
  "eslint-plugin-react": "^7.37.5",
125
127
  "eslint-plugin-react-hooks": "^7.0.1",
126
- "firebase-admin": "^13.7.0",
128
+ "firebase-admin": "^13.8.0",
127
129
  "modern-normalize": "^3.0.1",
128
130
  "typescript": "^5.9.3",
129
131
  "vite": "^7.3.2",
130
132
  "vite-tsconfig-paths": "^6.1.1",
131
- "wrangler": "^4.81.0"
133
+ "wrangler": "^4.81.1"
132
134
  },
133
135
  "overrides": {
134
- "@tootallnate/once": "3.0.1",
135
- "tar": "7.5.11",
136
- "undici": "7.24.1"
136
+ "@tootallnate/once": "3.0.1"
137
137
  },
138
138
  "engines": {
139
139
  "node": ">=20.19.0"
@@ -127,8 +127,8 @@ echo ""
127
127
  # Step 5: Deploy Pages Secrets
128
128
  echo -e "${PURPLE}Step 5/6: Deploying Pages Secrets${NC}"
129
129
  echo "----------------------------------"
130
- echo -e "${YELLOW}🔐 Deploying Pages environment variables to production only...${NC}"
131
- if ! bash "$SCRIPT_DIR/deploy-pages-secrets.sh" --production-only; then
130
+ echo -e "${YELLOW}🔐 Deploying Pages environment variables...${NC}"
131
+ if ! bash "$SCRIPT_DIR/deploy-pages-secrets.sh"; then
132
132
  echo -e "${RED}❌ Pages secrets deployment failed!${NC}"
133
133
  exit 1
134
134
  fi
@@ -0,0 +1,102 @@
1
+ #!/bin/bash
2
+
3
+ # ============================================
4
+ # MEMBERS EMAIL LIST DEPLOYMENT SCRIPT
5
+ # ============================================
6
+ # Reads members.emails, updates REGISTRATION_EMAILS in .env,
7
+ # then deploys that secret directly to Cloudflare Pages (production).
8
+
9
+ set -e
10
+ set -o pipefail
11
+
12
+ RED='\033[0;31m'
13
+ GREEN='\033[0;32m'
14
+ YELLOW='\033[1;33m'
15
+ BLUE='\033[0;34m'
16
+ NC='\033[0m'
17
+
18
+ echo -e "${BLUE}👥 Members Email List Deployment${NC}"
19
+ echo "=================================="
20
+
21
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
23
+ cd "$PROJECT_ROOT"
24
+
25
+ trap 'echo -e "\n${RED}❌ deploy-members-emails.sh failed near line ${LINENO}${NC}"' ERR
26
+
27
+ # ── Read emails file ──────────────────────────────────────────────────────────
28
+
29
+ EMAILS_FILE="$PROJECT_ROOT/members.emails"
30
+
31
+ if [ ! -f "$EMAILS_FILE" ]; then
32
+ echo -e "${RED}❌ members.emails not found at: $EMAILS_FILE${NC}"
33
+ echo -e "${YELLOW} Create it with one email address or @domain.com wildcard per line.${NC}"
34
+ echo -e "${YELLOW} See members.emails.example for the format.${NC}"
35
+ exit 1
36
+ fi
37
+
38
+ # Strip comment lines and blank lines, then join with commas
39
+ # Use || true to avoid failure if paste gets no input (handles empty file gracefully)
40
+ REGISTRATION_EMAILS=$(grep -v '^[[:space:]]*#' "$EMAILS_FILE" | grep -v '^[[:space:]]*$' | paste -sd ',' - || true)
41
+
42
+ if [ -z "$REGISTRATION_EMAILS" ]; then
43
+ echo -e "${YELLOW}⚠️ members.emails contains no active entries.${NC}"
44
+ echo -e "${YELLOW} The secret will be set to an empty string, disabling the gateway (open registration).${NC}"
45
+ fi
46
+
47
+ ENTRY_COUNT=$(echo "$REGISTRATION_EMAILS" | tr ',' '\n' | grep -c '[^[:space:]]' || true)
48
+ echo -e "${GREEN}✅ Loaded $ENTRY_COUNT entry(ies) from members.emails${NC}"
49
+
50
+ # ── Update .env ───────────────────────────────────────────────────────────────
51
+
52
+ ENV_FILE="$PROJECT_ROOT/.env"
53
+
54
+ if [ ! -f "$ENV_FILE" ]; then
55
+ echo -e "${RED}❌ .env not found. Run deploy-config first.${NC}"
56
+ exit 1
57
+ fi
58
+
59
+ # Replace the REGISTRATION_EMAILS= line in .env (handles both empty and populated values)
60
+ if grep -q '^REGISTRATION_EMAILS=' "$ENV_FILE"; then
61
+ # Use a temp file to avoid sed -i portability issues across macOS/Linux
62
+ local_tmp=$(mktemp)
63
+ sed "s|^REGISTRATION_EMAILS=.*|REGISTRATION_EMAILS=${REGISTRATION_EMAILS}|" "$ENV_FILE" > "$local_tmp"
64
+ mv "$local_tmp" "$ENV_FILE"
65
+ echo -e "${GREEN}✅ Updated REGISTRATION_EMAILS in .env${NC}"
66
+ else
67
+ echo "" >> "$ENV_FILE"
68
+ echo "REGISTRATION_EMAILS=${REGISTRATION_EMAILS}" >> "$ENV_FILE"
69
+ echo -e "${GREEN}✅ Appended REGISTRATION_EMAILS to .env${NC}"
70
+ fi
71
+
72
+ # ── Deploy to Cloudflare Pages ────────────────────────────────────────────────
73
+
74
+ if ! command -v wrangler > /dev/null 2>&1; then
75
+ echo -e "${RED}❌ wrangler is not installed or not in PATH${NC}"
76
+ exit 1
77
+ fi
78
+
79
+ source "$ENV_FILE"
80
+
81
+ PAGES_PROJECT_NAME=$(echo "$PAGES_PROJECT_NAME" | tr -d '\r')
82
+ if [ -z "$PAGES_PROJECT_NAME" ]; then
83
+ echo -e "${RED}❌ PAGES_PROJECT_NAME is missing from .env${NC}"
84
+ exit 1
85
+ fi
86
+
87
+ echo -e "${YELLOW} Setting REGISTRATION_EMAILS for production...${NC}"
88
+ printf '%s' "$REGISTRATION_EMAILS" | wrangler pages secret put REGISTRATION_EMAILS \
89
+ --project-name "$PAGES_PROJECT_NAME"
90
+
91
+ echo -e "${GREEN}✅ REGISTRATION_EMAILS deployed to production${NC}"
92
+
93
+ # Deploy Pages so the new secret takes effect immediately
94
+ echo -e "\n${YELLOW}🚀 Building and deploying Pages to activate new secret...${NC}"
95
+
96
+ if ! npm run deploy-pages; then
97
+ echo -e "${RED}❌ Pages deployment failed${NC}"
98
+ exit 1
99
+ fi
100
+ echo -e "${GREEN}✅ Pages deployment complete${NC}"
101
+
102
+ echo -e "\n${GREEN}🎉 Members email list deployment complete!${NC}"
@@ -24,46 +24,6 @@ cd "$PROJECT_ROOT"
24
24
 
25
25
  trap 'echo -e "\n${RED}❌ deploy-pages-secrets.sh failed near line ${LINENO}${NC}"' ERR
26
26
 
27
- show_help=false
28
- deploy_production=true
29
- deploy_preview=true
30
-
31
- for arg in "$@"; do
32
- case "$arg" in
33
- -h|--help)
34
- show_help=true
35
- ;;
36
- --production-only)
37
- deploy_production=true
38
- deploy_preview=false
39
- ;;
40
- --preview-only)
41
- deploy_production=false
42
- deploy_preview=true
43
- ;;
44
- *)
45
- echo -e "${RED}❌ Unknown option: $arg${NC}"
46
- echo "Use --help to see supported options."
47
- exit 1
48
- ;;
49
- esac
50
- done
51
-
52
- if [ "$show_help" = "true" ]; then
53
- echo "Usage: bash ./scripts/deploy-pages-secrets.sh [--production-only|--preview-only]"
54
- echo ""
55
- echo "Options:"
56
- echo " --production-only Deploy secrets only to the production Pages environment"
57
- echo " --preview-only Deploy secrets only to the preview Pages environment"
58
- echo " -h, --help Show this help message"
59
- exit 0
60
- fi
61
-
62
- if [ "$deploy_production" != "true" ] && [ "$deploy_preview" != "true" ]; then
63
- echo -e "${RED}❌ No target environment selected${NC}"
64
- exit 1
65
- fi
66
-
67
27
  require_command() {
68
28
  local cmd=$1
69
29
  if ! command -v "$cmd" > /dev/null 2>&1; then
@@ -145,40 +105,29 @@ get_optional_value() {
145
105
  printf '%s' "$value"
146
106
  }
147
107
 
148
- set_pages_secret() {
149
- local secret_name=$1
150
- local secret_value=$2
151
- local pages_env=$3
152
-
153
- echo -e "${YELLOW} Setting $secret_name for $pages_env...${NC}"
154
-
155
- if [ "$pages_env" = "production" ]; then
156
- printf '%s' "$secret_value" | wrangler pages secret put "$secret_name" --project-name "$PAGES_PROJECT_NAME"
157
- return 0
158
- fi
159
-
160
- printf '%s' "$secret_value" | wrangler pages secret put "$secret_name" --project-name "$PAGES_PROJECT_NAME" --env "$pages_env"
161
- }
162
-
163
- deploy_pages_environment_secrets() {
164
- local pages_env=$1
108
+ deploy_pages_secrets() {
165
109
  local secret
166
110
  local secret_value
167
111
 
168
- echo -e "\n${BLUE}🔧 Deploying Pages secrets to $pages_env...${NC}"
112
+ echo -e "\n${BLUE}🔧 Deploying Pages secrets to production...${NC}"
169
113
 
170
114
  for secret in "${required_pages_secrets[@]}"; do
171
115
  secret_value=$(get_required_value "$secret")
172
- set_pages_secret "$secret" "$secret_value" "$pages_env"
116
+ echo -e "${YELLOW} Setting $secret...${NC}"
117
+ printf '%s' "$secret_value" | wrangler pages secret put "$secret" --project-name "$PAGES_PROJECT_NAME"
173
118
  done
174
119
 
175
120
  local optional_primershear_emails
176
121
  optional_primershear_emails=$(get_optional_value "PRIMERSHEAR_EMAILS")
177
- if [ -n "$optional_primershear_emails" ]; then
178
- set_pages_secret "PRIMERSHEAR_EMAILS" "$optional_primershear_emails" "$pages_env"
179
- fi
122
+ echo -e "${YELLOW} Setting PRIMERSHEAR_EMAILS...${NC}"
123
+ printf '%s' "$optional_primershear_emails" | wrangler pages secret put "PRIMERSHEAR_EMAILS" --project-name "$PAGES_PROJECT_NAME"
180
124
 
181
- echo -e "${GREEN}✅ Pages secrets deployed to $pages_env${NC}"
125
+ local optional_registration_emails
126
+ optional_registration_emails=$(get_optional_value "REGISTRATION_EMAILS")
127
+ echo -e "${YELLOW} Setting REGISTRATION_EMAILS...${NC}"
128
+ printf '%s' "$optional_registration_emails" | wrangler pages secret put "REGISTRATION_EMAILS" --project-name "$PAGES_PROJECT_NAME"
129
+
130
+ echo -e "${GREEN}✅ Pages secrets deployed to production${NC}"
182
131
  }
183
132
 
184
133
  require_command wrangler
@@ -220,12 +169,6 @@ for secret in "${required_pages_secrets[@]}"; do
220
169
  done
221
170
  echo -e "${GREEN}✅ Required Pages secret values found${NC}"
222
171
 
223
- if [ "$deploy_production" = "true" ]; then
224
- deploy_pages_environment_secrets "production"
225
- fi
226
-
227
- if [ "$deploy_preview" = "true" ]; then
228
- deploy_pages_environment_secrets "preview"
229
- fi
172
+ deploy_pages_secrets
230
173
 
231
174
  echo -e "\n${GREEN}🎉 Pages secrets deployment completed!${NC}"