Skip to content

Commit

Permalink
update connect route options settings and authorizer
Browse files Browse the repository at this point in the history
  • Loading branch information
knihit committed Jul 16, 2024
1 parent 3764956 commit 9b69b74
Show file tree
Hide file tree
Showing 26 changed files with 2,998 additions and 551 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,10 @@ export interface ApiGatewayV2WebSocketToSqsProps {
*/
readonly createDefaultRoute?: boolean;
/**
* Add IAM authorization to the $connect path by default. Only set to false if: 1) The connect Lambda function implements custom
* authorization; or 2) The API should be open (no authorization) (AWS recommends against deploying unprotected APIs). If an authorizer
* is specified in connectRouteOptions, this parameter is ignored and no default IAM authorizer will be cr
* Add IAM authorization to the $connect path by default. Only set to false if: 1) If plan to provide an authorizer with
* the `$connect` route; or 2) The API should be open (no authorization) (AWS recommends against deploying unprotected APIs).
*
* If an authorizer is specified in connectRouteOptions, this parameter is ignored and no default IAM authorizer will be created
*
* @default - true
*/
Expand Down Expand Up @@ -169,6 +170,7 @@ export class ApiGatewayV2WebSocketToSqs extends Construct {
webSocketApiProps: props.webSocketApiProps,
existingWebSocketApi: props.existingWebSocketApi,
logGroupProps: props.logGroupProps,
defaultIamAuthorization: props.defaultIamAuthorization !== undefined ? props.defaultIamAuthorization : true,
});

this.webSocketApi = buildWebSocketQueueApiResponse.webSocketApi;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ import * as cdk from "aws-cdk-lib";
import * as apigwv2 from "aws-cdk-lib/aws-apigatewayv2";
import * as lambda from "aws-cdk-lib/aws-lambda";

import { COMMERCIAL_REGION_LAMBDA_NODE_RUNTIME } from "@aws-solutions-constructs/core";
import { Capture, Match, Template } from "aws-cdk-lib/assertions";
import { WebSocketIamAuthorizer } from "aws-cdk-lib/aws-apigatewayv2-authorizers";
import { WebSocketLambdaIntegration } from "aws-cdk-lib/aws-apigatewayv2-integrations";
import { WebSocketIamAuthorizer, WebSocketLambdaAuthorizer } from "aws-cdk-lib/aws-apigatewayv2-authorizers";
import { WebSocketLambdaIntegration, WebSocketMockIntegration } from "aws-cdk-lib/aws-apigatewayv2-integrations";
import { ApiGatewayV2WebSocketToSqs } from "../lib";
import { COMMERCIAL_REGION_LAMBDA_NODE_RUNTIME } from "@aws-solutions-constructs/core";

