@auth-craft/aws-cf-stack 1.8.0 → 1.10.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 CHANGED
@@ -155,6 +155,21 @@ LAMBDA_REFRESH_COOKIE_TENANT=refresh_token_tenant
155
155
  LAMBDA_REFRESH_COOKIE_SYSTEM=refresh_token_system
156
156
  LAMBDA_AUDIT_ENABLED=false
157
157
  LAMBDA_DEVICE_TRACKING=true
158
+
159
+ # ─── Observability cost controls (optional) ──────────────────────────────────
160
+ # CloudWatch Logs are billed for ingestion AND stored volume; retention only affects
161
+ # storage. Short retention in dev is the cheap default.
162
+ # Unset → 3 days in dev, 30 days in staging/prod.
163
+ # Valid → 1,3,5,7,14,30,60,90,120,150,180,365,400,545,731,1096,1827,2192,2557,
164
+ # 2922,3288,3653 (CloudWatch's fixed set — anything else fails at synth),
165
+ # or 'infinite' to never expire.
166
+ LAMBDA_LOG_RETENTION_DAYS=
167
+ # X-Ray tracing is billed per recorded trace. Unset → disabled in dev, active otherwise.
168
+ # active | disabled | pass_through
169
+ LAMBDA_TRACING=
170
+ # Adding missing GSIs to an existing table (one per CloudFormation update): max minutes to
171
+ # wait for each to backfill before giving up (re-running resumes). Unset → 60.
172
+ AUTH_CRAFT_GSI_WAIT_MINUTES=
158
173
  LAMBDA_APP_URL=
159
174
  LAMBDA_SUPPORT_EMAIL=
160
175
  LAMBDA_CORS_ORIGINS=
package/README.md CHANGED
@@ -87,7 +87,9 @@ all point at the single worker.
87
87
 
88
88
  - `aws` CLI authenticated (or `AWS_*` creds in env), `cdk` (or `npx cdk`), `wrangler`,
89
89
  `node` ≥ 20, `jq`, `curl`.
