@auth-craft/aws-cf-stack 1.8.0 → 1.9.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/bin/deploy.sh CHANGED
@@ -54,6 +54,12 @@ Options (each also reads an env var; the flag wins):
54
54
  --edge-gate-secret <v> env CF_EDGE_GATE_SECRET
55
55
  --cf-account-id <v> env CLOUDFLARE_ACCOUNT_ID
56
56
  --cf-api-token <v> env CLOUDFLARE_API_TOKEN
57
+ --log-retention-days <n> CloudWatch log retention (env LAMBDA_LOG_RETENTION_DAYS).
58
+ Default: 3 in dev, 30 otherwise. Use 'infinite' to never
59
+ expire. Only CloudWatch's fixed set is valid
60
+ (1,3,5,7,14,30,60,90,120,150,180,365,400,545,731,…).
61
+ --tracing <mode> X-Ray tracing: active|disabled|pass_through
62
+ (env LAMBDA_TRACING; default disabled in dev, active otherwise)
57
63
  --outputs-file <path> (lambda) write CDK outputs JSON here for your own steps
58
64
  --env-file <path> Optional: source a file first (flags/env still override)
59
65
  --skip-build No-op (artifacts are prebuilt in the package)
@@ -89,6 +95,8 @@ while [[ $# -gt 0 ]]; do
89
95
  --edge-gate-secret) FLAG[CF_EDGE_GATE_SECRET]="${2:?}"; shift 2 ;;
90
96
  --cf-account-id) FLAG[CLOUDFLARE_ACCOUNT_ID]="${2:?}"; shift 2 ;;
91
97
  --cf-api-token) FLAG[CLOUDFLARE_API_TOKEN]="${2:?}"; shift 2 ;;
98
+ --log-retention-days) FLAG[LAMBDA_LOG_RETENTION_DAYS]="${2:?}"; shift 2 ;;
99
+ --tracing) FLAG[LAMBDA_TRACING]="${2:?}"; shift 2 ;;
92
100
  --outputs-file) OUTPUTS_FILE="${2:?}"; export OUTPUTS_FILE; shift 2 ;;
93
101
  --env-file) ENV_FILE="${2:?}"; shift 2 ;;
94
102
  --skip-build) shift ;;