describe("When instantiating the ApiGatewayV2WebSocketToSqs construct with WebSocketApiProps", () => {
let template: Template;
Expand All @@ -43,7 +43,7 @@ describe("When instantiating the ApiGatewayV2WebSocketToSqs construct with WebSo
webSocketApiProps: {
connectRouteOptions: {
integration: new WebSocketLambdaIntegration("ConnectIntegration", mockConnectLambda),
authorizer: new WebSocketIamAuthorizer()
authorizer: new WebSocketIamAuthorizer(),
},
disconnectRouteOptions: {
integration: new WebSocketLambdaIntegration("DisconnectIntegration", mockDisconnectLambda),
Expand All @@ -54,6 +54,7 @@ describe("When instantiating the ApiGatewayV2WebSocketToSqs construct with WebSo
});

it("should have a FIFO queue and a DLQ", () => {
template.resourceCountIs("AWS::SQS::Queue", 2);
template.hasResourceProperties("AWS::SQS::Queue", {
DeduplicationScope: "messageGroup",
FifoQueue: true,
Expand All @@ -68,6 +69,7 @@ describe("When instantiating the ApiGatewayV2WebSocketToSqs construct with WebSo
FifoQueue: true,
DeduplicationScope: "messageGroup",
FifoThroughputLimit: "perMessageGroupId",
RedriveAllowPolicy: Match.absent(),
});
});

Expand Down Expand Up @@ -338,18 +340,160 @@ describe("When an existing instance of WebSocketApi and WebSocketApiProps both a
});
});

describe('When neither existingWebSocketApi nor webSocketApiProps are provided', () => {
it('should throw an error', () => {
describe("When instantiating the ApiGatewayV2WebSocketToSqs construct when not setting defaultIamAuthorizer", () => {
let template: Template;

beforeAll(() => {
const app = new cdk.App();
const stack = new cdk.Stack(app, 'TestStack');
const stack = new cdk.Stack(app, "TestStack");

const mockDisconnectLambda = new lambda.Function(stack, "mockDisconnectFunction", {
code: lambda.Code.fromAsset(`${__dirname}/lambda`),
runtime: COMMERCIAL_REGION_LAMBDA_NODE_RUNTIME,
handler: "disconnect.handler",
});

new ApiGatewayV2WebSocketToSqs(stack, "ApiGatewayV2WebSocketToSqs", {
webSocketApiProps: {
disconnectRouteOptions: {
integration: new WebSocketLambdaIntegration("DisconnectIntegration", mockDisconnectLambda),
},
},
});
template = Template.fromStack(stack);
});

it("should contain a websocket endpoint with a default implementation of $connect", () => {
template.hasResourceProperties("AWS::ApiGatewayV2::Route", {
RouteKey: "$connect",
AuthorizationType: "AWS_IAM",
Target: {
"Fn::Join": [
"",
[
"integrations/",
{
Ref: Match.stringLikeRegexp(
"ApiGatewayV2WebSocketToSqsWebSocketApiApiGatewayV2WebSocketToSqsconnectRouteconnect"
),
},
],
],
},
});
});
});

describe("When instantiating the ApiGatewayV2WebSocketToSqs construct when not setting defaultIamAuthorizer", () => {
let template: Template;

beforeAll(() => {
const app = new cdk.App();
const stack = new cdk.Stack(app, "TestStack");

const mockConnectLambda = new lambda.Function(stack, "mockConnectFunction", {
code: lambda.Code.fromAsset(`${__dirname}/lambda`),
runtime: COMMERCIAL_REGION_LAMBDA_NODE_RUNTIME,
handler: "connect.handler",
});

new ApiGatewayV2WebSocketToSqs(stack, "ApiGatewayV2WebSocketToSqs", {
webSocketApiProps: {
connectRouteOptions: {
integration: new WebSocketMockIntegration("connect"),
authorizer: new WebSocketLambdaAuthorizer("custom-authorizer", mockConnectLambda, {
identitySource: ["route.request.querystring.Authorization"],
}),
},
},
});
template = Template.fromStack(stack);
});

it("should contain a websocket endpoint with a default implementation of $connect", () => {
template.hasResourceProperties("AWS::ApiGatewayV2::Authorizer", {
ApiId: {
Ref: Match.stringLikeRegexp("ApiGatewayV2WebSocketToSqsWebSocketApiApiGatewayV2WebSocketToSqs"),
},
AuthorizerType: "REQUEST",
AuthorizerUri: {
"Fn::Join": [
"",
[
"arn:",
{
Ref: "AWS::Partition",
},
":apigateway:",
{
Ref: "AWS::Region",
},
":lambda:path/2015-03-31/functions/",
{
"Fn::GetAtt": [Match.stringLikeRegexp("mockConnectFunction"), "Arn"],
},
"/invocations",
],
],
},
IdentitySource: ["route.request.querystring.Authorization"],
Name: "custom-authorizer",
});

template.hasResourceProperties("AWS::ApiGatewayV2::Route", {
RouteKey: "$connect",
AuthorizationType: "CUSTOM",
Target: {
"Fn::Join": [
"",
[
"integrations/",
{
Ref: Match.stringLikeRegexp(
"ApiGatewayV2WebSocketToSqsWebSocketApiApiGatewayV2WebSocketToSqsconnectRouteconnect"
),
},
],
],
},
});
});
});

describe("When instantiating the ApiGatewayV2WebSocketToSqs construct when setting defaultIamAuthorizer as false", () => {
let template: Template;

beforeAll(() => {
const app = new cdk.App();
const stack = new cdk.Stack(app, "TestStack");

new ApiGatewayV2WebSocketToSqs(stack, "ApiGatewayV2WebSocketToSqs", {
defaultIamAuthorization: false,
createDefaultRoute: false
});
template = Template.fromStack(stack);
});

it("should contain a websocket endpoint with a default implementation of $connect", () => {
template.resourceCountIs("AWS::ApiGatewayV2::Authorizer", 0);
template.resourceCountIs("AWS::ApiGatewayV2::Route", 0);
template.resourceCountIs("AWS::ApiGatewayV2::Integration", 0);
template.resourceCountIs("AWS::ApiGatewayV2::Api", 1);
});
});

describe("When neither existingWebSocketApi nor webSocketApiProps are provided", () => {
it("should throw an error", () => {
const app = new cdk.App();
const stack = new cdk.Stack(app, "TestStack");

try {
new ApiGatewayV2WebSocketToSqs(stack, 'ApiGatewayV2WebSocketToSqs', {});
new ApiGatewayV2WebSocketToSqs(stack, "ApiGatewayV2WebSocketToSqs", {});
} catch (error) {
expect(error).toBeInstanceOf(Error);
expect((error as Error).message).toEqual(
'Provide either an existing WebSocketApi instance or WebSocketApiProps, but not both'
"Provide either an existing WebSocketApi instance or WebSocketApiProps, but not both"
);
}
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const handler = async (event) => {
return {
statusCode: 200,
body: 'Connected'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const handler = async (event) => {
return {
statusCode: 200,
body: 'Disconnected'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version":"36.0.0"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "36.0.0",
"testCases": {
"wssqs-custom-connect-route/Integ/DefaultTest": {
"stacks": [
"wssqs-custom-connect-route"
],
"assertionStack": "wssqs-custom-connect-route/Integ/DefaultTest/DeployAssert",
"assertionStackName": "wssqscustomconnectrouteIntegDefaultTestDeployAssert158EFF93"
}
}
}
Loading

0 comments on commit 9b69b74

Please sign in to comment.