90
- - AWS + Cloudflare credentials.
90
+ - AWS + Cloudflare credentials. Besides the CDK deploy roles, the identity running the CLI
91
+ needs `dynamodb:DescribeTable` on the `<app>-<stage>-<layer>-data` table and
92
+ `cloudformation:GetTemplate` on the data stack (see [Table schema & GSI rollout](#table-schema--gsi-rollout)).
91
93
 
92
94
  ## Usage
93
95
 
@@ -163,6 +165,75 @@ CF_EDGE_GATE_SECRET="$SNAP_EDGE" CLOUDFLARE_ACCOUNT_ID="$SNAP_CF_ACCT" \
163
165
  npx auth-craft-stack all --stage "$STAGE"
164
166
  ```
165
167
 
168
+ ## Table schema & GSI rollout
169
+
170
+ The table's keys and GSIs come from one source: `TABLE_SCHEMA` in
171
+ `@auth-craft/database-plugin-dynamodb`, snapshotted into `cdk/lib/table-schema.json` at build
172
+ time. The CDK stack, the integration tests and the create-table script all read it, so a deployed
173
+ table can no longer miss an index the repositories query.
174
+
175
+ CloudFormation creates or deletes **at most one GSI per table update**. A new table gets every GSI
176
+ at once, but an existing table deployed by an older version may lack several. Before the normal
177
+ deploy, `lambda` therefore:
178
+
179
+ 1. `DescribeTable`s the existing table (nothing to do if it doesn't exist yet).
180
+ 2. Deploys the data stack with the table's current GSIs **plus the next missing one**.
181
+ 3. Waits for that GSI to finish backfilling (`ACTIVE`), then repeats until the schema is complete.
182
+
183
+ Each GSI takes a few minutes on a populated table; the wait is capped by
184
+ `AUTH_CRAFT_GSI_WAIT_MINUTES` (default 60) and an interrupted run resumes where it stopped.
185
+
186
+ GSIs are compared by their full definition (keys, key attribute types, projection), not by name.
187
+ The CLI stops before deploying, and prints how to fix it, when:
188
+
189
+ - the table has a GSI that CloudFormation doesn't know about (created by hand);
190
+ - the stack declares a GSI the table lacks (deleted by hand). The CLI prints the
191
+ `aws dynamodb update-table` command that recreates it as the stack declares it;
192
+ - the table has a GSI the schema no longer declares;
193
+ - a GSI's definition differs from the schema. CloudFormation would have to replace it, which is
194
+ two GSI operations in one update. A changed index needs a new name in `TABLE_SCHEMA`;
195
+ - a rollout step finished without adding its GSI.
196
+
197
+ The GSI subset for each rollout step goes to the CDK app as context (`-c dataGsis=...`), never as
198
+ an environment variable, so nothing in your shell or `--env-file` can shrink the full deploy.
199
+
200
+ ## Observability cost controls (log retention, tracing)
201
+
202
+ CloudWatch Logs and X-Ray are usually the two line items that make a low-traffic **dev**
203
+ stage cost more than the Lambda itself. Both are configurable, with stage-aware defaults
204
+ that keep dev cheap without touching staging/prod behaviour:
205
+
206
+ | Setting | Env var / flag | Default (dev) | Default (staging/prod) |
207
+ |---------|----------------|---------------|------------------------|
208
+ | Log retention | `LAMBDA_LOG_RETENTION_DAYS` / `--log-retention-days` | `3` days | `30` days |
209
+ | X-Ray tracing | `LAMBDA_TRACING` / `--tracing` | `disabled` | `active` |
210
+
211
+ ```bash
212
+ # Cheapest dev: keep 1 day of logs, no tracing (tracing is already off by default in dev)
213
+ npx auth-craft-stack lambda --stage dev --log-retention-days 1
214
+
215
+ # Turn tracing back on in dev while debugging
216
+ npx auth-craft-stack lambda --stage dev --tracing active
217
+
218
+ # Long retention for an audited prod
219
+ npx auth-craft-stack lambda --stage prod --log-retention-days 365
220
+ ```
221
+
222
+ Notes:
223
+ - Retention accepts only **CloudWatch's fixed set** — `1, 3, 5, 7, 14, 30, 60, 90, 120, 150,
224
+ 180, 365, 400, 545, 731, 1096, 1827, 2192, 2557, 2922, 3288, 3653` — or `infinite` to never
225
+ expire. Any other value fails at **synth** time with the valid list, so a typo can't
226
+ silently deploy the wrong retention.
227
+ - Retention caps **storage** cost only; you are still billed for log **ingestion**. To cut
228
+ ingestion, reduce what the app logs.
229
+ - Changing retention applies to the existing log group too (it's the same CloudFormation
230
+ resource), so old logs beyond the new window age out — it is not just for new logs.
231
+ - In `prod` the log group has `RemovalPolicy.RETAIN`, so destroying the stack keeps the logs
232
+ (and the group must be deleted manually before a same-name redeploy). In dev/staging it is
233
+ destroyed with the stack.
234
+ - `LogRetentionDays` and `TracingMode` are emitted as CDK outputs, so
235
+ `npx auth-craft-stack outputs` shows what is actually deployed.
236
+
166
237
  ## CORS (per scope + dynamic allowlist)
167
238
 
168
239
  CORS is resolved **per scope** (system / tenant / customer) — the 3 workers no longer
@@ -326,6 +397,11 @@ time). Bump the version to deploy newer auth-craft.
326
397
  already-deployed queue. The stack grants the Lambda `sqs:SendMessage` on that ARN, but it
327
398
  can't create a cross-stack queue — deploy your email/queue stack first, then this one.
328
399
 
400
+ - **`DATABASE_QUERY_ERROR` from invite flows on a table deployed by ≤ 1.9.0** — the table lacks
401
+ `GSI_InviteToken` / `GSI_InviteeEmail` / `GSI_InviteePhone`. Deploy `lambda` with ≥ 1.10.0; it
402
+ adds them one at a time. The Lambda log names the cause:
403
+ `ValidationException: The table does not have the specified index: …`.
404
+
329
405
  ## Email delivery modes
330
406
 
331
407
  `LAMBDA_EMAIL_MODE` = `console` (default) | `smtp` | `queue`.
@@ -345,6 +421,9 @@ time). Bump the version to deploy newer auth-craft.
345
421
  (ESM, `node:*` external — provided by the Workers runtime), the same way wrangler bundles
346
422
  internally. Uses the esbuild binary in the app's `node_modules` (the root `.bin/esbuild`
347
423
  symlink can be stale because esbuild is in pnpm `ignoredBuiltDependencies`).
424
+ - **Table schema**: `scripts/sync-table-schema.mjs` writes `cdk/lib/table-schema.json` from
425
+ `TABLE_SCHEMA` (database-plugin-dynamodb is unpublished, so the CDK app can't import it at the
426
+ consumer). The JSON is committed; `pnpm test` fails if it's stale (`pnpm sync:table-schema`).
348
427
  - **Admin CLI**: esbuilds `packages/auth-admin-cli/src/index.ts` → `assets/admin/cli.mjs`,
349
428
  fully inlined (auth-core + plugins + `pg` + aws-sdk). It is **bundled, not a dependency**,
350
429
  because `@auth-craft/auth-admin-cli` is `private` and pulls in ~7 workspace packages —
@@ -352,6 +431,5 @@ time). Bump the version to deploy newer auth-craft.
352
431
 
353
432
  The bundled `cdk/lib/lambda-stack.ts` deploys the Lambda via
354
433
  `lambda.Code.fromAsset('../../assets/lambda')` + handler `index.handler` (not `NodejsFunction`),
355
- so no source build happens at the consumer's deploy time. It is a **copy** of the app's
356
- `infrastructure/lib/lambda-stack.ts` — keep `cdk/lib/*.ts` in sync until a future refactor
357
- unifies them.
434
+ so no source build happens at the consumer's deploy time. These `cdk/lib/*.ts` stacks are the
435
+ only CDK definition of the auth deploy — the app's old `infrastructure/` copy was removed.