UNPKG

cdk-serverless-agentic-api

Version:

CDK construct for serverless web applications with CloudFront, S3, Cognito, API Gateway, and Lambda

397 lines 18.3 kB
"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