Skip to content

Commit 56ccd64

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/aws-sdk-v3-24e39e0115
2 parents a40efad + a05c074 commit 56ccd64

File tree

18 files changed

+425
-348
lines changed

18 files changed

+425
-348
lines changed

examples/snippets/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"@valkey/valkey-glide": "^2.1.0",
4444
"aws-sdk": "^2.1692.0",
4545
"aws-sdk-client-mock": "^4.1.0",
46-
"zod": "^4.1.9"
46+
"zod": "^4.1.11"
4747
},
4848
"dependencies": {
4949
"arktype": "^2.1.22",

package-lock.json

Lines changed: 85 additions & 164 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"@types/node": "^24.5.2",
5656
"@vitest/coverage-v8": "^3.2.4",
5757
"husky": "^9.1.7",
58-
"lint-staged": "^16.1.6",
58+
"lint-staged": "^16.2.0",
5959
"markdownlint-cli2": "^0.18.1",
6060
"middy5": "npm:@middy/core@^5.4.3",
6161
"middy6": "npm:@middy/core@^6.0.0",

packages/batch/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,6 @@
8787
"devDependencies": {
8888
"@aws-lambda-powertools/parser": "2.26.1",
8989
"@aws-lambda-powertools/testing-utils": "file:../testing",
90-
"zod": "^4.1.9"
90+
"zod": "^4.1.11"
9191
}
9292
}

