@go-to-k/cdkd 0.71.0 → 0.73.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/dist/cli.js CHANGED
@@ -70703,41 +70703,6 @@ function runForeground(cmd, args) {
70703
70703
 
70704
70704
  // src/local/docker-image-builder.ts
70705
70705
  import { createHash as createHash2 } from "node:crypto";
70706
- async function buildContainerImage(asset, cdkOutDir, options) {
70707
- const tag = computeLocalTag(asset.source);
70708
- const platform = architectureToPlatform(options.architecture);
70709
- const logger = getLogger().child("local-invoke-build");
70710
- logger.info(`Building container image (platform=${platform})...`);
70711
- logger.debug(`Local tag: ${tag}`);
70712
- await buildDockerImage(asset, cdkOutDir, tag, {
70713
- platform,
70714
- wrapError: (stderr) => new LocalInvokeBuildError(
70715
- `docker build failed for container Lambda asset (${asset.source.directory}): ${stderr}`
70716
- )
70717
- });
70718
- return tag;
70719
- }
70720
- function architectureToPlatform(architecture) {
70721
- return architecture === "arm64" ? "linux/arm64" : "linux/amd64";
70722
- }
70723
- function computeLocalTag(source) {
70724
- const hash = createHash2("sha256");
70725
- hash.update(source.directory);
70726
- hash.update("\0");
70727
- hash.update(source.dockerFile ?? "");
70728
- hash.update("\0");
70729
- hash.update(source.dockerBuildTarget ?? "");
70730
- hash.update("\0");
70731
- if (source.dockerBuildArgs) {
70732
- for (const [k, v] of Object.entries(source.dockerBuildArgs)) {
70733
- hash.update(k);
70734
- hash.update("=");
70735
- hash.update(v);
70736
- hash.update("\0");
70737
- }
70738
- }
70739
- return `cdkd-local-invoke-${hash.digest("hex").slice(0, 16)}`;
70740
- }
70741
70706
 
70742
70707
  // src/local/ecr-puller.ts
70743
70708
  import { execFile as execFile4, spawn as spawn4 } from "node:child_process";
@@ -70845,6 +70810,14 @@ async function verifyImageInLocalCache(imageUri) {
70845
70810
  );
70846
70811
  }
70847
70812
  }
70813
+ async function isImageInLocalCache(imageRef) {
70814
+ try {
70815
+ await execFileAsync4("docker", ["image", "inspect", imageRef]);
70816
+ return true;
70817
+ } catch {
70818
+ return false;
70819
+ }
70820
+ }
70848
70821
  function runForeground2(cmd, args) {
70849
70822
  return new Promise((resolve6, reject) => {
70850
70823
  const proc = spawn4(cmd, args, { stdio: "inherit" });
@@ -70858,6 +70831,53 @@ function runForeground2(cmd, args) {
70858
70831
  });
70859
70832
  }
70860
70833
 
70834
+ // src/local/docker-image-builder.ts
70835
+ async function buildContainerImage(asset, cdkOutDir, options) {
70836
+ const tag = computeLocalTag(asset.source);
70837
+ const platform = architectureToPlatform(options.architecture);
70838
+ const logger = getLogger().child("local-invoke-build");
70839
+ if (options.noBuild === true) {
70840
+ logger.info(`Skipping docker build (--no-build). Verifying ${tag} is in local registry...`);
70841
+ if (!await isImageInLocalCache(tag)) {
70842
+ throw new LocalInvokeBuildError(
70843
+ `image '${tag}' not in local registry and --no-build is set; remove --no-build or run \`docker build\` manually first.`
70844
+ );
70845
+ }
70846
+ logger.debug(`Local tag ${tag} is cached; skipping build.`);
70847
+ return tag;
70848
+ }
70849
+ logger.info(`Building container image (platform=${platform})...`);
70850
+ logger.debug(`Local tag: ${tag}`);
70851
+ await buildDockerImage(asset, cdkOutDir, tag, {
70852
+ platform,
70853
+ wrapError: (stderr) => new LocalInvokeBuildError(
70854
+ `docker build failed for container Lambda asset (${asset.source.directory}): ${stderr}`
70855
+ )
70856
+ });
70857
+ return tag;
70858
+ }
70859
+ function architectureToPlatform(architecture) {
70860
+ return architecture === "arm64" ? "linux/arm64" : "linux/amd64";
70861
+ }
70862
+ function computeLocalTag(source) {
70863
+ const hash = createHash2("sha256");
70864
+ hash.update(source.directory);
70865
+ hash.update("\0");
70866
+ hash.update(source.dockerFile ?? "");
70867
+ hash.update("\0");
70868
+ hash.update(source.dockerBuildTarget ?? "");
70869
+ hash.update("\0");
70870
+ if (source.dockerBuildArgs) {
70871
+ for (const [k, v] of Object.entries(source.dockerBuildArgs)) {
70872
+ hash.update(k);
70873
+ hash.update("=");
70874
+ hash.update(v);
70875
+ hash.update("\0");
70876
+ }
70877
+ }
70878
+ return `cdkd-local-invoke-${hash.digest("hex").slice(0, 16)}`;
70879
+ }
70880
+
70861
70881
  // src/local/rie-client.ts
70862
70882
  import { setTimeout as delay } from "node:timers/promises";
70863
70883
  var INVOKE_PATH = "/2015-03-31/functions/function/invocations";
@@ -71758,6 +71778,48 @@ function buildRestV1Event(req, ctx, opts = {}) {
71758
71778
  };
71759
71779
  return event;
71760
71780
  }