package/cdk/bin/app.ts CHANGED
@@ -27,8 +27,7 @@ const env = {
27
27
  // it carries no component so the iss claim stays stable across resources.
28
28
  // Examples: snapshot-commerce-prod-auth-data, snapshot-commerce-prod-auth-api
29
29
 
30
- const appName =
31
- process.env.AUTH_CRAFT_APP_NAME || (project === 'default' ? 'auth-craft' : `auth-craft-${project}`);
30
+ const appName = process.env.AUTH_CRAFT_APP_NAME || (project === 'default' ? 'auth-craft' : `auth-craft-${project}`);
32
31
 
33
32
  // Base name shared by every resource (also AUTH_SYSTEM_NAME / JWT issuer).
34
33
  const resourceBase = `${appName}-${stage}-${layer}`;
@@ -16,6 +16,78 @@ const __dirname = path.dirname(__filename);
16
16
  // packages/aws-cf-stack/cdk/lib/lambda-stack.ts -> packages/aws-cf-stack/assets/lambda
17
17
  const LAMBDA_ASSET_DIR = path.resolve(__dirname, '../../assets/lambda');
18
18
 
19
+ // CloudWatch Logs only accepts a fixed set of retention periods — an arbitrary day
20
+ // count is rejected at deploy time. Map the supported values so a typo fails fast at
21
+ // synth (with the valid list) instead of mid-deploy.
22
+ const RETENTION_BY_DAYS: Record<number, logs.RetentionDays> = {
23
+ 1: logs.RetentionDays.ONE_DAY,
24
+ 3: logs.RetentionDays.THREE_DAYS,
25
+ 5: logs.RetentionDays.FIVE_DAYS,
26
+ 7: logs.RetentionDays.ONE_WEEK,
27
+ 14: logs.RetentionDays.TWO_WEEKS,
28
+ 30: logs.RetentionDays.ONE_MONTH,
29
+ 60: logs.RetentionDays.TWO_MONTHS,
30
+ 90: logs.RetentionDays.THREE_MONTHS,
31
+ 120: logs.RetentionDays.FOUR_MONTHS,
32
+ 150: logs.RetentionDays.FIVE_MONTHS,
33
+ 180: logs.RetentionDays.SIX_MONTHS,
34
+ 365: logs.RetentionDays.ONE_YEAR,
35
+ 400: logs.RetentionDays.THIRTEEN_MONTHS,
36
+ 545: logs.RetentionDays.EIGHTEEN_MONTHS,
37
+ 731: logs.RetentionDays.TWO_YEARS,
38
+ 1096: logs.RetentionDays.THREE_YEARS,
39
+ 1827: logs.RetentionDays.FIVE_YEARS,
40
+ 2192: logs.RetentionDays.SIX_YEARS,
41
+ 2557: logs.RetentionDays.SEVEN_YEARS,
42
+ 2922: logs.RetentionDays.EIGHT_YEARS,
43
+ 3288: logs.RetentionDays.NINE_YEARS,
44
+ 3653: logs.RetentionDays.TEN_YEARS,
45
+ };
46
+
47
+ /**
48
+ * Resolve the CloudWatch log retention from LAMBDA_LOG_RETENTION_DAYS.
49
+ * Accepts a supported day count (e.g. "3") or 'infinite' / 'never' to keep logs
50
+ * forever. Unset → stage default: 3 days in dev (cheap), 1 month otherwise.
51
+ */
52
+ export function resolveLogRetention(raw: string | undefined, stage: string): logs.RetentionDays {
53
+ const value = raw?.trim().toLowerCase();
54
+
55
+ if (!value) {
56
+ return stage === 'dev' ? logs.RetentionDays.THREE_DAYS : logs.RetentionDays.ONE_MONTH;
57
+ }
58
+
59
+ if (value === 'infinite' || value === 'never' || value === '0') {
60
+ return logs.RetentionDays.INFINITE;
61
+ }
62
+
63
+ const days = Number(value);
64
+ const retention = Number.isInteger(days) ? RETENTION_BY_DAYS[days] : undefined;
65
+ if (retention === undefined) {
66
+ throw new Error(
67
+ `Unsupported LAMBDA_LOG_RETENTION_DAYS: '${raw}'. ` +
68
+ `CloudWatch Logs accepts only: ${Object.keys(RETENTION_BY_DAYS).join(', ')} (or 'infinite').`,
69
+ );
70
+ }
71
+ return retention;
72
+ }
73
+
74
+ /**
75
+ * Resolve X-Ray tracing from LAMBDA_TRACING ('active' | 'disabled').
76
+ * Unset → disabled in dev (traces are billed per request), active otherwise.
77
+ */
78
+ export function resolveTracing(raw: string | undefined, stage: string): lambda.Tracing {
79
+ const value = raw?.trim().toLowerCase();
80
+
81
+ if (!value) {
82
+ return stage === 'dev' ? lambda.Tracing.DISABLED : lambda.Tracing.ACTIVE;
83
+ }
84
+ if (value === 'active' || value === 'true' || value === 'enabled') return lambda.Tracing.ACTIVE;
85
+ if (value === 'disabled' || value === 'false' || value === 'off') return lambda.Tracing.DISABLED;
86
+ if (value === 'pass_through' || value === 'passthrough') return lambda.Tracing.PASS_THROUGH;
87
+
88
+ throw new Error(`Unsupported LAMBDA_TRACING: '${raw}'. Use 'active', 'disabled', or 'pass_through'.`);
89
+ }
90
+
19
91
  interface LambdaStackProps extends cdk.StackProps {
20
92
  table: dynamodb.ITable;
21
93
  functionName: string; // {app}-{env}-{layer}-api, e.g. "snapshot-commerce-prod-auth-api"
@@ -93,13 +165,24 @@ export class LambdaStack extends cdk.Stack {
93
165
  // Naming: functionName = {app}-{env}-{layer}-api (the deployed resource);
94
166
  // resourceName = {app}-{env}-{layer} base (AUTH_SYSTEM_NAME / JWT issuer).
95
167
 
96
- // Log group for Lambda function (matches the function name)
168
+ // Log group for Lambda function (matches the function name).
169
+ // Retention drives CloudWatch Logs storage cost, so it is configurable:
170
+ // LAMBDA_LOG_RETENTION_DAYS accepts any retention CloudWatch supports, plus
171
+ // 'infinite' (never expire). Unset → a stage-aware default that keeps dev cheap
172
+ // (3 days) while staging/prod keep a useful history (1 month).
173
+ const retention = resolveLogRetention(process.env.LAMBDA_LOG_RETENTION_DAYS, stage);
174
+
97
175
  const logGroup = new logs.LogGroup(this, 'AuthLambdaLogGroup', {
98
176
  logGroupName: `/aws/lambda/${functionName}`,
99
- retention: logs.RetentionDays.ONE_MONTH,
177
+ retention,
100
178
  removalPolicy: stage === 'prod' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
101
179
  });
102
180
 
181
+ // X-Ray tracing is billed per trace and is rarely worth it in dev, so it is
182
+ // opt-out there: LAMBDA_TRACING = 'active' | 'disabled' (default: disabled in
183
+ // dev, active elsewhere).
184
+ const tracing = resolveTracing(process.env.LAMBDA_TRACING, stage);
185
+
103
186
  // Lambda function from the PREBUILT bundle shipped in this package.
104
187
  // Unlike the in-repo app stack (which uses NodejsFunction to build from
105
188
  // src/index.ts at synth time), this self-contained package deploys the
@@ -234,8 +317,8 @@ export class LambdaStack extends cdk.Stack {
234
317
  }),
235
318
  },
236
319
 
237
- // Tracing
238
- tracing: lambda.Tracing.ACTIVE, // Enable X-Ray tracing
320
+ // Tracing (X-Ray) — see resolveTracing above; off by default in dev.
321
+ tracing,
239
322
  });
