@auth-craft/aws-cf-stack 1.1.0 → 1.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/lib/common.sh CHANGED
@@ -227,6 +227,17 @@ print_outputs_json() {
227
227
  jwtAlg="$(lambda_env "$fn" JWT_ALGORITHM)"; jwtAlg="${jwtAlg:-${LAMBDA_JWT_ALGORITHM:-EdDSA}}"
228
228
  svcPub="$(lambda_env "$fn" INTERNAL_JWT_PUBLIC_KEY)"; svcPub="${svcPub:-${LAMBDA_INTERNAL_JWT_PUBLIC_KEY:-}}"
229
229
 
230
+ # Worker names per scope (mirror deploy-gateway.sh: ${APP_NAME}-${STAGE}-${scope}).
231
+ # These are what a consumer's Cloudflare Pages project references in a Service Binding
232
+ # (`[[services]] service = "<worker name>"`) to front /{scope}/auth/* same-origin via
233
+ # @auth-craft/pages-auth-proxy. The binding lives in the consumer's Pages project, not
234
+ # here — `serviceBindings` is a ready-to-paste snippet they drop into their wrangler.toml.
235
+ local wnSystem="${APP_NAME}-${STAGE}-system"
236
+ local wnTenant="${APP_NAME}-${STAGE}-tenant"
237
+ local wnCustomer="${APP_NAME}-${STAGE}-customer"
238
+ local bindingSnippet
239
+ bindingSnippet=$(printf '[[services]]\nbinding = "AUTH_GATEWAY"\nservice = "%s"\n' "$wnCustomer")
240
+
230
241
  jq -n \
231
242
  --arg url "$url" --arg base "$base" --arg name "$name" \
232
243
  --arg table "$table" --arg backend "$backend" \
@@ -240,13 +251,17 @@ print_outputs_json() {
240
251
  --arg rpSystem "${CF_WORKER_ROUTE_PREFIX_SYSTEM:-/system}" \
241
252
  --arg rpTenant "${CF_WORKER_ROUTE_PREFIX_TENANT:-/tenant}" \
242
253
  --arg rpCustomer "${CF_WORKER_ROUTE_PREFIX_CUSTOMER:-/customer}" \
254
+ --arg wnSystem "$wnSystem" --arg wnTenant "$wnTenant" --arg wnCustomer "$wnCustomer" \
255
+ --arg bindingSnippet "$bindingSnippet" \
243
256
  '{stage:$stage, project:$project, region:$region,
244
257
  lambdaFunctionUrl:$url, apiBasePath:$base, authSystemName:$name,
245
258
  dynamodbTable:$table, backendUrl:$backend,
246
259
  jwtPublicKey:$jwtPub, jwtIssuer:$jwtIssuer, jwtAlgorithm:$jwtAlg,
247
260
  serviceJwtPublicKey:$svcPub,
248
261
  gatewayUrls:{system:$gwSystem, tenant:$gwTenant, customer:$gwCustomer},
249
- routePrefixes:{system:$rpSystem, tenant:$rpTenant, customer:$rpCustomer}}'
262
+ workerNames:{system:$wnSystem, tenant:$wnTenant, customer:$wnCustomer},
263
+ routePrefixes:{system:$rpSystem, tenant:$rpTenant, customer:$rpCustomer},
264
+ pagesProxy:{serviceBindingExample:$bindingSnippet}}'
250
265
  }
251
266
 
252
267
  # ── Tool command resolution (#7: let a consumer pin its own binaries) ─────────
@@ -30,6 +30,33 @@ custom_domain_for() {
30
30
  echo "${!var:-}"
31
31
  }
32
32
 
