1
1
import type {
2
2
CorsOptions ,
3
- HandlerResponse ,
4
3
Middleware ,
5
- RequestContext ,
6
4
} from '../../types/rest.js' ;
7
5
import {
8
6
DEFAULT_CORS_OPTIONS ,
9
7
HttpErrorCodes ,
10
8
HttpVerbs ,
11
9
} from '../constants.js' ;
12
10
13
- /**
14
- * Resolved CORS configuration with all defaults applied
15
- */
16
- type ResolvedCorsConfig = {
17
- origin : CorsOptions [ 'origin' ] ;
18
- allowMethods : string [ ] ;
19
- allowHeaders : string [ ] ;
20
- exposeHeaders : string [ ] ;
21
- credentials : boolean ;
22
- maxAge ?: number ;
23
- } ;
24
-
25
- /**
26
- * Resolves and validates the CORS configuration
27
- */
28
- const resolveConfiguration = ( userOptions : CorsOptions ) : ResolvedCorsConfig => {
29
- return {
30
- origin : userOptions . origin ?? DEFAULT_CORS_OPTIONS . origin ,
31
- allowMethods : userOptions . allowMethods ?? [
32
- ...DEFAULT_CORS_OPTIONS . allowMethods ,
33
- ] ,
34
- allowHeaders : userOptions . allowHeaders ?? [
35
- ...DEFAULT_CORS_OPTIONS . allowHeaders ,
36
- ] ,
37
- exposeHeaders : userOptions . exposeHeaders ?? [
38
- ...DEFAULT_CORS_OPTIONS . exposeHeaders ,
39
- ] ,
40
- credentials : userOptions . credentials ?? DEFAULT_CORS_OPTIONS . credentials ,
41
- maxAge : userOptions . maxAge ,
42
- } ;
43
- } ;
44
-
45
11
/**
46
12
* Resolves the origin value based on the configuration
47
13
*/
48
14
const resolveOrigin = (
49
- originConfig : CorsOptions [ 'origin' ] ,
50
- requestOrigin : string | null | undefined ,
51
- reqCtx : RequestContext
15
+ originConfig : NonNullable < CorsOptions [ 'origin' ] > ,
16
+ requestOrigin : string | null ,
52
17
) : string => {
53
- const origin = requestOrigin || undefined ;
54
-
55
- if ( typeof originConfig === 'function' ) {
56
- const result = originConfig ( origin , reqCtx ) ;
57
- if ( typeof result === 'boolean' ) {
58
- return result ? origin || '*' : '' ;
59
- }
60
- return result ;
61
- }
62
-
63
18
if ( Array . isArray ( originConfig ) ) {
64
- return origin && originConfig . includes ( origin ) ? origin : '' ;
65
- }
66
-
67
- if ( typeof originConfig === 'string' ) {
68
- return originConfig ;
69
- }
70
-
71
- return DEFAULT_CORS_OPTIONS . origin ;
72
- } ;
73
-
74
- /**
75
- * Handles preflight OPTIONS requests
76
- */
77
- const handlePreflight = (
78
- config : ResolvedCorsConfig ,
79
- reqCtx : RequestContext
80
- ) : Response => {
81
- const { request, res } = reqCtx ;
82
- const requestOrigin = request . headers . get ( 'Origin' ) ;
83
- const resolvedOrigin = resolveOrigin ( config . origin , requestOrigin , reqCtx ) ;
84
-
85
- // Mutate existing response headers
86
- if ( resolvedOrigin ) {
87
- res . headers . set ( 'Access-Control-Allow-Origin' , resolvedOrigin ) ;
88
- }
89
-
90
- if ( config . allowMethods . length > 0 ) {
91
- res . headers . set (
92
- 'Access-Control-Allow-Methods' ,
93
- config . allowMethods . join ( ', ' )
94
- ) ;
95
- }
96
-
97
- if ( config . allowHeaders . length > 0 ) {
98
- res . headers . set (
99
- 'Access-Control-Allow-Headers' ,
100
- config . allowHeaders . join ( ', ' )
101
- ) ;
102
- }
103
-
104
- if ( config . credentials ) {
105
- res . headers . set ( 'Access-Control-Allow-Credentials' , 'true' ) ;
106
- }
107
-
108
- if ( config . maxAge !== undefined ) {
109
- res . headers . set ( 'Access-Control-Max-Age' , config . maxAge . toString ( ) ) ;
110
- }
111
-
112
- return new Response ( null , {
113
- status : HttpErrorCodes . NO_CONTENT ,
114
- headers : res . headers ,
115
- } ) ;
116
- } ;
117
-
118
- /**
119
- * Adds CORS headers to regular requests
120
- */
121
- const addCorsHeaders = (
122
- config : ResolvedCorsConfig ,
123
- reqCtx : RequestContext
124
- ) : void => {
125
- const { request, res } = reqCtx ;
126
- const requestOrigin = request . headers . get ( 'Origin' ) ;
127
- const resolvedOrigin = resolveOrigin ( config . origin , requestOrigin , reqCtx ) ;
128
-
129
- if ( resolvedOrigin ) {
130
- res . headers . set ( 'Access-Control-Allow-Origin' , resolvedOrigin ) ;
131
- }
132
-
133
- if ( config . exposeHeaders . length > 0 ) {
134
- res . headers . set (
135
- 'Access-Control-Expose-Headers' ,
136
- config . exposeHeaders . join ( ', ' )
137
- ) ;
138
- }
139
-
140
- if ( config . credentials ) {
141
- res . headers . set ( 'Access-Control-Allow-Credentials' , 'true' ) ;
19
+ return requestOrigin && originConfig . includes ( requestOrigin ) ? requestOrigin : '' ;
142
20
}
21
+ return originConfig ;
143
22
} ;
144
23
145
24
/**
@@ -148,8 +27,11 @@ const addCorsHeaders = (
148
27
*
149
28
* @example
150
29
* ```typescript
151
- * import { cors } from '@aws-lambda-powertools/event-handler/rest';
152
- *
30
+ * import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
31
+ * import { cors } from '@aws-lambda-powertools/event-handler/experimental-rest/middleware';
32
+ *
33
+ * const app = new Router();
34
+ *
153
35
* // Use default configuration
154
36
* app.use(cors());
155
37
*
@@ -168,28 +50,51 @@ const addCorsHeaders = (
168
50
* }
169
51
* }));
170
52
* ```
171
- * @param options - CORS configuration options
53
+ *
54
+ * @param options.origin - The origin to allow requests from
55
+ * @param options.allowMethods - The HTTP methods to allow
56
+ * @param options.allowHeaders - The headers to allow
57
+ * @param options.exposeHeaders - The headers to expose
58
+ * @param options.credentials - Whether to allow credentials
59
+ * @param options.maxAge - The maximum age for the preflight response
172
60
*/
173
- export const cors = ( options : CorsOptions = { } ) : Middleware => {
174
- const config = resolveConfiguration ( options ) ;
175
-
176
- return async (
177
- _params : Record < string , string > ,
178
- reqCtx : RequestContext ,
179
- next : ( ) => Promise < HandlerResponse | void >
180
- ) => {
181
- const { request } = reqCtx ;
182
- const method = request . method . toUpperCase ( ) ;
183
-
61
+ export const cors = ( options ?: CorsOptions ) : Middleware => {
62
+ const config = {
63
+ ...DEFAULT_CORS_OPTIONS ,
64
+ ...options
65
+ } ;
66
+
67
+ return async ( _params , reqCtx , next ) => {
68
+ const requestOrigin = reqCtx . request . headers . get ( 'Origin' ) ;
69
+ const resolvedOrigin = resolveOrigin ( config . origin , requestOrigin ) ;
70
+
71
+ reqCtx . res . headers . set ( 'access-control-allow-origin' , resolvedOrigin ) ;
72
+ if ( resolvedOrigin !== '*' ) {
73
+ reqCtx . res . headers . set ( 'Vary' , 'Origin' ) ;
74
+ }
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 ( ) ) ;
87
+ }
88
+
184
89
// Handle preflight OPTIONS request
185
- if ( method === HttpVerbs . OPTIONS ) {
186
- return handlePreflight ( config , reqCtx ) ;
90
+ if ( reqCtx . request . method === HttpVerbs . OPTIONS && reqCtx . request . headers . has ( 'Access-Control-Request-Method' ) ) {
91
+ return new Response ( null , {
92
+ status : HttpErrorCodes . NO_CONTENT ,
93
+ headers : reqCtx . res . headers ,
94
+ } ) ;
187
95
}
188
-
189
- // Continue to next middleware/handler first
96
+
97
+ // Continue to next middleware/handler
190
98
await next ( ) ;
191
-
192
- // Add CORS headers to the response after handler
193
- addCorsHeaders ( config , reqCtx ) ;
194
99
} ;
195
100
} ;
0 commit comments