@boo-labs/server 0.0.0-stage → 1.0.0
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/README.md +229 -2
- package/index.mjs +23 -0
- package/package.json +71 -4
- package/source/application/Kernel/index.mjs +1137 -0
- package/source/application/Server/index.mjs +1201 -0
- package/source/application/configurations/Http2CleartextServerConfiguration/index.mjs +168 -0
- package/source/application/configurations/Http2SecureServerConfiguration/index.mjs +172 -0
- package/source/application/configurations/HttpServerConfiguration/index.mjs +169 -0
- package/source/application/configurations/HttpsServerConfiguration/index.mjs +172 -0
- package/source/application/configurations/ServerConfiguration/Options/CORSOptions/CORSOptionsValue.mjs +158 -0
- package/source/application/configurations/ServerConfiguration/Options/CORSOptions/index.mjs +87 -0
- package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/CSRFOptionsValue.mjs +178 -0
- package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/index.mjs +80 -0
- package/source/application/configurations/ServerConfiguration/Options/CompressOptions/CompressOptionsValue.mjs +126 -0
- package/source/application/configurations/ServerConfiguration/Options/CompressOptions/index.mjs +81 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/CookieParserOptionsValue.mjs +107 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/index.mjs +82 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/CookieSessionOptionsValue.mjs +182 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/index.mjs +86 -0
- package/source/application/configurations/ServerConfiguration/Options/ListenOptions/ListenOptionsValue.mjs +95 -0
- package/source/application/configurations/ServerConfiguration/Options/ListenOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/MiddlewareOption.mjs +102 -0
- package/source/application/configurations/ServerConfiguration/Options/Options.mjs +105 -0
- package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/RateLimitOptionsValue.mjs +164 -0
- package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/index.mjs +84 -0
- package/source/application/configurations/ServerConfiguration/Options/RequestOptions/RequestOptionsValue.mjs +108 -0
- package/source/application/configurations/ServerConfiguration/Options/RequestOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/ResponseOptionsValue.mjs +101 -0
- package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/SecurityHeadersOptionsValue.mjs +272 -0
- package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/index.mjs +85 -0
- package/source/application/configurations/ServerConfiguration/Options/ServerOption.mjs +50 -0
- package/source/application/configurations/ServerConfiguration/Options/StaticOptions/StaticOptionsValue.mjs +164 -0
- package/source/application/configurations/ServerConfiguration/Options/StaticOptions/index.mjs +89 -0
- package/source/application/configurations/ServerConfiguration/index.mjs +781 -0
- package/source/application/resolvers/TransportResolver/index.mjs +119 -0
- package/source/application/use-cases/CloseServer/index.mjs +85 -0
- package/source/application/use-cases/ListenServer/index.mjs +98 -0
- package/source/core/EventSystem/index.mjs +110 -0
- package/source/core/Exchange/ExchangeContext.mjs +291 -0
- package/source/core/Exchange/Telemetry.mjs +193 -0
- package/source/core/Exchange/index.mjs +604 -0
- package/source/core/ExchangeLayerBuilder/index.mjs +133 -0
- package/source/core/IPCMain/IPCMain.mjs +507 -0
- package/source/core/IPCMain/IPCProtocol.mjs +441 -0
- package/source/core/IPCMain/IPCServerChannel.mjs +331 -0
- package/source/core/IPCMain/WebSocketAdapter.mjs +189 -0
- package/source/core/Request/BodyParser/BodyParser.mjs +197 -0
- package/source/core/Request/BodyParser/JSONParser.mjs +70 -0
- package/source/core/Request/BodyParser/MultipartParser.mjs +612 -0
- package/source/core/Request/BodyParser/URLEncodedParser.mjs +71 -0
- package/source/core/Request/RequestHeaders.mjs +177 -0
- package/source/core/Request/index.mjs +805 -0
- package/source/core/Response/ResponseHeaders.mjs +402 -0
- package/source/core/Response/index.mjs +1370 -0
- package/source/core/middlewares/CORSMiddleware/index.mjs +289 -0
- package/source/core/middlewares/CSRFMiddleware/index.mjs +284 -0
- package/source/core/middlewares/CompressMiddleware/index.mjs +460 -0
- package/source/core/middlewares/CookieParserMiddleware/index.mjs +194 -0
- package/source/core/middlewares/CookieSessionMiddleware/index.mjs +460 -0
- package/source/core/middlewares/RateLimitMiddleware/index.mjs +245 -0
- package/source/core/middlewares/SecurityHeadersMiddleware/index.mjs +151 -0
- package/source/core/middlewares/StaticMiddleware/index.mjs +628 -0
- package/source/core/routing/ExchangeLayer/ExchangeMatcher.mjs +127 -0
- package/source/core/routing/ExchangeLayer/index.mjs +254 -0
- package/source/core/routing/Pipeline/index.mjs +157 -0
- package/source/core/routing/Router/Route.mjs +184 -0
- package/source/core/routing/Router/index.mjs +523 -0
- package/source/core/routing/RouterRegister/OptionsMiddleware.mjs +147 -0
- package/source/core/routing/RouterRegister/index.mjs +150 -0
- package/source/core/routing/RouterSystem/index.mjs +225 -0
- package/source/core/value-objects/Address/index.mjs +281 -0
- package/source/core/value-objects/Endpoint/index.mjs +293 -0
- package/source/index.mjs +62 -0
- package/source/infrastructure/adapters/Http2Adapter/index.mjs +144 -0
- package/source/infrastructure/adapters/Http2CleartextAdapter/index.mjs +147 -0
- package/source/infrastructure/adapters/HttpAdapter/index.mjs +124 -0
- package/source/infrastructure/adapters/HttpsAdapter/index.mjs +124 -0
- package/source/infrastructure/adapters/TransportAdapter/createWebSocketOpenRequest.mjs +843 -0
- package/source/infrastructure/adapters/TransportAdapter/index.mjs +555 -0
- package/source/shared/Identifier/index.mjs +671 -0
- package/source/shared/constants/symbols/brands/index.mjs +45 -0
- package/source/shared/domain/UUIDV7/index.mjs +346 -0
- package/source/shared/errors/BaseError/index.mjs +157 -0
- package/source/shared/errors/IPCError/index.mjs +97 -0
- package/source/shared/errors/MiddlewareError/index.mjs +106 -0
- package/source/shared/errors/PathnamePatternError/index.mjs +103 -0
- package/source/shared/errors/RequestError/index.mjs +116 -0
- package/source/shared/errors/ResponseError/index.mjs +140 -0
- package/source/shared/errors/RouterError/index.mjs +90 -0
- package/source/shared/errors/ServerConfigurationError/index.mjs +551 -0
- package/source/shared/errors/ServerError/index.mjs +127 -0
- package/source/shared/errors/WebSocketError/index.mjs +58 -0
- package/source/shared/outcome/ErrorSchema.mjs +361 -0
- package/source/shared/outcome/Result/index.mjs +239 -0
- package/source/shared/outcome/Validation/index.mjs +208 -0
- package/source/shared/validations/Middleware/index.mjs +160 -0
- package/source/shared/validations/PathnamePattern/index.mjs +256 -0
- package/source/shared/validations/Server/NotFoundHandler/index.mjs +94 -0
- package/source/shared/validations/Server/Router/index.mjs +68 -0
- package/source/shared/validations/Server/ServerConfiguration/index.mjs +68 -0
- package/source/shared/validations/Server/SystemErrorHandler/index.mjs +94 -0
- package/source/shared/validations/ServerConfiguration/CORSOptions/index.mjs +310 -0
- package/source/shared/validations/ServerConfiguration/CSRFOptions/index.mjs +299 -0
- package/source/shared/validations/ServerConfiguration/CompressOptions/index.mjs +204 -0
- package/source/shared/validations/ServerConfiguration/CookieParserOptions/index.mjs +102 -0
- package/source/shared/validations/ServerConfiguration/CookieSessionOptions/index.mjs +332 -0
- package/source/shared/validations/ServerConfiguration/ListenOptions/index.mjs +178 -0
- package/source/shared/validations/ServerConfiguration/RateLimitOptions/index.mjs +229 -0
- package/source/shared/validations/ServerConfiguration/RequestOptions/index.mjs +132 -0
- package/source/shared/validations/ServerConfiguration/ResponseOptions/index.mjs +122 -0
- package/source/shared/validations/ServerConfiguration/SecurityHeadersOptions/index.mjs +137 -0
- package/source/shared/validations/ServerConfiguration/StaticOptions/index.mjs +248 -0
- package/types/index.d.ts +719 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Router validation utilities.
|
|
4
|
+
*
|
|
5
|
+
* @description Validates objects that implement the Router contract.
|
|
6
|
+
*
|
|
7
|
+
* @module Validation/Router
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
11
|
+
// » IMPORT LOCAL MODULES
|
|
12
|
+
import { ROUTER_BRAND } from '#shared/constants/symbols/brands';
|
|
13
|
+
import Validation from '#shared/outcome/Validation';
|
|
14
|
+
import ServerError from '#shared/errors/ServerError';
|
|
15
|
+
|
|
16
|
+
// ━━ FUNCTIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
/**
|
|
18
|
+
* Validates that a value implements the Router contract.
|
|
19
|
+
*
|
|
20
|
+
* @param {unknown} value - Value to validate.
|
|
21
|
+
* @returns {Validation} Validation result.
|
|
22
|
+
* @example
|
|
23
|
+
* ```js
|
|
24
|
+
* const validation = validateRouter(router);
|
|
25
|
+
*
|
|
26
|
+
* console.log(validation.ok);
|
|
27
|
+
* // => true
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function validateRouter(value) {
|
|
31
|
+
if (value === null || typeof value !== 'object') {
|
|
32
|
+
return Validation.failure({
|
|
33
|
+
error: ServerError,
|
|
34
|
+
message: `Expected a Router instance, received: ${value === null ? 'null' : typeof value}.`,
|
|
35
|
+
code: 'INVALID_ROUTER_TYPE',
|
|
36
|
+
custom: true,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (Reflect.get(value, ROUTER_BRAND) !== true) {
|
|
41
|
+
return Validation.failure({
|
|
42
|
+
error: ServerError,
|
|
43
|
+
message: 'Expected a Router instance.',
|
|
44
|
+
code: 'INVALID_ROUTER_INSTANCE',
|
|
45
|
+
custom: true,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return Validation.success();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Asserts that a value implements the Router contract.
|
|
54
|
+
*
|
|
55
|
+
* @param {unknown} value - Value to assert.
|
|
56
|
+
* @throws {ServerError} If the value does not implement the Router contract.
|
|
57
|
+
* @example
|
|
58
|
+
* ```js
|
|
59
|
+
* assertRouter(router);
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function assertRouter(value) {
|
|
63
|
+
const validation = validateRouter(value);
|
|
64
|
+
|
|
65
|
+
if (!validation.ok) {
|
|
66
|
+
throw validation.error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file ServerConfiguration validation utilities.
|
|
4
|
+
*
|
|
5
|
+
* @description Validates objects that implement the ServerConfiguration contract.
|
|
6
|
+
*
|
|
7
|
+
* @module Validation/Server/ServerConfiguration
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
11
|
+
// » IMPORT LOCAL MODULES
|
|
12
|
+
import { SERVER_CONFIGURATION_BRAND } from '#shared/constants/symbols/brands';
|
|
13
|
+
import Validation from '#shared/outcome/Validation';
|
|
14
|
+
import ServerError from '#shared/errors/ServerError';
|
|
15
|
+
|
|
16
|
+
// ━━ FUNCTIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
/**
|
|
18
|
+
* Validates that a value implements the ServerConfiguration contract.
|
|
19
|
+
*
|
|
20
|
+
* @param {unknown} value - Value to validate.
|
|
21
|
+
* @returns {Validation} Validation result.
|
|
22
|
+
* @example
|
|
23
|
+
* ```js
|
|
24
|
+
* const validation = validateServerConfiguration(configuration);
|
|
25
|
+
*
|
|
26
|
+
* console.log(validation.ok);
|
|
27
|
+
* // => true
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function validateServerConfiguration(value) {
|
|
31
|
+
if (value === null || typeof value !== 'object') {
|
|
32
|
+
return Validation.failure({
|
|
33
|
+
error: ServerError,
|
|
34
|
+
message: `Expected a ServerConfiguration instance, received: ${value === null ? 'null' : typeof value}.`,
|
|
35
|
+
code: 'INVALID_SERVER_CONFIGURATION_TYPE',
|
|
36
|
+
custom: true,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (Reflect.get(value, SERVER_CONFIGURATION_BRAND) !== true) {
|
|
41
|
+
return Validation.failure({
|
|
42
|
+
error: ServerError,
|
|
43
|
+
message: 'Expected a ServerConfiguration instance.',
|
|
44
|
+
code: 'INVALID_SERVER_CONFIGURATION_INSTANCE',
|
|
45
|
+
custom: true,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return Validation.success();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Asserts that a value implements the ServerConfiguration contract.
|
|
54
|
+
*
|
|
55
|
+
* @param {unknown} value - Value to assert.
|
|
56
|
+
* @throws {ServerError} If the value does not implement the ServerConfiguration contract.
|
|
57
|
+
* @example
|
|
58
|
+
* ```js
|
|
59
|
+
* assertServerConfiguration(configuration);
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function assertServerConfiguration(value) {
|
|
63
|
+
const validation = validateServerConfiguration(value);
|
|
64
|
+
|
|
65
|
+
if (!validation.ok) {
|
|
66
|
+
throw validation.error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file SystemErrorHandler validation utilities.
|
|
4
|
+
*
|
|
5
|
+
* @description Validates handlers used to resolve errors that propagate during
|
|
6
|
+
* request processing.
|
|
7
|
+
*
|
|
8
|
+
* @module Validation/Server/SystemErrorHandler
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
12
|
+
// » IMPORT LOCAL MODULES
|
|
13
|
+
import Validation from '#shared/outcome/Validation';
|
|
14
|
+
import ServerError from '#shared/errors/ServerError';
|
|
15
|
+
|
|
16
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
/**
|
|
18
|
+
* Validates a system error handler.
|
|
19
|
+
*
|
|
20
|
+
* A system error handler must be a function that declares exactly two
|
|
21
|
+
* parameters: `error` and `exchange`.
|
|
22
|
+
*
|
|
23
|
+
* @param {unknown} handler - Handler function to validate.
|
|
24
|
+
* @returns {Validation} Validation result.
|
|
25
|
+
* @example
|
|
26
|
+
* ```js
|
|
27
|
+
* const validation = validateSystemErrorHandler((error, exchange) => {
|
|
28
|
+
* exchange.response.status(500).end();
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* console.log(validation.ok);
|
|
32
|
+
* // => true
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```js
|
|
37
|
+
* const validation = validateSystemErrorHandler((error) => {});
|
|
38
|
+
*
|
|
39
|
+
* console.log(validation.ok);
|
|
40
|
+
* // => false
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export function validateSystemErrorHandler(handler) {
|
|
44
|
+
if (typeof handler !== 'function') {
|
|
45
|
+
return Validation.failure({
|
|
46
|
+
error: ServerError,
|
|
47
|
+
code: 'INVALID_SYSTEM_ERROR_HANDLER_TYPE',
|
|
48
|
+
custom: true,
|
|
49
|
+
options: {
|
|
50
|
+
values: {
|
|
51
|
+
expected: 'function',
|
|
52
|
+
received: typeof handler,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (handler.length !== 2) {
|
|
59
|
+
return Validation.failure({
|
|
60
|
+
error: ServerError,
|
|
61
|
+
code: 'INVALID_SYSTEM_ERROR_HANDLER_ARITY',
|
|
62
|
+
custom: true,
|
|
63
|
+
options: {
|
|
64
|
+
values: {
|
|
65
|
+
expected: 2,
|
|
66
|
+
received: handler.length,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return Validation.success();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Asserts that a system error handler is valid.
|
|
77
|
+
*
|
|
78
|
+
* @param {unknown} handler - Handler function to validate.
|
|
79
|
+
* @throws {ServerError} If the handler is not a function.
|
|
80
|
+
* @throws {ServerError} If the handler does not declare exactly two parameters.
|
|
81
|
+
* @example
|
|
82
|
+
* ```js
|
|
83
|
+
* assertSystemErrorHandler((error, exchange) => {
|
|
84
|
+
* exchange.response.status(500).end();
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export function assertSystemErrorHandler(handler) {
|
|
89
|
+
const validation = validateSystemErrorHandler(handler);
|
|
90
|
+
|
|
91
|
+
if (!validation.ok) {
|
|
92
|
+
throw validation.error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file CORS options validation utilities.
|
|
4
|
+
*
|
|
5
|
+
* @description Validates CORS middleware options used by
|
|
6
|
+
* ServerConfiguration, including origin, methods, allowed/exposed headers,
|
|
7
|
+
* credentials, preflight caching, and preflight response behavior.
|
|
8
|
+
*
|
|
9
|
+
* @module Validation/ServerConfiguration/CORSOptions
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
13
|
+
// » IMPORT LOCAL MODULES
|
|
14
|
+
import Validation from '#shared/outcome/Validation';
|
|
15
|
+
import ServerConfigurationError from '#shared/errors/ServerConfigurationError';
|
|
16
|
+
|
|
17
|
+
// ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
18
|
+
/**
|
|
19
|
+
* Minimum valid HTTP status code for a successful preflight response.
|
|
20
|
+
*
|
|
21
|
+
* @private
|
|
22
|
+
* @type {number}
|
|
23
|
+
*/
|
|
24
|
+
const MIN_OPTIONS_SUCCESS_STATUS = 100;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Maximum valid HTTP status code for a successful preflight response.
|
|
28
|
+
*
|
|
29
|
+
* @private
|
|
30
|
+
* @type {number}
|
|
31
|
+
*/
|
|
32
|
+
const MAX_OPTIONS_SUCCESS_STATUS = 599;
|
|
33
|
+
|
|
34
|
+
// ━━ FUNCTIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
35
|
+
/**
|
|
36
|
+
* Determines whether a value is a non-null plain object.
|
|
37
|
+
*
|
|
38
|
+
* @private
|
|
39
|
+
* @param {unknown} value - Value to inspect.
|
|
40
|
+
* @returns {value is Record<string, unknown>} `true` if the value is a plain object.
|
|
41
|
+
*/
|
|
42
|
+
function isPlainObject(value) {
|
|
43
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Determines whether a value is a non-empty string.
|
|
48
|
+
*
|
|
49
|
+
* @private
|
|
50
|
+
* @param {unknown} value - Value to inspect.
|
|
51
|
+
* @returns {value is string} `true` if the value is a non-empty string.
|
|
52
|
+
*/
|
|
53
|
+
function isNonEmptyString(value) {
|
|
54
|
+
return typeof value === 'string' && value.length > 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Determines whether every item in an array is a non-empty string.
|
|
59
|
+
*
|
|
60
|
+
* @private
|
|
61
|
+
* @param {unknown[]} values - Array of values to inspect.
|
|
62
|
+
* @returns {boolean} `true` if every item is a non-empty string.
|
|
63
|
+
*/
|
|
64
|
+
function everyNonEmptyString(values) {
|
|
65
|
+
return values.every(isNonEmptyString);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Validates CORS options.
|
|
70
|
+
*
|
|
71
|
+
* Validation is performed in the following order:
|
|
72
|
+
*
|
|
73
|
+
* 1. `options` may be `undefined`, in which case validation succeeds.
|
|
74
|
+
* 2. `options` must be a plain object.
|
|
75
|
+
* 3. `origin`, if present, must be `true`, a string, or an array of non-empty strings.
|
|
76
|
+
* 4. `methods`, if present, must be an array of non-empty strings.
|
|
77
|
+
* 5. `allowedHeaders`, if present, must be an array of non-empty strings.
|
|
78
|
+
* 6. `exposedHeaders`, if present, must be an array of non-empty strings.
|
|
79
|
+
* 7. `credentials`, if present, must be a boolean.
|
|
80
|
+
* 8. `maxAge`, if present, must be a non-negative integer.
|
|
81
|
+
* 9. `preflightContinue`, if present, must be a boolean.
|
|
82
|
+
* 10. `optionsSuccessStatus`, if present, must be an integer between 100 and 599.
|
|
83
|
+
*
|
|
84
|
+
* @param {unknown} options - CORS options to validate.
|
|
85
|
+
* @returns {Validation} Validation result.
|
|
86
|
+
* @example
|
|
87
|
+
* ```js
|
|
88
|
+
* const validation = validateCORSOptions({ origin: true });
|
|
89
|
+
*
|
|
90
|
+
* console.log(validation.ok);
|
|
91
|
+
* // => true
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```js
|
|
96
|
+
* const validation = validateCORSOptions({ origin: 123 });
|
|
97
|
+
*
|
|
98
|
+
* console.log(validation.ok);
|
|
99
|
+
* // => false
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export function validateCORSOptions(options) {
|
|
103
|
+
if (options === undefined) {
|
|
104
|
+
return Validation.success();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!isPlainObject(options)) {
|
|
108
|
+
return Validation.failure({
|
|
109
|
+
error: ServerConfigurationError,
|
|
110
|
+
code: 'INVALID_CORS_OPTIONS',
|
|
111
|
+
custom: true,
|
|
112
|
+
options: { values: { received: typeof options } },
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if ('origin' in options) {
|
|
117
|
+
const { origin } = options;
|
|
118
|
+
|
|
119
|
+
const isValidOriginType =
|
|
120
|
+
origin === true ||
|
|
121
|
+
typeof origin === 'string' ||
|
|
122
|
+
(Array.isArray(origin) && everyNonEmptyString(origin));
|
|
123
|
+
|
|
124
|
+
if (!isValidOriginType) {
|
|
125
|
+
if (Array.isArray(origin)) {
|
|
126
|
+
return Validation.failure({
|
|
127
|
+
error: ServerConfigurationError,
|
|
128
|
+
code: 'INVALID_CORS_ORIGIN_ITEM',
|
|
129
|
+
custom: true,
|
|
130
|
+
options: { details: { origin } },
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return Validation.failure({
|
|
135
|
+
error: ServerConfigurationError,
|
|
136
|
+
code: 'INVALID_CORS_ORIGIN_TYPE',
|
|
137
|
+
custom: true,
|
|
138
|
+
options: { values: { received: typeof origin } },
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if ('methods' in options) {
|
|
144
|
+
const { methods } = options;
|
|
145
|
+
|
|
146
|
+
if (!Array.isArray(methods)) {
|
|
147
|
+
return Validation.failure({
|
|
148
|
+
error: ServerConfigurationError,
|
|
149
|
+
code: 'INVALID_CORS_METHODS_TYPE',
|
|
150
|
+
custom: true,
|
|
151
|
+
options: { values: { received: typeof methods } },
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!everyNonEmptyString(methods)) {
|
|
156
|
+
return Validation.failure({
|
|
157
|
+
error: ServerConfigurationError,
|
|
158
|
+
code: 'INVALID_CORS_METHODS_ITEM',
|
|
159
|
+
custom: true,
|
|
160
|
+
options: { details: { methods } },
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if ('allowedHeaders' in options) {
|
|
166
|
+
const { allowedHeaders } = options;
|
|
167
|
+
|
|
168
|
+
if (!Array.isArray(allowedHeaders)) {
|
|
169
|
+
return Validation.failure({
|
|
170
|
+
error: ServerConfigurationError,
|
|
171
|
+
code: 'INVALID_CORS_ALLOWED_HEADERS_TYPE',
|
|
172
|
+
custom: true,
|
|
173
|
+
options: { values: { received: typeof allowedHeaders } },
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!everyNonEmptyString(allowedHeaders)) {
|
|
178
|
+
return Validation.failure({
|
|
179
|
+
error: ServerConfigurationError,
|
|
180
|
+
code: 'INVALID_CORS_ALLOWED_HEADERS_ITEM',
|
|
181
|
+
custom: true,
|
|
182
|
+
options: { details: { allowedHeaders } },
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if ('exposedHeaders' in options) {
|
|
188
|
+
const { exposedHeaders } = options;
|
|
189
|
+
|
|
190
|
+
if (!Array.isArray(exposedHeaders)) {
|
|
191
|
+
return Validation.failure({
|
|
192
|
+
error: ServerConfigurationError,
|
|
193
|
+
code: 'INVALID_CORS_EXPOSED_HEADERS_TYPE',
|
|
194
|
+
custom: true,
|
|
195
|
+
options: { values: { received: typeof exposedHeaders } },
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!everyNonEmptyString(exposedHeaders)) {
|
|
200
|
+
return Validation.failure({
|
|
201
|
+
error: ServerConfigurationError,
|
|
202
|
+
code: 'INVALID_CORS_EXPOSED_HEADERS_ITEM',
|
|
203
|
+
custom: true,
|
|
204
|
+
options: { details: { exposedHeaders } },
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if ('credentials' in options) {
|
|
210
|
+
const { credentials } = options;
|
|
211
|
+
|
|
212
|
+
if (typeof credentials !== 'boolean') {
|
|
213
|
+
return Validation.failure({
|
|
214
|
+
error: ServerConfigurationError,
|
|
215
|
+
code: 'INVALID_CORS_CREDENTIALS_TYPE',
|
|
216
|
+
custom: true,
|
|
217
|
+
options: { values: { received: typeof credentials } },
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if ('maxAge' in options) {
|
|
223
|
+
const { maxAge } = options;
|
|
224
|
+
|
|
225
|
+
if (typeof maxAge !== 'number') {
|
|
226
|
+
return Validation.failure({
|
|
227
|
+
error: ServerConfigurationError,
|
|
228
|
+
code: 'INVALID_CORS_MAX_AGE_TYPE',
|
|
229
|
+
custom: true,
|
|
230
|
+
options: { values: { received: typeof maxAge } },
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!Number.isInteger(maxAge) || maxAge < 0) {
|
|
235
|
+
return Validation.failure({
|
|
236
|
+
error: ServerConfigurationError,
|
|
237
|
+
code: 'INVALID_CORS_MAX_AGE_RANGE',
|
|
238
|
+
custom: true,
|
|
239
|
+
options: { details: { maxAge } },
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if ('preflightContinue' in options) {
|
|
245
|
+
const { preflightContinue } = options;
|
|
246
|
+
|
|
247
|
+
if (typeof preflightContinue !== 'boolean') {
|
|
248
|
+
return Validation.failure({
|
|
249
|
+
error: ServerConfigurationError,
|
|
250
|
+
code: 'INVALID_CORS_PREFLIGHT_CONTINUE_TYPE',
|
|
251
|
+
custom: true,
|
|
252
|
+
options: { values: { received: typeof preflightContinue } },
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if ('optionsSuccessStatus' in options) {
|
|
258
|
+
const { optionsSuccessStatus } = options;
|
|
259
|
+
|
|
260
|
+
if (typeof optionsSuccessStatus !== 'number') {
|
|
261
|
+
return Validation.failure({
|
|
262
|
+
error: ServerConfigurationError,
|
|
263
|
+
code: 'INVALID_CORS_OPTIONS_SUCCESS_STATUS_TYPE',
|
|
264
|
+
custom: true,
|
|
265
|
+
options: { values: { received: typeof optionsSuccessStatus } },
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (
|
|
270
|
+
!Number.isInteger(optionsSuccessStatus) ||
|
|
271
|
+
optionsSuccessStatus < MIN_OPTIONS_SUCCESS_STATUS ||
|
|
272
|
+
optionsSuccessStatus > MAX_OPTIONS_SUCCESS_STATUS
|
|
273
|
+
) {
|
|
274
|
+
return Validation.failure({
|
|
275
|
+
error: ServerConfigurationError,
|
|
276
|
+
code: 'INVALID_CORS_OPTIONS_SUCCESS_STATUS_RANGE',
|
|
277
|
+
custom: true,
|
|
278
|
+
options: { details: { optionsSuccessStatus } },
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return Validation.success();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Asserts that CORS options are valid.
|
|
288
|
+
*
|
|
289
|
+
* @param {unknown} options - CORS options to validate.
|
|
290
|
+
* @throws {ServerConfigurationError} If `options` is not a plain object.
|
|
291
|
+
* @throws {ServerConfigurationError} If `origin` is not `true`, a string, or an array of non-empty strings.
|
|
292
|
+
* @throws {ServerConfigurationError} If `methods` is not an array of non-empty strings.
|
|
293
|
+
* @throws {ServerConfigurationError} If `allowedHeaders` is not an array of non-empty strings.
|
|
294
|
+
* @throws {ServerConfigurationError} If `exposedHeaders` is not an array of non-empty strings.
|
|
295
|
+
* @throws {ServerConfigurationError} If `credentials` is not a boolean.
|
|
296
|
+
* @throws {ServerConfigurationError} If `maxAge` is not a non-negative integer.
|
|
297
|
+
* @throws {ServerConfigurationError} If `preflightContinue` is not a boolean.
|
|
298
|
+
* @throws {ServerConfigurationError} If `optionsSuccessStatus` is not an integer between 100 and 599.
|
|
299
|
+
* @example
|
|
300
|
+
* ```js
|
|
301
|
+
* assertCORSOptions({ origin: true, credentials: true });
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
export function assertCORSOptions(options) {
|
|
305
|
+
const validation = validateCORSOptions(options);
|
|
306
|
+
|
|
307
|
+
if (!validation.ok) {
|
|
308
|
+
throw validation.error;
|
|
309
|
+
}
|
|
310
|
+
}
|