@boo-labs/server 1.0.1 → 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/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
|
* Property names recognized as security header options.
|
|
@@ -50,6 +64,7 @@ function isPlainObject(value) {
|
|
|
50
64
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
51
65
|
}
|
|
52
66
|
|
|
67
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
53
68
|
/**
|
|
54
69
|
* Validates security headers options.
|
|
55
70
|
*
|
|
@@ -63,7 +78,8 @@ function isPlainObject(value) {
|
|
|
63
78
|
* Unknown properties are ignored.
|
|
64
79
|
*
|
|
65
80
|
* @param {unknown} options - Security headers options to validate.
|
|
66
|
-
* @returns {
|
|
81
|
+
* @returns {SuccessValidation|FailureValidation<ServerConfigurationError>} Validation result.
|
|
82
|
+
*
|
|
67
83
|
* @example
|
|
68
84
|
* ```js
|
|
69
85
|
* const validation = validateSecurityHeadersOptions({ xFrameOptions: 'DENY' });
|
|
@@ -86,12 +102,11 @@ export function validateSecurityHeadersOptions(options) {
|
|
|
86
102
|
}
|
|
87
103
|
|
|
88
104
|
if (!isPlainObject(options)) {
|
|
89
|
-
return Validation.failure(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
105
|
+
return Validation.failure(
|
|
106
|
+
new ServerConfigurationError('INVALID_SECURITY_HEADERS_OPTIONS', {
|
|
107
|
+
values: { received: typeof options },
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
95
110
|
}
|
|
96
111
|
|
|
97
112
|
for (const key of SECURITY_HEADER_KEYS) {
|
|
@@ -102,15 +117,12 @@ export function validateSecurityHeadersOptions(options) {
|
|
|
102
117
|
const value = options[key];
|
|
103
118
|
|
|
104
119
|
if (typeof value !== 'string' && value !== false) {
|
|
105
|
-
return Validation.failure(
|
|
106
|
-
|
|
107
|
-
code: 'INVALID_SECURITY_HEADERS_OPTION_TYPE',
|
|
108
|
-
custom: true,
|
|
109
|
-
options: {
|
|
120
|
+
return Validation.failure(
|
|
121
|
+
new ServerConfigurationError('INVALID_SECURITY_HEADERS_OPTION_TYPE', {
|
|
110
122
|
values: { key, received: typeof value },
|
|
111
123
|
details: { key, value },
|
|
112
|
-
},
|
|
113
|
-
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
114
126
|
}
|
|
115
127
|
}
|
|
116
128
|
|
|
@@ -123,6 +135,7 @@ export function validateSecurityHeadersOptions(options) {
|
|
|
123
135
|
* @param {unknown} options - Security headers options to validate.
|
|
124
136
|
* @throws {ServerConfigurationError} If `options` is not a plain object.
|
|
125
137
|
* @throws {ServerConfigurationError} If a recognized security header option is not a string or `false`.
|
|
138
|
+
*
|
|
126
139
|
* @example
|
|
127
140
|
* ```js
|
|
128
141
|
* assertSecurityHeadersOptions({ xFrameOptions: 'DENY' });
|
|
@@ -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
|
// ━━ FUNCTIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
18
32
|
/**
|
|
19
33
|
* Determines whether a value is a non-null plain object.
|
|
@@ -26,6 +40,7 @@ function isPlainObject(value) {
|
|
|
26
40
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
27
41
|
}
|
|
28
42
|
|
|
43
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
29
44
|
/**
|
|
30
45
|
* Validates static middleware options.
|
|
31
46
|
*
|
|
@@ -43,7 +58,8 @@ function isPlainObject(value) {
|
|
|
43
58
|
* 10. `dotfiles`, when present, must be a boolean.
|
|
44
59
|
*
|
|
45
60
|
* @param {unknown} options - Static options to validate.
|
|
46
|
-
* @returns {
|
|
61
|
+
* @returns {SuccessValidation|FailureValidation<ServerConfigurationError>} Validation result.
|
|
62
|
+
*
|
|
47
63
|
* @example
|
|
48
64
|
* ```js
|
|
49
65
|
* const validation = validateStaticOptions({ root: '/var/www/public' });
|
|
@@ -66,41 +82,34 @@ export function validateStaticOptions(options) {
|
|
|
66
82
|
}
|
|
67
83
|
|
|
68
84
|
if (!isPlainObject(options)) {
|
|
69
|
-
return Validation.failure(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
});
|
|
85
|
+
return Validation.failure(
|
|
86
|
+
new ServerConfigurationError('INVALID_STATIC_OPTIONS', {
|
|
87
|
+
values: { received: typeof options },
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
75
90
|
}
|
|
76
91
|
|
|
77
92
|
if ('root' in options === false) {
|
|
78
|
-
return Validation.failure(
|
|
79
|
-
error: ServerConfigurationError,
|
|
80
|
-
code: 'MISSING_STATIC_ROOT',
|
|
81
|
-
custom: true,
|
|
82
|
-
});
|
|
93
|
+
return Validation.failure(new ServerConfigurationError('MISSING_STATIC_ROOT'));
|
|
83
94
|
}
|
|
84
95
|
|
|
85
96
|
if ('root' in options) {
|
|
86
97
|
const { root } = options;
|
|
87
98
|
|
|
88
99
|
if (typeof root !== 'string') {
|
|
89
|
-
return Validation.failure(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
100
|
+
return Validation.failure(
|
|
101
|
+
new ServerConfigurationError('INVALID_STATIC_ROOT_TYPE', {
|
|
102
|
+
values: { received: typeof root },
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
if (root.length === 0) {
|
|
98
|
-
return Validation.failure(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
});
|
|
108
|
+
return Validation.failure(
|
|
109
|
+
new ServerConfigurationError('INVALID_STATIC_ROOT_VALUE', {
|
|
110
|
+
details: { root },
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
104
113
|
}
|
|
105
114
|
}
|
|
106
115
|
|
|
@@ -108,22 +117,20 @@ export function validateStaticOptions(options) {
|
|
|
108
117
|
const { index } = options;
|
|
109
118
|
|
|
110
119
|
if (!Array.isArray(index)) {
|
|
111
|
-
return Validation.failure(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
});
|
|
120
|
+
return Validation.failure(
|
|
121
|
+
new ServerConfigurationError('INVALID_STATIC_INDEX_TYPE', {
|
|
122
|
+
values: { received: typeof index },
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
117
125
|
}
|
|
118
126
|
|
|
119
127
|
for (const item of index) {
|
|
120
128
|
if (typeof item !== 'string' || item.length === 0) {
|
|
121
|
-
return Validation.failure(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
});
|
|
129
|
+
return Validation.failure(
|
|
130
|
+
new ServerConfigurationError('INVALID_STATIC_INDEX_ITEM', {
|
|
131
|
+
details: { item },
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
127
134
|
}
|
|
128
135
|
}
|
|
129
136
|
}
|
|
@@ -132,54 +139,50 @@ export function validateStaticOptions(options) {
|
|
|
132
139
|
const { maxAge } = options;
|
|
133
140
|
|
|
134
141
|
if (typeof maxAge !== 'number') {
|
|
135
|
-
return Validation.failure(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
});
|
|
142
|
+
return Validation.failure(
|
|
143
|
+
new ServerConfigurationError('INVALID_STATIC_MAX_AGE_TYPE', {
|
|
144
|
+
values: { received: typeof maxAge },
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
141
147
|
}
|
|
142
148
|
|
|
143
149
|
if (!Number.isInteger(maxAge) || maxAge < 0) {
|
|
144
|
-
return Validation.failure(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
return Validation.failure(
|
|
151
|
+
new ServerConfigurationError('INVALID_STATIC_MAX_AGE_VALUE', {
|
|
152
|
+
values: { maxAge },
|
|
153
|
+
details: { maxAge },
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
150
156
|
}
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
if ('etag' in options) {
|
|
154
160
|
if (typeof options.etag !== 'boolean') {
|
|
155
|
-
return Validation.failure(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
});
|
|
161
|
+
return Validation.failure(
|
|
162
|
+
new ServerConfigurationError('INVALID_STATIC_ETAG_TYPE', {
|
|
163
|
+
values: { received: typeof options.etag },
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
161
166
|
}
|
|
162
167
|
}
|
|
163
168
|
|
|
164
169
|
if ('lastModified' in options) {
|
|
165
170
|
if (typeof options.lastModified !== 'boolean') {
|
|
166
|
-
return Validation.failure(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
});
|
|
171
|
+
return Validation.failure(
|
|
172
|
+
new ServerConfigurationError('INVALID_STATIC_LAST_MODIFIED_TYPE', {
|
|
173
|
+
values: { received: typeof options.lastModified },
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
172
176
|
}
|
|
173
177
|
}
|
|
174
178
|
|
|
175
179
|
if ('fallthrough' in options) {
|
|
176
180
|
if (typeof options.fallthrough !== 'boolean') {
|
|
177
|
-
return Validation.failure(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
});
|
|
181
|
+
return Validation.failure(
|
|
182
|
+
new ServerConfigurationError('INVALID_STATIC_FALLTHROUGH_TYPE', {
|
|
183
|
+
values: { received: typeof options.fallthrough },
|
|
184
|
+
}),
|
|
185
|
+
);
|
|
183
186
|
}
|
|
184
187
|
}
|
|
185
188
|
|
|
@@ -187,34 +190,31 @@ export function validateStaticOptions(options) {
|
|
|
187
190
|
const { extensions } = options;
|
|
188
191
|
|
|
189
192
|
if (!Array.isArray(extensions)) {
|
|
190
|
-
return Validation.failure(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
});
|
|
193
|
+
return Validation.failure(
|
|
194
|
+
new ServerConfigurationError('INVALID_STATIC_EXTENSIONS_TYPE', {
|
|
195
|
+
values: { received: typeof extensions },
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
196
198
|
}
|
|
197
199
|
|
|
198
200
|
for (const item of extensions) {
|
|
199
201
|
if (typeof item !== 'string' || item.length === 0) {
|
|
200
|
-
return Validation.failure(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
});
|
|
202
|
+
return Validation.failure(
|
|
203
|
+
new ServerConfigurationError('INVALID_STATIC_EXTENSIONS_ITEM', {
|
|
204
|
+
details: { item },
|
|
205
|
+
}),
|
|
206
|
+
);
|
|
206
207
|
}
|
|
207
208
|
}
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
if ('dotfiles' in options) {
|
|
211
212
|
if (typeof options.dotfiles !== 'boolean') {
|
|
212
|
-
return Validation.failure(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
});
|
|
213
|
+
return Validation.failure(
|
|
214
|
+
new ServerConfigurationError('INVALID_STATIC_DOTFILES_TYPE', {
|
|
215
|
+
values: { received: typeof options.dotfiles },
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
220
|
|
|
@@ -234,6 +234,7 @@ export function validateStaticOptions(options) {
|
|
|
234
234
|
* @throws {ServerConfigurationError} If `fallthrough` is not a boolean.
|
|
235
235
|
* @throws {ServerConfigurationError} If `extensions` is not an array of non-empty strings.
|
|
236
236
|
* @throws {ServerConfigurationError} If `dotfiles` is not a boolean.
|
|
237
|
+
*
|
|
237
238
|
* @example
|
|
238
239
|
* ```js
|
|
239
240
|
* assertStaticOptions({ root: '/var/www/public' });
|