diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-authorizers/lib/http/lambda.ts b/packages/aws-cdk-lib/aws-apigatewayv2-authorizers/lib/http/lambda.ts index 1c919e9e811e3..6bfa39a7bbe4a 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-authorizers/lib/http/lambda.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-authorizers/lib/http/lambda.ts @@ -49,14 +49,30 @@ export interface HttpLambdaAuthorizerProps { readonly resultsCacheTtl?: Duration; /** - * The type of response the lambda can return + * The payload format version + * + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html#http-api-lambda-authorizer.payload-format * - * If set to HttpLambdaResponseType.SIMPLE then - * response format 2.0 will be used. + * Note: if `responseType` is set to simple, then + * setting this property to version 1.0 is not allowed. + * + * @default - If `responseType` is not set or is set to IAM, + * then payload format version is set to 1.0. If `responseType` + * is set to simple, then payload format version is set to 2.0. + */ + readonly payloadFormatVersion?: AuthorizerPayloadVersion; + + /** + * The type of response the lambda can return * * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html#http-api-lambda-authorizer.payload-format-response * - * @default HttpLambdaResponseType.IAM + * Note: if `payloadFormatVersion` is set to version 1.0, then + * setting this property to simple is not allowed. + * + * @default - if `payloadFormatVersion` is not set or is set + * to 1.0, then response type is set to IAM. If `payloadFormatVersion` + * is set to 2.0, then response type is set to simple. */ readonly responseType?: HttpLambdaResponseType; } @@ -78,6 +94,9 @@ export class HttpLambdaAuthorizer implements IHttpRouteAuthorizer { private readonly id: string, private readonly handler: IFunction, private readonly props: HttpLambdaAuthorizerProps = {}) { + if (props.payloadFormatVersion === AuthorizerPayloadVersion.VERSION_1_0 && props.responseType === HttpLambdaResponseType.SIMPLE) { + throw new Error('payload format version is set to 1.0 but response type is set to SIMPLE'); + } } public bind(options: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig { @@ -86,8 +105,25 @@ export class HttpLambdaAuthorizer implements IHttpRouteAuthorizer { } if (!this.authorizer) { - const responseType = this.props.responseType ?? HttpLambdaResponseType.IAM; - const enableSimpleResponses = responseType === HttpLambdaResponseType.SIMPLE || undefined; + let enableSimpleResponses: boolean; + let payloadFormatVersion: AuthorizerPayloadVersion; + + const payloadFormatVersionInner = this.props.payloadFormatVersion; + const responseType = this.props.responseType; + + if (payloadFormatVersionInner === undefined) { + enableSimpleResponses = responseType === HttpLambdaResponseType.SIMPLE; + payloadFormatVersion = enableSimpleResponses ? AuthorizerPayloadVersion.VERSION_2_0 : AuthorizerPayloadVersion.VERSION_1_0; + } else if (responseType === undefined) { + enableSimpleResponses = payloadFormatVersionInner === AuthorizerPayloadVersion.VERSION_2_0; + payloadFormatVersion = payloadFormatVersionInner; + } else { + // No need to check here whether payload format version is 1.0 + // and response type is simple since this is already handled + // by the constructor + enableSimpleResponses = responseType === HttpLambdaResponseType.SIMPLE; + payloadFormatVersion = payloadFormatVersionInner; + } this.httpApi = options.route.httpApi; this.authorizer = new HttpAuthorizer(options.scope, this.id, { @@ -98,7 +134,7 @@ export class HttpLambdaAuthorizer implements IHttpRouteAuthorizer { type: HttpAuthorizerType.LAMBDA, authorizerName: this.props.authorizerName ?? this.id, enableSimpleResponses, - payloadFormatVersion: enableSimpleResponses ? AuthorizerPayloadVersion.VERSION_2_0 : AuthorizerPayloadVersion.VERSION_1_0, + payloadFormatVersion, authorizerUri: lambdaAuthorizerArn(this.handler), resultsCacheTtl: this.props.resultsCacheTtl ?? Duration.minutes(5), }); diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-authorizers/test/http/lambda.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-authorizers/test/http/lambda.test.ts index 319c823e2daa6..2183d5151dde9 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-authorizers/test/http/lambda.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-authorizers/test/http/lambda.test.ts @@ -2,7 +2,7 @@ import { HttpLambdaAuthorizer, HttpLambdaResponseType } from './../../lib/http/l import { DummyRouteIntegration } from './integration'; import { Duration, Stack } from '../../..'; import { Match, Template } from '../../../assertions'; -import { HttpApi } from '../../../aws-apigatewayv2'; +import { AuthorizerPayloadVersion, HttpApi } from '../../../aws-apigatewayv2'; import { Code, Function } from '../../../aws-lambda'; import * as lambda from '../../../aws-lambda'; @@ -34,6 +34,7 @@ describe('HttpLambdaAuthorizer', () => { AuthorizerType: 'REQUEST', AuthorizerResultTtlInSeconds: 300, AuthorizerPayloadFormatVersion: '1.0', + EnableSimpleResponses: false, IdentitySource: [ '$request.header.Authorization', ], @@ -44,7 +45,7 @@ describe('HttpLambdaAuthorizer', () => { }); }); - test('should use format 2.0 and simple responses when simple response type is requested', () => { + test('should use payload format version 2.0 and simple responses when payload format version is undefined and simple response type is requested', () => { // GIVEN const stack = new Stack(); const api = new HttpApi(stack, 'HttpApi'); @@ -73,7 +74,7 @@ describe('HttpLambdaAuthorizer', () => { }); }); - test('should use format 1.0 when only IAM response type is requested', () => { + test('should use payload format version 2.0 and non-simple responses when payload format version is undefined and IAM response type is requested', () => { // GIVEN const stack = new Stack(); const api = new HttpApi(stack, 'HttpApi'); @@ -98,10 +99,175 @@ describe('HttpLambdaAuthorizer', () => { // THEN Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Authorizer', { AuthorizerPayloadFormatVersion: '1.0', - EnableSimpleResponses: Match.absent(), + EnableSimpleResponses: false, }); }); + test('should not use simple responses when payload format version is 1.0 and response type is undefined', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + + const handler = new Function(stack, 'auth-function', { + runtime: lambda.Runtime.NODEJS_LATEST, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', + }); + + const authorizer = new HttpLambdaAuthorizer('BooksAuthorizer', handler, { + payloadFormatVersion: AuthorizerPayloadVersion.VERSION_1_0, + }); + + // WHEN + api.addRoutes({ + integration: new DummyRouteIntegration(), + path: '/books', + authorizer, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Authorizer', { + AuthorizerPayloadFormatVersion: '1.0', + EnableSimpleResponses: false, + }); + }); + + test('should not use simple responses when payload format version is 1.0 and IAM response type is requested', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + + const handler = new Function(stack, 'auth-function', { + runtime: lambda.Runtime.NODEJS_LATEST, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', + }); + + const authorizer = new HttpLambdaAuthorizer('BooksAuthorizer', handler, { + payloadFormatVersion: AuthorizerPayloadVersion.VERSION_1_0, + responseType: HttpLambdaResponseType.IAM, + }); + + // WHEN + api.addRoutes({ + integration: new DummyRouteIntegration(), + path: '/books', + authorizer, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Authorizer', { + AuthorizerPayloadFormatVersion: '1.0', + EnableSimpleResponses: false, + }); + }); + + test('should use simple responses when payload format version 2.0 is specified and response type is undefined', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + + const handler = new Function(stack, 'auth-function', { + runtime: lambda.Runtime.NODEJS_LATEST, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', + }); + + const authorizer = new HttpLambdaAuthorizer('BooksAuthorizer', handler, { + payloadFormatVersion: AuthorizerPayloadVersion.VERSION_2_0, + }); + + // WHEN + api.addRoutes({ + integration: new DummyRouteIntegration(), + path: '/books', + authorizer, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Authorizer', { + AuthorizerPayloadFormatVersion: '2.0', + EnableSimpleResponses: true, + }); + }); + + test('should use simple responses when payload format version 2.0 is specified and simple response type is requested', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + + const handler = new Function(stack, 'auth-function', { + runtime: lambda.Runtime.NODEJS_LATEST, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', + }); + + const authorizer = new HttpLambdaAuthorizer('BooksAuthorizer', handler, { + payloadFormatVersion: AuthorizerPayloadVersion.VERSION_2_0, + responseType: HttpLambdaResponseType.SIMPLE, + }); + + // WHEN + api.addRoutes({ + integration: new DummyRouteIntegration(), + path: '/books', + authorizer, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Authorizer', { + AuthorizerPayloadFormatVersion: '2.0', + EnableSimpleResponses: true, + }); + }); + + test('should not use simple responses when payload format version is 2.0 and IAM response type is requested', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + + const handler = new Function(stack, 'auth-function', { + runtime: lambda.Runtime.NODEJS_LATEST, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', + }); + + const authorizer = new HttpLambdaAuthorizer('BooksAuthorizer', handler, { + payloadFormatVersion: AuthorizerPayloadVersion.VERSION_2_0, + responseType: HttpLambdaResponseType.IAM, + }); + + // WHEN + api.addRoutes({ + integration: new DummyRouteIntegration(), + path: '/books', + authorizer, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Authorizer', { + AuthorizerPayloadFormatVersion: '2.0', + EnableSimpleResponses: false, + }); + }); + + test('error when payload format version 1.0 is specified and simple response type is requested', () => { + const stack = new Stack(); + + const handler = new Function(stack, 'auth-function', { + runtime: lambda.Runtime.NODEJS_LATEST, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', + }); + + expect(() => { + new HttpLambdaAuthorizer('BooksAuthorizer', handler, { + payloadFormatVersion: AuthorizerPayloadVersion.VERSION_1_0, + responseType: HttpLambdaResponseType.SIMPLE, + }); + }).toThrow(/payload format version is set to 1.0 but response type is set to SIMPLE/); + }); + test('can override cache ttl', () => { // GIVEN const stack = new Stack();