packages/event-handler/src/rest/Router.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,9 @@ class Router {
164164
*
165165
* @example
166166
* ```typescript
167-
* const authMiddleware: Middleware = async (params, reqCtx, next) => {
167+
* const authMiddleware: Middleware = async ({ params, reqCtx, next }) => {
168168
* // Authentication logic
169-
* if (!isAuthenticated(reqCtx.request)) {
169+
* if (!isAuthenticated(reqCtx.req)) {
170170
* return new Response('Unauthorized', { status: 401 });
171171
* }
172172
* await next();
@@ -215,23 +215,27 @@ class Router {
215215
};
216216
}
217217

218-
const request = proxyEventToWebRequest(event);
218+
const req = proxyEventToWebRequest(event);
219219

220220
const requestContext: RequestContext = {
221221
event,
222222
context,
223-
request,
223+
req,
224224
// this response should be overwritten by the handler, if it isn't
225225
// it means something went wrong with the middleware chain
226226
res: new Response('', { status: 500 }),
227227
};
228228

229229
try {
230-
const path = new URL(request.url).pathname as Path;
230+
const path = new URL(req.url).pathname as Path;
231231

232232
const route = this.routeRegistry.resolve(method, path);
233233

234-
const handlerMiddleware: Middleware = async (params, reqCtx, next) => {
234+
const handlerMiddleware: Middleware = async ({
235+
params,
236+
reqCtx,
237+
next,
238+
}) => {
235239
if (route === null) {
236240
const notFoundRes = await this.handleError(
237241
new NotFoundError(`Route ${path} for method ${method} not found`),
@@ -263,11 +267,11 @@ class Router {
263267
handlerMiddleware,
264268
]);
265269

266-
const middlewareResult = await middleware(
267-
route?.params ?? {},
268-
requestContext,
269-
() => Promise.resolve()
270-
);
270+
const middlewareResult = await middleware({
271+
params: route?.params ?? {},
272+
reqCtx: requestContext,
273+
next: () => Promise.resolve(),
274+
});
271275

272276
// middleware result takes precedence to allow short-circuiting
273277
const result = middlewareResult ?? requestContext.res;

packages/event-handler/src/rest/middleware/compress.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,10 @@ const compress = (options?: CompressionOptions): Middleware => {
6767
const threshold =
6868
options?.threshold ?? DEFAULT_COMPRESSION_RESPONSE_THRESHOLD;
6969

70-
return async (_, reqCtx, next) => {
70+
return async ({ reqCtx, next }) => {
7171
await next();
7272

73-
if (
74-
!shouldCompress(reqCtx.request, reqCtx.res, preferredEncoding, threshold)
75-
) {
73+
if (!shouldCompress(reqCtx.req, reqCtx.res, preferredEncoding, threshold)) {
7674
return;
7775
}
7876

Lines changed: 79 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,10 @@
1-
import type {
2-
CorsOptions,
3-
Middleware,
4-
} from '../../types/rest.js';
1+
import type { CorsOptions, Middleware } from '../../types/rest.js';
52
import {
63
DEFAULT_CORS_OPTIONS,
74
HttpErrorCodes,
85
HttpVerbs,
96
} from '../constants.js';
107

11-
/**
12-
* Resolves the origin value based on the configuration
13-
*/
14-
const resolveOrigin = (
15-
originConfig: NonNullable<CorsOptions['origin']>,
16-
requestOrigin: string | null,
17-
): string => {
18-
if (Array.isArray(originConfig)) {
19-
return requestOrigin && originConfig.includes(requestOrigin) ? requestOrigin : '';
20-
}
21-
return originConfig;
22-
};
23-
248
/**
259
* Creates a CORS middleware that adds appropriate CORS headers to responses
2610
* and handles OPTIONS preflight requests.
@@ -29,9 +13,9 @@ const resolveOrigin = (
2913
* ```typescript
3014
* import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
3115
* import { cors } from '@aws-lambda-powertools/event-handler/experimental-rest/middleware';
32-
*
16+
*
3317
* const app = new Router();
34-
*
18+
*
3519
* // Use default configuration
3620
* app.use(cors());
3721
*
@@ -50,7 +34,7 @@ const resolveOrigin = (
5034
* }
5135
* }));
5236
* ```
53-
*
37+
*
5438
* @param options.origin - The origin to allow requests from
5539
* @param options.allowMethods - The HTTP methods to allow
5640
* @param options.allowHeaders - The headers to allow
@@ -61,38 +45,93 @@ const resolveOrigin = (
6145
export const cors = (options?: CorsOptions): Middleware => {
6246
const config = {
6347
...DEFAULT_CORS_OPTIONS,
64-
...options
48+
...options,
6549
};
50+
const allowedOrigins =
51+
typeof config.origin === 'string' ? [config.origin] : config.origin;
52+
const allowsWildcard = allowedOrigins.includes('*');
53+
const allowedMethods = config.allowMethods.map((method) =>
54+
method.toUpperCase()
55+
);
56+
const allowedHeaders = config.allowHeaders.map((header) =>
57+
header.toLowerCase()
58+
);
6659

67-
return async (_params, reqCtx, next) => {
68-
const requestOrigin = reqCtx.request.headers.get('Origin');
69-
const resolvedOrigin = resolveOrigin(config.origin, requestOrigin);
60+
const isOriginAllowed = (
61+
requestOrigin: string | null
62+
): requestOrigin is string => {
63+
return (
64+
requestOrigin !== null &&
65+
(allowsWildcard || allowedOrigins.includes(requestOrigin))
66+
);
67+
};
7068

71-
reqCtx.res.headers.set('access-control-allow-origin', resolvedOrigin);
72-
if (resolvedOrigin !== '*') {
73-
reqCtx.res.headers.set('Vary', 'Origin');
69+
const isValidPreflightRequest = (requestHeaders: Headers) => {
70+
const accessControlRequestMethod = requestHeaders
71+
.get('Access-Control-Request-Method')
72+
?.toUpperCase();
73+
const accessControlRequestHeaders = requestHeaders
74+
.get('Access-Control-Request-Headers')
75+
?.toLowerCase();
76+
return (
77+
accessControlRequestMethod &&
78+
allowedMethods.includes(accessControlRequestMethod) &&
79+
accessControlRequestHeaders
80+
?.split(',')
81+
.every((header) => allowedHeaders.includes(header.trim()))
82+
);
83+
};
84+
85+
const setCORSBaseHeaders = (
86+
requestOrigin: string,
87+
responseHeaders: Headers
88+
) => {
89+
const resolvedOrigin = allowsWildcard ? '*' : requestOrigin;
90+
responseHeaders.set('access-control-allow-origin', resolvedOrigin);
91+
if (!allowsWildcard && Array.isArray(config.origin)) {
92+
responseHeaders.set('vary', 'Origin');
7493
}
75-
config.allowMethods.forEach(method => {
76-
reqCtx.res.headers.append('access-control-allow-methods', method);
77-
});
78-
config.allowHeaders.forEach(header => {
79-
reqCtx.res.headers.append('access-control-allow-headers', header);
80-
});
81-
config.exposeHeaders.forEach(header => {
82-
reqCtx.res.headers.append('access-control-expose-headers', header);
83-
});
84-
reqCtx.res.headers.set('access-control-allow-credentials', config.credentials.toString());
85-
if (config.maxAge !== undefined) {
86-
reqCtx.res.headers.set('access-control-max-age', config.maxAge.toString());
94+
if (config.credentials) {
95+
responseHeaders.set('access-control-allow-credentials', 'true');
96+
}
97+
};
98+
99+
return async ({ reqCtx, next }) => {
100+
const requestOrigin = reqCtx.req.headers.get('Origin');
101+
if (!isOriginAllowed(requestOrigin)) {
102+
await next();
103+
return;
87104
}
88105

89106
// Handle preflight OPTIONS request
90-
if (reqCtx.request.method === HttpVerbs.OPTIONS && reqCtx.request.headers.has('Access-Control-Request-Method')) {
107+
if (reqCtx.req.method === HttpVerbs.OPTIONS) {
108+
if (!isValidPreflightRequest(reqCtx.req.headers)) {
109+
await next();
110+
return;
111+
}
112+
setCORSBaseHeaders(requestOrigin, reqCtx.res.headers);
113+
if (config.maxAge !== undefined) {
114+
reqCtx.res.headers.set(
115+
'access-control-max-age',
116+
config.maxAge.toString()
117+
);
118+
}
119+
for (const method of allowedMethods) {
120+
reqCtx.res.headers.append('access-control-allow-methods', method);
121+
}
122+
for (const header of allowedHeaders) {
123+
reqCtx.res.headers.append('access-control-allow-headers', header);
124+
}
91125
return new Response(null, {
92126
status: HttpErrorCodes.NO_CONTENT,
93127
headers: reqCtx.res.headers,
94128
});
95129
}
130+
131+
setCORSBaseHeaders(requestOrigin, reqCtx.res.headers);
132+
for (const header of config.exposeHeaders) {
133+
reqCtx.res.headers.append('access-control-expose-headers', header);
134+
}
96135
await next();
97136
};
98137
};

packages/event-handler/src/rest/utils.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import type {
66
HttpMethod,
77
Middleware,
88
Path,
9-
RequestContext,
109
ValidationResult,
1110
} from '../types/rest.js';
1211
import {
@@ -129,13 +128,13 @@ export const isAPIGatewayProxyResult = (
129128
*
130129
* @example
131130
* ```typescript
132-
* const middleware1: Middleware = async (params, options, next) => {
131+
* const middleware1: Middleware = async ({params, options, next}) => {
133132
* console.log('middleware1 start');
134133
* await next();
135134
* console.log('middleware1 end');
136135
* };
137136
*
138-
* const middleware2: Middleware = async (params, options, next) => {
137+
* const middleware2: Middleware = async ({params, options, next}) => {
139138
* console.log('middleware2 start');
140139
* await next();
141140
* console.log('middleware2 end');
@@ -151,11 +150,7 @@ export const isAPIGatewayProxyResult = (
151150
* ```
152151
*/
153152
export const composeMiddleware = (middleware: Middleware[]): Middleware => {
154-
return async (
155-
params: Record<string, string>,
156-
reqCtx: RequestContext,
157-
next: () => Promise<HandlerResponse | void>
158-
): Promise<HandlerResponse | void> => {
153+
return async ({ params, reqCtx, next }): Promise<HandlerResponse | void> => {
159154
let index = -1;
160155
let result: HandlerResponse | undefined;
161156

@@ -181,7 +176,11 @@ export const composeMiddleware = (middleware: Middleware[]): Middleware => {
181176
return result;
182177
};
183178

184-
const middlewareResult = await middlewareFn(params, reqCtx, nextFn);
179+
const middlewareResult = await middlewareFn({
180+
params,
181+
reqCtx,
182+
next: nextFn,
183+
});
185184

186185
if (nextPromise && !nextAwaited && i < middleware.length - 1) {
187186
throw new Error(

packages/event-handler/src/types/rest.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type ErrorResponse = {
1515
};
1616

1717
type RequestContext = {
18-
request: Request;
18+
req: Request;
1919
event: APIGatewayProxyEvent;
2020
context: Context;
2121
res: Response;
@@ -86,11 +86,11 @@ type RestRouteOptions = {
8686

8787
type NextFunction = () => Promise<HandlerResponse | void>;
8888

89-
type Middleware = (
90-
params: Record<string, string>,
91-
reqCtx: RequestContext,
92-
next: NextFunction
93-
) => Promise<void | HandlerResponse>;
89+
type Middleware = (args: {
90+
params: Record<string, string>;
91+
reqCtx: RequestContext;
92+
next: NextFunction;
93+
}) => Promise<void | HandlerResponse>;
9494

9595
type RouteRegistryOptions = {
9696
/**

packages/event-handler/tests/unit/rest/Router/basic-routing.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ describe('Class: Router - Basic Routing', () => {
9191

9292
app.get('/test', async (_params, reqCtx) => {
9393
return {
94-
hasRequest: reqCtx.request instanceof Request,
94+
hasRequest: reqCtx.req instanceof Request,
9595
hasEvent: reqCtx.event === testEvent,
9696
hasContext: reqCtx.context === context,
9797
};

0 commit comments

Comments
 (0)