diff --git a/packages/type-safe-api/src/construct/prepare-spec-event-handler/prepare-spec.ts b/packages/type-safe-api/src/construct/prepare-spec-event-handler/prepare-spec.ts index ec939cc89..72b3466c5 100644 --- a/packages/type-safe-api/src/construct/prepare-spec-event-handler/prepare-spec.ts +++ b/packages/type-safe-api/src/construct/prepare-spec-event-handler/prepare-spec.ts @@ -470,6 +470,25 @@ const validateAuthorizerReference = ( } }; +/** + * Find all unique header parameters used in operations + */ +const findHeaderParameters = (spec: OpenAPIV3.Document): string[] => { + const allHeaderParameters = Object.values(spec.paths).flatMap((pathDetails) => + Object.values(HttpMethods).flatMap((method) => + (pathDetails?.[method]?.parameters ?? []).flatMap((parameter) => + "in" in parameter && parameter.in === "header" ? [parameter.name] : [] + ) + ) + ); + const headerParameterSet = new Set(); + return allHeaderParameters.filter((p) => { + const seen = headerParameterSet.has(p); + headerParameterSet.add(p); + return !seen; + }); +}; + /** * Prepares the api spec for deployment by adding integrations, configuring auth, etc */ @@ -498,6 +517,23 @@ export const prepareApiSpec = ( spec.security ); + // If there are cors options, add any header parameters defined in the spec as allowed headers to + // save users from having to manually specify these (or face cors issues!) + const corsOptions: SerializedCorsOptions | undefined = options.corsOptions + ? { + ...options.corsOptions, + allowHeaders: [ + ...options.corsOptions.allowHeaders, + ...findHeaderParameters(spec), + ], + } + : undefined; + + const updatedOptions: PrepareApiSpecOptions = { + ...options, + corsOptions, + }; + return { ...spec, // https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-request-validators.html @@ -516,10 +552,10 @@ export const prepareApiSpec = ( "application/json": '{"message": "$context.error.validationErrorString"}', }, - ...(options.corsOptions + ...(corsOptions ? { responseParameters: generateCorsResponseParameters( - options.corsOptions, + corsOptions, "gatewayresponse.header" ), } @@ -530,7 +566,7 @@ export const prepareApiSpec = ( ...Object.fromEntries( Object.entries(spec.paths).map(([path, pathDetails]) => [ path, - preparePathSpec(path, pathDetails!, options, getOperationName), + preparePathSpec(path, pathDetails!, updatedOptions, getOperationName), ]) ), }, @@ -540,13 +576,14 @@ export const prepareApiSpec = ( // Apply any security schemes that already exist in the spec ...spec.components?.securitySchemes, // Construct security schemes override any in the spec with the same id - ...options.securitySchemes, + ...updatedOptions.securitySchemes, }, }, - ...(options.apiKeyOptions + ...(updatedOptions.apiKeyOptions ? { // https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-api-key-source.html - "x-amazon-apigateway-api-key-source": options.apiKeyOptions.source, + "x-amazon-apigateway-api-key-source": + updatedOptions.apiKeyOptions.source, } : {}), } as any; diff --git a/packages/type-safe-api/test/construct/__snapshots__/type-safe-rest-api.test.ts.snap b/packages/type-safe-api/test/construct/__snapshots__/type-safe-rest-api.test.ts.snap index 51c922f82..6f59cb71a 100644 --- a/packages/type-safe-api/test/construct/__snapshots__/type-safe-rest-api.test.ts.snap +++ b/packages/type-safe-api/test/construct/__snapshots__/type-safe-rest-api.test.ts.snap @@ -625,7 +625,7 @@ exports[`Type Safe Rest Api Construct Unit Tests Create 2 APIs on same stack 1`] "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec85532C36", "Handler": "index.handler", @@ -1749,7 +1749,7 @@ exports[`Type Safe Rest Api Construct Unit Tests Create 2 APIs on same stack 1`] "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec300D0E5F", "Handler": "index.handler", @@ -3087,7 +3087,7 @@ exports[`Type Safe Rest Api Construct Unit Tests Local Mode 1`] = ` "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec3E755E54", "Handler": "index.handler", @@ -4330,7 +4330,7 @@ exports[`Type Safe Rest Api Construct Unit Tests Permits Matching No Authorizers "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec3E755E54", "Handler": "index.handler", @@ -4977,6 +4977,2074 @@ exports[`Type Safe Rest Api Construct Unit Tests Permits Matching No Authorizers } `; +exports[`Type Safe Rest Api Construct Unit Tests Should add header parameters to CORS Access-Control-Allow-Headers 1`] = ` +{ + "Outputs": { + "ApiTestEndpoint34A72375": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "ApiTestEE73F324", + }, + ".execute-api.", + { + "Ref": "AWS::Region", + }, + ".", + { + "Ref": "AWS::URLSuffix", + }, + "/", + { + "Ref": "ApiTestDeploymentStageprod660267A6", + }, + "/", + ], + ], + }, + }, + }, + "Parameters": { + "BootstrapVersion": { + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]", + "Type": "AWS::SSM::Parameter::Value", + }, + }, + "Resources": { + "ApiTestAccessLogs92CFE051": { + "DeletionPolicy": "Retain", + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "RetentionInDays": 731, + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "ApiTestAccount272B5CDD": { + "DeletionPolicy": "Retain", + "DependsOn": [ + "ApiTestEE73F324", + "ApiTestPrepareSpecResource58706514", + ], + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "ApiTestCloudWatchRole56ED0814", + "Arn", + ], + }, + }, + "Type": "AWS::ApiGateway::Account", + "UpdateReplacePolicy": "Retain", + }, + "ApiTestApiTestAclWebACL9E75156F": { + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "DefaultAction": { + "Allow": {}, + }, + "Name": "Default--ApiTest-Acl-WebAcl", + "Rules": [ + { + "Name": "AWS-AWSManagedRulesCommonRuleSet", + "OverrideAction": { + "None": {}, + }, + "Priority": 2, + "Statement": { + "ManagedRuleGroupStatement": { + "Name": "AWSManagedRulesCommonRuleSet", + "VendorName": "AWS", + }, + }, + "VisibilityConfig": { + "CloudWatchMetricsEnabled": true, + "MetricName": "Default--ApiTest-Acl-WebAcl-AWS-AWSManagedRulesCommonRuleSet", + "SampledRequestsEnabled": true, + }, + }, + ], + "Scope": "REGIONAL", + "VisibilityConfig": { + "CloudWatchMetricsEnabled": true, + "MetricName": "Default--ApiTest-Acl-WebAcl", + "SampledRequestsEnabled": true, + }, + }, + "Type": "AWS::WAFv2::WebACL", + }, + "ApiTestApiTestAclWebACLAssociation54801610": { + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "ResourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":apigateway:", + { + "Ref": "AWS::Region", + }, + "::/restapis/", + { + "Ref": "ApiTestEE73F324", + }, + "/stages/", + { + "Ref": "ApiTestDeploymentStageprod660267A6", + }, + ], + ], + }, + "WebACLArn": { + "Fn::GetAtt": [ + "ApiTestApiTestAclWebACL9E75156F", + "Arn", + ], + }, + }, + "Type": "AWS::WAFv2::WebACLAssociation", + }, + "ApiTestCloudWatchRole56ED0814": { + "DeletionPolicy": "Retain", + "DependsOn": [ + "ApiTestPrepareSpecResource58706514", + ], + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + "UpdateReplacePolicy": "Retain", + }, + "ApiTestDeployment153EC47845bc057e867fdbb6199242334c8d117a": { + "DependsOn": [ + "ApiTestPrepareSpecResource58706514", + ], + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "Description": "Automatically created by the RestApi construct", + "RestApiId": { + "Ref": "ApiTestEE73F324", + }, + }, + "Type": "AWS::ApiGateway::Deployment", + }, + "ApiTestDeploymentStageprod660267A6": { + "DependsOn": [ + "ApiTestAccount272B5CDD", + "ApiTestPrepareSpecResource58706514", + ], + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "AccessLogSetting": { + "DestinationArn": { + "Fn::GetAtt": [ + "ApiTestAccessLogs92CFE051", + "Arn", + ], + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] "$context.httpMethod $context.resourcePath $context.protocol" $context.status $context.responseLength $context.requestId", + }, + "DeploymentId": { + "Ref": "ApiTestDeployment153EC47845bc057e867fdbb6199242334c8d117a", + }, + "MethodSettings": [ + { + "DataTraceEnabled": false, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*", + }, + ], + "RestApiId": { + "Ref": "ApiTestEE73F324", + }, + "StageName": "prod", + }, + "Type": "AWS::ApiGateway::Stage", + }, + "ApiTestEE73F324": { + "DependsOn": [ + "ApiTestPrepareSpecResource58706514", + ], + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "BodyS3Location": { + "Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", + }, + "Key": { + "Fn::GetAtt": [ + "ApiTestPrepareSpecResource58706514", + "outputSpecKey", + ], + }, + }, + "Name": "ApiTest", + }, + "Type": "AWS::ApiGateway::RestApi", + }, + "ApiTestLambdaPermissiondeleteOperationCF8B1751": { + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Lambda35298F1AD", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:", + { + "Ref": "AWS::Region", + }, + ":", + { + "Ref": "AWS::AccountId", + }, + ":", + { + "Ref": "ApiTestEE73F324", + }, + "/*/DELETE/test", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "ApiTestLambdaPermissiongetOperationE32C7896": { + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Lambda1DB8E9965", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:", + { + "Ref": "AWS::Region", + }, + ":", + { + "Ref": "AWS::AccountId", + }, + ":", + { + "Ref": "ApiTestEE73F324", + }, + "/*/GET/test", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "ApiTestLambdaPermissionpostOperation5287C28F": { + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Lambda461E84558", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:", + { + "Ref": "AWS::Region", + }, + ":", + { + "Ref": "AWS::AccountId", + }, + ":", + { + "Ref": "ApiTestEE73F324", + }, + "/*/POST/test", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "ApiTestLambdaPermissionputOperation80355FB2": { + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Lambda217CFB423", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:", + { + "Ref": "AWS::Region", + }, + ":", + { + "Ref": "AWS::AccountId", + }, + ":", + { + "Ref": "ApiTestEE73F324", + }, + "/*/PUT/test", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "ApiTestPrepareSpecA3536D2B": { + "DependsOn": [ + "ApiTestPrepareSpecRole44D562E5", + ], + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", + }, + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", + }, + "FunctionName": "Default-PrepareSpec3E755E54", + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "ApiTestPrepareSpecRole44D562E5", + "Arn", + ], + }, + "Runtime": "nodejs18.x", + "Timeout": 30, + }, + "Type": "AWS::Lambda::Function", + }, + "ApiTestPrepareSpecProviderRoleDefaultPolicy99662E78": { + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "id": "AwsSolutions-IAM5", + "reason": "Cloudwatch resources have been scoped down to the LogGroup level, however * is still needed as stream names are created just in time.", + }, + { + "id": "AwsPrototyping-IAMNoWildcardPermissions", + "reason": "Cloudwatch resources have been scoped down to the LogGroup level, however * is still needed as stream names are created just in time.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "ApiTestPrepareSpecA3536D2B", + "Arn", + ], + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "ApiTestPrepareSpecA3536D2B", + "Arn", + ], + }, + ":*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "ApiTestPrepareSpecProviderRoleDefaultPolicy99662E78", + "Roles": [ + { + "Ref": "ApiTestPrepareSpecProviderRoleF47822B8", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "ApiTestPrepareSpecProviderRoleF47822B8": { + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "id": "AwsSolutions-IAM5", + "reason": "Cloudwatch resources have been scoped down to the LogGroup level, however * is still needed as stream names are created just in time.", + }, + { + "id": "AwsPrototyping-IAMNoWildcardPermissions", + "reason": "Cloudwatch resources have been scoped down to the LogGroup level, however * is still needed as stream names are created just in time.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region", + }, + ":", + { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/Default-PrepareSpec3E755E54-Provider", + ], + ], + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region", + }, + ":", + { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/Default-PrepareSpec3E755E54-Provider:*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "logs", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "ApiTestPrepareSpecResource58706514": { + "DeletionPolicy": "Delete", + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "ApiTestPrepareSpecResourceProviderframeworkonEventDB3DA300", + "Arn", + ], + }, + "corsOptions": { + "allowHeaders": [ + "Content-Type", + "X-Amz-Date", + "Authorization", + "X-Api-Key", + "X-Amz-Security-Token", + "X-Amz-User-Agent", + "x-amz-content-sha256", + ], + "allowMethods": [ + "OPTIONS", + "GET", + "PUT", + "POST", + "DELETE", + "PATCH", + "HEAD", + ], + "allowOrigins": [ + "*", + ], + "statusCode": 200, + }, + "defaultAuthorizerReference": { + "authorizerId": "aws.auth.sigv4", + }, + "inputSpecLocation": { + "bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", + }, + "key": "1b379c070728ce6e390dc775a85f16303a2625450133ff14c948409d76eb41c8.json", + }, + "integrations": { + "deleteOperation": { + "integration": { + "httpMethod": "POST", + "passthroughBehavior": "WHEN_NO_MATCH", + "type": "AWS_PROXY", + "uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":apigateway:", + { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "Lambda35298F1AD", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + }, + "getOperation": { + "integration": { + "httpMethod": "POST", + "passthroughBehavior": "WHEN_NO_MATCH", + "type": "AWS_PROXY", + "uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":apigateway:", + { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "Lambda1DB8E9965", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + }, + "postOperation": { + "integration": { + "httpMethod": "POST", + "passthroughBehavior": "WHEN_NO_MATCH", + "type": "AWS_PROXY", + "uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":apigateway:", + { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "Lambda461E84558", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + }, + "putOperation": { + "integration": { + "httpMethod": "POST", + "passthroughBehavior": "WHEN_NO_MATCH", + "type": "AWS_PROXY", + "uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":apigateway:", + { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "Lambda217CFB423", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + }, + }, + "operationLookup": { + "deleteOperation": { + "method": "delete", + "path": "/test", + }, + "getOperation": { + "method": "get", + "path": "/test", + }, + "postOperation": { + "method": "post", + "path": "/test", + }, + "putOperation": { + "method": "put", + "path": "/test", + }, + }, + "outputSpecLocation": { + "bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", + }, + "key": "1b379c070728ce6e390dc775a85f16303a2625450133ff14c948409d76eb41c8.json-prepared", + }, + "securitySchemes": { + "aws.auth.sigv4": { + "in": "header", + "name": "Authorization", + "type": "apiKey", + "x-amazon-apigateway-authtype": "awsSigv4", + }, + }, + }, + "Type": "AWS::CloudFormation::CustomResource", + "UpdateReplacePolicy": "Delete", + }, + "ApiTestPrepareSpecResourceProviderframeworkonEventDB3DA300": { + "DependsOn": [ + "ApiTestPrepareSpecProviderRoleDefaultPolicy99662E78", + "ApiTestPrepareSpecProviderRoleF47822B8", + ], + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "id": "AwsSolutions-L1", + "reason": "Latest runtime cannot be configured. CDK will need to upgrade the Provider construct accordingly.", + }, + { + "id": "AwsPrototyping-LambdaLatestVersion", + "reason": "Latest runtime cannot be configured. CDK will need to upgrade the Provider construct accordingly.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", + }, + "S3Key": "f2d30cfc360482320a52a4fcde8a70f3569df79ab30be24650fda58eb60052cf.zip", + }, + "Description": "AWS CDK resource provider framework - onEvent (Default/ApiTest/PrepareSpecResourceProvider)", + "Environment": { + "Variables": { + "USER_ON_EVENT_FUNCTION_ARN": { + "Fn::GetAtt": [ + "ApiTestPrepareSpecA3536D2B", + "Arn", + ], + }, + }, + }, + "FunctionName": "Default-PrepareSpec3E755E54-Provider", + "Handler": "framework.onEvent", + "Role": { + "Fn::GetAtt": [ + "ApiTestPrepareSpecProviderRoleF47822B8", + "Arn", + ], + }, + "Runtime": "nodejs18.x", + "Timeout": 900, + }, + "Type": "AWS::Lambda::Function", + }, + "ApiTestPrepareSpecRole44D562E5": { + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "applies_to": [ + { + "regex": "/^Resource::arn:aws:logs:::log-group:/aws/lambda/Default-PrepareSpec3E755E54:*/g", + }, + ], + "id": "AwsSolutions-IAM5", + "reason": "Cloudwatch resources have been scoped down to the LogGroup level, however * is still needed as stream names are created just in time.", + }, + { + "applies_to": [ + { + "regex": "/^Resource::arn::s3:.*/1b379c070728ce6e390dc775a85f16303a2625450133ff14c948409d76eb41c8.json-prepared/*/g", + }, + ], + "id": "AwsSolutions-IAM5", + "reason": "S3 resources have been scoped down to the appropriate prefix in the CDK asset bucket, however * is still needed as since the prepared spec hash is not known until deploy time.", + }, + { + "applies_to": [ + { + "regex": "/^Resource::arn:aws:logs:::log-group:/aws/lambda/Default-PrepareSpec3E755E54:*/g", + }, + ], + "id": "AwsPrototyping-IAMNoWildcardPermissions", + "reason": "Cloudwatch resources have been scoped down to the LogGroup level, however * is still needed as stream names are created just in time.", + }, + { + "applies_to": [ + { + "regex": "/^Resource::arn::s3:.*/1b379c070728ce6e390dc775a85f16303a2625450133ff14c948409d76eb41c8.json-prepared/*/g", + }, + ], + "id": "AwsPrototyping-IAMNoWildcardPermissions", + "reason": "S3 resources have been scoped down to the appropriate prefix in the CDK asset bucket, however * is still needed as since the prepared spec hash is not known until deploy time.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsSolutions-IAM4", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "applies_to": [ + { + "regex": "/^Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs$/g", + }, + ], + "id": "AwsPrototyping-IAMNoManagedPolicies", + "reason": "Cloudwatch Role requires access to create/read groups at the root level.", + }, + { + "id": "AwsSolutions-APIG2", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + { + "id": "AwsPrototyping-APIGWRequestValidation", + "reason": "This construct implements fine grained validation via OpenApi.", + }, + ], + }, + }, + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region", + }, + ":", + { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/Default-PrepareSpec3E755E54", + ], + ], + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region", + }, + ":", + { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/Default-PrepareSpec3E755E54:*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "logs", + }, + { + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:getObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":s3:::", + { + "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", + }, + "/1b379c070728ce6e390dc775a85f16303a2625450133ff14c948409d76eb41c8.json", + ], + ], + }, + }, + { + "Action": "s3:putObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":s3:::", + { + "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", + }, + "/1b379c070728ce6e390dc775a85f16303a2625450133ff14c948409d76eb41c8.json-prepared/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "s3", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "Lambda1DB8E9965": { + "DependsOn": [ + "Lambda1ServiceRoleF188C4B8", + ], + "Properties": { + "Code": { + "ZipFile": "code", + }, + "Handler": "handler", + "Role": { + "Fn::GetAtt": [ + "Lambda1ServiceRoleF188C4B8", + "Arn", + ], + }, + "Runtime": "nodejs16.x", + }, + "Type": "AWS::Lambda::Function", + }, + "Lambda1ServiceRoleF188C4B8": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "Lambda217CFB423": { + "DependsOn": [ + "Lambda2ServiceRole31A072E1", + ], + "Properties": { + "Code": { + "ZipFile": "code", + }, + "Handler": "handler", + "Role": { + "Fn::GetAtt": [ + "Lambda2ServiceRole31A072E1", + "Arn", + ], + }, + "Runtime": "nodejs16.x", + }, + "Type": "AWS::Lambda::Function", + }, + "Lambda2ServiceRole31A072E1": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "Lambda35298F1AD": { + "DependsOn": [ + "Lambda3ServiceRole54BB0786", + ], + "Properties": { + "Code": { + "ZipFile": "code", + }, + "Handler": "handler", + "Role": { + "Fn::GetAtt": [ + "Lambda3ServiceRole54BB0786", + "Arn", + ], + }, + "Runtime": "nodejs16.x", + }, + "Type": "AWS::Lambda::Function", + }, + "Lambda3ServiceRole54BB0786": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "Lambda461E84558": { + "DependsOn": [ + "Lambda4ServiceRole72B40363", + ], + "Properties": { + "Code": { + "ZipFile": "code", + }, + "Handler": "handler", + "Role": { + "Fn::GetAtt": [ + "Lambda4ServiceRole72B40363", + "Arn", + ], + }, + "Runtime": "nodejs16.x", + }, + "Type": "AWS::Lambda::Function", + }, + "Lambda4ServiceRole72B40363": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5", + ], + { + "Ref": "BootstrapVersion", + }, + ], + }, + ], + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.", + }, + ], + }, + }, +} +`; + +exports[`Type Safe Rest Api Construct Unit Tests Should add header parameters to CORS Access-Control-Allow-Headers 2`] = ` +{ + "components": { + "securitySchemes": { + "aws.auth.sigv4": { + "in": "header", + "name": "Authorization", + "type": "apiKey", + "x-amazon-apigateway-authtype": "awsSigv4", + }, + }, + }, + "info": { + "title": "Test API", + "version": "1.0.0", + }, + "openapi": "3.0.3", + "paths": { + "/test": { + "delete": { + "operationId": "deleteOperation", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "Successful response", + "headers": { + "Access-Control-Allow-Headers": { + "schema": { + "type": "string", + }, + }, + "Access-Control-Allow-Methods": { + "schema": { + "type": "string", + }, + }, + "Access-Control-Allow-Origin": { + "schema": { + "type": "string", + }, + }, + }, + }, + }, + "security": [ + { + "aws.auth.sigv4": [], + }, + ], + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "passthroughBehavior": "WHEN_NO_MATCH", + "type": "AWS_PROXY", + "uri": "arn:\${}:apigateway:\${}:lambda:path/2015-03-31/functions/\${}/invocations", + }, + }, + "get": { + "operationId": "getOperation", + "parameters": [ + { + "in": "query", + "name": "notAHeader", + "schema": { + "type": "string", + }, + }, + { + "in": "header", + "name": "x-shared-header", + "schema": { + "type": "string", + }, + }, + { + "in": "header", + "name": "X-Different-Header", + "schema": { + "type": "number", + }, + }, + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "Successful response", + "headers": { + "Access-Control-Allow-Headers": { + "schema": { + "type": "string", + }, + }, + "Access-Control-Allow-Methods": { + "schema": { + "type": "string", + }, + }, + "Access-Control-Allow-Origin": { + "schema": { + "type": "string", + }, + }, + }, + }, + }, + "security": [ + { + "aws.auth.sigv4": [], + }, + ], + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "passthroughBehavior": "WHEN_NO_MATCH", + "type": "AWS_PROXY", + "uri": "arn:\${}:apigateway:\${}:lambda:path/2015-03-31/functions/\${}/invocations", + }, + }, + "options": { + "description": "Enable CORS by returning the correct headers", + "responses": { + "200": { + "content": {}, + "description": "Default response for CORS method", + "headers": { + "Access-Control-Allow-Headers": { + "schema": { + "type": "string", + }, + }, + "Access-Control-Allow-Methods": { + "schema": { + "type": "string", + }, + }, + "Access-Control-Allow-Origin": { + "schema": { + "type": "string", + }, + }, + }, + }, + }, + "security": [], + "summary": "CORS Support", + "x-amazon-apigateway-auth": { + "type": "NONE", + }, + "x-amazon-apigateway-integration": { + "requestTemplates": { + "application/json": "{"statusCode": 200}", + }, + "responses": { + "default": { + "responseParameters": { + "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent,x-amz-content-sha256,x-shared-header,X-Different-Header,x-another-header'", + "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'", + "method.response.header.Access-Control-Allow-Origin": "'*'", + }, + "responseTemplates": { + "application/json": "{}", + }, + "statusCode": "200", + }, + }, + "type": "mock", + }, + }, + "post": { + "operationId": "postOperation", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "Successful response", + "headers": { + "Access-Control-Allow-Headers": { + "schema": { + "type": "string", + }, + }, + "Access-Control-Allow-Methods": { + "schema": { + "type": "string", + }, + }, + "Access-Control-Allow-Origin": { + "schema": { + "type": "string", + }, + }, + }, + }, + }, + "security": [ + { + "aws.auth.sigv4": [], + }, + ], + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "passthroughBehavior": "WHEN_NO_MATCH", + "type": "AWS_PROXY", + "uri": "arn:\${}:apigateway:\${}:lambda:path/2015-03-31/functions/\${}/invocations", + }, + }, + "put": { + "operationId": "putOperation", + "parameters": [ + { + "in": "query", + "name": "anotherQueryParam", + "schema": { + "type": "string", + }, + }, + { + "in": "header", + "name": "x-shared-header", + "schema": { + "type": "string", + }, + }, + { + "in": "header", + "name": "x-another-header", + "schema": { + "type": "integer", + }, + }, + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "Successful response", + "headers": { + "Access-Control-Allow-Headers": { + "schema": { + "type": "string", + }, + }, + "Access-Control-Allow-Methods": { + "schema": { + "type": "string", + }, + }, + "Access-Control-Allow-Origin": { + "schema": { + "type": "string", + }, + }, + }, + }, + }, + "security": [ + { + "aws.auth.sigv4": [], + }, + ], + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "passthroughBehavior": "WHEN_NO_MATCH", + "type": "AWS_PROXY", + "uri": "arn:\${}:apigateway:\${}:lambda:path/2015-03-31/functions/\${}/invocations", + }, + }, + }, + }, + "x-amazon-apigateway-gateway-responses": { + "BAD_REQUEST_BODY": { + "responseParameters": { + "gatewayresponse.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent,x-amz-content-sha256,x-shared-header,X-Different-Header,x-another-header'", + "gatewayresponse.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'", + "gatewayresponse.header.Access-Control-Allow-Origin": "'*'", + }, + "responseTemplates": { + "application/json": "{"message": "$context.error.validationErrorString"}", + }, + "statusCode": 400, + }, + }, + "x-amazon-apigateway-request-validator": "all", + "x-amazon-apigateway-request-validators": { + "all": { + "validateRequestBody": true, + "validateRequestParameters": true, + }, + }, +} +`; + exports[`Type Safe Rest Api Construct Unit Tests Should consolidate permissions for reused lambdas 1`] = ` { "Outputs": { @@ -5645,7 +7713,7 @@ exports[`Type Safe Rest Api Construct Unit Tests Should consolidate permissions "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec3E755E54", "Handler": "index.handler", @@ -7041,7 +9109,7 @@ exports[`Type Safe Rest Api Construct Unit Tests Should enable compression 1`] = "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec3E755E54", "Handler": "index.handler", @@ -8284,7 +10352,7 @@ exports[`Type Safe Rest Api Construct Unit Tests Synth 1`] = ` "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec3E755E54", "Handler": "index.handler", @@ -10626,7 +12694,7 @@ exports[`Type Safe Rest Api Construct Unit Tests With Cognito Auth 1`] = ` "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec3E755E54", "Handler": "index.handler", @@ -12067,7 +14135,7 @@ exports[`Type Safe Rest Api Construct Unit Tests With Custom Auth 1`] = ` "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec3E755E54", "Handler": "index.handler", @@ -13493,7 +15561,7 @@ exports[`Type Safe Rest Api Construct Unit Tests With Custom Managed Rules 1`] = "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec3E755E54", "Handler": "index.handler", @@ -14736,7 +16804,7 @@ exports[`Type Safe Rest Api Construct Unit Tests With IAM Auth and CORS 1`] = ` "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec3E755E54", "Handler": "index.handler", @@ -16431,7 +18499,7 @@ exports[`Type Safe Rest Api Construct Unit Tests With Mixed Auth 1`] = ` "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec3E755E54", "Handler": "index.handler", @@ -18209,7 +20277,7 @@ exports[`Type Safe Rest Api Construct Unit Tests With Mock Integration 1`] = ` "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec3E755E54", "Handler": "index.handler", @@ -19388,7 +21456,7 @@ exports[`Type Safe Rest Api Construct Unit Tests With Mock Integration and CORS "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec3E755E54", "Handler": "index.handler", @@ -20739,7 +22807,7 @@ exports[`Type Safe Rest Api Construct Unit Tests With Path Parameters 1`] = ` "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec3E755E54", "Handler": "index.handler", @@ -22110,7 +24178,7 @@ exports[`Type Safe Rest Api Construct Unit Tests With Waf IP Set 1`] = ` "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec3E755E54", "Handler": "index.handler", @@ -23220,7 +25288,7 @@ exports[`Type Safe Rest Api Construct Unit Tests Without Waf 1`] = ` "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "a9e5c149a9a8378a9cdc124d67237c2221ce8cd7a00777497f24f49f503f272c.zip", + "S3Key": "7f5f734c4b1912c2e0519cc77c4a123953110c510f49ff2b58500ae796674fbf.zip", }, "FunctionName": "Default-PrepareSpec3E755E54", "Handler": "index.handler", diff --git a/packages/type-safe-api/test/construct/type-safe-rest-api.test.ts b/packages/type-safe-api/test/construct/type-safe-rest-api.test.ts index 54c289bd6..31546111a 100644 --- a/packages/type-safe-api/test/construct/type-safe-rest-api.test.ts +++ b/packages/type-safe-api/test/construct/type-safe-rest-api.test.ts @@ -1127,4 +1127,111 @@ describe("Type Safe Rest Api Construct Unit Tests", () => { expect(Template.fromStack(stack).toJSON()).toMatchSnapshot(); }); }); + + it("Should add header parameters to CORS Access-Control-Allow-Headers", () => { + const stack = new Stack(); + + const spec: OpenAPIV3.Document = { + ...multiOperationSpec, + paths: { + "/test": { + ...multiOperationSpec.paths["/test"]!, + get: { + ...multiOperationSpec.paths["/test"]!.get!, + parameters: [ + { + in: "query", + name: "notAHeader", + schema: { type: "string" }, + }, + { + in: "header", + name: "x-shared-header", + schema: { type: "string" }, + }, + { + in: "header", + name: "X-Different-Header", + schema: { type: "number" }, + }, + ], + }, + put: { + ...multiOperationSpec.paths["/test"]!.put!, + parameters: [ + { + in: "query", + name: "anotherQueryParam", + schema: { type: "string" }, + }, + { + in: "header", + name: "x-shared-header", + schema: { type: "string" }, + }, + { + in: "header", + name: "x-another-header", + schema: { type: "integer" }, + }, + ], + }, + }, + }, + }; + + withTempSpec(spec, (specPath) => { + const api = new TypeSafeRestApi(stack, "ApiTest", { + defaultAuthorizer: Authorizers.iam(), + corsOptions: { + allowOrigins: Cors.ALL_ORIGINS, + allowMethods: Cors.ALL_METHODS, + allowCredentials: true, + statusCode: 200, + }, + specPath, + operationLookup: multiOperationLookup as any, + integrations: { + getOperation: { + integration: Integrations.lambda( + new Function(stack, "Lambda1", { + code: Code.fromInline("code"), + handler: "handler", + runtime: Runtime.NODEJS_16_X, + }) + ), + }, + putOperation: { + integration: Integrations.lambda( + new Function(stack, "Lambda2", { + code: Code.fromInline("code"), + handler: "handler", + runtime: Runtime.NODEJS_16_X, + }) + ), + }, + deleteOperation: { + integration: Integrations.lambda( + new Function(stack, "Lambda3", { + code: Code.fromInline("code"), + handler: "handler", + runtime: Runtime.NODEJS_16_X, + }) + ), + }, + postOperation: { + integration: Integrations.lambda( + new Function(stack, "Lambda4", { + code: Code.fromInline("code"), + handler: "handler", + runtime: Runtime.NODEJS_16_X, + }) + ), + }, + }, + }); + expect(Template.fromStack(stack).toJSON()).toMatchSnapshot(); + snapshotExtendedSpec(api); + }); + }); });