240
323
 
241
324
  // Grant DynamoDB permissions
@@ -291,6 +374,20 @@ export class LambdaStack extends cdk.Stack {
291
374
  exportName: `${this.stackName}-ApiBasePath`,
292
375
  });
293
376
 
377
+ // Cost-relevant knobs, echoed so `auth-craft-stack outputs` can show what was
378
+ // actually deployed (rather than what the caller thinks they set).
379
+ new cdk.CfnOutput(this, 'LogRetentionDays', {
380
+ value: String(retention),
381
+ description: 'CloudWatch log retention in days (0 = never expire)',
382
+ exportName: `${this.stackName}-LogRetentionDays`,
383
+ });
384
+
385
+ new cdk.CfnOutput(this, 'TracingMode', {
386
+ value: tracing,
387
+ description: 'X-Ray tracing mode',
388
+ exportName: `${this.stackName}-TracingMode`,
389
+ });
390
+
294
391
  // Output auth system name — used as JWT issuer by Workers to verify tokens
295
392
  new cdk.CfnOutput(this, 'AuthSystemName', {
296
393
  value: resourceName,
package/lib/common.sh CHANGED
@@ -259,6 +259,14 @@ print_outputs_json() {
259
259
  # in a Service Binding (`[[services]] service = "<worker name>"`) to front auth
260
260
  # same-origin via @auth-craft/pages-auth-proxy. The binding lives in the consumer's
261
261
  # Pages project, not here — `serviceBindings` is a ready-to-paste snippet.
262
+ # Cost knobs, read back from the deployed stack so `outputs` reports what is live
263
+ # rather than what the caller's env happens to say.
264
+ local logRetention tracingMode
265
+ logRetention="$(cfn_output "$api_stack" LogRetentionDays)"
266
+ tracingMode="$(cfn_output "$api_stack" TracingMode)"
267
+ [[ -n "$logRetention" && "$logRetention" != "None" ]] || logRetention=""
268
+ [[ -n "$tracingMode" && "$tracingMode" != "None" ]] || tracingMode=""
269
+
262
270
  local mode wnSystem wnTenant wnCustomer bindingService
263
271
  mode="$(gateway_mode)"
264
272
  if [[ "$mode" == "shared" ]]; then
@@ -288,11 +296,13 @@ print_outputs_json() {
288
296
  --arg rpCustomer "${CF_WORKER_ROUTE_PREFIX_CUSTOMER:-/customer}" \
289
297
  --arg wnSystem "$wnSystem" --arg wnTenant "$wnTenant" --arg wnCustomer "$wnCustomer" \
290
298
  --arg bindingSnippet "$bindingSnippet" \
299
+ --arg logRetention "$logRetention" --arg tracingMode "$tracingMode" \
291
300
  '{stage:$stage, project:$project, region:$region,
292
301
  lambdaFunctionUrl:$url, apiBasePath:$base, authSystemName:$name,
293
302
  dynamodbTable:$table, backendUrl:$backend,
294
303
  jwtPublicKey:$jwtPub, jwtIssuer:$jwtIssuer, jwtAlgorithm:$jwtAlg,
295
304
  serviceJwtPublicKey:$svcPub,
305
+ observability:{logRetentionDays:$logRetention, tracingMode:$tracingMode},
296
306
  gatewayUrls:{system:$gwSystem, tenant:$gwTenant, customer:$gwCustomer},
297
307
  workerNames:{system:$wnSystem, tenant:$wnTenant, customer:$wnCustomer},
298
308
  routePrefixes:{system:$rpSystem, tenant:$rpTenant, customer:$rpCustomer},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@auth-craft/aws-cf-stack",
3
- "version": "1.8.0",
3
+ "version": "1.9.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": {
@@ -10,19 +10,23 @@
10
10
  "bin",
11
11
  "lib",
12
12
  "cdk",
13
+ "!cdk/cdk.out",
13
14
  "assets",
14
15
  "wrangler",
15
16
  "README.md",
16
17
  ".env.example"
17
18
  ],
18
19
  "dependencies": {
19
- "aws-cdk-lib": "^2.260.0",
20
- "constructs": "^10.6.0"
20
+ "aws-cdk-lib": "^2.265.0",
21
+ "constructs": "^10.8.1"
21
22
  },
22
23
  "devDependencies": {
23
- "@types/node": "^25.9.3",
24
- "tsx": "^4.22.4",
25
- "typescript": "^6.0.3"
24
+ "@types/node": "^26.2.0",
25
+ "tsx": "^4.23.12",
26
+ "typescript": "^7.0.2",
27
+ "@auth-craft/auth-admin-cli": "2.0.0",
28
+ "@auth-craft/auth-hono-dynamodb-lambda": "1.0.0",
29
+ "@auth-craft/cloudflare-auth-gateway": "1.0.0"
26
30
  },
27
31
  "peerDependencies": {
28
32
  "aws-cdk": "^2.1128.0",