71781
+ function applyAuthorizerOverlay(event, overlay) {
71782
+ const requestContext = event["requestContext"] ?? {};
71783
+ let authorizer;
71784
+ switch (overlay.kind) {
71785
+ case "lambda-rest-v1": {
71786
+ authorizer = {
71787
+ ...overlay.principalId !== void 0 && { principalId: overlay.principalId },
71788
+ ...overlay.context ?? {}
71789
+ };
71790
+ break;
71791
+ }
71792
+ case "lambda-http-v2": {
71793
+ authorizer = {
71794
+ lambda: {
71795
+ ...overlay.principalId !== void 0 && { principalId: overlay.principalId },
71796
+ ...overlay.context ?? {}
71797
+ }
71798
+ };
71799
+ break;
71800
+ }
71801
+ case "cognito-rest-v1": {
71802
+ authorizer = { claims: { ...overlay.claims } };
71803
+ break;
71804
+ }
71805
+ case "jwt-http-v2": {
71806
+ authorizer = {
71807
+ jwt: {
71808
+ claims: { ...overlay.claims },
71809
+ scopes: overlay.scopes ?? []
71810
+ }
71811
+ };
71812
+ break;
71813
+ }
71814
+ }
71815
+ return {
71816
+ ...event,
71817
+ requestContext: {
71818
+ ...requestContext,
71819
+ authorizer
71820
+ }
71821
+ };
71822
+ }
71761
71823
  function splitRawUrl(rawUrl) {
71762
71824
  const q = rawUrl.indexOf("?");
71763
71825
  if (q === -1)
@@ -72124,143 +72186,1246 @@ function isPlaceholder(segment) {
72124
72186
  return /^\{[^/{}+]+\}$/.test(segment);
72125
72187
  }
72126
72188
 
72127
- // src/local/http-server.ts
72128
- async function startApiServer(opts) {
72129
- const logger = getLogger().child("start-api");
72130
- const server = createServer2((req, res) => {
72131
- handleRequest(req, res, opts).catch((err) => {
72132
- logger.error(
72133
- `Unhandled request error: ${err instanceof Error ? err.stack ?? err.message : String(err)}`
72189
+ // src/local/authorizer-resolver.ts
72190
+ function resolveRestV1Authorizer(authorizerLogicalId, template, stackName, declaredAt) {
72191
+ const authResource = template.Resources?.[authorizerLogicalId];
72192
+ if (!authResource || authResource.Type !== "AWS::ApiGateway::Authorizer") {
72193
+ throw new RouteDiscoveryError(
72194
+ `${declaredAt}: AuthorizerId '${authorizerLogicalId}' does not point at an AWS::ApiGateway::Authorizer in stack '${stackName}'.`
72195
+ );
72196
+ }
72197
+ const props = authResource.Properties ?? {};
72198
+ const type = props["Type"];
72199
+ if (type === "TOKEN") {
72200
+ const lambdaLogicalId = resolveLambdaArn(
72201
+ props["AuthorizerUri"],
72202
+ `${stackName}/${authorizerLogicalId}.AuthorizerUri`
72203
+ );
72204
+ const identitySource = typeof props["IdentitySource"] === "string" ? props["IdentitySource"] : "method.request.header.Authorization";
72205
+ const tokenHeader = parseRestV1HeaderSelector(identitySource, stackName, authorizerLogicalId);
72206
+ const ttl = parseTtl(props["AuthorizerResultTtlInSeconds"], 300, 3600);
72207
+ return {
72208
+ kind: "lambda-token",
72209
+ logicalId: authorizerLogicalId,
72210
+ lambdaLogicalId,
72211
+ tokenHeader,
72212
+ resultTtlSeconds: ttl,
72213
+ declaredAt
72214
+ };
72215
+ }
72216
+ if (type === "REQUEST") {
72217
+ const lambdaLogicalId = resolveLambdaArn(
72218
+ props["AuthorizerUri"],
72219
+ `${stackName}/${authorizerLogicalId}.AuthorizerUri`
72220
+ );
72221
+ const identitySources = parseRestV1IdentitySources(
72222
+ typeof props["IdentitySource"] === "string" ? props["IdentitySource"] : ""
72223
+ );
72224
+ const ttl = parseTtl(props["AuthorizerResultTtlInSeconds"], 300, 3600);
72225
+ return {
72226
+ kind: "lambda-request",
72227
+ logicalId: authorizerLogicalId,
72228
+ lambdaLogicalId,
72229
+ identitySources,
72230
+ resultTtlSeconds: ttl,
72231
+ apiVersion: "v1",
72232
+ declaredAt
72233
+ };
72234
+ }
72235
+ if (type === "COGNITO_USER_POOLS") {
72236
+ const arns = props["ProviderARNs"];
72237
+ if (!Array.isArray(arns) || arns.length === 0) {
72238
+ throw new RouteDiscoveryError(
72239
+ `${stackName}/${authorizerLogicalId}: COGNITO_USER_POOLS authorizer is missing ProviderARNs.`
72134
72240
  );
72135
- if (!res.headersSent) {
72136
- writeError(res, 502);
72137
- }
72138
- });
72139
- });
72140
- server.on("connection", (socket) => {
72141
- socket.setNoDelay(true);
72142
- });
72143
- const { actualPort, actualHost } = await new Promise(
72144
- (resolveListen, rejectListen) => {
72145
- server.once("error", rejectListen);
72146
- server.listen(opts.port, opts.host, () => {
72147
- const addr = server.address();
72148
- if (addr === null || typeof addr === "string") {
72149
- rejectListen(new Error("Could not determine listening address"));
72150
- return;
72151
- }
72152
- resolveListen({ actualPort: addr.port, actualHost: opts.host });
72153
- });
72154
72241
  }
72242
+ const arn = pickStringFromArn(arns[0], `${stackName}/${authorizerLogicalId}.ProviderARNs[0]`);
72243
+ const parsed = parseCognitoUserPoolArn(arn, `${stackName}/${authorizerLogicalId}`);
72244
+ return {
72245
+ kind: "cognito",
72246
+ logicalId: authorizerLogicalId,
72247
+ userPoolArn: arn,
72248
+ region: parsed.region,
72249
+ userPoolId: parsed.userPoolId,
72250
+ declaredAt
72251
+ };
72252
+ }
72253
+ throw new RouteDiscoveryError(
72254
+ `${stackName}/${authorizerLogicalId}: AWS::ApiGateway::Authorizer.Type '${String(type)}' is not supported by cdkd local start-api (only TOKEN / REQUEST / COGNITO_USER_POOLS \u2014 IAM / mTLS authorizers are deferred to a follow-up PR).`
72155
72255
  );
72156
- let closed = false;
72157
- return {
72158
- port: actualPort,
72159
- host: actualHost,
72160
- server,
72161
- close: async () => {
72162
- if (closed)
72163
- return;
72164
- closed = true;
72165
- await new Promise((resolveClose) => {
72166
- server.close(() => resolveClose());
72167
- server.closeAllConnections?.();
72168
- });
72169
- }
72170
- };
72171
72256
  }
72172
- async function handleRequest(req, res, opts) {
72173
- const logger = getLogger().child("start-api");
72174
- const bodyBuf = await readBody(req);
72175
- const rawUrl = req.url ?? "/";
72176
- const method = (req.method ?? "GET").toUpperCase();
72177
- const requestPath = rawUrl.split("?")[0] ?? "/";
72178
- const match = matchRoute(method, requestPath, opts.routes);
72179
- if (!match) {
72180
- writeError(res, 404, '{"message":"Not Found"}');
72181
- return;
72182
- }
72183
- const snapshot = {
72184
- method,
72185
- rawUrl,
72186
- headers: collectHeaders(req),
72187
- body: bodyBuf,
72188
- ...req.socket.remoteAddress !== void 0 && { sourceIp: req.socket.remoteAddress }
72189
- };
72190
- const matchCtx = {
72191
- route: match.route,
72192
- pathParameters: match.pathParameters,
72193
- matchedPath: requestPath
72194
- };
72195
- const event = match.route.apiVersion === "v1" ? buildRestV1Event(snapshot, matchCtx) : buildHttpApiV2Event(snapshot, matchCtx);
72196
- let handle;
72197
- try {
72198
- handle = await opts.pool.acquire(match.route.lambdaLogicalId);
72199
- } catch (err) {
72200
- logger.error(
72201
- `Failed to acquire container for ${match.route.lambdaLogicalId}: ${err instanceof Error ? err.message : String(err)}`
72257
+ function resolveHttpApiAuthorizer(authorizerLogicalId, routeAuthorizationScopes, template, stackName, declaredAt) {
72258
+ const authResource = template.Resources?.[authorizerLogicalId];
72259
+ if (!authResource || authResource.Type !== "AWS::ApiGatewayV2::Authorizer") {
72260
+ throw new RouteDiscoveryError(
72261
+ `${declaredAt}: AuthorizerId '${authorizerLogicalId}' does not point at an AWS::ApiGatewayV2::Authorizer in stack '${stackName}'.`
72202
72262
  );
72203
- writeError(res, 502);
72204
- return;
72205
72263
  }
72206
- try {
72207
- const invokeResult = await invokeRie(
72208
- handle.containerHost,
72209
- handle.hostPort,
72210
- event,
72211
- opts.rieTimeoutMs
72264
+ const props = authResource.Properties ?? {};
72265
+ const authType = props["AuthorizerType"];
72266
+ if (authType === "REQUEST") {
72267
+ const lambdaLogicalId = resolveLambdaArn(
72268
+ props["AuthorizerUri"],
72269
+ `${stackName}/${authorizerLogicalId}.AuthorizerUri`
72212
72270
  );
72213
- const translated = translateLambdaResponse(invokeResult.payload, match.route.apiVersion);
72214
- res.statusCode = translated.statusCode;
72215
- for (const [name, value] of Object.entries(translated.headers)) {
72216
- res.setHeader(name, value);
72217
- }
72218
- if (translated.cookies.length > 0) {
72219
- res.setHeader("set-cookie", translated.cookies);
72271
+ const identitySources = parseHttpV2IdentitySources(props["IdentitySource"]);
72272
+ const ttl = parseTtl(props["AuthorizerResultTtlInSeconds"], 0, 3600);
72273
+ return {
72274
+ kind: "lambda-request",
72275
+ logicalId: authorizerLogicalId,
72276
+ lambdaLogicalId,
72277
+ identitySources,
72278
+ resultTtlSeconds: ttl,
72279
+ apiVersion: "v2",
72280
+ declaredAt
72281
+ };
72282
+ }
72283
+ if (authType === "JWT") {
72284
+ const jwt = props["JwtConfiguration"];
72285
+ if (!jwt || typeof jwt !== "object") {
72286
+ throw new RouteDiscoveryError(
72287
+ `${stackName}/${authorizerLogicalId}: AWS::ApiGatewayV2::Authorizer.JwtConfiguration is required for AuthorizerType=JWT.`
72288
+ );
72220
72289
  }
72221
- res.end(translated.body);
72222
- } catch (err) {
72223
- logger.error(
72224
- `RIE invoke failed for ${match.route.lambdaLogicalId}: ${err instanceof Error ? err.message : String(err)}`
72225
- );
72226
- if (!res.headersSent) {
72227
- writeError(res, 502);
72228
- } else {
72229
- res.end();
72290
+ const obj = jwt;
72291
+ const issuer = obj["Issuer"];
72292
+ if (typeof issuer !== "string" || issuer.length === 0) {
72293
+ throw new RouteDiscoveryError(
72294
+ `${stackName}/${authorizerLogicalId}: JwtConfiguration.Issuer must be a string.`
72295
+ );
72230
72296
  }
72231
- } finally {
72232
- opts.pool.release(handle);
72297
+ const audienceRaw = obj["Audience"];
72298
+ const audience = Array.isArray(audienceRaw) ? audienceRaw.filter((s) => typeof s === "string") : [];
72299
+ const cognito = parseCognitoIssuer(issuer);
72300
+ return {
72301
+ kind: "jwt",
72302
+ logicalId: authorizerLogicalId,
72303
+ issuer,
72304
+ audience,
72305
+ ...cognito && { region: cognito.region, userPoolId: cognito.userPoolId },
72306
+ declaredAt
72307
+ };
72233
72308
  }
72309
+ throw new RouteDiscoveryError(
72310
+ `${stackName}/${authorizerLogicalId}: AWS::ApiGatewayV2::Authorizer.AuthorizerType '${String(authType)}' is not supported by cdkd local start-api (only REQUEST / JWT).`
72311
+ );
72234
72312
  }
72235
- function readBody(req) {
72236
- return new Promise((resolveBody, rejectBody) => {
72237
- const chunks = [];
72238
- req.on("data", (chunk) => {
72239
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
72240
- });
72241
- req.on("end", () => resolveBody(Buffer.concat(chunks)));
72242
- req.on("error", rejectBody);
72243
- });
72244
- }
72245
- function collectHeaders(req) {
72246
- const out = {};
72247
- for (const [name, value] of Object.entries(req.headers)) {
72248
- if (Array.isArray(value)) {
72249
- out[name] = value;
72250
- } else if (typeof value === "string") {
72251
- out[name] = [value];
72313
+ function resolveLambdaArn(value, location) {
72314
+ if (value && typeof value === "object" && !Array.isArray(value)) {
72315
+ const obj = value;
72316
+ if ("Ref" in obj && typeof obj["Ref"] === "string")
72317
+ return obj["Ref"];
72318
+ if ("Fn::GetAtt" in obj) {
72319
+ const arg = obj["Fn::GetAtt"];
72320
+ if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && arg[1] === "Arn") {
72321
+ return arg[0];
72322
+ }
72252
72323
  }
72253
- }
72254
- return out;
72255
- }
72256
- function writeError(res, statusCode, body = '{"message":"Internal server error"}') {
72257
- res.statusCode = statusCode;
72258
- res.setHeader("content-type", "application/json");
72259
- res.setHeader("content-length", String(Buffer.byteLength(body, "utf-8")));
72260
- res.end(body);
72261
- }
72262
-
72263
- // src/cli/commands/local-start-api.ts
72324
+ if ("Fn::Join" in obj) {
72325
+ const join9 = obj["Fn::Join"];
72326
+ if (Array.isArray(join9) && join9.length === 2 && Array.isArray(join9[1])) {
72327
+ const parts = join9[1];
72328
+ const literal = parts.filter((p) => typeof p === "string").join("");
72329
+ if (literal.includes(":lambda:path/2015-03-31/functions/")) {
72330
+ for (const p of parts) {
72331
+ if (p && typeof p === "object" && !Array.isArray(p)) {
72332
+ const inner = p;
72333
+ const arg = inner["Fn::GetAtt"];
72334
+ if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && arg[1] === "Arn") {
72335
+ return arg[0];
72336
+ }
72337
+ }
72338
+ }
72339
+ }
72340
+ }
72341
+ }
72342
+ }
72343
+ throw new RouteDiscoveryError(
72344
+ `${location}: only { Ref }, { Fn::GetAtt: [..., 'Arn'] }, or the REST v1 invoke-ARN Fn::Join wrapper are supported (got ${shortJson2(value)}).`
72345
+ );
72346
+ }
72347
+ function parseRestV1HeaderSelector(identitySource, stackName, authorizerLogicalId) {
72348
+ const m = /^method\.request\.header\.([A-Za-z0-9-_]+)$/.exec(identitySource.trim());
72349
+ if (!m) {
72350
+ throw new RouteDiscoveryError(
72351
+ `${stackName}/${authorizerLogicalId}: TOKEN authorizer IdentitySource '${identitySource}' must be 'method.request.header.<HeaderName>'.`
72352
+ );
72353
+ }
72354
+ return m[1].toLowerCase();
72355
+ }
72356
+ function parseRestV1IdentitySources(raw) {
72357
+ const out = [];
72358
+ for (const tokenRaw of raw.split(",")) {
72359
+ const token = tokenRaw.trim();
72360
+ if (token.length === 0)
72361
+ continue;
72362
+ const headerMatch = /^method\.request\.header\.([A-Za-z0-9-_]+)$/.exec(token);
72363
+ if (headerMatch) {
72364
+ out.push({ kind: "header", name: headerMatch[1].toLowerCase() });
72365
+ continue;
72366
+ }
72367
+ const queryMatch = /^method\.request\.querystring\.([A-Za-z0-9-_]+)$/.exec(token);
72368
+ if (queryMatch) {
72369
+ out.push({ kind: "query", name: queryMatch[1] });
72370
+ continue;
72371
+ }
72372
+ const contextMatch = /^context\.([A-Za-z0-9._-]+)$/.exec(token);
72373
+ if (contextMatch) {
72374
+ out.push({ kind: "context", name: contextMatch[1] });
72375
+ continue;
72376
+ }
72377
+ const stageMatch = /^stageVariables\.([A-Za-z0-9._-]+)$/.exec(token);
72378
+ if (stageMatch) {
72379
+ out.push({ kind: "stage-variable", name: stageMatch[1] });
72380
+ continue;
72381
+ }
72382
+ out.push({ kind: "header", name: token.toLowerCase() });
72383
+ }
72384
+ return out;
72385
+ }
72386
+ function parseHttpV2IdentitySources(raw) {
72387
+ if (!Array.isArray(raw))
72388
+ return [];
72389
+ const out = [];
72390
+ for (const entry of raw) {
72391
+ if (typeof entry !== "string")
72392
+ continue;
72393
+ const headerMatch = /^\$request\.header\.([A-Za-z0-9-_]+)$/.exec(entry);
72394
+ if (headerMatch) {
72395
+ out.push({ kind: "header", name: headerMatch[1].toLowerCase() });
72396
+ continue;
72397
+ }
72398
+ const queryMatch = /^\$request\.querystring\.([A-Za-z0-9-_]+)$/.exec(entry);
72399
+ if (queryMatch) {
72400
+ out.push({ kind: "query", name: queryMatch[1] });
72401
+ continue;
72402
+ }
72403
+ out.push({ kind: "header", name: entry.toLowerCase() });
72404
+ }
72405
+ return out;
72406
+ }
72407
+ function parseCognitoUserPoolArn(arn, location) {
72408
+ const m = /^arn:aws[a-z0-9-]*:cognito-idp:([a-z0-9-]+):[0-9]+:userpool\/(.+)$/.exec(arn);
72409
+ if (!m) {
72410
+ throw new RouteDiscoveryError(
72411
+ `${location}: malformed Cognito User Pool ARN '${arn}'. Expected 'arn:aws:cognito-idp:<region>:<account>:userpool/<id>'.`
72412
+ );
72413
+ }
72414
+ return { region: m[1], userPoolId: m[2] };
72415
+ }
72416
+ function parseCognitoIssuer(issuer) {
72417
+ const m = /^https:\/\/cognito-idp\.([a-z0-9-]+)\.amazonaws\.com\/([^/]+)\/?$/.exec(issuer);
72418
+ if (!m)
72419
+ return void 0;
72420
+ return { region: m[1], userPoolId: m[2] };
72421
+ }
72422
+ function pickStringFromArn(value, location) {
72423
+ if (typeof value === "string")
72424
+ return value;
72425
+ if (value && typeof value === "object" && !Array.isArray(value)) {
72426
+ const obj = value;
72427
+ if ("Fn::GetAtt" in obj) {
72428
+ const arg = obj["Fn::GetAtt"];
72429
+ if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && arg[1] === "Arn") {
72430
+ throw new RouteDiscoveryError(
72431
+ `${location}: ProviderARNs[0] uses Fn::GetAtt against logical ID '${arg[0]}'. cdkd local start-api needs the literal ARN string to derive the JWKS URL \u2014 set the user pool ARN explicitly via 'authorizer.providerArns' on the CDK construct, or upgrade to JWT (HTTP v2) which encodes the pool in the Issuer URL.`
72432
+ );
72433
+ }
72434
+ }
72435
+ }
72436
+ throw new RouteDiscoveryError(
72437
+ `${location}: ProviderARNs[0] must be a literal string (got ${shortJson2(value)}).`
72438
+ );
72439
+ }
72440
+ function parseTtl(raw, fallback, max) {
72441
+ if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0)
72442
+ return fallback;
72443
+ return Math.min(Math.trunc(raw), max);
72444
+ }
72445
+ function buildIdentityHash(parts) {
72446
+ return parts.map((p) => p ?? "").join("\0");
72447
+ }
72448
+ function attachAuthorizers(stacks, routes) {
72449
+ const stackByRoute = /* @__PURE__ */ new Map();
72450
+ for (const stack of stacks) {
72451
+ const prefix = `${stack.stackName}/`;
72452
+ for (const route of routes) {
72453
+ if (route.declaredAt.startsWith(prefix))
72454
+ stackByRoute.set(route.declaredAt, stack);
72455
+ }
72456
+ }
72457
+ const out = [];
72458
+ const errors = [];
72459
+ for (const route of routes) {
72460
+ const stack = stackByRoute.get(route.declaredAt);
72461
+ if (!stack) {
72462
+ out.push({ route });
72463
+ continue;
72464
+ }
72465
+ try {
72466
+ const authorizer = detectAuthorizer(route, stack);
72467
+ out.push({ route, ...authorizer && { authorizer } });
72468
+ } catch (err) {
72469
+ errors.push(err instanceof Error ? err.message : String(err));
72470
+ }
72471
+ }
72472
+ if (errors.length > 0) {
72473
+ throw new RouteDiscoveryError(
72474
+ `cdkd local start-api: ${errors.length} authorizer error(s):
72475
+ ` + errors.map((e) => ` - ${e}`).join("\n")
72476
+ );
72477
+ }
72478
+ return out;
72479
+ }
72480
+ function detectAuthorizer(route, stack) {
72481
+ const slash = route.declaredAt.indexOf("/");
72482
+ if (slash < 0)
72483
+ return void 0;
72484
+ const logicalId = route.declaredAt.slice(slash + 1);
72485
+ const resource = stack.template.Resources?.[logicalId];
72486
+ if (!resource)
72487
+ return void 0;
72488
+ if (resource.Type === "AWS::ApiGateway::Method") {
72489
+ return detectRestV1Authorizer(resource, logicalId, stack);
72490
+ }
72491
+ if (resource.Type === "AWS::ApiGatewayV2::Route") {
72492
+ return detectHttpApiAuthorizer(resource, logicalId, stack);
72493
+ }
72494
+ return void 0;
72495
+ }
72496
+ function detectRestV1Authorizer(methodResource, methodLogicalId, stack) {
72497
+ const props = methodResource.Properties ?? {};
72498
+ const authType = props["AuthorizationType"];
72499
+ if (authType === void 0 || authType === "NONE")
72500
+ return void 0;
72501
+ const authorizerId = props["AuthorizerId"];
72502
+ const refLogicalId = pickRefLogicalId2(authorizerId);
72503
+ if (authType === "AWS_IAM") {
72504
+ throw new RouteDiscoveryError(
72505
+ `${stack.stackName}/${methodLogicalId}: REST v1 AWS_IAM authorization is not supported by cdkd local start-api (deferred follow-up PR).`
72506
+ );
72507
+ }
72508
+ if (!refLogicalId) {
72509
+ throw new RouteDiscoveryError(
72510
+ `${stack.stackName}/${methodLogicalId}: AuthorizationType='${String(authType)}' but AuthorizerId is missing or not a {Ref:...}.`
72511
+ );
72512
+ }
72513
+ return resolveRestV1Authorizer(
72514
+ refLogicalId,
72515
+ stack.template,
72516
+ stack.stackName,
72517
+ `${stack.stackName}/${methodLogicalId}`
72518
+ );
72519
+ }
72520
+ function detectHttpApiAuthorizer(routeResource, routeLogicalId, stack) {
72521
+ const props = routeResource.Properties ?? {};
72522
+ const authType = props["AuthorizationType"];
72523
+ if (authType === void 0 || authType === "NONE")
72524
+ return void 0;
72525
+ const authorizerId = props["AuthorizerId"];
72526
+ const refLogicalId = pickRefLogicalId2(authorizerId);
72527
+ if (!refLogicalId) {
72528
+ throw new RouteDiscoveryError(
72529
+ `${stack.stackName}/${routeLogicalId}: AuthorizationType='${String(authType)}' but AuthorizerId is missing or not a {Ref:...}.`
72530
+ );
72531
+ }
72532
+ const scopesRaw = props["AuthorizationScopes"];
72533
+ const scopes = Array.isArray(scopesRaw) ? scopesRaw.filter((s) => typeof s === "string") : void 0;
72534
+ return resolveHttpApiAuthorizer(
72535
+ refLogicalId,
72536
+ scopes,
72537
+ stack.template,
72538
+ stack.stackName,
72539
+ `${stack.stackName}/${routeLogicalId}`
72540
+ );
72541
+ }
72542
+ function pickRefLogicalId2(value) {
72543
+ if (value && typeof value === "object" && !Array.isArray(value)) {
72544
+ const ref = value["Ref"];
72545
+ if (typeof ref === "string")
72546
+ return ref;
72547
+ }
72548
+ return null;
72549
+ }
72550
+ function shortJson2(value) {
72551
+ try {
72552
+ const s = JSON.stringify(value);
72553
+ return s.length > 200 ? `${s.slice(0, 200)}\u2026` : s;
72554
+ } catch {
72555
+ return String(value);
72556
+ }
72557
+ }
72558
+
72559
+ // src/local/lambda-authorizer.ts
72560
+ function buildMethodArn(opts) {
72561
+ const region = opts.region ?? "local";
72562
+ const trimmedPath = opts.path.replace(/^\//, "");
72563
+ return `arn:aws:execute-api:${region}:${opts.accountId}:${opts.apiId}/${opts.stage}/${opts.method.toUpperCase()}/${trimmedPath}`;
72564
+ }
72565
+ async function invokeTokenAuthorizer(authorizer, request, ctx) {
72566
+ const token = request.headers[authorizer.tokenHeader];
72567
+ if (!token || token.length === 0) {
72568
+ return { allow: false, identityHash: void 0 };
72569
+ }
72570
+ const event = {
72571
+ type: "TOKEN",
72572
+ authorizationToken: token,
72573
+ methodArn: ctx.methodArn
72574
+ };
72575
+ const identityHash = buildIdentityHash([token]);
72576
+ const result = await invokeAuthorizerLambda(authorizer.lambdaLogicalId, event, ctx);
72577
+ return parseLambdaAuthorizerResponse(result, ctx.methodArn, identityHash);
72578
+ }
72579
+ async function invokeRequestAuthorizer(authorizer, request, ctx) {
72580
+ const { identityHash, missing } = computeRequestIdentityHash(authorizer, request);
72581
+ if (missing) {
72582
+ return { allow: false, identityHash: void 0 };
72583
+ }
72584
+ const event = authorizer.apiVersion === "v1" ? buildRequestEventV1(authorizer, request, ctx) : buildRequestEventV2(authorizer, request, ctx);
72585
+ const result = await invokeAuthorizerLambda(authorizer.lambdaLogicalId, event, ctx);
72586
+ if (authorizer.apiVersion === "v2") {
72587
+ return parseHttpV2RequestResponse(result, ctx.methodArn, identityHash);
72588
+ }
72589
+ return parseLambdaAuthorizerResponse(result, ctx.methodArn, identityHash);
72590
+ }
72591
+ function extractIdentityValue(sel, request) {
72592
+ switch (sel.kind) {
72593
+ case "header":
72594
+ return request.headers[sel.name];
72595
+ case "query":
72596
+ return request.queryStringParameters[sel.name];
72597
+ case "context":
72598
+ return void 0;
72599
+ case "stage-variable":
72600
+ return void 0;
72601
+ }
72602
+ }
72603
+ function computeRequestIdentityHash(authorizer, request) {
72604
+ const identityValues = authorizer.identitySources.map(
72605
+ (sel) => extractIdentityValue(sel, request)
72606
+ );
72607
+ const missing = authorizer.apiVersion === "v1" && authorizer.identitySources.length > 0 && identityValues.every((v) => v === void 0 || v === "");
72608
+ return { identityHash: buildIdentityHash(identityValues), missing };
72609
+ }
72610
+ function buildRequestEventV1(authorizer, request, ctx) {
72611
+ return {
72612
+ type: "REQUEST",
72613
+ methodArn: ctx.methodArn,
72614
+ resource: request.matchedPath,
72615
+ path: request.matchedPath,
72616
+ httpMethod: request.method,
72617
+ headers: request.headers,
72618
+ multiValueHeaders: Object.fromEntries(
72619
+ Object.entries(request.headers).map(([k, v]) => [k, v.split(",")])
72620
+ ),
72621
+ queryStringParameters: request.queryStringParameters,
72622
+ multiValueQueryStringParameters: Object.fromEntries(
72623
+ Object.entries(request.queryStringParameters).map(([k, v]) => [k, [v]])
72624
+ ),
72625
+ pathParameters: request.pathParameters,
72626
+ stageVariables: null,
72627
+ requestContext: {
72628
+ accountId: ctx.mockAccountId,
72629
+ apiId: ctx.mockApiId,
72630
+ httpMethod: request.method,
72631
+ identity: { sourceIp: request.sourceIp },
72632
+ path: `/${request.stage}${request.matchedPath}`,
72633
+ stage: request.stage
72634
+ },
72635
+ authorizationToken: request.headers[authorizer.identitySources[0]?.name ?? "authorization"]
72636
+ };
72637
+ }
72638
+ function buildRequestEventV2(_authorizer, request, ctx) {
72639
+ return {
72640
+ version: "2.0",
72641
+ type: "REQUEST",
72642
+ routeArn: ctx.methodArn,
72643
+ identitySource: [],
72644
+ // Honored by AWS but not interpreted by user code.
72645
+ routeKey: `${request.method} ${request.matchedPath}`,
72646
+ rawPath: request.matchedPath,
72647
+ rawQueryString: "",
72648
+ headers: request.headers,
72649
+ queryStringParameters: request.queryStringParameters,
72650
+ pathParameters: request.pathParameters,
72651
+ stageVariables: null,
72652
+ requestContext: {
72653
+ accountId: ctx.mockAccountId,
72654
+ apiId: ctx.mockApiId,
72655
+ domainName: "localhost",
72656
+ domainPrefix: "local",
72657
+ http: {
72658
+ method: request.method,
72659
+ path: request.matchedPath,
72660
+ protocol: "HTTP/1.1",
72661
+ sourceIp: request.sourceIp,
72662
+ userAgent: request.headers["user-agent"] ?? ""
72663
+ },
72664
+ requestId: "local-authorizer",
72665
+ routeKey: `${request.method} ${request.matchedPath}`,
72666
+ stage: request.stage,
72667
+ time: "",
72668
+ timeEpoch: 0
72669
+ }
72670
+ };
72671
+ }
72672
+ async function invokeAuthorizerLambda(lambdaLogicalId, event, ctx) {
72673
+ const handle = await ctx.pool.acquire(lambdaLogicalId);
72674
+ try {
72675
+ const result = await invokeRie(handle.containerHost, handle.hostPort, event, ctx.rieTimeoutMs);
72676
+ return result.payload;
72677
+ } finally {
72678
+ ctx.pool.release(handle);
72679
+ }
72680
+ }
72681
+ function parseLambdaAuthorizerResponse(payload, methodArn, identityHash) {
72682
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
72683
+ return { allow: false, identityHash };
72684
+ }
72685
+ const obj = payload;
72686
+ const principalId = typeof obj["principalId"] === "string" ? obj["principalId"] : void 0;
72687
+ const context = obj["context"] && typeof obj["context"] === "object" && !Array.isArray(obj["context"]) ? obj["context"] : void 0;
72688
+ const policy = obj["policyDocument"];
72689
+ if (!policy || typeof policy !== "object") {
72690
+ return {
72691
+ allow: false,
72692
+ identityHash,
72693
+ ...principalId !== void 0 && { principalId },
72694
+ ...context && { context }
72695
+ };
72696
+ }
72697
+ const stmts = policy["Statement"];
72698
+ if (!Array.isArray(stmts)) {
72699
+ return {
72700
+ allow: false,
72701
+ identityHash,
72702
+ ...principalId !== void 0 && { principalId },
72703
+ ...context && { context },
72704
+ policy
72705
+ };
72706
+ }
72707
+ const allow = stmts.some((stmt) => {
72708
+ if (!stmt || typeof stmt !== "object" || Array.isArray(stmt))
72709
+ return false;
72710
+ const s = stmt;
72711
+ if (s["Effect"] !== "Allow")
72712
+ return false;
72713
+ const resources = Array.isArray(s["Resource"]) ? s["Resource"] : [s["Resource"]];
72714
+ return resources.some((r) => typeof r === "string" && resourceMatches(r, methodArn));
72715
+ });
72716
+ return {
72717
+ allow,
72718
+ identityHash,
72719
+ ...principalId !== void 0 && { principalId },
72720
+ ...context && { context },
72721
+ policy
72722
+ };
72723
+ }
72724
+ function parseHttpV2RequestResponse(payload, methodArn, identityHash) {
72725
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
72726
+ const obj = payload;
72727
+ if (typeof obj["isAuthorized"] === "boolean") {
72728
+ const context = obj["context"] && typeof obj["context"] === "object" && !Array.isArray(obj["context"]) ? obj["context"] : void 0;
72729
+ return {
72730
+ allow: obj["isAuthorized"],
72731
+ identityHash,
72732
+ ...context && { context }
72733
+ };
72734
+ }
72735
+ }
72736
+ return parseLambdaAuthorizerResponse(payload, methodArn, identityHash);
72737
+ }
72738
+ function resourceMatches(pattern, methodArn) {
72739
+ if (pattern === methodArn)
72740
+ return true;
72741
+ if (!pattern.includes("*") && !pattern.includes("?"))
72742
+ return false;
72743
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
72744
+ const re = new RegExp(`^${escaped}$`);
72745
+ return re.test(methodArn);
72746
+ }
72747
+ function evaluateCachedLambdaPolicy(cached, methodArn) {
72748
+ const policy = cached.policy;
72749
+ if (!policy || typeof policy !== "object") {
72750
+ return { ...cached, allow: false };
72751
+ }
72752
+ const stmts = policy["Statement"];
72753
+ if (!Array.isArray(stmts)) {
72754
+ return { ...cached, allow: false };
72755
+ }
72756
+ const allow = stmts.some((stmt) => {
72757
+ if (!stmt || typeof stmt !== "object" || Array.isArray(stmt))
72758
+ return false;
72759
+ const s = stmt;
72760
+ if (s["Effect"] !== "Allow")
72761
+ return false;
72762
+ const resources = Array.isArray(s["Resource"]) ? s["Resource"] : [s["Resource"]];
72763
+ return resources.some((r) => typeof r === "string" && resourceMatches(r, methodArn));
72764
+ });
72765
+ return { ...cached, allow };
72766
+ }
72767
+
72768
+ // src/local/cognito-jwt.ts
72769
+ import { createPublicKey, createVerify } from "node:crypto";
72770
+ var DEFAULT_JWKS_TTL_MS = 60 * 60 * 1e3;
72771
+ var FAILURE_JWKS_TTL_MS = 60 * 1e3;
72772
+ function createJwksCache(opts = {}) {
72773
+ const fetchImpl = opts.fetchImpl ?? (async (url) => globalThis.fetch(url));
72774
+ const now = opts.now ?? (() => Date.now());
72775
+ const ttlMs = opts.ttlMs ?? DEFAULT_JWKS_TTL_MS;
72776
+ const failureTtlMs = opts.failureTtlMs ?? FAILURE_JWKS_TTL_MS;
72777
+ const map = /* @__PURE__ */ new Map();
72778
+ return {
72779
+ async fetchAndCache(jwksUrl) {
72780
+ const cached = map.get(jwksUrl);
72781
+ if (cached && cached.expiresAt > now())
72782
+ return cached;
72783
+ const logger = getLogger().child("cognito-jwt");
72784
+ try {
72785
+ const response = await fetchImpl(jwksUrl);
72786
+ if (!response.ok) {
72787
+ throw new Error(`JWKS fetch returned HTTP ${response.status}`);
72788
+ }
72789
+ const body = await response.text();
72790
+ const parsed = JSON.parse(body);
72791
+ const keys = Array.isArray(parsed.keys) ? parsed.keys : [];
72792
+ const byKid = /* @__PURE__ */ new Map();
72793
+ for (const k of keys) {
72794
+ if (!k || typeof k !== "object" || Array.isArray(k))
72795
+ continue;
72796
+ const obj = k;
72797
+ if (typeof obj["kid"] === "string" && typeof obj["n"] === "string" && typeof obj["e"] === "string" && typeof obj["kty"] === "string") {
72798
+ byKid.set(obj["kid"], {
72799
+ kid: obj["kid"],
72800
+ n: obj["n"],
72801
+ e: obj["e"],
72802
+ kty: obj["kty"],
72803
+ ...typeof obj["alg"] === "string" && { alg: obj["alg"] },
72804
+ ...typeof obj["use"] === "string" && { use: obj["use"] }
72805
+ });
72806
+ }
72807
+ }
72808
+ const entry = {
72809
+ byKid,
72810
+ expiresAt: now() + ttlMs,
72811
+ passThrough: false
72812
+ };
72813
+ map.set(jwksUrl, entry);
72814
+ return entry;
72815
+ } catch (err) {
72816
+ logger.warn(
72817
+ `JWKS unreachable at ${jwksUrl}: ${err instanceof Error ? err.message : String(err)}. JWT validation will allow all tokens \u2014 local dev fallback. Configure network access to the JWKS URL to enable real signature verification.`
72818
+ );
72819
+ const entry = {
72820
+ byKid: /* @__PURE__ */ new Map(),
72821
+ expiresAt: now() + failureTtlMs,
72822
+ passThrough: true
72823
+ };
72824
+ map.set(jwksUrl, entry);
72825
+ return entry;
72826
+ }
72827
+ },
72828
+ peek(jwksUrl) {
72829
+ return map.get(jwksUrl);
72830
+ },
72831
+ clear() {
72832
+ map.clear();
72833
+ }
72834
+ };
72835
+ }
72836
+ function buildCognitoJwksUrl(region, userPoolId) {
72837
+ return `https://cognito-idp.${region}.amazonaws.com/${userPoolId}/.well-known/jwks.json`;
72838
+ }
72839
+ function buildJwksUrlFromIssuer(issuer) {
72840
+ const stripped = issuer.replace(/\/+$/, "");
72841
+ return `${stripped}/.well-known/jwks.json`;
72842
+ }
72843
+ async function verifyCognitoJwt(authorizer, authorizationHeader, jwksCache, opts = {}) {
72844
+ const now = opts.now ?? (() => Date.now());
72845
+ const token = extractBearer(authorizationHeader);
72846
+ if (!token) {
72847
+ return { allow: false, identityHash: void 0, ttlSeconds: 0 };
72848
+ }
72849
+ const jwksUrl = buildCognitoJwksUrl(authorizer.region, authorizer.userPoolId);
72850
+ const expectedIssuer = `https://cognito-idp.${authorizer.region}.amazonaws.com/${authorizer.userPoolId}`;
72851
+ return verifyAndShape(token, jwksUrl, expectedIssuer, void 0, jwksCache, opts.warned, now);
72852
+ }
72853
+ async function verifyJwtAuthorizer(authorizer, authorizationHeader, jwksCache, opts = {}) {
72854
+ const now = opts.now ?? (() => Date.now());
72855
+ const token = extractBearer(authorizationHeader);
72856
+ if (!token) {
72857
+ return { allow: false, identityHash: void 0, ttlSeconds: 0 };
72858
+ }
72859
+ const jwksUrl = authorizer.region && authorizer.userPoolId ? buildCognitoJwksUrl(authorizer.region, authorizer.userPoolId) : buildJwksUrlFromIssuer(authorizer.issuer);
72860
+ return verifyAndShape(
72861
+ token,
72862
+ jwksUrl,
72863
+ authorizer.issuer.replace(/\/+$/, ""),
72864
+ authorizer.audience,
72865
+ jwksCache,
72866
+ opts.warned,
72867
+ now
72868
+ );
72869
+ }
72870
+ async function verifyAndShape(token, jwksUrl, expectedIssuer, expectedAudience, jwksCache, warned, now) {
72871
+ const identityHash = buildIdentityHash([token]);
72872
+ const jwks = await jwksCache.fetchAndCache(jwksUrl);
72873
+ if (jwks.passThrough) {
72874
+ if (warned && !warned.has(jwksUrl)) {
72875
+ warned.add(jwksUrl);
72876
+ getLogger().child("cognito-jwt").warn(
72877
+ `JWKS pass-through mode for ${jwksUrl}: token accepted without signature verification.`
72878
+ );
72879
+ }
72880
+ const parsed2 = parseJwt(token);
72881
+ if (parsed2) {
72882
+ return shapeAllowResult(parsed2, identityHash, now);
72883
+ }
72884
+ return {
72885
+ allow: true,
72886
+ principalId: "unknown",
72887
+ context: {},
72888
+ identityHash,
72889
+ ttlSeconds: 0
72890
+ };
72891
+ }
72892
+ const parsed = parseJwt(token);
72893
+ if (!parsed) {
72894
+ return { allow: false, identityHash, ttlSeconds: 0 };
72895
+ }
72896
+ const kid = parsed.header["kid"];
72897
+ if (typeof kid !== "string") {
72898
+ return { allow: false, identityHash, ttlSeconds: 0 };
72899
+ }
72900
+ const key = jwks.byKid.get(kid);
72901
+ if (!key) {
72902
+ return { allow: false, identityHash, ttlSeconds: 0 };
72903
+ }
72904
+ if (!verifyRs256(token, key)) {
72905
+ return { allow: false, identityHash, ttlSeconds: 0 };
72906
+ }
72907
+ const claims = parsed.payload;
72908
+ if (typeof claims["exp"] !== "number" || claims["exp"] * 1e3 <= now()) {
72909
+ return { allow: false, identityHash, ttlSeconds: 0 };
72910
+ }
72911
+ if (typeof claims["iss"] !== "string" || claims["iss"].replace(/\/+$/, "") !== expectedIssuer) {
72912
+ return { allow: false, identityHash, ttlSeconds: 0 };
72913
+ }
72914
+ if (expectedAudience && expectedAudience.length > 0) {
72915
+ const aud = claims["aud"];
72916
+ const clientId = claims["client_id"];
72917
+ const audValues = Array.isArray(aud) ? aud : aud !== void 0 ? [aud] : [];
72918
+ const matches = audValues.some((v) => typeof v === "string" && expectedAudience.includes(v)) || typeof clientId === "string" && expectedAudience.includes(clientId);
72919
+ if (!matches) {
72920
+ return { allow: false, identityHash, ttlSeconds: 0 };
72921
+ }
72922
+ }
72923
+ return shapeAllowResult(parsed, identityHash, now);
72924
+ }
72925
+ function shapeAllowResult(parsed, identityHash, now) {
72926
+ const claims = parsed.payload;
72927
+ const principalId = pickStringClaim(claims, "sub") ?? pickStringClaim(claims, "cognito:username") ?? pickStringClaim(claims, "username") ?? "unknown";
72928
+ const expMs = typeof claims["exp"] === "number" ? claims["exp"] * 1e3 : 0;
72929
+ const remainingSeconds = Math.max(0, Math.floor((expMs - now()) / 1e3));
72930
+ const ttlSeconds = Math.min(300, remainingSeconds);
72931
+ return {
72932
+ allow: true,
72933
+ principalId,
72934
+ context: claims,
72935
+ identityHash,
72936
+ ttlSeconds
72937
+ };
72938
+ }
72939
+ function pickStringClaim(claims, key) {
72940
+ const v = claims[key];
72941
+ return typeof v === "string" ? v : void 0;
72942
+ }
72943
+ function extractBearer(header) {
72944
+ if (!header)
72945
+ return void 0;
72946
+ const m = /^\s*Bearer\s+(.+)\s*$/i.exec(header);
72947
+ if (!m)
72948
+ return void 0;
72949
+ return m[1].trim();
72950
+ }
72951
+ function parseJwt(token) {
72952
+ const parts = token.split(".");
72953
+ if (parts.length !== 3)
72954
+ return void 0;
72955
+ try {
72956
+ const headerJson = base64UrlDecodeToString(parts[0]);
72957
+ const payloadJson = base64UrlDecodeToString(parts[1]);
72958
+ const header = JSON.parse(headerJson);
72959
+ const payload = JSON.parse(payloadJson);
72960
+ return {
72961
+ header,
72962
+ payload,
72963
+ signingInput: `${parts[0]}.${parts[1]}`,
72964
+ signatureB64: parts[2]
72965
+ };
72966
+ } catch {
72967
+ return void 0;
72968
+ }
72969
+ }
72970
+ function verifyRs256(token, key) {
72971
+ const parts = token.split(".");
72972
+ if (parts.length !== 3)
72973
+ return false;
72974
+ const signingInput = `${parts[0]}.${parts[1]}`;
72975
+ const signature = base64UrlDecodeToBuffer(parts[2]);
72976
+ try {
72977
+ const publicKey = createPublicKey({
72978
+ key: { kty: key.kty, n: key.n, e: key.e },
72979
+ format: "jwk"
72980
+ });
72981
+ const verifier = createVerify("RSA-SHA256");
72982
+ verifier.update(signingInput);
72983
+ verifier.end();
72984
+ return verifier.verify(publicKey, signature);
72985
+ } catch {
72986
+ return false;
72987
+ }
72988
+ }
72989
+ function base64UrlDecodeToString(input) {
72990
+ return base64UrlDecodeToBuffer(input).toString("utf-8");
72991
+ }
72992
+ function base64UrlDecodeToBuffer(input) {
72993
+ const padded = input.replace(/-/g, "+").replace(/_/g, "/");
72994
+ const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
72995
+ return Buffer.from(padded + padding, "base64");
72996
+ }
72997
+
72998
+ // src/local/http-server.ts
72999
+ async function startApiServer(opts) {
73000
+ const logger = getLogger().child("start-api");
73001
+ const server = createServer2((req, res) => {
73002
+ handleRequest(req, res, opts).catch((err) => {
73003
+ logger.error(
73004
+ `Unhandled request error: ${err instanceof Error ? err.stack ?? err.message : String(err)}`
73005
+ );
73006
+ if (!res.headersSent) {
73007
+ writeError(res, 502);
73008
+ }
73009
+ });
73010
+ });
73011
+ server.on("connection", (socket) => {
73012
+ socket.setNoDelay(true);
73013
+ });
73014
+ const { actualPort, actualHost } = await new Promise(
73015
+ (resolveListen, rejectListen) => {
73016
+ server.once("error", rejectListen);
73017
+ server.listen(opts.port, opts.host, () => {
73018
+ const addr = server.address();
73019
+ if (addr === null || typeof addr === "string") {
73020
+ rejectListen(new Error("Could not determine listening address"));
73021
+ return;
73022
+ }
73023
+ resolveListen({ actualPort: addr.port, actualHost: opts.host });
73024
+ });
73025
+ }
73026
+ );
73027
+ let closed = false;
73028
+ return {
73029
+ port: actualPort,
73030
+ host: actualHost,
73031
+ server,
73032
+ close: async () => {
73033
+ if (closed)
73034
+ return;
73035
+ closed = true;
73036
+ await new Promise((resolveClose) => {
73037
+ server.close(() => resolveClose());
73038
+ server.closeAllConnections?.();
73039
+ });
73040
+ }
73041
+ };
73042
+ }
73043
+ async function handleRequest(req, res, opts) {
73044
+ const logger = getLogger().child("start-api");
73045
+ const bodyBuf = await readBody(req);
73046
+ const rawUrl = req.url ?? "/";
73047
+ const method = (req.method ?? "GET").toUpperCase();
73048
+ const requestPath = rawUrl.split("?")[0] ?? "/";
73049
+ const flatRoutes = opts.routes.map((r) => r.route);
73050
+ const match = matchRoute(method, requestPath, flatRoutes);
73051
+ if (!match) {
73052
+ writeError(res, 404, '{"message":"Not Found"}');
73053
+ return;
73054
+ }
73055
+ const matchedEntry = opts.routes.find(
73056
+ (r) => r.route.declaredAt === match.route.declaredAt && r.route.method === match.route.method
73057
+ );
73058
+ const authorizer = matchedEntry?.authorizer;
73059
+ const snapshot = {
73060
+ method,
73061
+ rawUrl,
73062
+ headers: collectHeaders(req),
73063
+ body: bodyBuf,
73064
+ ...req.socket.remoteAddress !== void 0 && { sourceIp: req.socket.remoteAddress }
73065
+ };
73066
+ const matchCtx = {
73067
+ route: match.route,
73068
+ pathParameters: match.pathParameters,
73069
+ matchedPath: requestPath
73070
+ };
73071
+ let baseEvent = match.route.apiVersion === "v1" ? buildRestV1Event(snapshot, matchCtx) : buildHttpApiV2Event(snapshot, matchCtx);
73072
+ let authResult;
73073
+ if (authorizer) {
73074
+ let outcome;
73075
+ try {
73076
+ outcome = await runAuthorizerPass(
73077
+ authorizer,
73078
+ snapshot,
73079
+ matchCtx,
73080
+ opts,
73081
+ baseEvent["requestContext"]
73082
+ );
73083
+ } catch (err) {
73084
+ logger.error(
73085
+ `Authorizer ${authorizer.logicalId} threw for ${match.route.declaredAt}: ${err instanceof Error ? err.message : String(err)}`
73086
+ );
73087
+ writeAuthRejection(res, match.route.apiVersion, "policy-deny");
73088
+ return;
73089
+ }
73090
+ if (!outcome.result.allow) {
73091
+ writeAuthRejection(res, match.route.apiVersion, outcome.denyKind ?? "policy-deny");
73092
+ return;
73093
+ }
73094
+ authResult = outcome.result;
73095
+ const overlay = buildOverlay(authorizer, authResult);
73096
+ if (overlay) {
73097
+ baseEvent = applyAuthorizerOverlay(baseEvent, overlay);
73098
+ }
73099
+ }
73100
+ let handle;
73101
+ try {
73102
+ handle = await opts.pool.acquire(match.route.lambdaLogicalId);
73103
+ } catch (err) {
73104
+ logger.error(
73105
+ `Failed to acquire container for ${match.route.lambdaLogicalId}: ${err instanceof Error ? err.message : String(err)}`
73106
+ );
73107
+ writeError(res, 502);
73108
+ return;
73109
+ }
73110
+ try {
73111
+ const invokeResult = await invokeRie(
73112
+ handle.containerHost,
73113
+ handle.hostPort,
73114
+ baseEvent,
73115
+ opts.rieTimeoutMs
73116
+ );
73117
+ const translated = translateLambdaResponse(invokeResult.payload, match.route.apiVersion);
73118
+ res.statusCode = translated.statusCode;
73119
+ for (const [name, value] of Object.entries(translated.headers)) {
73120
+ res.setHeader(name, value);
73121
+ }
73122
+ if (translated.cookies.length > 0) {
73123
+ res.setHeader("set-cookie", translated.cookies);
73124
+ }
73125
+ res.end(translated.body);
73126
+ } catch (err) {
73127
+ logger.error(
73128
+ `RIE invoke failed for ${match.route.lambdaLogicalId}: ${err instanceof Error ? err.message : String(err)}`
73129
+ );
73130
+ if (!res.headersSent) {
73131
+ writeError(res, 502);
73132
+ } else {
73133
+ res.end();
73134
+ }
73135
+ } finally {
73136
+ opts.pool.release(handle);
73137
+ }
73138
+ }
73139
+ async function runAuthorizerPass(authorizer, snapshot, matchCtx, opts, requestContextV2) {
73140
+ const headers = lowercaseSingularHeaders(snapshot.headers);
73141
+ const queryStringParameters = parseQueryStringSingular(snapshot.rawUrl);
73142
+ const sourceIp = pickSourceIp(matchCtx.route.apiVersion, requestContextV2, snapshot);
73143
+ const reqSnap = {
73144
+ method: snapshot.method.toUpperCase(),
73145
+ headers,
73146
+ queryStringParameters,
73147
+ pathParameters: matchCtx.pathParameters,
73148
+ sourceIp,
73149
+ matchedPath: matchCtx.matchedPath,
73150
+ stage: matchCtx.route.stage
73151
+ };
73152
+ const methodArn = buildMethodArn({
73153
+ apiId: "local",
73154
+ accountId: "123456789012",
73155
+ stage: matchCtx.route.stage,
73156
+ method: snapshot.method,
73157
+ path: matchCtx.matchedPath
73158
+ });
73159
+ const cache2 = opts.authorizerCache;
73160
+ if (authorizer.kind === "lambda-token") {
73161
+ const token = headers[authorizer.tokenHeader];
73162
+ if (!token) {
73163
+ return { result: { allow: false }, denyKind: "missing-identity" };
73164
+ }
73165
+ if (cache2) {
73166
+ const cached = cache2.get(authorizer.logicalId, hashOne(token));
73167
+ if (cached) {
73168
+ if (cached.policy !== void 0) {
73169
+ return shapeOutcome(evaluateCachedLambdaPolicy(cached, methodArn));
73170
+ }
73171
+ return shapeOutcome(cached);
73172
+ }
73173
+ }
73174
+ const result2 = await invokeTokenAuthorizer(authorizer, reqSnap, {
73175
+ pool: opts.pool,
73176
+ rieTimeoutMs: opts.rieTimeoutMs,
73177
+ methodArn,
73178
+ mockAccountId: "123456789012",
73179
+ mockApiId: "local"
73180
+ });
73181
+ if (cache2 && result2.identityHash !== void 0) {
73182
+ cache2.set(
73183
+ authorizer.logicalId,
73184
+ result2.identityHash,
73185
+ authorizer.resultTtlSeconds,
73186
+ stripHash(result2)
73187
+ );
73188
+ }
73189
+ return shapeOutcome(stripHash(result2));
73190
+ }
73191
+ if (authorizer.kind === "lambda-request") {
73192
+ const { identityHash, missing } = computeRequestIdentityHash(authorizer, reqSnap);
73193
+ if (missing) {
73194
+ return { result: { allow: false }, denyKind: "missing-identity" };
73195
+ }
73196
+ if (cache2 && authorizer.resultTtlSeconds > 0) {
73197
+ const cached = cache2.get(authorizer.logicalId, identityHash);
73198
+ if (cached) {
73199
+ if (cached.policy !== void 0) {
73200
+ return shapeOutcome(evaluateCachedLambdaPolicy(cached, methodArn));
73201
+ }
73202
+ return shapeOutcome(cached);
73203
+ }
73204
+ }
73205
+ const result2 = await invokeRequestAuthorizer(authorizer, reqSnap, {
73206
+ pool: opts.pool,
73207
+ rieTimeoutMs: opts.rieTimeoutMs,
73208
+ methodArn,
73209
+ mockAccountId: "123456789012",
73210
+ mockApiId: "local"
73211
+ });
73212
+ if (cache2 && result2.identityHash !== void 0 && authorizer.resultTtlSeconds > 0) {
73213
+ cache2.set(
73214
+ authorizer.logicalId,
73215
+ result2.identityHash,
73216
+ authorizer.resultTtlSeconds,
73217
+ stripHash(result2)
73218
+ );
73219
+ }
73220
+ return shapeOutcome(stripHash(result2));
73221
+ }
73222
+ if (!opts.jwksCache) {
73223
+ return { result: { allow: false }, denyKind: "policy-deny" };
73224
+ }
73225
+ const authHeader = headers["authorization"];
73226
+ const jwksOpts = { ...opts.jwksWarnedUrls && { warned: opts.jwksWarnedUrls } };
73227
+ if (authorizer.kind === "cognito") {
73228
+ if (cache2 && authHeader !== void 0) {
73229
+ const cached = cache2.get(authorizer.logicalId, hashOne(authHeader));
73230
+ if (cached)
73231
+ return shapeOutcome(cached);
73232
+ }
73233
+ const result2 = await verifyCognitoJwt(authorizer, authHeader, opts.jwksCache, jwksOpts);
73234
+ if (cache2 && result2.identityHash !== void 0 && result2.ttlSeconds > 0) {
73235
+ cache2.set(
73236
+ authorizer.logicalId,
73237
+ result2.identityHash,
73238
+ result2.ttlSeconds,
73239
+ stripHashAndTtl(result2)
73240
+ );
73241
+ }
73242
+ if (!result2.allow && authHeader === void 0) {
73243
+ return { result: stripHashAndTtl(result2), denyKind: "missing-identity" };
73244
+ }
73245
+ return shapeOutcome(stripHashAndTtl(result2));
73246
+ }
73247
+ if (cache2 && authHeader !== void 0) {
73248
+ const cached = cache2.get(authorizer.logicalId, hashOne(authHeader));
73249
+ if (cached)
73250
+ return shapeOutcome(cached);
73251
+ }
73252
+ const result = await verifyJwtAuthorizer(authorizer, authHeader, opts.jwksCache, jwksOpts);
73253
+ if (cache2 && result.identityHash !== void 0 && result.ttlSeconds > 0) {
73254
+ cache2.set(
73255
+ authorizer.logicalId,
73256
+ result.identityHash,
73257
+ result.ttlSeconds,
73258
+ stripHashAndTtl(result)
73259
+ );
73260
+ }
73261
+ if (!result.allow && authHeader === void 0) {
73262
+ return { result: stripHashAndTtl(result), denyKind: "missing-identity" };
73263
+ }
73264
+ return shapeOutcome(stripHashAndTtl(result));
73265
+ }
73266
+ function shapeOutcome(result) {
73267
+ if (result.allow)
73268
+ return { result };
73269
+ return { result, denyKind: "policy-deny" };
73270
+ }
73271
+ function pickSourceIp(apiVersion, requestContext, snapshot) {
73272
+ if (apiVersion === "v1") {
73273
+ const identity = requestContext["identity"];
73274
+ if (identity && typeof identity === "object" && !Array.isArray(identity) && typeof identity["sourceIp"] === "string") {
73275
+ return identity["sourceIp"];
73276
+ }
73277
+ } else {
73278
+ const http = requestContext["http"];
73279
+ if (http && typeof http === "object" && !Array.isArray(http) && typeof http["sourceIp"] === "string") {
73280
+ return http["sourceIp"];
73281
+ }
73282
+ }
73283
+ return snapshot.sourceIp ?? "127.0.0.1";
73284
+ }
73285
+ function buildOverlay(authorizer, result) {
73286
+ if (authorizer.kind === "lambda-token" || authorizer.kind === "lambda-request") {
73287
+ const isV2 = authorizer.kind === "lambda-request" && authorizer.apiVersion === "v2";
73288
+ return isV2 ? {
73289
+ kind: "lambda-http-v2",
73290
+ ...result.principalId !== void 0 && { principalId: result.principalId },
73291
+ ...result.context && { context: result.context }
73292
+ } : {
73293
+ kind: "lambda-rest-v1",
73294
+ ...result.principalId !== void 0 && { principalId: result.principalId },
73295
+ ...result.context && { context: result.context }
73296
+ };
73297
+ }
73298
+ if (authorizer.kind === "cognito") {
73299
+ return { kind: "cognito-rest-v1", claims: result.context ?? {} };
73300
+ }
73301
+ return { kind: "jwt-http-v2", claims: result.context ?? {} };
73302
+ }
73303
+ function writeAuthRejection(res, apiVersion, denyKind) {
73304
+ if (apiVersion === "v2") {
73305
+ writeError(res, 401, '{"message":"Unauthorized"}');
73306
+ return;
73307
+ }
73308
+ if (denyKind === "missing-identity") {
73309
+ writeError(res, 401, '{"message":"Unauthorized"}');
73310
+ return;
73311
+ }
73312
+ writeError(res, 403, '{"message":"Forbidden"}');
73313
+ }
73314
+ function hashOne(value) {
73315
+ return value;
73316
+ }
73317
+ function stripHash(r) {
73318
+ const { identityHash, ...rest } = r;
73319
+ return rest;
73320
+ }
73321
+ function stripHashAndTtl(r) {
73322
+ const { identityHash, ttlSeconds, ...rest } = r;
73323
+ return rest;
73324
+ }
73325
+ function lowercaseSingularHeaders(raw) {
73326
+ const out = {};
73327
+ for (const [name, values] of Object.entries(raw)) {
73328
+ out[name.toLowerCase()] = values.join(",");
73329
+ }
73330
+ return out;
73331
+ }
73332
+ function parseQueryStringSingular(rawUrl) {
73333
+ const q = rawUrl.indexOf("?");
73334
+ if (q < 0)
73335
+ return {};
73336
+ const raw = rawUrl.slice(q + 1);
73337
+ if (raw.length === 0)
73338
+ return {};
73339
+ const out = {};
73340
+ for (const pair of raw.split("&")) {
73341
+ if (pair.length === 0)
73342
+ continue;
73343
+ const eq = pair.indexOf("=");
73344
+ const rawKey = eq === -1 ? pair : pair.slice(0, eq);
73345
+ const rawValue = eq === -1 ? "" : pair.slice(eq + 1);
73346
+ let key = rawKey;
73347
+ let value = rawValue;
73348
+ try {
73349
+ key = decodeURIComponent(rawKey);
73350
+ } catch {
73351
+ }
73352
+ try {
73353
+ value = decodeURIComponent(rawValue);
73354
+ } catch {
73355
+ }
73356
+ out[key] = value;
73357
+ }
73358
+ return out;
73359
+ }
73360
+ function readBody(req) {
73361
+ return new Promise((resolveBody, rejectBody) => {
73362
+ const chunks = [];
73363
+ req.on("data", (chunk) => {
73364
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
73365
+ });
73366
+ req.on("end", () => resolveBody(Buffer.concat(chunks)));
73367
+ req.on("error", rejectBody);
73368
+ });
73369
+ }
73370
+ function collectHeaders(req) {
73371
+ const out = {};
73372
+ for (const [name, value] of Object.entries(req.headers)) {
73373
+ if (Array.isArray(value)) {
73374
+ out[name] = value;
73375
+ } else if (typeof value === "string") {
73376
+ out[name] = [value];
73377
+ }
73378
+ }
73379
+ return out;
73380
+ }
73381
+ function writeError(res, statusCode, body = '{"message":"Internal server error"}') {
73382
+ res.statusCode = statusCode;
73383
+ res.setHeader("content-type", "application/json");
73384
+ res.setHeader("content-length", String(Buffer.byteLength(body, "utf-8")));
73385
+ res.end(body);
73386
+ }
73387
+
73388
+ // src/local/authorizer-cache.ts
73389
+ function createAuthorizerCache(opts = {}) {
73390
+ const now = opts.now ?? (() => Date.now());
73391
+ const map = /* @__PURE__ */ new Map();
73392
+ const buildKey = (auth, identity) => `${auth}\0${identity}`;
73393
+ const sweep = () => {
73394
+ const t = now();
73395
+ for (const [k, v] of map) {
73396
+ if (v.expiresAt <= t)
73397
+ map.delete(k);
73398
+ }
73399
+ };
73400
+ return {
73401
+ get(authorizerLogicalId, identityHash) {
73402
+ const key = buildKey(authorizerLogicalId, identityHash);
73403
+ const entry = map.get(key);
73404
+ if (!entry)
73405
+ return void 0;
73406
+ if (entry.expiresAt <= now()) {
73407
+ map.delete(key);
73408
+ return void 0;
73409
+ }
73410
+ return entry.result;
73411
+ },
73412
+ set(authorizerLogicalId, identityHash, ttlSeconds, result) {
73413
+ if (ttlSeconds <= 0)
73414
+ return;
73415
+ const key = buildKey(authorizerLogicalId, identityHash);
73416
+ map.set(key, { expiresAt: now() + ttlSeconds * 1e3, result });
73417
+ },
73418
+ clear() {
73419
+ map.clear();
73420
+ },
73421
+ size() {
73422
+ sweep();
73423
+ return map.size;
73424
+ }
73425
+ };
73426
+ }
73427
+
73428
+ // src/cli/commands/local-start-api.ts
72264
73429
  async function localStartApiCommand(options) {
72265
73430
  const logger = getLogger();
72266
73431
  if (options.verbose) {
@@ -72294,7 +73459,8 @@ async function localStartApiCommand(options) {
72294
73459
  "No supported API routes were discovered. cdkd local start-api supports AWS::ApiGateway::* (REST v1), AWS::ApiGatewayV2::* (HTTP), and AWS::Lambda::Url (Function URL) with AWS_PROXY integrations only."
72295
73460
  );
72296
73461
  }
72297
- const lambdaIds = uniqueLambdaIds(routes);
73462
+ const routesWithAuth = attachAuthorizers(targetStacks, routes);
73463
+ const lambdaIds = uniqueLambdaIds(routes, routesWithAuth);
72298
73464
  const overrides = readEnvOverridesFile(options.envVars);
72299
73465
  const debugPortBase = options.debugPortBase ? parseDebugPort(options.debugPortBase) : void 0;
72300
73466
  const specs = /* @__PURE__ */ new Map();
@@ -72348,12 +73514,20 @@ async function localStartApiCommand(options) {
72348
73514
  if (!Number.isFinite(port) || port < 0 || port > 65535) {
72349
73515
  throw new Error(`--port must be 0..65535 (got ${options.port}).`);
72350
73516
  }
73517
+ const authorizerCache = createAuthorizerCache();
73518
+ const jwksCache = createJwksCache();
73519
+ const jwksWarnedUrls = /* @__PURE__ */ new Set();
73520
+ await prewarmJwks(routesWithAuth, jwksCache);
73521
+ warnVpcConfigLambdas(routesWithAuth, targetStacks);
72351
73522
  const server = await startApiServer({
72352
- routes,
73523
+ routes: routesWithAuth,
72353
73524
  pool,
72354
73525
  rieTimeoutMs,
72355
73526
  host: options.host,
72356
- port
73527
+ port,
73528
+ authorizerCache,
73529
+ jwksCache,
73530
+ jwksWarnedUrls
72357
73531
  });
72358
73532
  printRouteTable(routes);
72359
73533
  logger.info(
@@ -72430,7 +73604,7 @@ function pickTargetStacks(stacks, pattern) {
72430
73604
  `Multi-stack app: pass --stack <name> to pick a target. Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`
72431
73605
  );
72432
73606
  }
72433
- function uniqueLambdaIds(routes) {
73607
+ function uniqueLambdaIds(routes, routesWithAuth) {
72434
73608
  const seen = /* @__PURE__ */ new Set();
72435
73609
  const out = [];
72436
73610
  for (const r of routes) {
@@ -72439,8 +73613,67 @@ function uniqueLambdaIds(routes) {
72439
73613
  out.push(r.lambdaLogicalId);
72440
73614
  }
72441
73615
  }
73616
+ for (const entry of routesWithAuth) {
73617
+ const auth = entry.authorizer;
73618
+ if (!auth)
73619
+ continue;
73620
+ if (auth.kind === "lambda-token" || auth.kind === "lambda-request") {
73621
+ if (!seen.has(auth.lambdaLogicalId)) {
73622
+ seen.add(auth.lambdaLogicalId);
73623
+ out.push(auth.lambdaLogicalId);
73624
+ }
73625
+ }
73626
+ }
72442
73627
  return out;
72443
73628
  }
73629
+ async function prewarmJwks(routesWithAuth, jwksCache) {
73630
+ const urls = /* @__PURE__ */ new Set();
73631
+ for (const entry of routesWithAuth) {
73632
+ const auth = entry.authorizer;
73633
+ if (!auth)
73634
+ continue;
73635
+ if (auth.kind === "cognito") {
73636
+ urls.add(buildCognitoJwksUrl(auth.region, auth.userPoolId));
73637
+ } else if (auth.kind === "jwt") {
73638
+ const url = auth.region && auth.userPoolId ? buildCognitoJwksUrl(auth.region, auth.userPoolId) : buildJwksUrlFromIssuer(auth.issuer);
73639
+ urls.add(url);
73640
+ }
73641
+ }
73642
+ await Promise.all([...urls].map((u) => jwksCache.fetchAndCache(u)));
73643
+ }
73644
+ function warnVpcConfigLambdas(routesWithAuth, stacks) {
73645
+ const logger = getLogger();
73646
+ const seen = /* @__PURE__ */ new Set();
73647
+ const reachable = [];
73648
+ for (const entry of routesWithAuth) {
73649
+ if (!seen.has(entry.route.lambdaLogicalId)) {
73650
+ seen.add(entry.route.lambdaLogicalId);
73651
+ reachable.push(entry.route.lambdaLogicalId);
73652
+ }
73653
+ const auth = entry.authorizer;
73654
+ if (auth && (auth.kind === "lambda-token" || auth.kind === "lambda-request")) {
73655
+ if (!seen.has(auth.lambdaLogicalId)) {
73656
+ seen.add(auth.lambdaLogicalId);
73657
+ reachable.push(auth.lambdaLogicalId);
73658
+ }
73659
+ }
73660
+ }
73661
+ for (const logicalId of reachable) {
73662
+ for (const stack of stacks) {
73663
+ const resource = stack.template.Resources?.[logicalId];
73664
+ if (!resource || resource.Type !== "AWS::Lambda::Function")
73665
+ continue;
73666
+ const props = resource.Properties ?? {};
73667
+ const vpcConfig = props["VpcConfig"];
73668
+ if (vpcConfig && typeof vpcConfig === "object" && Object.keys(vpcConfig).length > 0) {
73669
+ logger.warn(
73670
+ `Lambda ${logicalId} has VpcConfig \u2014 local container will reach external services via the host's network, NOT through the deployed VPC's NAT/private subnets. Calls to private RDS/ElastiCache will fail. See docs/cli-reference.md (cdkd local start-api \u2014 Limitations) for details.`
73671
+ );
73672
+ }
73673
+ break;
73674
+ }
73675
+ }
73676
+ }
72444
73677
  async function buildContainerSpec(args) {
72445
73678
  const {
72446
73679
  logicalId,
@@ -72681,7 +73914,7 @@ function parseDebugPort(raw) {
72681
73914
  }
72682
73915
  function createLocalStartApiCommand() {
72683
73916
  const startApi = new Command14("start-api").description(
72684
- "Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required)."
73917
+ "Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required). Supports Lambda TOKEN/REQUEST authorizers and Cognito User Pool / HTTP v2 JWT authorizers; when JWKS is unreachable, JWT authorizers fall back to pass-through (every token accepted) with a warn line \u2014 local dev fallback. VPC-config Lambdas run locally and surface a warn line at startup; their containers do NOT get attached to the deployed VPC subnets, so calls to private RDS / ElastiCache will fail."
72685
73918
  ).addOption(
72686
73919
  new Option7("--port <port>", "HTTP server port (default: auto-allocate)").default("0")
72687
73920
  ).addOption(new Option7("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option7("--stack <name>", "Stack to start (single-stack apps auto-detect)")).addOption(
@@ -72895,7 +74128,11 @@ async function resolveContainerImagePlan(lambda, options) {
72895
74128
  let imageRef;
72896
74129
  if (localBuild) {
72897
74130
  imageRef = await buildContainerImage(localBuild.asset, localBuild.cdkOutDir, {
72898
- architecture: lambda.architecture
74131
+ architecture: lambda.architecture,
74132
+ // `options.build === false` triggers the no-build path: skip
74133
+ // `docker build` and verify the deterministic tag is already
74134
+ // cached. Default `true` (build as usual). Closes #233.
74135
+ noBuild: options.build === false
72899
74136
  });
72900
74137
  } else {
72901
74138
  if (!parseEcrUri(lambda.imageUri)) {
@@ -73171,6 +74408,11 @@ function createLocalCommand() {
73171
74408
  "--no-pull",
73172
74409
  "Skip docker pull (use cached image) \u2014 no-op for IMAGE local-build path; `docker build` does not pull base layers by default"
73173
74410
  )
74411
+ ).addOption(
74412
+ new Option8(
74413
+ "--no-build",
74414
+ "Skip docker build on the IMAGE local-build path (use the previously-built tag). Requires the deterministic tag to already be in the local registry; errors with an actionable message when missing. No-op for ZIP Lambdas and the IMAGE ECR-pull path. Compatible with --no-pull."
74415
+ )
73174
74416
  ).addOption(new Option8("--debug-port <port>", "Node --inspect-brk port (default: off)")).addOption(
73175
74417
  new Option8("--container-host <host>", "Host to bind the RIE port to").default("127.0.0.1")
73176
74418
  ).addOption(
@@ -73227,7 +74469,7 @@ function reorderArgs(argv) {
73227
74469
  }
73228
74470
  async function main() {
73229
74471
  const program = new Command16();
73230
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.71.0");
74472
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.73.0");
73231
74473
  program.addCommand(createBootstrapCommand());
73232
74474
  program.addCommand(createSynthCommand());
73233
74475
  program.addCommand(createListCommand());