@go-to-k/cdkd 0.72.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/README.md +22 -5
- package/dist/cli.js +1222 -9
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.73.0.tgz +0 -0
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.72.0.tgz +0 -0
package/dist/cli.js
CHANGED
|
@@ -71778,6 +71778,48 @@ function buildRestV1Event(req, ctx, opts = {}) {
|
|
|
71778
71778
|
};
|
|
71779
71779
|
return event;
|
|
71780
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
|
+
}
|
|
71781
71823
|
function splitRawUrl(rawUrl) {
|
|
71782
71824
|
const q = rawUrl.indexOf("?");
|
|
71783
71825
|
if (q === -1)
|
|
@@ -72144,6 +72186,815 @@ function isPlaceholder(segment) {
|
|
|
72144
72186
|
return /^\{[^/{}+]+\}$/.test(segment);
|
|
72145
72187
|
}
|
|
72146
72188
|
|
|
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.`
|
|
72240
|
+
);
|
|
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).`
|
|
72255
|
+
);
|
|
72256
|
+
}
|
|
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}'.`
|
|
72262
|
+
);
|
|
72263
|
+
}
|
|
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`
|
|
72270
|
+
);
|
|
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
|
+
);
|
|
72289
|
+
}
|
|
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
|
+
);
|
|
72296
|
+
}
|
|
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
|
+
};
|
|
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
|
+
);
|
|
72312
|
+
}
|
|
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
|
+
}
|
|
72323
|
+
}
|
|
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
|
+
|
|
72147
72998
|
// src/local/http-server.ts
|
|
72148
72999
|
async function startApiServer(opts) {
|
|
72149
73000
|
const logger = getLogger().child("start-api");
|
|
@@ -72195,11 +73046,16 @@ async function handleRequest(req, res, opts) {
|
|
|
72195
73046
|
const rawUrl = req.url ?? "/";
|
|
72196
73047
|
const method = (req.method ?? "GET").toUpperCase();
|
|
72197
73048
|
const requestPath = rawUrl.split("?")[0] ?? "/";
|
|
72198
|
-
const
|
|
73049
|
+
const flatRoutes = opts.routes.map((r) => r.route);
|
|
73050
|
+
const match = matchRoute(method, requestPath, flatRoutes);
|
|
72199
73051
|
if (!match) {
|
|
72200
73052
|
writeError(res, 404, '{"message":"Not Found"}');
|
|
72201
73053
|
return;
|
|
72202
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;
|
|
72203
73059
|
const snapshot = {
|
|
72204
73060
|
method,
|
|
72205
73061
|
rawUrl,
|
|
@@ -72212,7 +73068,35 @@ async function handleRequest(req, res, opts) {
|
|
|
72212
73068
|
pathParameters: match.pathParameters,
|
|
72213
73069
|
matchedPath: requestPath
|
|
72214
73070
|
};
|
|
72215
|
-
|
|
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
|
+
}
|
|
72216
73100
|
let handle;
|
|
72217
73101
|
try {
|
|
72218
73102
|
handle = await opts.pool.acquire(match.route.lambdaLogicalId);
|
|
@@ -72227,7 +73111,7 @@ async function handleRequest(req, res, opts) {
|
|
|
72227
73111
|
const invokeResult = await invokeRie(
|
|
72228
73112
|
handle.containerHost,
|
|
72229
73113
|
handle.hostPort,
|
|
72230
|
-
|
|
73114
|
+
baseEvent,
|
|
72231
73115
|
opts.rieTimeoutMs
|
|
72232
73116
|
);
|
|
72233
73117
|
const translated = translateLambdaResponse(invokeResult.payload, match.route.apiVersion);
|
|
@@ -72252,6 +73136,227 @@ async function handleRequest(req, res, opts) {
|
|
|
72252
73136
|
opts.pool.release(handle);
|
|
72253
73137
|
}
|
|
72254
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
|
+
}
|
|
72255
73360
|
function readBody(req) {
|
|
72256
73361
|
return new Promise((resolveBody, rejectBody) => {
|
|
72257
73362
|
const chunks = [];
|
|
@@ -72280,6 +73385,46 @@ function writeError(res, statusCode, body = '{"message":"Internal server error"}
|
|
|
72280
73385
|
res.end(body);
|
|
72281
73386
|
}
|
|
72282
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
|
+
|
|
72283
73428
|
// src/cli/commands/local-start-api.ts
|
|
72284
73429
|
async function localStartApiCommand(options) {
|
|
72285
73430
|
const logger = getLogger();
|
|
@@ -72314,7 +73459,8 @@ async function localStartApiCommand(options) {
|
|
|
72314
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."
|
|
72315
73460
|
);
|
|
72316
73461
|
}
|
|
72317
|
-
const
|
|
73462
|
+
const routesWithAuth = attachAuthorizers(targetStacks, routes);
|
|
73463
|
+
const lambdaIds = uniqueLambdaIds(routes, routesWithAuth);
|
|
72318
73464
|
const overrides = readEnvOverridesFile(options.envVars);
|
|
72319
73465
|
const debugPortBase = options.debugPortBase ? parseDebugPort(options.debugPortBase) : void 0;
|
|
72320
73466
|
const specs = /* @__PURE__ */ new Map();
|
|
@@ -72368,12 +73514,20 @@ async function localStartApiCommand(options) {
|
|
|
72368
73514
|
if (!Number.isFinite(port) || port < 0 || port > 65535) {
|
|
72369
73515
|
throw new Error(`--port must be 0..65535 (got ${options.port}).`);
|
|
72370
73516
|
}
|
|
73517
|
+
const authorizerCache = createAuthorizerCache();
|
|
73518
|
+
const jwksCache = createJwksCache();
|
|
73519
|
+
const jwksWarnedUrls = /* @__PURE__ */ new Set();
|
|
73520
|
+
await prewarmJwks(routesWithAuth, jwksCache);
|
|
73521
|
+
warnVpcConfigLambdas(routesWithAuth, targetStacks);
|
|
72371
73522
|
const server = await startApiServer({
|
|
72372
|
-
routes,
|
|
73523
|
+
routes: routesWithAuth,
|
|
72373
73524
|
pool,
|
|
72374
73525
|
rieTimeoutMs,
|
|
72375
73526
|
host: options.host,
|
|
72376
|
-
port
|
|
73527
|
+
port,
|
|
73528
|
+
authorizerCache,
|
|
73529
|
+
jwksCache,
|
|
73530
|
+
jwksWarnedUrls
|
|
72377
73531
|
});
|
|
72378
73532
|
printRouteTable(routes);
|
|
72379
73533
|
logger.info(
|
|
@@ -72450,7 +73604,7 @@ function pickTargetStacks(stacks, pattern) {
|
|
|
72450
73604
|
`Multi-stack app: pass --stack <name> to pick a target. Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`
|
|
72451
73605
|
);
|
|
72452
73606
|
}
|
|
72453
|
-
function uniqueLambdaIds(routes) {
|
|
73607
|
+
function uniqueLambdaIds(routes, routesWithAuth) {
|
|
72454
73608
|
const seen = /* @__PURE__ */ new Set();
|
|
72455
73609
|
const out = [];
|
|
72456
73610
|
for (const r of routes) {
|
|
@@ -72459,8 +73613,67 @@ function uniqueLambdaIds(routes) {
|
|
|
72459
73613
|
out.push(r.lambdaLogicalId);
|
|
72460
73614
|
}
|
|
72461
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
|
+
}
|
|
72462
73627
|
return out;
|
|
72463
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
|
+
}
|
|
72464
73677
|
async function buildContainerSpec(args) {
|
|
72465
73678
|
const {
|
|
72466
73679
|
logicalId,
|
|
@@ -72701,7 +73914,7 @@ function parseDebugPort(raw) {
|
|
|
72701
73914
|
}
|
|
72702
73915
|
function createLocalStartApiCommand() {
|
|
72703
73916
|
const startApi = new Command14("start-api").description(
|
|
72704
|
-
"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."
|
|
72705
73918
|
).addOption(
|
|
72706
73919
|
new Option7("--port <port>", "HTTP server port (default: auto-allocate)").default("0")
|
|
72707
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(
|
|
@@ -73256,7 +74469,7 @@ function reorderArgs(argv) {
|
|
|
73256
74469
|
}
|
|
73257
74470
|
async function main() {
|
|
73258
74471
|
const program = new Command16();
|
|
73259
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
74472
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.73.0");
|
|
73260
74473
|
program.addCommand(createBootstrapCommand());
|
|
73261
74474
|
program.addCommand(createSynthCommand());
|
|
73262
74475
|
program.addCommand(createListCommand());
|