@auth-craft/aws-cf-stack 1.7.1 → 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
@@ -44,6 +44,9 @@ Options (each also reads an env var; the flag wins):
44
44
  --stage <dev|staging|prod> Stage (env STAGE, default dev; accepts production/development)
45
45
  --project <name> CDK project (env PROJECT, default "default")
46
46
  --app-name <name> Override app name (env AUTH_CRAFT_APP_NAME; default auth-craft[-project])
47
+ --layer <name> Layer namespace within the app (env LAYER, default "auth").
48
+ Resource names are {app}-{env}-{layer}-{component}, so a larger
49
+ app with other layers (e.g. billing) reuses this CLI per layer.
47
50
  --region <region> AWS region (env LAMBDA_AWS_REGION, default us-east-1)
48
51
  --gateway-secret <v> env LAMBDA_GATEWAY_SECRET
49
52
  --jwt-public-key <v> env LAMBDA_JWT_PUBLIC_KEY
@@ -51,6 +54,12 @@ Options (each also reads an env var; the flag wins):
51
54
  --edge-gate-secret <v> env CF_EDGE_GATE_SECRET
52
55
  --cf-account-id <v> env CLOUDFLARE_ACCOUNT_ID
53
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)
54
63
  --outputs-file <path> (lambda) write CDK outputs JSON here for your own steps
55
64
  --env-file <path> Optional: source a file first (flags/env still override)
56
65
  --skip-build No-op (artifacts are prebuilt in the package)
@@ -78,6 +87,7 @@ while [[ $# -gt 0 ]]; do
78
87
  --stage) FLAG[STAGE]="${2:?}"; shift 2 ;;
79
88
  --project) FLAG[PROJECT]="${2:?}"; shift 2 ;;
80
89
  --app-name) FLAG[AUTH_CRAFT_APP_NAME]="${2:?}"; shift 2 ;;
90
+ --layer) FLAG[LAYER]="${2:?}"; shift 2 ;;
81
91
  --region) FLAG[LAMBDA_AWS_REGION]="${2:?}"; shift 2 ;;
82
92
  --gateway-secret) FLAG[LAMBDA_GATEWAY_SECRET]="${2:?}"; shift 2 ;;
83
93
  --jwt-public-key) FLAG[LAMBDA_JWT_PUBLIC_KEY]="${2:?}"; shift 2 ;;
@@ -85,6 +95,8 @@ while [[ $# -gt 0 ]]; do
85
95
  --edge-gate-secret) FLAG[CF_EDGE_GATE_SECRET]="${2:?}"; shift 2 ;;
86
96
  --cf-account-id) FLAG[CLOUDFLARE_ACCOUNT_ID]="${2:?}"; shift 2 ;;
87
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 ;;
88
100
  --outputs-file) OUTPUTS_FILE="${2:?}"; export OUTPUTS_FILE; shift 2 ;;
89
101
  --env-file) ENV_FILE="${2:?}"; shift 2 ;;
90
102
  --skip-build) shift ;;
@@ -142,7 +154,7 @@ case "$COMMAND" in
142
154
  # if not already exported in this process (e.g. snapshot drove `lambda` separately).
143
155
  if [[ -z "${LAMBDA_FUNCTION_URL:-}" || -z "${API_BASE_PATH:-}" ]]; then
144
156
  require_aws_identity
145
- api_stack="${APP_NAME}-${STAGE}-api"
157
+ api_stack="${RESOURCE_NAME}-api"
146
158
  LAMBDA_FUNCTION_URL="$(cfn_output "$api_stack" LambdaFunctionUrl)"
147
159
  API_BASE_PATH="$(cfn_output "$api_stack" ApiBasePath)"
148
160
  AUTH_SYSTEM_NAME="$(cfn_output "$api_stack" AuthSystemName)"
package/cdk/bin/app.ts CHANGED
@@ -10,6 +10,7 @@ const app = new cdk.App();
10
10
  const rawStage = process.env.STAGE || 'dev';
