@boo-labs/server 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +125 -67
- package/package.json +1 -1
- package/source/core/IPCMain/IPCProtocol.mjs +80 -75
- package/source/shared/domain/UUIDV7/index.mjs +31 -39
- package/source/shared/errors/ServerError/index.mjs +48 -2
- package/source/shared/outcome/Result/index.mjs +77 -130
- package/source/shared/outcome/Validation/index.mjs +56 -114
- package/source/shared/validations/Middleware/index.mjs +32 -23
- package/source/shared/validations/PathnamePattern/index.mjs +38 -39
- package/source/shared/validations/Server/NotFoundHandler/index.mjs +25 -15
- package/source/shared/validations/Server/Router/index.mjs +24 -14
- package/source/shared/validations/Server/ServerConfiguration/index.mjs +24 -14
- package/source/shared/validations/Server/SystemErrorHandler/index.mjs +25 -15
- package/source/shared/validations/ServerConfiguration/CORSOptions/index.mjs +95 -91
- package/source/shared/validations/ServerConfiguration/CSRFOptions/index.mjs +96 -96
- package/source/shared/validations/ServerConfiguration/CompressOptions/index.mjs +55 -43
- package/source/shared/validations/ServerConfiguration/CookieParserOptions/index.mjs +28 -13
- package/source/shared/validations/ServerConfiguration/CookieSessionOptions/index.mjs +101 -111
- package/source/shared/validations/ServerConfiguration/ListenOptions/index.mjs +55 -43
- package/source/shared/validations/ServerConfiguration/RateLimitOptions/index.mjs +71 -61
- package/source/shared/validations/ServerConfiguration/RequestOptions/index.mjs +44 -31
- package/source/shared/validations/ServerConfiguration/ResponseOptions/index.mjs +39 -25
- package/source/shared/validations/ServerConfiguration/SecurityHeadersOptions/index.mjs +27 -14
- package/source/shared/validations/ServerConfiguration/StaticOptions/index.mjs +85 -84
- package/source/shared/outcome/ErrorSchema.mjs +0 -361
|
@@ -14,6 +14,20 @@
|
|
|
14
14
|
import Validation from '#shared/outcome/Validation';
|
|
15
15
|
import ServerConfigurationError from '#shared/errors/ServerConfigurationError';
|
|
16
16
|
|
|
17
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
18
|
+
/**
|
|
19
|
+
* A successful {@link Validation} instance.
|
|
20
|
+
*
|
|
21
|
+
* @typedef {import('#shared/outcome/Validation').SuccessValidation} SuccessValidation
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A failed {@link Validation} instance.
|
|
26
|
+
*
|
|
27
|
+
* @template {Error} E
|
|
28
|
+
* @typedef {import('#shared/outcome/Validation').FailureValidation<E>} FailureValidation
|
|
29
|
+
*/
|
|
30
|
+
|
|
17
31
|
// ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
18
32
|
/**
|
|
19
33
|
* Minimum valid HTTP status code for a successful preflight response.
|
|
@@ -65,6 +79,7 @@ function everyNonEmptyString(values) {
|
|
|
65
79
|
return values.every(isNonEmptyString);
|
|
66
80
|
}
|
|
67
81
|
|
|
82
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
68
83
|
/**
|
|
69
84
|
* Validates CORS options.
|
|
70
85
|
*
|
|
@@ -82,7 +97,8 @@ function everyNonEmptyString(values) {
|
|
|
82
97
|
* 10. `optionsSuccessStatus`, if present, must be an integer between 100 and 599.
|
|
83
98
|
*
|
|
84
99
|
* @param {unknown} options - CORS options to validate.
|
|
85
|
-
* @returns {
|
|
100
|
+
* @returns {SuccessValidation|FailureValidation<ServerConfigurationError>} Validation result.
|
|
101
|
+
*
|
|
86
102
|
* @example
|
|
87
103
|
* ```js
|
|
88
104
|
* const validation = validateCORSOptions({ origin: true });
|
|
@@ -105,12 +121,11 @@ export function validateCORSOptions(options) {
|
|
|
105
121
|
}
|
|
106
122
|
|
|
107
123
|
if (!isPlainObject(options)) {
|
|
108
|
-
return Validation.failure(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
});
|
|
124
|
+
return Validation.failure(
|
|
125
|
+
new ServerConfigurationError('INVALID_CORS_OPTIONS', {
|
|
126
|
+
values: { received: typeof options },
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
114
129
|
}
|
|
115
130
|
|
|
116
131
|
if ('origin' in options) {
|
|
@@ -123,20 +138,18 @@ export function validateCORSOptions(options) {
|
|
|
123
138
|
|
|
124
139
|
if (!isValidOriginType) {
|
|
125
140
|
if (Array.isArray(origin)) {
|
|
126
|
-
return Validation.failure(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
});
|
|
141
|
+
return Validation.failure(
|
|
142
|
+
new ServerConfigurationError('INVALID_CORS_ORIGIN_ITEM', {
|
|
143
|
+
details: { origin },
|
|
144
|
+
}),
|
|
145
|
+
);
|
|
132
146
|
}
|
|
133
147
|
|
|
134
|
-
return Validation.failure(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
});
|
|
148
|
+
return Validation.failure(
|
|
149
|
+
new ServerConfigurationError('INVALID_CORS_ORIGIN_TYPE', {
|
|
150
|
+
values: { received: typeof origin },
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
140
153
|
}
|
|
141
154
|
}
|
|
142
155
|
|
|
@@ -144,21 +157,19 @@ export function validateCORSOptions(options) {
|
|
|
144
157
|
const { methods } = options;
|
|
145
158
|
|
|
146
159
|
if (!Array.isArray(methods)) {
|
|
147
|
-
return Validation.failure(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
});
|
|
160
|
+
return Validation.failure(
|
|
161
|
+
new ServerConfigurationError('INVALID_CORS_METHODS_TYPE', {
|
|
162
|
+
values: { received: typeof methods },
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
153
165
|
}
|
|
154
166
|
|
|
155
167
|
if (!everyNonEmptyString(methods)) {
|
|
156
|
-
return Validation.failure(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
});
|
|
168
|
+
return Validation.failure(
|
|
169
|
+
new ServerConfigurationError('INVALID_CORS_METHODS_ITEM', {
|
|
170
|
+
details: { methods },
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
162
173
|
}
|
|
163
174
|
}
|
|
164
175
|
|
|
@@ -166,21 +177,19 @@ export function validateCORSOptions(options) {
|
|
|
166
177
|
const { allowedHeaders } = options;
|
|
167
178
|
|
|
168
179
|
if (!Array.isArray(allowedHeaders)) {
|
|
169
|
-
return Validation.failure(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
});
|
|
180
|
+
return Validation.failure(
|
|
181
|
+
new ServerConfigurationError('INVALID_CORS_ALLOWED_HEADERS_TYPE', {
|
|
182
|
+
values: { received: typeof allowedHeaders },
|
|
183
|
+
}),
|
|
184
|
+
);
|
|
175
185
|
}
|
|
176
186
|
|
|
177
187
|
if (!everyNonEmptyString(allowedHeaders)) {
|
|
178
|
-
return Validation.failure(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
});
|
|
188
|
+
return Validation.failure(
|
|
189
|
+
new ServerConfigurationError('INVALID_CORS_ALLOWED_HEADERS_ITEM', {
|
|
190
|
+
details: { allowedHeaders },
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
184
193
|
}
|
|
185
194
|
}
|
|
186
195
|
|
|
@@ -188,21 +197,19 @@ export function validateCORSOptions(options) {
|
|
|
188
197
|
const { exposedHeaders } = options;
|
|
189
198
|
|
|
190
199
|
if (!Array.isArray(exposedHeaders)) {
|
|
191
|
-
return Validation.failure(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
});
|
|
200
|
+
return Validation.failure(
|
|
201
|
+
new ServerConfigurationError('INVALID_CORS_EXPOSED_HEADERS_TYPE', {
|
|
202
|
+
values: { received: typeof exposedHeaders },
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
197
205
|
}
|
|
198
206
|
|
|
199
207
|
if (!everyNonEmptyString(exposedHeaders)) {
|
|
200
|
-
return Validation.failure(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
});
|
|
208
|
+
return Validation.failure(
|
|
209
|
+
new ServerConfigurationError('INVALID_CORS_EXPOSED_HEADERS_ITEM', {
|
|
210
|
+
details: { exposedHeaders },
|
|
211
|
+
}),
|
|
212
|
+
);
|
|
206
213
|
}
|
|
207
214
|
}
|
|
208
215
|
|
|
@@ -210,12 +217,11 @@ export function validateCORSOptions(options) {
|
|
|
210
217
|
const { credentials } = options;
|
|
211
218
|
|
|
212
219
|
if (typeof credentials !== 'boolean') {
|
|
213
|
-
return Validation.failure(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
});
|
|
220
|
+
return Validation.failure(
|
|
221
|
+
new ServerConfigurationError('INVALID_CORS_CREDENTIALS_TYPE', {
|
|
222
|
+
values: { received: typeof credentials },
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
219
225
|
}
|
|
220
226
|
}
|
|
221
227
|
|
|
@@ -223,21 +229,20 @@ export function validateCORSOptions(options) {
|
|
|
223
229
|
const { maxAge } = options;
|
|
224
230
|
|
|
225
231
|
if (typeof maxAge !== 'number') {
|
|
226
|
-
return Validation.failure(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
});
|
|
232
|
+
return Validation.failure(
|
|
233
|
+
new ServerConfigurationError('INVALID_CORS_MAX_AGE_TYPE', {
|
|
234
|
+
values: { received: typeof maxAge },
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
232
237
|
}
|
|
233
238
|
|
|
234
239
|
if (!Number.isInteger(maxAge) || maxAge < 0) {
|
|
235
|
-
return Validation.failure(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
240
|
+
return Validation.failure(
|
|
241
|
+
new ServerConfigurationError('INVALID_CORS_MAX_AGE_RANGE', {
|
|
242
|
+
values: { maxAge },
|
|
243
|
+
details: { maxAge },
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
241
246
|
}
|
|
242
247
|
}
|
|
243
248
|
|
|
@@ -245,12 +250,11 @@ export function validateCORSOptions(options) {
|
|
|
245
250
|
const { preflightContinue } = options;
|
|
246
251
|
|
|
247
252
|
if (typeof preflightContinue !== 'boolean') {
|
|
248
|
-
return Validation.failure(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
});
|
|
253
|
+
return Validation.failure(
|
|
254
|
+
new ServerConfigurationError('INVALID_CORS_PREFLIGHT_CONTINUE_TYPE', {
|
|
255
|
+
values: { received: typeof preflightContinue },
|
|
256
|
+
}),
|
|
257
|
+
);
|
|
254
258
|
}
|
|
255
259
|
}
|
|
256
260
|
|
|
@@ -258,12 +262,11 @@ export function validateCORSOptions(options) {
|
|
|
258
262
|
const { optionsSuccessStatus } = options;
|
|
259
263
|
|
|
260
264
|
if (typeof optionsSuccessStatus !== 'number') {
|
|
261
|
-
return Validation.failure(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
});
|
|
265
|
+
return Validation.failure(
|
|
266
|
+
new ServerConfigurationError('INVALID_CORS_OPTIONS_SUCCESS_STATUS_TYPE', {
|
|
267
|
+
values: { received: typeof optionsSuccessStatus },
|
|
268
|
+
}),
|
|
269
|
+
);
|
|
267
270
|
}
|
|
268
271
|
|
|
269
272
|
if (
|
|
@@ -271,12 +274,12 @@ export function validateCORSOptions(options) {
|
|
|
271
274
|
optionsSuccessStatus < MIN_OPTIONS_SUCCESS_STATUS ||
|
|
272
275
|
optionsSuccessStatus > MAX_OPTIONS_SUCCESS_STATUS
|
|
273
276
|
) {
|
|
274
|
-
return Validation.failure(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
return Validation.failure(
|
|
278
|
+
new ServerConfigurationError('INVALID_CORS_OPTIONS_SUCCESS_STATUS_RANGE', {
|
|
279
|
+
values: { optionsSuccessStatus },
|
|
280
|
+
details: { optionsSuccessStatus },
|
|
281
|
+
}),
|
|
282
|
+
);
|
|
280
283
|
}
|
|
281
284
|
}
|
|
282
285
|
|
|
@@ -296,6 +299,7 @@ export function validateCORSOptions(options) {
|
|
|
296
299
|
* @throws {ServerConfigurationError} If `maxAge` is not a non-negative integer.
|
|
297
300
|
* @throws {ServerConfigurationError} If `preflightContinue` is not a boolean.
|
|
298
301
|
* @throws {ServerConfigurationError} If `optionsSuccessStatus` is not an integer between 100 and 599.
|
|
302
|
+
*
|
|
299
303
|
* @example
|
|
300
304
|
* ```js
|
|
301
305
|
* assertCORSOptions({ origin: true, credentials: true });
|
|
@@ -13,6 +13,20 @@
|
|
|
13
13
|
import Validation from '#shared/outcome/Validation';
|
|
14
14
|
import ServerConfigurationError from '#shared/errors/ServerConfigurationError';
|
|
15
15
|
|
|
16
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
/**
|
|
18
|
+
* A successful {@link Validation} instance.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {import('#shared/outcome/Validation').SuccessValidation} SuccessValidation
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A failed {@link Validation} instance.
|
|
25
|
+
*
|
|
26
|
+
* @template {Error} E
|
|
27
|
+
* @typedef {import('#shared/outcome/Validation').FailureValidation<E>} FailureValidation
|
|
28
|
+
*/
|
|
29
|
+
|
|
16
30
|
// ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
31
|
/**
|
|
18
32
|
* Valid values for the SameSite cookie attribute.
|
|
@@ -56,6 +70,7 @@ function everyNonEmptyString(values) {
|
|
|
56
70
|
return values.every(isNonEmptyString);
|
|
57
71
|
}
|
|
58
72
|
|
|
73
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
59
74
|
/**
|
|
60
75
|
* Validates CSRF options.
|
|
61
76
|
*
|
|
@@ -74,7 +89,8 @@ function everyNonEmptyString(values) {
|
|
|
74
89
|
* 11. `sameSite: 'None'` requires an effective `secure: true`.
|
|
75
90
|
*
|
|
76
91
|
* @param {unknown} options - CSRF options to validate.
|
|
77
|
-
* @returns {
|
|
92
|
+
* @returns {SuccessValidation|FailureValidation<ServerConfigurationError>} Validation result.
|
|
93
|
+
*
|
|
78
94
|
* @example
|
|
79
95
|
* ```js
|
|
80
96
|
* const validation = validateCSRFOptions({ cookieName: 'csrf' });
|
|
@@ -97,33 +113,30 @@ export function validateCSRFOptions(options) {
|
|
|
97
113
|
}
|
|
98
114
|
|
|
99
115
|
if (!isPlainObject(options)) {
|
|
100
|
-
return Validation.failure(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
});
|
|
116
|
+
return Validation.failure(
|
|
117
|
+
new ServerConfigurationError('INVALID_CSRF_OPTIONS', {
|
|
118
|
+
values: { received: typeof options },
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
106
121
|
}
|
|
107
122
|
|
|
108
123
|
if ('cookieName' in options) {
|
|
109
124
|
const { cookieName } = options;
|
|
110
125
|
|
|
111
126
|
if (typeof cookieName !== 'string') {
|
|
112
|
-
return Validation.failure(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
});
|
|
127
|
+
return Validation.failure(
|
|
128
|
+
new ServerConfigurationError('INVALID_CSRF_COOKIE_NAME_TYPE', {
|
|
129
|
+
values: { received: typeof cookieName },
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
118
132
|
}
|
|
119
133
|
|
|
120
134
|
if (cookieName.length === 0) {
|
|
121
|
-
return Validation.failure(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
});
|
|
135
|
+
return Validation.failure(
|
|
136
|
+
new ServerConfigurationError('INVALID_CSRF_COOKIE_NAME_VALUE', {
|
|
137
|
+
details: { cookieName },
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
127
140
|
}
|
|
128
141
|
}
|
|
129
142
|
|
|
@@ -131,21 +144,19 @@ export function validateCSRFOptions(options) {
|
|
|
131
144
|
const { headerName } = options;
|
|
132
145
|
|
|
133
146
|
if (typeof headerName !== 'string') {
|
|
134
|
-
return Validation.failure(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
});
|
|
147
|
+
return Validation.failure(
|
|
148
|
+
new ServerConfigurationError('INVALID_CSRF_HEADER_NAME_TYPE', {
|
|
149
|
+
values: { received: typeof headerName },
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
140
152
|
}
|
|
141
153
|
|
|
142
154
|
if (headerName.length === 0) {
|
|
143
|
-
return Validation.failure(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
});
|
|
155
|
+
return Validation.failure(
|
|
156
|
+
new ServerConfigurationError('INVALID_CSRF_HEADER_NAME_VALUE', {
|
|
157
|
+
details: { headerName },
|
|
158
|
+
}),
|
|
159
|
+
);
|
|
149
160
|
}
|
|
150
161
|
}
|
|
151
162
|
|
|
@@ -153,21 +164,19 @@ export function validateCSRFOptions(options) {
|
|
|
153
164
|
const { safeMethods } = options;
|
|
154
165
|
|
|
155
166
|
if (!Array.isArray(safeMethods)) {
|
|
156
|
-
return Validation.failure(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
});
|
|
167
|
+
return Validation.failure(
|
|
168
|
+
new ServerConfigurationError('INVALID_CSRF_SAFE_METHODS_TYPE', {
|
|
169
|
+
values: { received: typeof safeMethods },
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
162
172
|
}
|
|
163
173
|
|
|
164
174
|
if (!everyNonEmptyString(safeMethods)) {
|
|
165
|
-
return Validation.failure(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
});
|
|
175
|
+
return Validation.failure(
|
|
176
|
+
new ServerConfigurationError('INVALID_CSRF_SAFE_METHODS_ITEM', {
|
|
177
|
+
details: { safeMethods },
|
|
178
|
+
}),
|
|
179
|
+
);
|
|
171
180
|
}
|
|
172
181
|
}
|
|
173
182
|
|
|
@@ -175,21 +184,20 @@ export function validateCSRFOptions(options) {
|
|
|
175
184
|
const { tokenBytes } = options;
|
|
176
185
|
|
|
177
186
|
if (typeof tokenBytes !== 'number') {
|
|
178
|
-
return Validation.failure(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
});
|
|
187
|
+
return Validation.failure(
|
|
188
|
+
new ServerConfigurationError('INVALID_CSRF_TOKEN_BYTES_TYPE', {
|
|
189
|
+
values: { received: typeof tokenBytes },
|
|
190
|
+
}),
|
|
191
|
+
);
|
|
184
192
|
}
|
|
185
193
|
|
|
186
194
|
if (!Number.isInteger(tokenBytes) || tokenBytes <= 0) {
|
|
187
|
-
return Validation.failure(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
195
|
+
return Validation.failure(
|
|
196
|
+
new ServerConfigurationError('INVALID_CSRF_TOKEN_BYTES_RANGE', {
|
|
197
|
+
values: { tokenBytes },
|
|
198
|
+
details: { tokenBytes },
|
|
199
|
+
}),
|
|
200
|
+
);
|
|
193
201
|
}
|
|
194
202
|
}
|
|
195
203
|
|
|
@@ -197,21 +205,19 @@ export function validateCSRFOptions(options) {
|
|
|
197
205
|
const { path } = options;
|
|
198
206
|
|
|
199
207
|
if (typeof path !== 'string') {
|
|
200
|
-
return Validation.failure(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
});
|
|
208
|
+
return Validation.failure(
|
|
209
|
+
new ServerConfigurationError('INVALID_CSRF_PATH_TYPE', {
|
|
210
|
+
values: { received: typeof path },
|
|
211
|
+
}),
|
|
212
|
+
);
|
|
206
213
|
}
|
|
207
214
|
|
|
208
215
|
if (path.length === 0) {
|
|
209
|
-
return Validation.failure(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
});
|
|
216
|
+
return Validation.failure(
|
|
217
|
+
new ServerConfigurationError('INVALID_CSRF_PATH_VALUE', {
|
|
218
|
+
details: { path },
|
|
219
|
+
}),
|
|
220
|
+
);
|
|
215
221
|
}
|
|
216
222
|
}
|
|
217
223
|
|
|
@@ -219,21 +225,19 @@ export function validateCSRFOptions(options) {
|
|
|
219
225
|
const { domain } = options;
|
|
220
226
|
|
|
221
227
|
if (typeof domain !== 'string') {
|
|
222
|
-
return Validation.failure(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
});
|
|
228
|
+
return Validation.failure(
|
|
229
|
+
new ServerConfigurationError('INVALID_CSRF_DOMAIN_TYPE', {
|
|
230
|
+
values: { received: typeof domain },
|
|
231
|
+
}),
|
|
232
|
+
);
|
|
228
233
|
}
|
|
229
234
|
|
|
230
235
|
if (domain.length === 0) {
|
|
231
|
-
return Validation.failure(
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
});
|
|
236
|
+
return Validation.failure(
|
|
237
|
+
new ServerConfigurationError('INVALID_CSRF_DOMAIN_VALUE', {
|
|
238
|
+
details: { domain },
|
|
239
|
+
}),
|
|
240
|
+
);
|
|
237
241
|
}
|
|
238
242
|
}
|
|
239
243
|
|
|
@@ -241,12 +245,11 @@ export function validateCSRFOptions(options) {
|
|
|
241
245
|
const { secure } = options;
|
|
242
246
|
|
|
243
247
|
if (typeof secure !== 'boolean') {
|
|
244
|
-
return Validation.failure(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
});
|
|
248
|
+
return Validation.failure(
|
|
249
|
+
new ServerConfigurationError('INVALID_CSRF_SECURE_TYPE', {
|
|
250
|
+
values: { received: typeof secure },
|
|
251
|
+
}),
|
|
252
|
+
);
|
|
250
253
|
}
|
|
251
254
|
}
|
|
252
255
|
|
|
@@ -254,12 +257,12 @@ export function validateCSRFOptions(options) {
|
|
|
254
257
|
const { sameSite } = options;
|
|
255
258
|
|
|
256
259
|
if (sameSite !== undefined && !SAME_SITE_VALUES.includes(String(sameSite))) {
|
|
257
|
-
return Validation.failure(
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
260
|
+
return Validation.failure(
|
|
261
|
+
new ServerConfigurationError('INVALID_CSRF_SAME_SITE_VALUE', {
|
|
262
|
+
values: { received: sameSite },
|
|
263
|
+
details: { sameSite },
|
|
264
|
+
}),
|
|
265
|
+
);
|
|
263
266
|
}
|
|
264
267
|
}
|
|
265
268
|
|
|
@@ -268,11 +271,7 @@ export function validateCSRFOptions(options) {
|
|
|
268
271
|
const effectiveSecure = options.secure ?? true;
|
|
269
272
|
|
|
270
273
|
if (effectiveSameSite === 'None' && effectiveSecure === false) {
|
|
271
|
-
return Validation.failure(
|
|
272
|
-
error: ServerConfigurationError,
|
|
273
|
-
code: 'CSRF_NONE_REQUIRES_SECURE',
|
|
274
|
-
custom: true,
|
|
275
|
-
});
|
|
274
|
+
return Validation.failure(new ServerConfigurationError('CSRF_NONE_REQUIRES_SECURE'));
|
|
276
275
|
}
|
|
277
276
|
|
|
278
277
|
return Validation.success();
|
|
@@ -285,6 +284,7 @@ export function validateCSRFOptions(options) {
|
|
|
285
284
|
* @throws {ServerConfigurationError} If `options` is not a plain object.
|
|
286
285
|
* @throws {ServerConfigurationError} If any option is invalid.
|
|
287
286
|
* @throws {ServerConfigurationError} If `sameSite: 'None'` is used without `secure: true`.
|
|
287
|
+
*
|
|
288
288
|
* @example
|
|
289
289
|
* ```js
|
|
290
290
|
* assertCSRFOptions({ cookieName: 'csrf' });
|