33
+ # Resolve per-scope static CORS origins. Precedence:
34
+ # CF_ALLOWED_ORIGINS_<SCOPE> > CF_ALLOWED_ORIGINS > LAMBDA_CORS_ORIGINS > '*'.
35
+ allowed_origins_for() {
36
+ local scope="$1" upper var
37
+ upper="$(echo "$scope" | tr '[:lower:]' '[:upper:]')"
38
+ var="CF_ALLOWED_ORIGINS_${upper}"
39
+ echo "${!var:-${CF_ALLOWED_ORIGINS:-${LAMBDA_CORS_ORIGINS:-*}}}"
40
+ }
41
+
42
+ # Resolve per-scope dynamic origin SUFFIXES (e.g. '.myplatform.com'). Precedence:
43
+ # CF_ALLOWED_ORIGIN_SUFFIXES_<SCOPE> > CF_ALLOWED_ORIGIN_SUFFIXES > '' (disabled).
44
+ origin_suffixes_for() {
45
+ local scope="$1" upper var
46
+ upper="$(echo "$scope" | tr '[:lower:]' '[:upper:]')"
47
+ var="CF_ALLOWED_ORIGIN_SUFFIXES_${upper}"
48
+ echo "${!var:-${CF_ALLOWED_ORIGIN_SUFFIXES:-}}"
49
+ }
50
+
51
+ # Resolve a per-scope CORS KV namespace id (CF_CORS_KV_ID_CUSTOMER, …) — empty = no
52
+ # dynamic KV allowlist for that scope. Typically only the customer scope sets this.
53
+ cors_kv_id_for() {
54
+ local scope="$1" upper var
55
+ upper="$(echo "$scope" | tr '[:lower:]' '[:upper:]')"
56
+ var="CF_CORS_KV_ID_${upper}"
57
+ echo "${!var:-}"
58
+ }
59
+
33
60
  # Enable the workers.dev subdomain for a worker with no custom domain (so it's reachable).
