Skip to content

Commit 81fc778

Browse files
authored
Merge pull request #26 from wesreid/fix/apig-error-message-response
update api gateway error message response
2 parents d91b587 + b35ceb8 commit 81fc778

File tree

1 file changed

+133
-127
lines changed

1 file changed

+133
-127
lines changed

src/lib/lambda-route-proxy-entry-handler.ts

Lines changed: 133 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import {
1212
SecurityConfig,
1313
} from "./types-and-interfaces";
1414
import { authorizeRoute } from "./authorization-helper";
15-
import {
16-
loadSecurityConfig,
17-
generateCorsHeaders,
18-
generateJwtRotationHeaders
15+
import {
16+
loadSecurityConfig,
17+
generateCorsHeaders,
18+
generateJwtRotationHeaders
1919
} from "./security-config-loader";
2020

2121
const getRouteConfigEntry = (
@@ -139,141 +139,147 @@ export function getRouteConfigByPath(
139139

140140
export const lambdaRouteProxyEntryHandler =
141141
(config: RouteConfig, availableRouteModules: { [key: string]: any }) =>
142-
async (
143-
event: APIGatewayProxyEventV2 | APIGatewayProxyEvent | APIGatewayEvent
144-
) => {
145-
// Load security configuration
146-
const securityConfig = config.security || loadSecurityConfig();
147-
console.log(`Event Data: ${JSON.stringify(event)}`);
148-
const isV2 = (event as APIGatewayProxyEventV2).version === "2.0";
142+
async (
143+
event: APIGatewayProxyEventV2 | APIGatewayProxyEvent | APIGatewayEvent
144+
) => {
145+
// Load security configuration
146+
const securityConfig = config.security || loadSecurityConfig();
147+
console.log(`Event Data: ${JSON.stringify(event)}`);
148+
const isV2 = (event as APIGatewayProxyEventV2).version === "2.0";
149149

150-
const isProxied = !isV2 && event.hasOwnProperty("requestContext");
150+
const isProxied = !isV2 && event.hasOwnProperty("requestContext");
151151

152-
const newEvent = isV2
153-
? v2ApiGatewayEvent(event as APIGatewayProxyEventV2)
154-
: v1ApiGatewayEvent(event as APIGatewayProxyEvent, config);
152+
const newEvent = isV2
153+
? v2ApiGatewayEvent(event as APIGatewayProxyEventV2)
154+
: v1ApiGatewayEvent(event as APIGatewayProxyEvent, config);
155155

156-
const {
157-
routeKey,
158-
queryStringParameters,
159-
pathParameters,
160-
body,
161-
isBase64Encoded,
162-
} = newEvent;
156+
const {
157+
routeKey,
158+
queryStringParameters,
159+
pathParameters,
160+
body,
161+
isBase64Encoded,
162+
} = newEvent;
163163

164-
let retVal: any = {};
165-
try {
166-
const [method = "", path = ""] = routeKey.split(" ");
167-
if (
168-
shouldAuthorizeRoute(config, getRouteConfigEntry(config, method, path))
169-
) {
170-
await authorizeRoute(event);
171-
}
164+
let retVal: any = {};
165+
try {
166+
const [method = "", path = ""] = routeKey.split(" ");
167+
if (
168+
shouldAuthorizeRoute(config, getRouteConfigEntry(config, method, path))
169+
) {
170+
await authorizeRoute(event);
171+
}
172172

173-
const routeModule = getRouteModule(
174-
config,
175-
method,
176-
path,
177-
availableRouteModules
178-
);
173+
const routeModule = getRouteModule(
174+
config,
175+
method,
176+
path,
177+
availableRouteModules
178+
);
179179

180-
console.log(`isBase64Encoded: ${isBase64Encoded}`);
181-
console.log(`body: ${body}`);
182-
const decodedBody = isBase64Encoded
183-
? Buffer.from(body!, "base64").toString("utf-8")
184-
: undefined;
185-
console.log(`decodedBody:
180+
console.log(`isBase64Encoded: ${isBase64Encoded}`);
181+
console.log(`body: ${body}`);
182+
const decodedBody = isBase64Encoded
183+
? Buffer.from(body!, "base64").toString("utf-8")
184+
: undefined;
185+
console.log(`decodedBody:
186186
${decodedBody}`);
187187

188-
const routeArgs: RouteArguments = {
189-
query: queryStringParameters,
190-
params: pathParameters,
191-
body: body ? decodedBody || JSON.parse(body) : undefined,
192-
rawEvent: event,
193-
};
194-
195-
retVal = await getRouteModuleResult(routeModule, routeArgs);
188+
const routeArgs: RouteArguments = {
189+
query: queryStringParameters,
190+
params: pathParameters,
191+
body: body ? decodedBody || JSON.parse(body) : undefined,
192+
rawEvent: event,
193+
};
196194

197-
if (isProxied) {
198-
if (retVal.statusCode && !retVal.body) {
199-
console.log("body must be included when status code is set", retVal);
200-
throw new CustomError("No body found", 500);
201-
} else if (retVal.statusCode && retVal.body) {
202-
// Generate secure headers based on configuration
203-
const requestOrigin = event.headers?.origin || event.headers?.Origin;
204-
const corsHeaders = generateCorsHeaders(securityConfig, requestOrigin);
205-
const jwtRotationHeaders = generateJwtRotationHeaders(securityConfig, routeArgs.routeData);
206-
207-
retVal = {
208-
...retVal,
209-
isBase64Encoded: false,
210-
headers: {
211-
"Content-Type": "application/json",
212-
// 1. Default security headers from config (lowest priority)
213-
...securityConfig.defaultHeaders,
214-
// 2. CORS headers (only if origin is allowed)
215-
...corsHeaders,
216-
// 3. JWT rotation headers (if needed)
217-
...jwtRotationHeaders,
218-
// 4. Middleware-provided headers (higher priority)
219-
...(routeArgs.responseHeaders ?? {}),
220-
// 5. Handler-provided headers (highest priority - can override everything)
221-
...(retVal.headers ?? {}),
222-
},
223-
body:
224-
typeof retVal.body === "object"
225-
? JSON.stringify(retVal.body)
226-
: retVal.body,
227-
};
195+
retVal = await getRouteModuleResult(routeModule, routeArgs);
196+
197+
if (isProxied) {
198+
if (retVal.statusCode && !retVal.body) {
199+
console.log("body must be included when status code is set", retVal);
200+
throw new CustomError("No body found", 500);
201+
} else if (retVal.statusCode && retVal.statusCode !== 200) {
202+
retVal = retVal;
203+
} else if (retVal.statusCode && retVal.body) {
204+
// Generate secure headers based on configuration
205+
const requestOrigin = event.headers?.origin || event.headers?.Origin;
206+
const corsHeaders = generateCorsHeaders(securityConfig, requestOrigin);
207+
const jwtRotationHeaders = generateJwtRotationHeaders(securityConfig, routeArgs.routeData);
208+
209+
retVal = {
210+
...retVal,
211+
isBase64Encoded: false,
212+
headers: {
213+
"Content-Type": "application/json",
214+
// 1. Default security headers from config (lowest priority)
215+
...securityConfig.defaultHeaders,
216+
// 2. CORS headers (only if origin is allowed)
217+
...corsHeaders,
218+
// 3. JWT rotation headers (if needed)
219+
...jwtRotationHeaders,
220+
// 4. Middleware-provided headers (higher priority)
221+
...(routeArgs.responseHeaders ?? {}),
222+
// 5. Handler-provided headers (highest priority - can override everything)
223+
...(retVal.headers ?? {}),
224+
},
225+
body:
226+
typeof retVal.body === "object"
227+
? JSON.stringify(retVal.body)
228+
: retVal.body,
229+
};
230+
}
231+
} else {
232+
if (retVal.statusCode && retVal.statusCode !== 200) {
233+
retVal = retVal;
234+
} else {
235+
retVal = {
236+
statusCode: 200,
237+
body: JSON.stringify(retVal),
238+
headers: {
239+
"Content-Type": "application/json",
240+
},
241+
};
242+
}
228243
}
229-
} else {
230-
retVal = {
231-
statusCode: 200,
232-
body: JSON.stringify(retVal),
233-
headers: {
234-
"Content-Type": "application/json",
235-
},
236-
};
237-
}
238-
} catch (error: any) {
239-
console.error(JSON.stringify({ error, stack: error.stack }));
240-
let headers = {
241-
"Content-Type": "application/json",
242-
} as Record<string, string>;
244+
} catch (error: any) {
245+
console.error(JSON.stringify({ error, stack: error.stack }));
246+
let headers = {
247+
"Content-Type": "application/json",
248+
} as Record<string, string>;
243249

244-
let statusCode = 500;
250+
let statusCode = 500;
245251

246-
if (isProxied) {
247-
const isOptions =
248-
(event.requestContext as any).httpMethod === "OPTIONS";
249-
if (isOptions) {
250-
statusCode = 200;
252+
if (isProxied) {
253+
const isOptions =
254+
(event.requestContext as any).httpMethod === "OPTIONS";
255+
if (isOptions) {
256+
statusCode = 200;
257+
} else {
258+
statusCode = error.httpStatusCode || 500;
259+
}
260+
headers = {
261+
...headers,
262+
"Access-Control-Allow-Origin": "*",
263+
"Access-Control-Allow-Methods":
264+
"GET, POST, PUT, DELETE, PATCH, OPTIONS",
265+
"Access-Control-Allow-Headers":
266+
"Content-Type, Authorization, X-Amz-Date, X-Api-Key, X-Amz-Security-Token",
267+
"Access-Control-Allow-Credentials": "true",
268+
};
269+
}
270+
if (error instanceof CustomError) {
271+
retVal = {
272+
statusCode,
273+
headers,
274+
body: error.message,
275+
};
251276
} else {
252-
statusCode = error.httpStatusCode || 500;
277+
retVal = {
278+
statusCode,
279+
headers,
280+
body: error.message || JSON.stringify(error),
281+
};
253282
}
254-
headers = {
255-
...headers,
256-
"Access-Control-Allow-Origin": "*",
257-
"Access-Control-Allow-Methods":
258-
"GET, POST, PUT, DELETE, PATCH, OPTIONS",
259-
"Access-Control-Allow-Headers":
260-
"Content-Type, Authorization, X-Amz-Date, X-Api-Key, X-Amz-Security-Token",
261-
"Access-Control-Allow-Credentials": "true",
262-
};
263283
}
264-
if (error instanceof CustomError) {
265-
retVal = {
266-
statusCode,
267-
headers,
268-
body: error.message,
269-
};
270-
} else {
271-
retVal = {
272-
statusCode,
273-
headers,
274-
body: error.message || JSON.stringify(error),
275-
};
276-
}
277-
}
278-
return retVal;
279-
};
284+
return retVal;
285+
};

0 commit comments

Comments
 (0)