@auth-craft/aws-cf-stack 1.3.0 → 1.6.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 +20 -2
- package/README.md +37 -5
- package/assets/admin/cli.mjs +16 -16
- package/assets/gateway/worker.js +1 -1
- package/lib/common.sh +36 -11
- package/lib/create-admin.sh +11 -7
- package/lib/deploy-gateway.sh +218 -92
- package/package.json +1 -1
package/.env.example
CHANGED
|
@@ -46,6 +46,13 @@ LAMBDA_APP_BASE_PATH= # recommended — set a stable, non-guessable valu
|
|
|
46
46
|
# changes between deploys, so clients/workers don't break.
|
|
47
47
|
|
|
48
48
|
# ─── Cloudflare gateway workers ──────────────────────────────────────────────
|
|
49
|
+
# Deploy model: 'multi' (default — 3 workers, one scope each; AUTH_SCOPE fixed per worker,
|
|
50
|
+
# so scope is ENFORCED) or 'shared' (1 worker that derives scope from the path's first
|
|
51
|
+
# segment). Shared is simpler (1 deploy/binding) but does NOT enforce scope on its own —
|
|
52
|
+
# pair it with a scope-locked Pages proxy (@auth-craft/pages-auth-proxy `scope` option) if
|
|
53
|
+
# customer apps must never reach system/tenant. In shared mode CORS unions all scopes and
|
|
54
|
+
# the CORS_KV uses CF_CORS_KV_ID_SHARED (or falls back to CF_CORS_KV_ID_CUSTOMER).
|
|
55
|
+
CF_GATEWAY_MODE=multi # multi | shared
|
|
49
56
|
CLOUDFLARE_API_TOKEN= # required for gateway (flag --cf-api-token)
|
|
50
57
|
CLOUDFLARE_ACCOUNT_ID= # required for gateway (flag --cf-account-id)
|
|
51
58
|
CF_EDGE_GATE_SECRET= # required for gateway (flag --edge-gate-secret) — Client → Worker gate
|
|
@@ -84,6 +91,15 @@ CF_WORKER_DISABLE_PUBLIC_URL= # shared default (e.g. true to disable al
|
|
|
84
91
|
CF_WORKER_DISABLE_PUBLIC_URL_SYSTEM= # e.g. true — admin app via Pages proxy
|
|
85
92
|
CF_WORKER_DISABLE_PUBLIC_URL_TENANT=
|
|
86
93
|
CF_WORKER_DISABLE_PUBLIC_URL_CUSTOMER=
|
|
94
|
+
# Skip the X-Edge-Gate (Client → Worker) check. Redundant when the worker has NO public
|
|
95
|
+
# URL and is reached only via a Service Binding (Pages proxy) — so it's ONLY honored when
|
|
96
|
+
# CF_WORKER_DISABLE_PUBLIC_URL is also set for that scope (else ignored with a warning).
|
|
97
|
+
# Does NOT affect the Worker → Backend secret (X-Gateway-Secret stays required). Truthy:
|
|
98
|
+
# 1/true/yes. When skipped, EDGE_GATE_SECRET is not needed.
|
|
99
|
+
CF_WORKER_SKIP_EDGE_GATE= # shared default
|
|
100
|
+
CF_WORKER_SKIP_EDGE_GATE_SYSTEM= # e.g. true (pairs with DISABLE_PUBLIC_URL_SYSTEM)
|
|
101
|
+
CF_WORKER_SKIP_EDGE_GATE_TENANT=
|
|
102
|
+
CF_WORKER_SKIP_EDGE_GATE_CUSTOMER=
|
|
87
103
|
|
|
88
104
|
# ─── Internal service JWT (optional — enables /internal/* + service worker verify) ─
|
|
89
105
|
LAMBDA_INTERNAL_JWT_PUBLIC_KEY= # optional — also fed to workers as SERVICE_JWT_PUBLIC_KEY
|
|
@@ -149,8 +165,10 @@ LAMBDA_FACEBOOK_CLIENT_ID=
|
|
|
149
165
|
LAMBDA_FACEBOOK_CLIENT_SECRET=
|
|
150
166
|
LAMBDA_FACEBOOK_REDIRECT_URI=
|
|
151
167
|
|
|
152
|
-
# ─── Super-admin bootstrap (optional —
|
|
168
|
+
# ─── Super-admin bootstrap (optional — the `admin` stage is skipped unless BOTH ───
|
|
169
|
+
# EMAIL and PASSWORD are set; the skip message names which are missing). The admin CLI
|
|
170
|
+
# talks to DynamoDB DIRECTLY and is idempotent: if an active owner already exists in the
|
|
171
|
+
# system tenant it's a no-op, so re-runs are safe. No setup token is needed.
|
|
153
172
|
LAMBDA_SUPER_ADMIN_EMAIL=
|
|
154
173
|
LAMBDA_SUPER_ADMIN_PASSWORD=
|
|
155
174
|
LAMBDA_SUPER_ADMIN_NAME=Super Admin
|
|
156
|
-
LAMBDA_AUTH_SETUP_TOKEN=
|
package/README.md
CHANGED
|
@@ -17,7 +17,9 @@ or building the auth-craft repo** — just pass env and run.
|
|
|
17
17
|
|
|
18
18
|
1. **DynamoDB + Lambda** (CDK) — the auth backend. Emits a Function URL + a base path.
|
|
19
19
|
2. **Super-admin** (idempotent) — bootstraps the `__system__` tenant owner.
|
|
20
|
-
3. **
|
|
20
|
+
3. **Gateway worker(s)** on Cloudflare, via wrangler. Default `multi` = **3 workers**
|
|
21
|
+
(system / tenant / customer); `CF_GATEWAY_MODE=shared` = **1 worker**. See
|
|
22
|
+
[Deploy model](#deploy-model-multi-vs-shared).
|
|
21
23
|
|
|
22
24
|
The gateway's `BACKEND_URL` is computed as `FunctionURL + ApiBasePath` read back from the
|
|
23
25
|
**live CDK outputs** of the same deploy — so the worker config can never drift from the
|
|
@@ -33,18 +35,38 @@ Everything is named from `--stage` (`dev|staging|prod`) and `--project` (default
|
|
|
33
35
|
|----------|------|
|
|
34
36
|
| CDK stacks | `<app>-<stage>-data`, `<app>-<stage>-api` |
|
|
35
37
|
| Lambda fn + DynamoDB table | `<app>-<stage>` |
|
|
36
|
-
| Gateway workers | `<app>-<stage>-system` / `-tenant` / `-customer` |
|
|
38
|
+
| Gateway workers (`multi`) | `<app>-<stage>-system` / `-tenant` / `-customer` |
|
|
39
|
+
| Gateway worker (`shared`) | `<app>-<stage>` |
|
|
37
40
|
|
|
38
41
|
So `outputs`/`gateway` look up the api stack `<app>-<stage>-api`. The CDK output keys are
|
|
39
42
|
`LambdaFunctionUrl`, `ApiBasePath`, `AuthSystemName`, `DynamoDBTableName`.
|
|
40
43
|
|
|
44
|
+
### Deploy model (multi vs shared)
|
|
45
|
+
|
|
46
|
+
`CF_GATEWAY_MODE` picks how the gateway is deployed:
|
|
47
|
+
|
|
48
|
+
- **`multi`** (default) — 3 workers, one scope each. Each worker has a fixed `AUTH_SCOPE`,
|
|
49
|
+
so the scope is **enforced server-side**: a customer worker can never act as system,
|
|
50
|
+
whatever path the client sends. Strongest isolation; 3 deploys + 3 Service Bindings.
|
|
51
|
+
- **`shared`** — 1 worker. Scope is derived from the path's first segment
|
|
52
|
+
(`/system/...` → `system`), so the worker by itself does **not** enforce scope (a client
|
|
53
|
+
could send `/system/...`). Simpler: 1 deploy, 1 binding, unioned CORS. **Pair it with a
|
|
54
|
+
scope-locked Pages proxy** (`createPagesAuthProxy({ scope: 'customer' })` from
|
|
55
|
+
[@auth-craft/pages-auth-proxy](../pages-auth-proxy/README.md)) so the *proxy* prepends
|
|
56
|
+
the scope and the client never picks it — that restores the "customer can't reach
|
|
57
|
+
system" guarantee at the edge.
|
|
58
|
+
|
|
59
|
+
In `shared` mode CORS unions all per-scope origins/suffixes, and the dynamic-CORS KV uses
|
|
60
|
+
`CF_CORS_KV_ID_SHARED` (falling back to `CF_CORS_KV_ID_CUSTOMER`). `outputs.workerNames.*`
|
|
61
|
+
all point at the single worker.
|
|
62
|
+
|
|
41
63
|
## Commands
|
|
42
64
|
|
|
43
65
|
| Command | Does |
|
|
44
66
|
|---------|------|
|
|
45
67
|
| `lambda` | Deploy the DynamoDB + Lambda CDK stacks. `--outputs-file <p>` writes the raw CDK outputs JSON. Runs a best-effort `/health` check. |
|
|
46
|
-
| `admin` | Create the super-admin (idempotent; **skips** if `
|
|
47
|
-
| `gateway` | Deploy the 3
|
|
68
|
+
| `admin` | Create the super-admin (idempotent; **skips** if `LAMBDA_SUPER_ADMIN_EMAIL`/`LAMBDA_SUPER_ADMIN_PASSWORD` unset; "already exists" = success). |
|
|
69
|
+
| `gateway` | Deploy the gateway worker(s) — 3 (`multi`, default) or 1 (`CF_GATEWAY_MODE=shared`). Re-reads the Lambda outputs from CloudFormation if run as a separate invocation. |
|
|
48
70
|
| `all` | `lambda` → `admin` → `gateway`, one run. |
|
|
49
71
|
| `outputs` | Print the live api-stack outputs as JSON (no deploy): `backendUrl`, `apiBasePath`, `dynamodbTable`, and the auth keys read **from the live Lambda config** (`jwtPublicKey`, `jwtIssuer`, `jwtAlgorithm`, `serviceJwtPublicKey`) + `gatewayUrls`/`routePrefixes`. Self-contained — works standalone (no env), so an orchestrator can auto-fill its `*_AUTH_*` vars. |
|
|
50
72
|
|
|
@@ -76,7 +98,7 @@ Inputs come from **flags or environment variables** (flag wins, then env, then
|
|
|
76
98
|
| Stage | Required |
|
|
77
99
|
|-------|----------|
|
|
78
100
|
| `lambda` | `LAMBDA_JWT_PRIVATE_KEY`, `LAMBDA_JWT_PUBLIC_KEY`, and `LAMBDA_GATEWAY_SECRET` (required for staging/prod; optional in dev). AWS creds in env. |
|
|
79
|
-
| `admin` | `LAMBDA_SUPER_ADMIN_EMAIL`, `LAMBDA_SUPER_ADMIN_PASSWORD
|
|
101
|
+
| `admin` | `LAMBDA_SUPER_ADMIN_EMAIL`, `LAMBDA_SUPER_ADMIN_PASSWORD` (else skipped). AWS creds. |
|
|
80
102
|
| `gateway` | Hard-required: `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID`. Needed in practice (pushed as worker secrets; empty ones are skipped but the gateway then rejects clients / can't reach the backend): `CF_EDGE_GATE_SECRET`, `LAMBDA_GATEWAY_SECRET`, `LAMBDA_JWT_PUBLIC_KEY`. |
|
|
81
103
|
|
|
82
104
|
Set `LAMBDA_APP_BASE_PATH` to a **stable** value — if unset, a random base path is generated
|
|
@@ -230,6 +252,16 @@ domain). This applies only when no `CF_WORKER_CUSTOM_DOMAIN_<SCOPE>` is set for
|
|
|
230
252
|
if you're using `AUTH_GATEWAY_URL` (no binding) leave it off, since that path needs the
|
|
231
253
|
public URL.
|
|
232
254
|
|
|
255
|
+
**Skip the edge gate (optional).** The `X-Edge-Gate` secret exists to stop the public
|
|
256
|
+
internet from hitting the worker. Once the public URL is disabled and the worker is
|
|
257
|
+
reached only via a Service Binding, that gate is redundant — set
|
|
258
|
+
`CF_WORKER_SKIP_EDGE_GATE=true` (or per scope) to drop it; the Pages proxy then needs no
|
|
259
|
+
`EDGE_GATE_SECRET`. For safety it's **only honored when the public URL is also disabled**
|
|
260
|
+
for that scope (otherwise ignored with a warning). It does **not** touch the
|
|
261
|
+
Worker → Backend secret: `X-Gateway-Secret` stays required because the backend (Lambda
|
|
262
|
+
Function URL) is still on the public internet — without it, anyone could call the Lambda
|
|
263
|
+
directly.
|
|
264
|
+
|
|
233
265
|
### Per-scope token transport
|
|
234
266
|
|
|
235
267
|
`REFRESH_TOKEN_STRATEGY` sets the transport for every scope. To mix, use the per-scope
|