cdk-serverless-agentic-api
Version:
CDK construct for serverless web applications with CloudFront, S3, Cognito, API Gateway, and Lambda
397 lines • 18.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.createApiGatewayLogGroup = createApiGatewayLogGroup;
exports.createApiGateway = createApiGateway;
exports.createCognitoAuthorizer = createCognitoAuthorizer;
exports.createApiGatewayResource = createApiGatewayResource;
exports.createApiGatewayMethod = createApiGatewayMethod;
const apigateway = __importStar(require("aws-cdk-lib/aws-apigateway"));
const logs = __importStar(require("aws-cdk-lib/aws-logs"));
const iam = __importStar(require("aws-cdk-lib/aws-iam"));
const aws_cdk_lib_1 = require("aws-cdk-lib");
/**
* Creates CloudWatch log group for API Gateway
*
* @param scope The construct scope
* @param id The construct ID
* @returns The created log group
*/
function createApiGatewayLogGroup(scope, id) {
return new logs.LogGroup(scope, 'ApiGatewayLogGroup', {
logGroupName: `/aws/apigateway/${id}-api`,
retention: logs.RetentionDays.ONE_MONTH,
removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY,
});
}
/**
* Creates the API Gateway REST API with comprehensive logging configuration
*
* @param scope The construct scope
* @param id The construct ID
* @param props Configuration properties
* @returns The created API Gateway REST API
*/
function createApiGateway(scope, id, props) {
// Create CloudWatch log group for API Gateway if logging is enabled
if (props?.enableLogging !== false) {
createApiGatewayLogGroup(scope, id);
}
return new apigateway.RestApi(scope, 'Api', {
restApiName: props?.apiName || `${id}-api`,
description: `REST API for ${id} serverless web application`,
// Configure deployment settings
deploy: true,
deployOptions: {
stageName: 'api',
description: 'Production deployment',
// Enable detailed CloudWatch metrics
metricsEnabled: props?.enableLogging !== false,
// Enable data trace logging for debugging
dataTraceEnabled: props?.enableLogging !== false,
// Log full requests and responses for debugging
loggingLevel: props?.enableLogging !== false
? apigateway.MethodLoggingLevel.INFO
: apigateway.MethodLoggingLevel.OFF,
// Enable throttling
throttlingBurstLimit: 5000,
throttlingRateLimit: 2000,
},
// Configure CORS for cross-origin requests
defaultCorsPreflightOptions: {
allowOrigins: apigateway.Cors.ALL_ORIGINS,
allowMethods: apigateway.Cors.ALL_METHODS,
allowHeaders: [
'Content-Type',
'X-Amz-Date',
'Authorization',
'X-Api-Key',
'X-Amz-Security-Token',
'X-Amz-User-Agent',
'X-Requested-With',
],
allowCredentials: true,
maxAge: aws_cdk_lib_1.Duration.hours(1),
},
// Configure binary media types for file uploads
binaryMediaTypes: [
'application/octet-stream',
'image/*',
'multipart/form-data',
],
// Configure endpoint configuration
endpointConfiguration: {
types: [apigateway.EndpointType.REGIONAL],
},
// Configure API key settings
apiKeySourceType: apigateway.ApiKeySourceType.HEADER,
// Configure minimum compression size
minCompressionSize: aws_cdk_lib_1.Size.bytes(1024),
// Configure policy for the API (will be restrictive by default)
policy: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
principals: [new iam.AnyPrincipal()],
actions: ['execute-api:Invoke'],
resources: ['*'],
conditions: {
IpAddress: {
'aws:SourceIp': ['0.0.0.0/0', '::/0'], // Allow all IPs by default
},
},
}),
],
}),
// Configure CloudWatch role for logging
cloudWatchRole: props?.enableLogging !== false,
});
}
/**
* Creates the Cognito authorizer for authenticated API endpoints
*
* @param scope The construct scope
* @param api The API Gateway REST API
* @param userPool The Cognito User Pool
* @param id The construct ID
* @returns The created Cognito authorizer
*/
function createCognitoAuthorizer(scope, api, userPool, id) {
// Create the authorizer using CfnAuthorizer
return new apigateway.CfnAuthorizer(scope, 'CognitoAuthorizer', {
restApiId: api.restApiId,
name: `${id}-cognito-authorizer`,
type: 'COGNITO_USER_POOLS',
identitySource: 'method.request.header.Authorization',
providerArns: [userPool.userPoolArn],
authorizerResultTtlInSeconds: 300,
});
}
/**
* Creates or retrieves an API Gateway resource for the given path
*
* @param api The API Gateway REST API
* @param resourcePath The full resource path (e.g., '/api/users')
* @returns The API Gateway resource
*/
function createApiGatewayResource(api, resourcePath) {
// Remove /api prefix for API Gateway resource creation
const pathWithoutApi = resourcePath.replace(/^\/api/, '');
// Split path into segments
const pathSegments = pathWithoutApi.split('/').filter(segment => segment.length > 0);
// Start from the root resource
let currentResource = api.root;
// Create nested resources for each path segment
for (const segment of pathSegments) {
// Check if resource already exists
const existingResource = currentResource.getResource(segment);
if (existingResource) {
currentResource = existingResource;
}
else {
// Create new resource
currentResource = currentResource.addResource(segment, {
defaultCorsPreflightOptions: {
allowOrigins: apigateway.Cors.ALL_ORIGINS,
allowMethods: apigateway.Cors.ALL_METHODS,
allowHeaders: [
'Content-Type',
'X-Amz-Date',
'Authorization',
'X-Api-Key',
'X-Amz-Security-Token',
'X-Amz-User-Agent',
'X-Requested-With',
],
allowCredentials: true,
maxAge: aws_cdk_lib_1.Duration.hours(1),
},
});
}
}
return currentResource;
}
/**
* Creates an API Gateway method and connects it to a Lambda function
*
* @param resource The API Gateway resource
* @param config Resource configuration
* @param lambdaFunction The Lambda function to connect
* @param cognitoAuthorizer The Cognito authorizer
* @param api The API Gateway REST API
* @returns The created API Gateway method
*/
function createApiGatewayMethod(resource, config, lambdaFunction, cognitoAuthorizer, api) {
// Create Lambda integration with comprehensive error handling
const integration = new apigateway.LambdaIntegration(lambdaFunction, {
proxy: true,
allowTestInvoke: true,
integrationResponses: [
{
statusCode: '200',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': "'*'",
'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Requested-With'",
'method.response.header.Access-Control-Allow-Methods': "'GET,POST,PUT,DELETE,PATCH,OPTIONS'",
'method.response.header.Access-Control-Allow-Credentials': "'true'",
},
},
{
statusCode: '400',
selectionPattern: '.*"statusCode": 400.*',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': "'*'",
'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Requested-With'",
'method.response.header.Access-Control-Allow-Methods': "'GET,POST,PUT,DELETE,PATCH,OPTIONS'",
'method.response.header.Access-Control-Allow-Credentials': "'true'",
},
},
{
statusCode: '401',
selectionPattern: '.*"statusCode": 401.*',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': "'*'",
'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Requested-With'",
'method.response.header.Access-Control-Allow-Methods': "'GET,POST,PUT,DELETE,PATCH,OPTIONS'",
'method.response.header.Access-Control-Allow-Credentials': "'true'",
},
},
{
statusCode: '403',
selectionPattern: '.*"statusCode": 403.*',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': "'*'",
'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Requested-With'",
'method.response.header.Access-Control-Allow-Methods': "'GET,POST,PUT,DELETE,PATCH,OPTIONS'",
'method.response.header.Access-Control-Allow-Credentials': "'true'",
},
},
{
statusCode: '404',
selectionPattern: '.*"statusCode": 404.*',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': "'*'",
'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Requested-With'",
'method.response.header.Access-Control-Allow-Methods': "'GET,POST,PUT,DELETE,PATCH,OPTIONS'",
'method.response.header.Access-Control-Allow-Credentials': "'true'",
},
},
{
statusCode: '405',
selectionPattern: '.*"statusCode": 405.*',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': "'*'",
'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Requested-With'",
'method.response.header.Access-Control-Allow-Methods': "'GET,POST,PUT,DELETE,PATCH,OPTIONS'",
'method.response.header.Access-Control-Allow-Credentials': "'true'",
},
},
{
statusCode: '429',
selectionPattern: '.*"statusCode": 429.*',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': "'*'",
'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Requested-With'",
'method.response.header.Access-Control-Allow-Methods': "'GET,POST,PUT,DELETE,PATCH,OPTIONS'",
'method.response.header.Access-Control-Allow-Credentials': "'true'",
'method.response.header.Retry-After': "'60'",
},
},
{
statusCode: '500',
selectionPattern: '.*"statusCode": 5\\d{2}.*',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': "'*'",
'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Requested-With'",
'method.response.header.Access-Control-Allow-Methods': "'GET,POST,PUT,DELETE,PATCH,OPTIONS'",
'method.response.header.Access-Control-Allow-Credentials': "'true'",
},
},
],
});
// Configure method options based on authentication requirements
const methodOptions = {
methodResponses: [
{
statusCode: '200',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': true,
'method.response.header.Access-Control-Allow-Headers': true,
'method.response.header.Access-Control-Allow-Methods': true,
'method.response.header.Access-Control-Allow-Credentials': true,
},
},
{
statusCode: '400',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': true,
'method.response.header.Access-Control-Allow-Headers': true,
'method.response.header.Access-Control-Allow-Methods': true,
'method.response.header.Access-Control-Allow-Credentials': true,
},
},
{
statusCode: '401',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': true,
'method.response.header.Access-Control-Allow-Headers': true,
'method.response.header.Access-Control-Allow-Methods': true,
'method.response.header.Access-Control-Allow-Credentials': true,
},
},
{
statusCode: '403',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': true,
'method.response.header.Access-Control-Allow-Headers': true,
'method.response.header.Access-Control-Allow-Methods': true,
'method.response.header.Access-Control-Allow-Credentials': true,
},
},
{
statusCode: '404',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': true,
'method.response.header.Access-Control-Allow-Headers': true,
'method.response.header.Access-Control-Allow-Methods': true,
'method.response.header.Access-Control-Allow-Credentials': true,
},
},
{
statusCode: '405',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': true,
'method.response.header.Access-Control-Allow-Headers': true,
'method.response.header.Access-Control-Allow-Methods': true,
'method.response.header.Access-Control-Allow-Credentials': true,
},
},
{
statusCode: '429',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': true,
'method.response.header.Access-Control-Allow-Headers': true,
'method.response.header.Access-Control-Allow-Methods': true,
'method.response.header.Access-Control-Allow-Credentials': true,
'method.response.header.Retry-After': true,
},
},
{
statusCode: '500',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': true,
'method.response.header.Access-Control-Allow-Headers': true,
'method.response.header.Access-Control-Allow-Methods': true,
'method.response.header.Access-Control-Allow-Credentials': true,
},
},
],
// Add authorization if required
...(config.requiresAuth && {
authorizationType: apigateway.AuthorizationType.COGNITO,
authorizer: {
authorizerId: cognitoAuthorizer.ref,
},
...(config.cognitoGroup && {
authorizationScopes: [`${config.cognitoGroup}`],
}),
}),
};
// Create the method
const method = resource.addMethod(config.method, integration, methodOptions);
// Note: Lambda permission is granted in the Lambda function constructor to avoid circular dependency
return method;
}
//# sourceMappingURL=api-gateway.js.map