UNPKG

cdk-serverless-agentic-api

Version:

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

329 lines 12.3 kB
"use strict"; /** * Error handling utilities for the serverless web app construct * Provides structured error responses and error page configuration */ 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.RateLimitError = exports.ConflictError = exports.NotFoundError = exports.AuthorizationError = exports.AuthenticationError = exports.ValidationError = exports.CORS_HEADERS = exports.HttpStatusCode = void 0; exports.createErrorResponse = createErrorResponse; exports.createSuccessResponse = createSuccessResponse; exports.withErrorHandling = withErrorHandling; exports.validateCognitoClaims = validateCognitoClaims; exports.validateRequestBody = validateRequestBody; exports.createErrorPages = createErrorPages; const aws_cdk_lib_1 = require("aws-cdk-lib"); const s3deploy = __importStar(require("aws-cdk-lib/aws-s3-deployment")); const constructs_1 = require("constructs"); const path = __importStar(require("path")); /** * HTTP status codes for common error scenarios */ var HttpStatusCode; (function (HttpStatusCode) { HttpStatusCode[HttpStatusCode["BAD_REQUEST"] = 400] = "BAD_REQUEST"; HttpStatusCode[HttpStatusCode["UNAUTHORIZED"] = 401] = "UNAUTHORIZED"; HttpStatusCode[HttpStatusCode["FORBIDDEN"] = 403] = "FORBIDDEN"; HttpStatusCode[HttpStatusCode["NOT_FOUND"] = 404] = "NOT_FOUND"; HttpStatusCode[HttpStatusCode["METHOD_NOT_ALLOWED"] = 405] = "METHOD_NOT_ALLOWED"; HttpStatusCode[HttpStatusCode["CONFLICT"] = 409] = "CONFLICT"; HttpStatusCode[HttpStatusCode["UNPROCESSABLE_ENTITY"] = 422] = "UNPROCESSABLE_ENTITY"; HttpStatusCode[HttpStatusCode["TOO_MANY_REQUESTS"] = 429] = "TOO_MANY_REQUESTS"; HttpStatusCode[HttpStatusCode["INTERNAL_SERVER_ERROR"] = 500] = "INTERNAL_SERVER_ERROR"; HttpStatusCode[HttpStatusCode["BAD_GATEWAY"] = 502] = "BAD_GATEWAY"; HttpStatusCode[HttpStatusCode["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE"; HttpStatusCode[HttpStatusCode["GATEWAY_TIMEOUT"] = 504] = "GATEWAY_TIMEOUT"; })(HttpStatusCode || (exports.HttpStatusCode = HttpStatusCode = {})); /** * Standard CORS headers for error responses */ exports.CORS_HEADERS = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Requested-With', 'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,PATCH,OPTIONS', 'Access-Control-Allow-Credentials': 'true', 'Access-Control-Max-Age': '86400' }; /** * Creates a standardized error response for Lambda functions */ function createErrorResponse(statusCode, error, message, requestId, details) { const errorResponse = { error, message, timestamp: new Date().toISOString(), requestId, details }; // Remove undefined values Object.keys(errorResponse).forEach(key => { if (errorResponse[key] === undefined) { delete errorResponse[key]; } }); return { statusCode, headers: { 'Content-Type': 'application/json', ...exports.CORS_HEADERS }, body: JSON.stringify(errorResponse) }; } /** * Creates a standardized success response for Lambda functions */ function createSuccessResponse(data, statusCode = 200, additionalHeaders) { return { statusCode, headers: { 'Content-Type': 'application/json', ...exports.CORS_HEADERS, ...additionalHeaders }, body: JSON.stringify({ ...data, timestamp: new Date().toISOString() }) }; } /** * Error handler wrapper for Lambda functions */ function withErrorHandling(handler) { return async (event, context) => { try { console.log('Request received:', JSON.stringify(event, null, 2)); const result = await handler(event, context); console.log('Response:', JSON.stringify(result, null, 2)); return result; } catch (error) { console.error('Unhandled error:', error); // Extract request ID from context if available const requestId = context?.awsRequestId; // Determine error type and status code let statusCode = HttpStatusCode.INTERNAL_SERVER_ERROR; let errorType = 'Internal Server Error'; let errorMessage = 'An unexpected error occurred while processing the request'; if (error instanceof ValidationError) { statusCode = HttpStatusCode.BAD_REQUEST; errorType = 'Validation Error'; errorMessage = error.message; } else if (error instanceof AuthenticationError) { statusCode = HttpStatusCode.UNAUTHORIZED; errorType = 'Authentication Error'; errorMessage = error.message; } else if (error instanceof AuthorizationError) { statusCode = HttpStatusCode.FORBIDDEN; errorType = 'Authorization Error'; errorMessage = error.message; } else if (error instanceof NotFoundError) { statusCode = HttpStatusCode.NOT_FOUND; errorType = 'Not Found'; errorMessage = error.message; } else if (error instanceof ConflictError) { statusCode = HttpStatusCode.CONFLICT; errorType = 'Conflict'; errorMessage = error.message; } else if (error instanceof RateLimitError) { statusCode = HttpStatusCode.TOO_MANY_REQUESTS; errorType = 'Rate Limit Exceeded'; errorMessage = error.message; } return createErrorResponse(statusCode, errorType, errorMessage, requestId, process.env.NODE_ENV === 'development' ? { stack: error instanceof Error ? error.stack : undefined, originalError: error instanceof Error ? error.message : String(error) } : undefined); } }; } /** * Custom error classes for different error scenarios */ class ValidationError extends Error { constructor(message, field) { super(message); this.field = field; this.name = 'ValidationError'; } } exports.ValidationError = ValidationError; class AuthenticationError extends Error { constructor(message = 'Authentication required') { super(message); this.name = 'AuthenticationError'; } } exports.AuthenticationError = AuthenticationError; class AuthorizationError extends Error { constructor(message = 'Insufficient permissions') { super(message); this.name = 'AuthorizationError'; } } exports.AuthorizationError = AuthorizationError; class NotFoundError extends Error { constructor(message = 'Resource not found') { super(message); this.name = 'NotFoundError'; } } exports.NotFoundError = NotFoundError; class ConflictError extends Error { constructor(message = 'Resource conflict') { super(message); this.name = 'ConflictError'; } } exports.ConflictError = ConflictError; class RateLimitError extends Error { constructor(message = 'Rate limit exceeded') { super(message); this.name = 'RateLimitError'; } } exports.RateLimitError = RateLimitError; /** * Validates Cognito claims and throws appropriate errors */ function validateCognitoClaims(event, requiredGroup) { const claims = event.requestContext?.authorizer?.claims; if (!claims) { throw new AuthenticationError('No authentication claims found'); } if (requiredGroup) { const userGroups = claims['cognito:groups'] ? claims['cognito:groups'].split(',') : []; if (!userGroups.includes(requiredGroup)) { throw new AuthorizationError(`Access denied. Required group: ${requiredGroup}`); } } return claims; } /** * Validates request body and throws validation errors */ function validateRequestBody(event, requiredFields = []) { if (!event.body) { if (requiredFields.length > 0) { throw new ValidationError('Request body is required'); } return {}; } let body; try { body = JSON.parse(event.body); } catch (error) { throw new ValidationError('Invalid JSON in request body'); } // Check required fields for (const field of requiredFields) { if (body[field] === undefined || body[field] === null || body[field] === '') { throw new ValidationError(`Missing required field: ${field}`, field); } } return body; } /** * Creates custom error pages for CloudFront distribution */ function createErrorPages(scope, bucket, constructId) { // Get custom error pages path if provided const errorPagesPath = scope instanceof constructs_1.Construct && scope.node.tryGetContext('props')?.errorPagesPath || path.join(__dirname, '../error-pages'); // Deploy error page assets to S3 new s3deploy.BucketDeployment(scope, `${constructId}ErrorPages`, { sources: [s3deploy.Source.asset(errorPagesPath)], destinationBucket: bucket, destinationKeyPrefix: 'error-pages/', cacheControl: [ s3deploy.CacheControl.setPublic(), s3deploy.CacheControl.maxAge(aws_cdk_lib_1.Duration.hours(1)) ], }); // Define custom error responses return [ { httpStatus: 400, responseHttpStatus: 400, responsePagePath: '/error-pages/400.html', ttl: aws_cdk_lib_1.Duration.minutes(5) }, { httpStatus: 403, responseHttpStatus: 403, responsePagePath: '/error-pages/403.html', ttl: aws_cdk_lib_1.Duration.minutes(5) }, { httpStatus: 404, responseHttpStatus: 404, responsePagePath: '/error-pages/404.html', ttl: aws_cdk_lib_1.Duration.hours(1) }, { httpStatus: 500, responseHttpStatus: 500, responsePagePath: '/error-pages/500.html', ttl: aws_cdk_lib_1.Duration.minutes(1) }, { httpStatus: 502, responseHttpStatus: 502, responsePagePath: '/error-pages/502.html', ttl: aws_cdk_lib_1.Duration.minutes(1) }, { httpStatus: 503, responseHttpStatus: 503, responsePagePath: '/error-pages/503.html', ttl: aws_cdk_lib_1.Duration.minutes(1) }, { httpStatus: 504, responseHttpStatus: 504, responsePagePath: '/error-pages/504.html', ttl: aws_cdk_lib_1.Duration.minutes(1) } ]; } //# sourceMappingURL=error-handling.js.map