@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,158 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Defines the CORSOptionsValue value object.
|
|
4
|
+
*
|
|
5
|
+
* @description Normalizes and validates CORS middleware configuration used
|
|
6
|
+
* by CORSMiddleware. When options are omitted, the value is undefined.
|
|
7
|
+
*
|
|
8
|
+
* @module CORSOptionsValue
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
12
|
+
// » IMPORT LOCAL MODULES
|
|
13
|
+
import { assertCORSOptions } from '#shared/validations/ServerConfiguration/CORSOptions';
|
|
14
|
+
|
|
15
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
16
|
+
/**
|
|
17
|
+
* Domain error thrown by server configuration validation.
|
|
18
|
+
*
|
|
19
|
+
* @typedef {import('#shared/errors/ServerConfigurationError').default} ServerConfigurationError
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Represents the CORS options accepted by the server configuration.
|
|
24
|
+
*
|
|
25
|
+
* @typedef {object} CORSOptionsInput
|
|
26
|
+
* @property {true|string|string[]} [origin] - Allowed origin(s). Defaults to `'*'`.
|
|
27
|
+
* @property {string[]} [methods] - Allowed HTTP methods for preflight responses. Defaults to a standard set.
|
|
28
|
+
* @property {string[]} [allowedHeaders] - Headers the client is allowed to send. When omitted, the value of Access-Control-Request-Headers is reflected.
|
|
29
|
+
* @property {string[]} [exposedHeaders] - Headers the client is allowed to read from the response.
|
|
30
|
+
* @property {boolean} [credentials] - Whether the response may include credentials. Defaults to `false`.
|
|
31
|
+
* @property {number} [maxAge] - How long (in seconds) the preflight response may be cached.
|
|
32
|
+
* @property {boolean} [preflightContinue] - Whether to call `next()` on successful preflight instead of ending the response. Defaults to `false`.
|
|
33
|
+
* @property {number} [optionsSuccessStatus] - Status code used for successful preflight responses. Defaults to `204`.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Represents the normalized CORS options.
|
|
38
|
+
*
|
|
39
|
+
* @typedef {object} CORSOptionsData
|
|
40
|
+
* @property {true|string|string[]} origin - Allowed origin(s).
|
|
41
|
+
* @property {Readonly<string[]>} methods - Allowed HTTP methods for preflight responses.
|
|
42
|
+
* @property {Readonly<string[]>|undefined} allowedHeaders - Headers the client is allowed to send, or `undefined` to reflect the request headers.
|
|
43
|
+
* @property {Readonly<string[]>|undefined} exposedHeaders - Headers the client is allowed to read from the response.
|
|
44
|
+
* @property {boolean} credentials - Whether the response may include credentials.
|
|
45
|
+
* @property {number|undefined} maxAge - How long (in seconds) the preflight response may be cached.
|
|
46
|
+
* @property {boolean} preflightContinue - Whether to call `next()` on successful preflight instead of ending the response.
|
|
47
|
+
* @property {number} optionsSuccessStatus - Status code used for successful preflight responses.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
// ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
51
|
+
/**
|
|
52
|
+
* Default allowed origin.
|
|
53
|
+
*
|
|
54
|
+
* @type {true|string|string[]}
|
|
55
|
+
*/
|
|
56
|
+
const DEFAULT_ORIGIN = '*';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Default allowed HTTP methods for preflight responses.
|
|
60
|
+
*
|
|
61
|
+
* @type {readonly string[]}
|
|
62
|
+
*/
|
|
63
|
+
const DEFAULT_METHODS = Object.freeze(['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE']);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Default credentials behavior.
|
|
67
|
+
*
|
|
68
|
+
* @type {boolean}
|
|
69
|
+
*/
|
|
70
|
+
const DEFAULT_CREDENTIALS = false;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Default preflight continuation behavior.
|
|
74
|
+
*
|
|
75
|
+
* @type {boolean}
|
|
76
|
+
*/
|
|
77
|
+
const DEFAULT_PREFLIGHT_CONTINUE = false;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Default status code used for successful preflight responses.
|
|
81
|
+
*
|
|
82
|
+
* @type {number}
|
|
83
|
+
*/
|
|
84
|
+
const DEFAULT_OPTIONS_SUCCESS_STATUS = 204;
|
|
85
|
+
|
|
86
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
87
|
+
/**
|
|
88
|
+
* Immutable value object representing normalized CORS middleware options.
|
|
89
|
+
*
|
|
90
|
+
* @classdesc Validates and normalizes CORS middleware options.
|
|
91
|
+
*/
|
|
92
|
+
class CORSOptionsValue {
|
|
93
|
+
/**
|
|
94
|
+
* The normalized CORS options.
|
|
95
|
+
*
|
|
96
|
+
* @type {Readonly<CORSOptionsData>|undefined}
|
|
97
|
+
*/
|
|
98
|
+
#value;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Creates a new {@link CORSOptionsValue}.
|
|
102
|
+
*
|
|
103
|
+
* @param {CORSOptionsInput} [options] - The CORS middleware options.
|
|
104
|
+
* @throws {ServerConfigurationError} If `options` is invalid.
|
|
105
|
+
* @example
|
|
106
|
+
* ```js
|
|
107
|
+
* const options = new CORSOptionsValue({ origin: true, credentials: true });
|
|
108
|
+
*
|
|
109
|
+
* options.value;
|
|
110
|
+
* // => {
|
|
111
|
+
* // origin: true,
|
|
112
|
+
* // methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
|
|
113
|
+
* // allowedHeaders: undefined,
|
|
114
|
+
* // exposedHeaders: undefined,
|
|
115
|
+
* // credentials: true,
|
|
116
|
+
* // maxAge: undefined,
|
|
117
|
+
* // preflightContinue: false,
|
|
118
|
+
* // optionsSuccessStatus: 204,
|
|
119
|
+
* // }
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
constructor(options) {
|
|
123
|
+
assertCORSOptions(options);
|
|
124
|
+
|
|
125
|
+
this.#value =
|
|
126
|
+
options === undefined
|
|
127
|
+
? options
|
|
128
|
+
: Object.freeze({
|
|
129
|
+
origin: options.origin ?? DEFAULT_ORIGIN,
|
|
130
|
+
methods: Object.freeze([...(options.methods ?? DEFAULT_METHODS)]),
|
|
131
|
+
allowedHeaders:
|
|
132
|
+
options.allowedHeaders === undefined
|
|
133
|
+
? undefined
|
|
134
|
+
: Object.freeze([...options.allowedHeaders]),
|
|
135
|
+
exposedHeaders:
|
|
136
|
+
options.exposedHeaders === undefined
|
|
137
|
+
? undefined
|
|
138
|
+
: Object.freeze([...options.exposedHeaders]),
|
|
139
|
+
credentials: options.credentials ?? DEFAULT_CREDENTIALS,
|
|
140
|
+
maxAge: options.maxAge,
|
|
141
|
+
preflightContinue: options.preflightContinue ?? DEFAULT_PREFLIGHT_CONTINUE,
|
|
142
|
+
optionsSuccessStatus: options.optionsSuccessStatus ?? DEFAULT_OPTIONS_SUCCESS_STATUS,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The normalized CORS options.
|
|
148
|
+
*
|
|
149
|
+
* @readonly
|
|
150
|
+
* @type {Readonly<CORSOptionsData>|undefined}
|
|
151
|
+
*/
|
|
152
|
+
get value() {
|
|
153
|
+
return this.#value;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
158
|
+
export default CORSOptionsValue;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Defines the CORSOptions middleware configuration option.
|
|
4
|
+
*
|
|
5
|
+
* @description CORSOptions wraps a normalized CORSOptionsValue into the
|
|
6
|
+
* middleware configuration option hierarchy.
|
|
7
|
+
*
|
|
8
|
+
* @module CORSOptions
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
12
|
+
// » IMPORT LOCAL MODULES
|
|
13
|
+
import MiddlewareOption from '../MiddlewareOption.mjs';
|
|
14
|
+
import CORSOptionsValue from './CORSOptionsValue.mjs';
|
|
15
|
+
|
|
16
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
/**
|
|
18
|
+
* Represents the CORS options accepted by the server configuration.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {import('./CORSOptionsValue.mjs').CORSOptionsInput} CORSOptionsInput
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Represents the normalized CORS options.
|
|
25
|
+
*
|
|
26
|
+
* @typedef {import('./CORSOptionsValue.mjs').CORSOptionsData} CORSOptionsData
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Represents one middleware configuration consumed by the exchange layer builder.
|
|
31
|
+
*
|
|
32
|
+
* @typedef {import('../MiddlewareOption.mjs').MiddlewareConfiguration} MiddlewareConfiguration
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
36
|
+
/**
|
|
37
|
+
* Middleware configuration option wrapping a normalized CORS configuration.
|
|
38
|
+
*
|
|
39
|
+
* @augments {MiddlewareOption<CORSOptionsData|undefined>}
|
|
40
|
+
*/
|
|
41
|
+
class CORSOptions extends MiddlewareOption {
|
|
42
|
+
/**
|
|
43
|
+
* Creates a new {@link CORSOptions}.
|
|
44
|
+
*
|
|
45
|
+
* @param {CORSOptionsInput} [options] - The CORS middleware options.
|
|
46
|
+
* @example
|
|
47
|
+
* ```js
|
|
48
|
+
* const CORSOptions = new CORSOptions({
|
|
49
|
+
* origin: true,
|
|
50
|
+
* methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
|
|
51
|
+
* credentials: true,
|
|
52
|
+
* maxAge: 86400,
|
|
53
|
+
* preflightContinue: false,
|
|
54
|
+
* optionsSuccessStatus: 204,
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
constructor(options) {
|
|
59
|
+
super('cors', new CORSOptionsValue(options));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Composes the CORS middleware configuration consumed by the exchange
|
|
64
|
+
* layer builder.
|
|
65
|
+
*
|
|
66
|
+
* CORS is a cross-cutting concern applied to every request regardless of
|
|
67
|
+
* pathname or method, so both `pathnamePattern` and `method` are
|
|
68
|
+
* `undefined`.
|
|
69
|
+
*
|
|
70
|
+
* @returns {MiddlewareConfiguration} The composed middleware configuration.
|
|
71
|
+
* @example
|
|
72
|
+
* ```js
|
|
73
|
+
* const configuration = CORSOptions.compose();
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
compose() {
|
|
77
|
+
return {
|
|
78
|
+
name: this.name,
|
|
79
|
+
options: this.value,
|
|
80
|
+
pathnamePattern: undefined,
|
|
81
|
+
method: undefined,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
87
|
+
export default CORSOptions;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Defines the CSRFOptionsValue value object.
|
|
4
|
+
*
|
|
5
|
+
* @description Normalizes and validates CSRF middleware configuration used
|
|
6
|
+
* by CsrfMiddleware. When options are omitted, the value is undefined and the
|
|
7
|
+
* middleware is not registered.
|
|
8
|
+
*
|
|
9
|
+
* @module CSRFOptionsValue
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
13
|
+
// » IMPORT LOCAL MODULES
|
|
14
|
+
import { assertCSRFOptions } from '#shared/validations/ServerConfiguration/CSRFOptions';
|
|
15
|
+
|
|
16
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
/**
|
|
18
|
+
* Domain error thrown by server configuration validation.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {import('#shared/errors/ServerConfigurationError').default} ServerConfigurationError
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Represents the CSRF options accepted by the server configuration.
|
|
25
|
+
*
|
|
26
|
+
* @typedef {object} CSRFOptionsInput
|
|
27
|
+
* @property {string} [cookieName] - Name of the CSRF cookie.
|
|
28
|
+
* @property {string} [headerName] - Name of the request header that must carry the token.
|
|
29
|
+
* @property {string[]} [safeMethods] - HTTP methods that skip CSRF validation.
|
|
30
|
+
* @property {number} [tokenBytes] - Number of random bytes used to generate the token.
|
|
31
|
+
* @property {string} [path] - Cookie Path attribute.
|
|
32
|
+
* @property {string} [domain] - Cookie Domain attribute.
|
|
33
|
+
* @property {boolean} [secure] - Whether the cookie is marked Secure.
|
|
34
|
+
* @property {'Strict'|'Lax'|'None'} [sameSite] - Cookie SameSite attribute.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Represents the normalized CSRF options.
|
|
39
|
+
*
|
|
40
|
+
* @typedef {object} CSRFOptionsData
|
|
41
|
+
* @property {string} cookieName - Name of the CSRF cookie.
|
|
42
|
+
* @property {string} headerName - Name of the request header that must carry the token.
|
|
43
|
+
* @property {Readonly<string[]>} safeMethods - HTTP methods that skip CSRF validation.
|
|
44
|
+
* @property {number} tokenBytes - Number of random bytes used to generate the token.
|
|
45
|
+
* @property {string} path - Cookie Path attribute.
|
|
46
|
+
* @property {string|undefined} domain - Cookie Domain attribute.
|
|
47
|
+
* @property {boolean} secure - Whether the cookie is marked Secure.
|
|
48
|
+
* @property {'Strict'|'Lax'|'None'} sameSite - Cookie SameSite attribute.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
// ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
52
|
+
/**
|
|
53
|
+
* Default CSRF cookie name.
|
|
54
|
+
*
|
|
55
|
+
* @type {string}
|
|
56
|
+
*/
|
|
57
|
+
const DEFAULT_COOKIE_NAME = 'csrf-token';
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Default request header name.
|
|
61
|
+
*
|
|
62
|
+
* @type {string}
|
|
63
|
+
*/
|
|
64
|
+
const DEFAULT_HEADER_NAME = 'x-csrf-token';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Default safe HTTP methods.
|
|
68
|
+
*
|
|
69
|
+
* @type {readonly string[]}
|
|
70
|
+
*/
|
|
71
|
+
const DEFAULT_SAFE_METHODS = Object.freeze(['GET', 'HEAD', 'OPTIONS']);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Default number of random bytes used to generate the token.
|
|
75
|
+
*
|
|
76
|
+
* @type {number}
|
|
77
|
+
*/
|
|
78
|
+
const DEFAULT_TOKEN_BYTES = 32;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Default cookie path attribute.
|
|
82
|
+
*
|
|
83
|
+
* @type {string}
|
|
84
|
+
*/
|
|
85
|
+
const DEFAULT_PATH = '/';
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Default Secure attribute.
|
|
89
|
+
*
|
|
90
|
+
* @type {boolean}
|
|
91
|
+
*/
|
|
92
|
+
const DEFAULT_SECURE = true;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Default SameSite attribute.
|
|
96
|
+
*
|
|
97
|
+
* @type {'Strict'|'Lax'|'None'}
|
|
98
|
+
*/
|
|
99
|
+
const DEFAULT_SAME_SITE = 'Lax';
|
|
100
|
+
|
|
101
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
102
|
+
/**
|
|
103
|
+
* Immutable value object representing normalized CSRF middleware options.
|
|
104
|
+
*
|
|
105
|
+
* @classdesc Validates and normalizes CSRF middleware options. When options
|
|
106
|
+
* are provided, defaults are applied for any omitted property. When options
|
|
107
|
+
* are omitted, the value is undefined.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```js
|
|
111
|
+
* const options = new CSRFOptionsValue({ cookieName: 'csrf' });
|
|
112
|
+
*
|
|
113
|
+
* options.value;
|
|
114
|
+
* // => {
|
|
115
|
+
* // cookieName: 'csrf',
|
|
116
|
+
* // headerName: 'x-csrf-token',
|
|
117
|
+
* // safeMethods: ['GET', 'HEAD', 'OPTIONS'],
|
|
118
|
+
* // tokenBytes: 32,
|
|
119
|
+
* // path: '/',
|
|
120
|
+
* // domain: undefined,
|
|
121
|
+
* // secure: true,
|
|
122
|
+
* // sameSite: 'Lax',
|
|
123
|
+
* // }
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```js
|
|
128
|
+
* const options = new CSRFOptionsValue();
|
|
129
|
+
*
|
|
130
|
+
* options.value;
|
|
131
|
+
* // => undefined
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
class CSRFOptionsValue {
|
|
135
|
+
/**
|
|
136
|
+
* The normalized CSRF options.
|
|
137
|
+
*
|
|
138
|
+
* @type {Readonly<CSRFOptionsData>|undefined}
|
|
139
|
+
*/
|
|
140
|
+
#value;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Creates a new {@link CSRFOptionsValue}.
|
|
144
|
+
*
|
|
145
|
+
* @param {CSRFOptionsInput} [options] - The CSRF middleware options.
|
|
146
|
+
* @throws {ServerConfigurationError} If `options` is invalid.
|
|
147
|
+
*/
|
|
148
|
+
constructor(options) {
|
|
149
|
+
assertCSRFOptions(options);
|
|
150
|
+
|
|
151
|
+
this.#value =
|
|
152
|
+
options === undefined
|
|
153
|
+
? options
|
|
154
|
+
: Object.freeze({
|
|
155
|
+
cookieName: options.cookieName ?? DEFAULT_COOKIE_NAME,
|
|
156
|
+
headerName: options.headerName ?? DEFAULT_HEADER_NAME,
|
|
157
|
+
safeMethods: Object.freeze([...(options.safeMethods ?? DEFAULT_SAFE_METHODS)]),
|
|
158
|
+
tokenBytes: options.tokenBytes ?? DEFAULT_TOKEN_BYTES,
|
|
159
|
+
path: options.path ?? DEFAULT_PATH,
|
|
160
|
+
domain: options.domain,
|
|
161
|
+
secure: options.secure ?? DEFAULT_SECURE,
|
|
162
|
+
sameSite: options.sameSite ?? DEFAULT_SAME_SITE,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The normalized CSRF options.
|
|
168
|
+
*
|
|
169
|
+
* @readonly
|
|
170
|
+
* @type {Readonly<CSRFOptionsData>|undefined}
|
|
171
|
+
*/
|
|
172
|
+
get value() {
|
|
173
|
+
return this.#value;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
178
|
+
export default CSRFOptionsValue;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Defines the CSRFOptions middleware configuration option.
|
|
4
|
+
*
|
|
5
|
+
* @description CSRFOptions wraps a normalized CSRFOptionsValue into the
|
|
6
|
+
* middleware configuration option hierarchy.
|
|
7
|
+
*
|
|
8
|
+
* @module CSRFOptions
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
12
|
+
// » IMPORT LOCAL MODULES
|
|
13
|
+
import MiddlewareOption from '../MiddlewareOption.mjs';
|
|
14
|
+
import CSRFOptionsValue from './CSRFOptionsValue.mjs';
|
|
15
|
+
|
|
16
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
/**
|
|
18
|
+
* Represents the CSRF options accepted by the server configuration.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {import('./CSRFOptionsValue.mjs').CSRFOptionsInput} CSRFOptionsInput
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Represents the normalized CSRF options.
|
|
25
|
+
*
|
|
26
|
+
* @typedef {import('./CSRFOptionsValue.mjs').CSRFOptionsData} CSRFOptionsData
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Represents one middleware configuration consumed by the exchange layer builder.
|
|
31
|
+
*
|
|
32
|
+
* @typedef {import('../MiddlewareOption.mjs').MiddlewareConfiguration} MiddlewareConfiguration
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
36
|
+
/**
|
|
37
|
+
* Middleware configuration option wrapping a normalized CSRF configuration.
|
|
38
|
+
*
|
|
39
|
+
* @augments {MiddlewareOption<CSRFOptionsData|undefined>}
|
|
40
|
+
*/
|
|
41
|
+
class CSRFOptions extends MiddlewareOption {
|
|
42
|
+
/**
|
|
43
|
+
* Creates a new {@link CSRFOptions}.
|
|
44
|
+
*
|
|
45
|
+
* @param {CSRFOptionsInput} [options] - The CSRF middleware options.
|
|
46
|
+
* @example
|
|
47
|
+
* ```js
|
|
48
|
+
* const CSRFOptions = new CSRFOptions({ cookieName: 'csrf' });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
constructor(options) {
|
|
52
|
+
super('csrf', new CSRFOptionsValue(options));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Composes the CSRF middleware configuration consumed by the exchange
|
|
57
|
+
* layer builder.
|
|
58
|
+
*
|
|
59
|
+
* CSRF protection is a cross-cutting concern applied to every request
|
|
60
|
+
* regardless of pathname or method, so both `pathnamePattern` and `method`
|
|
61
|
+
* are `undefined`.
|
|
62
|
+
*
|
|
63
|
+
* @returns {MiddlewareConfiguration} The composed middleware configuration.
|
|
64
|
+
* @example
|
|
65
|
+
* ```js
|
|
66
|
+
* const configuration = CSRFOptions.compose();
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
compose() {
|
|
70
|
+
return {
|
|
71
|
+
name: this.name,
|
|
72
|
+
options: this.value,
|
|
73
|
+
pathnamePattern: undefined,
|
|
74
|
+
method: undefined,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
80
|
+
export default CSRFOptions;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Defines the CompressOptionsValue value object.
|
|
4
|
+
*
|
|
5
|
+
* @description Normalizes and validates compression middleware configuration
|
|
6
|
+
* used by CompressMiddleware. When options are omitted, the value is
|
|
7
|
+
* undefined and the middleware is not registered.
|
|
8
|
+
*
|
|
9
|
+
* @module CompressOptionsValue
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
13
|
+
// » IMPORT LOCAL MODULES
|
|
14
|
+
import { assertCompressOptions } from '#shared/validations/ServerConfiguration/CompressOptions';
|
|
15
|
+
|
|
16
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
/**
|
|
18
|
+
* Domain error thrown by server configuration validation.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {import('#shared/errors/ServerConfigurationError').default} ServerConfigurationError
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Represents the compression options accepted by the server configuration.
|
|
25
|
+
*
|
|
26
|
+
* @typedef {object} CompressOptionsInput
|
|
27
|
+
* @property {number} [threshold] - Minimum body size in bytes required to compress. Use `0` to compress any non-empty body.
|
|
28
|
+
* @property {number} [level] - Compression level passed to the underlying zlib implementation.
|
|
29
|
+
* @property {string[]} [types] - Compressible media types. When omitted, the default allow-list is used.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Represents the normalized compression options.
|
|
34
|
+
*
|
|
35
|
+
* @typedef {object} CompressOptionsData
|
|
36
|
+
* @property {number} threshold - Minimum body size in bytes required to compress. Use `0` to compress any non-empty body.
|
|
37
|
+
* @property {number} level - Compression level passed to the underlying zlib implementation.
|
|
38
|
+
* @property {Readonly<string[]>|undefined} types - Compressible media types. When `undefined`, the default allow-list is used.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
// ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
42
|
+
/**
|
|
43
|
+
* Default minimum body size in bytes required to compress.
|
|
44
|
+
*
|
|
45
|
+
* @type {number}
|
|
46
|
+
*/
|
|
47
|
+
const DEFAULT_THRESHOLD = 1024;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Default compression level.
|
|
51
|
+
*
|
|
52
|
+
* `-1` delegates to the zlib default for the selected algorithm.
|
|
53
|
+
*
|
|
54
|
+
* @type {number}
|
|
55
|
+
*/
|
|
56
|
+
const DEFAULT_LEVEL = -1;
|
|
57
|
+
|
|
58
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
59
|
+
/**
|
|
60
|
+
* Immutable value object representing normalized compression middleware
|
|
61
|
+
* options.
|
|
62
|
+
*
|
|
63
|
+
* @classdesc Validates and normalizes compression middleware options. When
|
|
64
|
+
* options are provided, defaults are applied for any omitted property. When
|
|
65
|
+
* options are omitted, the value is undefined.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```js
|
|
69
|
+
* const options = new CompressOptionsValue({ threshold: 2048, level: 6 });
|
|
70
|
+
*
|
|
71
|
+
* options.value;
|
|
72
|
+
* // => {
|
|
73
|
+
* // threshold: 2048,
|
|
74
|
+
* // level: 6,
|
|
75
|
+
* // types: undefined,
|
|
76
|
+
* // }
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```js
|
|
81
|
+
* const options = new CompressOptionsValue();
|
|
82
|
+
*
|
|
83
|
+
* options.value;
|
|
84
|
+
* // => undefined
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
class CompressOptionsValue {
|
|
88
|
+
/**
|
|
89
|
+
* The normalized compression options.
|
|
90
|
+
*
|
|
91
|
+
* @type {Readonly<CompressOptionsData>|undefined}
|
|
92
|
+
*/
|
|
93
|
+
#value;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Creates a new {@link CompressOptionsValue}.
|
|
97
|
+
*
|
|
98
|
+
* @param {CompressOptionsInput} [options] - The compression middleware options.
|
|
99
|
+
* @throws {ServerConfigurationError} If `options` is invalid.
|
|
100
|
+
*/
|
|
101
|
+
constructor(options) {
|
|
102
|
+
assertCompressOptions(options);
|
|
103
|
+
|
|
104
|
+
this.#value =
|
|
105
|
+
options === undefined
|
|
106
|
+
? options
|
|
107
|
+
: Object.freeze({
|
|
108
|
+
threshold: options.threshold ?? DEFAULT_THRESHOLD,
|
|
109
|
+
level: options.level ?? DEFAULT_LEVEL,
|
|
110
|
+
types: options.types === undefined ? undefined : Object.freeze([...options.types]),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The normalized compression options.
|
|
116
|
+
*
|
|
117
|
+
* @readonly
|
|
118
|
+
* @type {Readonly<CompressOptionsData>|undefined}
|
|
119
|
+
*/
|
|
120
|
+
get value() {
|
|
121
|
+
return this.#value;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
126
|
+
export default CompressOptionsValue;
|