34
61
  enable_workers_dev() {
35
62
  local worker_name="$1"
@@ -60,11 +87,10 @@ deploy_gateway() {
60
87
  # Anti-drift: BACKEND_URL the worker forwards onto = Function URL + API base path.
61
88
  local backend_url="${LAMBDA_FUNCTION_URL%/}${API_BASE_PATH}"
62
89
 
63
- # Shared config across the 3 workers.
64
- local environment jwt_issuer allowed_origins
90
+ # Shared config across the 3 workers (CORS is now resolved PER SCOPE below).
91
+ local environment jwt_issuer
65
92
  environment="$RUNTIME_NODE_ENV"
66
93
  jwt_issuer="${LAMBDA_JWT_ISSUER:-${AUTH_SYSTEM_NAME:-$RESOURCE_NAME}}"
67
- allowed_origins="${CF_ALLOWED_ORIGINS:-${LAMBDA_CORS_ORIGINS:-*}}"
68
94
 
69
95
  local jwt_alg="${LAMBDA_JWT_ALGORITHM:-EdDSA}"
70
96
  local svc_route="${LAMBDA_SERVICE_ROUTE_PATH:-/internal}"
@@ -74,22 +100,43 @@ deploy_gateway() {
74
100
 
75
101
  GATEWAY_URLS=()
76
102
  local scope worker_name route_prefix custom_domain url
103
+ local allowed_origins origin_suffixes cors_kv_id config_file
77
104
  for scope in system tenant customer; do
78
105
  worker_name="${APP_NAME}-${STAGE}-${scope}"
79
106
  route_prefix="$(route_prefix_for "$scope")"
80
107
  custom_domain="$(custom_domain_for "$scope")"
108
+ allowed_origins="$(allowed_origins_for "$scope")"
109
+ origin_suffixes="$(origin_suffixes_for "$scope")"
110
+ cors_kv_id="$(cors_kv_id_for "$scope")"
81
111
 
82
112
  info "Deploying $worker_name (scope=$scope route_prefix=$route_prefix)…"
83
113
 
114
+ # When a CORS KV id is set for this scope (typically customer), deploy with a
115
+ # temp config that appends a [[kv_namespaces]] binding (CORS_KV) — a binding can't
116
+ # be set via --var. Otherwise use the shipped config unchanged (KV disabled).
117
+ config_file="$WRANGLER_DIR/wrangler.toml"
118
+ if [[ -n "$cors_kv_id" ]]; then
119
+ config_file="$(mktemp "${TMPDIR:-/tmp}/wrangler-${scope}.XXXXXX.toml")"
120
+ cat "$WRANGLER_DIR/wrangler.toml" >"$config_file"
121
+ {
122
+ echo ''
123
+ echo '[[kv_namespaces]]'
124
+ echo 'binding = "CORS_KV"'
125
+ echo "id = \"$cors_kv_id\""
126
+ } >>"$config_file"
127
+ info " dynamic CORS: KV binding CORS_KV → $cors_kv_id"
128
+ fi
129
+
84
130
  # Deploy the prebuilt bundle with per-scope vars (flags override the toml).
85
131
  wrangler deploy \
86
132
  --name "$worker_name" \
87
- --config "$WRANGLER_DIR/wrangler.toml" \
133
+ --config "$config_file" \
88
134
  --no-bundle \
89
135
  --var "ENVIRONMENT:$environment" \
90
136
  --var "AUTH_SCOPE:$scope" \
91
137
  --var "ROUTE_PREFIX:$route_prefix" \
92
138
  --var "ALLOWED_ORIGINS:$allowed_origins" \
139
+ --var "ALLOWED_ORIGIN_SUFFIXES:$origin_suffixes" \
93
140
  --var "JWT_ISSUER:$jwt_issuer" \
94
141
  --var "JWT_ALLOWED_ALGORITHMS:$jwt_alg" \
95
142
  --var "SERVICE_ROUTE_PREFIX:$svc_route" \
@@ -97,6 +144,9 @@ deploy_gateway() {
97
144
  --var "SERVICE_JWT_ISSUER:$svc_iss" \
98
145
  --var "SERVICE_JWT_ALLOWED_ALGORITHMS:$svc_alg" >/dev/null
99
146
 
147
+ # Clean up the temp config (only created when KV was bound).
148
+ [[ "$config_file" != "$WRANGLER_DIR/wrangler.toml" ]] && rm -f "$config_file"
149
+
100
150
  # Secrets (piped via stdin).
101
151
  put_secret "$worker_name" BACKEND_URL "$backend_url"
102
152
  put_secret "$worker_name" BACKEND_GATEWAY_SECRET "${LAMBDA_GATEWAY_SECRET:-}"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@auth-craft/aws-cf-stack",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Self-contained, versioned distribution of the Auth Craft AWS (DynamoDB + Lambda) + Cloudflare gateway stack. Bundles prebuilt Lambda/worker artifacts + CDK app so consumers deploy without cloning auth-craft.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,17 +16,17 @@
16
16
  ".env.example"
17
17
  ],
18
18
  "dependencies": {
19
- "aws-cdk-lib": "^2.258.1",
19
+ "aws-cdk-lib": "^2.260.0",
20
20
  "constructs": "^10.6.0"
21
21
  },
22
22
  "devDependencies": {
23
- "@types/node": "^25.9.2",
23
+ "@types/node": "^25.9.3",
24
24
  "tsx": "^4.22.4",
25
25
  "typescript": "^6.0.3"
26
26
  },
27
27
  "peerDependencies": {
28
- "aws-cdk": "^2.1126.0",
29
- "wrangler": "^4.99.0"
28
+ "aws-cdk": "^2.1128.0",
29
+ "wrangler": "^4.101.0"
30
30
  },
31
31
  "peerDependenciesMeta": {
32
32
  "aws-cdk": {
@@ -4,6 +4,11 @@
4
4
  # (assets/gateway/worker.js). deploy-gateway.sh overrides `name` and injects vars
5
5
  # per scope via `wrangler deploy --name <name> --var K:V ...`, and pushes secrets via
6
6
  # `wrangler secret put`. No source compile happens at deploy time.
7
+ #
8
+ # CORS_KV binding (dynamic CORS allowlist) is NOT declared here: a KV binding can't be
9
+ # set via --var, so when CF_CORS_KV_ID_<SCOPE> is set deploy-gateway.sh deploys that
10
+ # scope with a temp copy of this file that appends a [[kv_namespaces]] block. Leaving
11
+ # it out here keeps scopes without a KV id (system/tenant) binding-free.
7
12
  name = "auth-craft-gateway"
8
13
  main = "../assets/gateway/worker.js"
9
14
  compatibility_date = "2024-11-27"