11
11
  const stage = rawStage === 'production' ? 'prod' : rawStage === 'development' ? 'dev' : rawStage;
12
12
  const project = process.env.PROJECT || 'default'; // Client project name
13
+ const layer = process.env.LAYER || 'auth'; // Layer namespace within the app (default "auth")
13
14
 
14
15
  const env = {
15
16
  account: process.env.CDK_DEFAULT_ACCOUNT,
@@ -17,18 +18,24 @@ const env = {
17
18
  };
18
19
 
19
20
  // ==================== Naming Convention ====================
20
- // Stack naming: {app}-{env}-{layer}
21
- // - app: AUTH_CRAFT_APP_NAME if set, else "auth-craft" (base) / "auth-craft-{project}"
22
- // - env: "dev", "staging", "prod"
23
- // - layer: "data", "api"
24
- // Examples: auth-craft-dev-data, auth-craft-staging-api, snapshot-auth-prod-api
21
+ // Pattern: {app}-{env}-{layer}-{component}
22
+ // - app: AUTH_CRAFT_APP_NAME if set, else "auth-craft" (base) / "auth-craft-{project}"
23
+ // - env: "dev", "staging", "prod"
24
+ // - layer: LAYER (default "auth") — namespaces this deploy within a larger app
25
+ // - component: per-resource suffix ("data" for the table, "api" for the Lambda)
26
+ // The {app}-{env}-{layer} base is the resourceName (AUTH_SYSTEM_NAME / JWT issuer);
27
+ // it carries no component so the iss claim stays stable across resources.
28
+ // Examples: snapshot-commerce-prod-auth-data, snapshot-commerce-prod-auth-api
25
29
 
26
- const appName =
27
- 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}`);
31
+
32
+ // Base name shared by every resource (also AUTH_SYSTEM_NAME / JWT issuer).
33
+ const resourceBase = `${appName}-${stage}-${layer}`;
28
34
 
29
35
  console.log(`🚀 Deploying Auth Craft to AWS`);
30
36
  console.log(` App: ${appName}`);
31
37
  console.log(` Stage: ${stage}`);
38
+ console.log(` Layer: ${layer}`);
32
39
  console.log(` Region: ${env.region}`);
33
40
 
34
41
  // Common tags for cost allocation
@@ -39,30 +46,29 @@ const costTags = {
39
46
  ManagedBy: 'CDK',
40
47
  };
41
48
 
42
- // Stack naming helper: {app}-{env}-{layer}
43
- const getStackName = (layer: string) => `${appName}-${stage}-${layer}`;
44
-
45
- // Resource naming helper: {app}-{env}
46
- const getResourceName = () => `${appName}-${stage}`;
49
+ // Stack / resource naming helper: {app}-{env}-{layer}-{component}
50
+ const nameFor = (component: string) => `${resourceBase}-${component}`;
47
51
 
48
- // DynamoDB Stack (data layer)
49
- const dynamodbStack = new DynamoDBStack(app, `${appName}-${stage}-data`, {
52
+ // DynamoDB Stack (data component)
53
+ const dynamodbStack = new DynamoDBStack(app, nameFor('data'), {
50
54
  env,
51
- stackName: getStackName('data'),
52
- description: `Auth Craft DynamoDB (${appName}-${stage})`,
55
+ stackName: nameFor('data'),
56
+ description: `Auth Craft DynamoDB (${resourceBase})`,
53
57
  tags: costTags,
54
- resourceName: getResourceName(),
58
+ tableName: nameFor('data'),
55
59
  stage,
56
60
  });
57
61
 
58
- // Lambda Stack (api layer)
59
- const lambdaStack = new LambdaStack(app, `${appName}-${stage}-api`, {
62
+ // Lambda Stack (api component)
63
+ const lambdaStack = new LambdaStack(app, nameFor('api'), {
60
64
  env,
61
- stackName: getStackName('api'),
62
- description: `Auth Craft Lambda API (${appName}-${stage})`,
65
+ stackName: nameFor('api'),
66
+ description: `Auth Craft Lambda API (${resourceBase})`,
63
67
  table: dynamodbStack.table,
64
68
  tags: costTags,
65
- resourceName: getResourceName(),
69
+ functionName: nameFor('api'),
70
+ // Base {app}-{env}-{layer} — AUTH_SYSTEM_NAME / JWT issuer (no component).
71
+ resourceName: resourceBase,
66
72
  stage,
67
73
  });
68
74
 
@@ -3,7 +3,7 @@ import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
3
3
  import type { Construct } from 'constructs';
4
4
 
5
5
  interface DynamoDBStackProps extends cdk.StackProps {
6
- resourceName: string; // e.g., "auth-craft-dev", "auth-craft-prod"
6
+ tableName: string; // {app}-{env}-{layer}-data, e.g. "snapshot-commerce-prod-auth-data"
7
7
  stage: string; // "dev" | "staging" | "prod" — passed from the app, single source of truth
8
8
  }
9
9
 
@@ -13,12 +13,10 @@ export class DynamoDBStack extends cdk.Stack {
13
13
  constructor(scope: Construct, id: string, props: DynamoDBStackProps) {
14
14
  super(scope, id, props);
15
15
 
16
- const { resourceName, stage } = props;
16
+ const { tableName, stage } = props;
17
17
 
18
- // Resource naming: {app}-{env}
19
- // Examples: auth-craft-dev, auth-craft-staging, auth-craft-prod
20
- // Multi-project: auth-craft-ecommerce-dev, auth-craft-saas-prod
21
- const tableName = resourceName;
18
+ // Table naming: {app}-{env}-{layer}-data
19
+ // Examples: snapshot-commerce-prod-auth-data, auth-craft-dev-auth-data
22
20
 
23
21
  this.table = new dynamodb.Table(this, 'AuthCraftTable', {
24
22
  tableName,
@@ -16,9 +16,82 @@ 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
- resourceName: string; // e.g., "auth-craft-dev", "auth-craft-prod"
93
+ functionName: string; // {app}-{env}-{layer}-api, e.g. "snapshot-commerce-prod-auth-api"
94
+ resourceName: string; // base {app}-{env}-{layer} — AUTH_SYSTEM_NAME / JWT issuer (no component)
22
95
  stage: string; // "dev" | "staging" | "prod" — passed from the app, single source of truth
23
96
  }
24
97
 
@@ -30,7 +103,7 @@ export class LambdaStack extends cdk.Stack {
30
103
  constructor(scope: Construct, id: string, props: LambdaStackProps) {
31
104
  super(scope, id, props);
32
105
 
33
- const { resourceName, stage } = props;
106
+ const { resourceName, functionName, stage } = props;
34
107
 
35
108
  // API base path — obscures the Lambda route surface and is forwarded onto by
36
109
  // the gateway worker (BACKEND_URL = FunctionUrl + this path). It MUST be stable
@@ -89,25 +162,36 @@ export class LambdaStack extends cdk.Stack {
89
162
  }
90
163
  }
91
164
 
92
- // Resource naming: {app}-{env}
93
- // Examples: auth-craft-dev, auth-craft-staging, auth-craft-prod
165
+ // Naming: functionName = {app}-{env}-{layer}-api (the deployed resource);
166
+ // resourceName = {app}-{env}-{layer} base (AUTH_SYSTEM_NAME / JWT issuer).
167
+
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);
94
174
 
95
- // Log group for Lambda function
96
175
  const logGroup = new logs.LogGroup(this, 'AuthLambdaLogGroup', {
97
- logGroupName: `/aws/lambda/${resourceName}`,
98
- retention: logs.RetentionDays.ONE_MONTH,
176
+ logGroupName: `/aws/lambda/${functionName}`,
177
+ retention,
99
178
  removalPolicy: stage === 'prod' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
100
179
  });
101
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
+
102
186
  // Lambda function from the PREBUILT bundle shipped in this package.
103
187
  // Unlike the in-repo app stack (which uses NodejsFunction to build from
104
188
  // src/index.ts at synth time), this self-contained package deploys the
105
189
  // already-bundled assets/lambda/index.mjs — no monorepo / source needed.
106
190
  // The app build (esbuild, ESM, @aws-sdk/* external) emits `export const handler`
107
191
  // in index.mjs → handler 'index.handler'.
108
- // Function name: {app}-{env}
192
+ // Function name: {app}-{env}-{layer}-api
109
193
  this.lambdaFunction = new lambda.Function(this, 'AuthLambda', {
110
- functionName: resourceName,
194
+ functionName,
111
195
  code: lambda.Code.fromAsset(LAMBDA_ASSET_DIR),
112
196
  handler: 'index.handler',
113
197
  runtime: lambda.Runtime.NODEJS_24_X,
@@ -233,8 +317,8 @@ export class LambdaStack extends cdk.Stack {
233
317
  }),
234
318
  },
235
319
 
236
- // Tracing
237
- tracing: lambda.Tracing.ACTIVE, // Enable X-Ray tracing
320
+ // Tracing (X-Ray) — see resolveTracing above; off by default in dev.
321
+ tracing,
238
322
  });
239
323
 
240
324
  // Grant DynamoDB permissions
@@ -290,6 +374,20 @@ export class LambdaStack extends cdk.Stack {
290
374
  exportName: `${this.stackName}-ApiBasePath`,
291
375
  });
292
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
+
293
391
  // Output auth system name — used as JWT issuer by Workers to verify tokens
294
392
  new cdk.CfnOutput(this, 'AuthSystemName', {
295
393
  value: resourceName,
package/lib/common.sh CHANGED
@@ -76,11 +76,18 @@ load_env_file() {
76
76
  }
77
77
 
78
78
  # ── Stage / naming derivation (mirrors the CI workflow + CDK app.ts) ──────────
79
- # Inputs: STAGE, PROJECT, optional AUTH_CRAFT_APP_NAME.
80
- # Exports STAGE (normalized), PROJECT, APP_NAME, RESOURCE_NAME, RUNTIME_NODE_ENV.
79
+ # Inputs: STAGE, PROJECT, LAYER, optional AUTH_CRAFT_APP_NAME.
80
+ # Exports STAGE (normalized), PROJECT, APP_NAME, LAYER, RESOURCE_NAME, RUNTIME_NODE_ENV.
81
+ #
82
+ # Naming pattern: {app}-{env}-{layer}, with a per-resource {component} appended
83
+ # (data/api/system/…). So table = {app}-{env}-{layer}-data, lambda = …-api, etc.
84
+ # RESOURCE_NAME is the {app}-{env}-{layer} base (also the AUTH_SYSTEM_NAME / JWT
85
+ # issuer). LAYER (default "auth") namespaces this deploy within a larger app that
86
+ # has other layers (e.g. billing); a consumer sets --layer/LAYER to reuse the CLI.
81
87
  derive_naming() {
82
88
  STAGE="${STAGE:-dev}"
83
89
  PROJECT="${PROJECT:-default}"
90
+ LAYER="${LAYER:-auth}"
84
91
 
85
92
  # #2 Normalize common stage aliases so a consumer can pass either form.
86
93
  # production -> prod, development -> dev, staging stays staging.
@@ -103,9 +110,12 @@ derive_naming() {
103
110
  APP_NAME="auth-craft-${PROJECT}"
104
111
  fi
105
112
 
106
- RESOURCE_NAME="${APP_NAME}-${STAGE}"
113
+ # Base name shared by every resource: {app}-{env}-{layer}. Per-resource component
114
+ # (data/api/system/…) is appended at each call site. Also the AUTH_SYSTEM_NAME /
115
+ # JWT issuer, so it must NOT carry a component (keeps the iss claim stable).
116
+ RESOURCE_NAME="${APP_NAME}-${STAGE}-${LAYER}"
107
117
  RUNTIME_NODE_ENV="$([[ "$STAGE" == "prod" ]] && echo production || echo development)"
108
- export STAGE PROJECT APP_NAME RESOURCE_NAME RUNTIME_NODE_ENV
118
+ export STAGE PROJECT APP_NAME LAYER RESOURCE_NAME RUNTIME_NODE_ENV
109
119
  }
110
120
 
111
121
  # ── Gateway JWT key pair (#5: one source, derive the rest) ────────────────────
@@ -188,16 +198,16 @@ gateway_mode() {
188
198
  }
189
199
 
190
200
  # Derive a gateway worker URL for a scope (custom domain > workers.dev > empty).
191
- # In shared mode all scopes resolve to the single worker ${APP_NAME}-${STAGE}.
201
+ # In shared mode all scopes resolve to the single worker ${RESOURCE_NAME}.
192
202
  gateway_url_for() {
193
203
  local scope="$1" upper dom_var custom worker_name
194
204
  upper="$(echo "$scope" | tr '[:lower:]' '[:upper:]')"
195
205
  dom_var="CF_WORKER_CUSTOM_DOMAIN_${upper}"
196
206
  custom="${!dom_var:-}"
197
207
  if [[ "$(gateway_mode)" == "shared" ]]; then
198
- worker_name="${APP_NAME}-${STAGE}"
208
+ worker_name="${RESOURCE_NAME}"
199
209
  else
200
- worker_name="${APP_NAME}-${STAGE}-${scope}"
210
+ worker_name="${RESOURCE_NAME}-${scope}"
201
211
  fi
202
212
  if [[ -n "$custom" ]]; then
203
213
  echo "https://$custom"
@@ -221,7 +231,7 @@ gateway_url_for() {
221
231
  print_outputs_json() {
222
232
  require_cmd aws; require_cmd jq
223
233
  require_aws_identity
224
- local api_stack="${APP_NAME}-${STAGE}-api"
234
+ local api_stack="${RESOURCE_NAME}-api"
225
235
  local url base name table fn backend issuer
226
236
  url="$(cfn_output "$api_stack" LambdaFunctionUrl)"
227
237
  base="$(cfn_output "$api_stack" ApiBasePath)"
@@ -244,20 +254,28 @@ print_outputs_json() {
244
254
  svcPub="$(lambda_env "$fn" INTERNAL_JWT_PUBLIC_KEY)"; svcPub="${svcPub:-${LAMBDA_INTERNAL_JWT_PUBLIC_KEY:-}}"
245
255
 
246
256
  # Worker names per scope. In multi mode (default) each scope is its own worker
247
- # (${APP_NAME}-${STAGE}-${scope}); in shared mode all scopes are the single worker
248
- # ${APP_NAME}-${STAGE}. These are what a consumer's Cloudflare Pages project references
257
+ # (${RESOURCE_NAME}-${scope}); in shared mode all scopes are the single worker
258
+ # ${RESOURCE_NAME}. These are what a consumer's Cloudflare Pages project references
249
259
  # in a Service Binding (`[[services]] service = "<worker name>"`) to front auth
250
260
  # same-origin via @auth-craft/pages-auth-proxy. The binding lives in the consumer's
251
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
+
252
270
  local mode wnSystem wnTenant wnCustomer bindingService
253
271
  mode="$(gateway_mode)"
254
272
  if [[ "$mode" == "shared" ]]; then
255
- wnSystem="${APP_NAME}-${STAGE}"; wnTenant="$wnSystem"; wnCustomer="$wnSystem"
273
+ wnSystem="${RESOURCE_NAME}"; wnTenant="$wnSystem"; wnCustomer="$wnSystem"
256
274
  bindingService="$wnSystem"
257
275
  else
258
- wnSystem="${APP_NAME}-${STAGE}-system"
259
- wnTenant="${APP_NAME}-${STAGE}-tenant"
260
- wnCustomer="${APP_NAME}-${STAGE}-customer"
276
+ wnSystem="${RESOURCE_NAME}-system"
277
+ wnTenant="${RESOURCE_NAME}-tenant"
278
+ wnCustomer="${RESOURCE_NAME}-customer"
261
279
  bindingService="$wnCustomer"
262
280
  fi
263
281
  local bindingSnippet
@@ -278,11 +296,13 @@ print_outputs_json() {
278
296
  --arg rpCustomer "${CF_WORKER_ROUTE_PREFIX_CUSTOMER:-/customer}" \
279
297
  --arg wnSystem "$wnSystem" --arg wnTenant "$wnTenant" --arg wnCustomer "$wnCustomer" \
280
298
  --arg bindingSnippet "$bindingSnippet" \
299
+ --arg logRetention "$logRetention" --arg tracingMode "$tracingMode" \
281
300
  '{stage:$stage, project:$project, region:$region,
282
301
  lambdaFunctionUrl:$url, apiBasePath:$base, authSystemName:$name,
283
302
  dynamodbTable:$table, backendUrl:$backend,
284
303
  jwtPublicKey:$jwtPub, jwtIssuer:$jwtIssuer, jwtAlgorithm:$jwtAlg,
285
304
  serviceJwtPublicKey:$svcPub,
305
+ observability:{logRetentionDays:$logRetention, tracingMode:$tracingMode},
286
306
  gatewayUrls:{system:$gwSystem, tenant:$gwTenant, customer:$gwCustomer},
287
307
  workerNames:{system:$wnSystem, tenant:$wnTenant, customer:$wnCustomer},
288
308
  routePrefixes:{system:$rpSystem, tenant:$rpTenant, customer:$rpCustomer},
@@ -295,7 +295,7 @@ deploy_gateway() {
295
295
  # ── Shared worker: one worker serves all scopes; scope comes from the path's first
296
296
  # segment (worker AUTH_SCOPE unset). CORS unions all scopes; KV uses the customer id.
297
297
  local worker_name custom_domain allowed_origins origin_suffixes cors_kv_id
298
- worker_name="${APP_NAME}-${STAGE}"
298
+ worker_name="${RESOURCE_NAME}"
299
299
  custom_domain="$(custom_domain_for shared)"
300
300
  allowed_origins="$(merge_cors_for_shared allowed_origins_for)"
301
301
  origin_suffixes="$(merge_cors_for_shared origin_suffixes_for)"
@@ -324,7 +324,7 @@ deploy_gateway() {
324
324
  local scope worker_name route_prefix custom_domain
325
325
  local allowed_origins origin_suffixes cors_kv_id
326
326
  for scope in system tenant customer; do
327
- worker_name="${APP_NAME}-${STAGE}-${scope}"
327
+ worker_name="${RESOURCE_NAME}-${scope}"
328
328
  route_prefix="$(route_prefix_for "$scope")"
329
329
  custom_domain="$(custom_domain_for "$scope")"
330
330
  allowed_origins="$(allowed_origins_for "$scope")"
@@ -21,11 +21,11 @@ deploy_lambda() {
21
21
  [[ -f "$LAMBDA_ASSET_DIR/index.mjs" ]] \
22
22
  || die "Prebuilt Lambda bundle missing: $LAMBDA_ASSET_DIR/index.mjs (was the package built?)"
23
23
 
24
- local api_stack="${APP_NAME}-${STAGE}-api"
24
+ local api_stack="${RESOURCE_NAME}-api"
25
25
  OUTPUTS_FILE="${OUTPUTS_FILE:-$(mktemp -t auth-craft-cdk-outputs.XXXXXX.json)}"
26
26
  export OUTPUTS_FILE
27
27
 
28
- info "Deploying CDK stacks (${APP_NAME}-${STAGE}-data, ${api_stack})…"
28
+ info "Deploying CDK stacks (${RESOURCE_NAME}-data, ${api_stack})…"
29
29
  (
30
30
  cd "$CDK_DIR"
31
31
  CDK_DEFAULT_ACCOUNT="$AWS_ACCOUNT_ID" \
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@auth-craft/aws-cf-stack",
3
- "version": "1.7.1",
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",