@depup/hapi 18.1.0-depup.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/.npmignore +4 -0
- package/LICENSE +26 -0
- package/README.md +47 -0
- package/lib/auth.js +545 -0
- package/lib/compression.js +107 -0
- package/lib/config.js +436 -0
- package/lib/core.js +680 -0
- package/lib/cors.js +207 -0
- package/lib/ext.js +94 -0
- package/lib/handler.js +165 -0
- package/lib/headers.js +200 -0
- package/lib/index.js +11 -0
- package/lib/methods.js +123 -0
- package/lib/request.js +623 -0
- package/lib/response.js +730 -0
- package/lib/route.js +519 -0
- package/lib/security.js +84 -0
- package/lib/server.js +557 -0
- package/lib/streams.js +37 -0
- package/lib/toolkit.js +201 -0
- package/lib/transmit.js +369 -0
- package/lib/validation.js +198 -0
- package/npm-shrinkwrap.json +2456 -0
- package/package.json +132 -0
package/lib/route.js
ADDED
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Assert = require('assert');
|
|
4
|
+
const Boom = require('boom');
|
|
5
|
+
const Bounce = require('bounce');
|
|
6
|
+
const Catbox = require('catbox');
|
|
7
|
+
const Hoek = require('hoek');
|
|
8
|
+
const Joi = require('joi');
|
|
9
|
+
const Subtext = require('subtext');
|
|
10
|
+
|
|
11
|
+
const Auth = require('./auth');
|
|
12
|
+
const Config = require('./config');
|
|
13
|
+
const Cors = require('./cors');
|
|
14
|
+
const Ext = require('./ext');
|
|
15
|
+
const Handler = require('./handler');
|
|
16
|
+
const Headers = require('./headers');
|
|
17
|
+
const Security = require('./security');
|
|
18
|
+
const Streams = require('./streams');
|
|
19
|
+
const Validation = require('./validation');
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
const internals = {};
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
exports = module.exports = internals.Route = class {
|
|
26
|
+
|
|
27
|
+
constructor(route, server, options = {}) {
|
|
28
|
+
|
|
29
|
+
const core = server._core;
|
|
30
|
+
const realm = server.realm;
|
|
31
|
+
|
|
32
|
+
// Routing information
|
|
33
|
+
|
|
34
|
+
Config.apply('route', route, route.method, route.path);
|
|
35
|
+
|
|
36
|
+
const method = route.method.toLowerCase();
|
|
37
|
+
Hoek.assert(method !== 'head', 'Cannot set HEAD route:', route.path);
|
|
38
|
+
|
|
39
|
+
const path = (realm.modifiers.route.prefix ? realm.modifiers.route.prefix + (route.path !== '/' ? route.path : '') : route.path);
|
|
40
|
+
Hoek.assert(path === '/' || path[path.length - 1] !== '/' || !core.settings.router.stripTrailingSlash, 'Path cannot end with a trailing slash when configured to strip:', route.method, route.path);
|
|
41
|
+
|
|
42
|
+
const vhost = (realm.modifiers.route.vhost || route.vhost);
|
|
43
|
+
|
|
44
|
+
// Set identifying members (assert)
|
|
45
|
+
|
|
46
|
+
this.method = method;
|
|
47
|
+
this.path = path;
|
|
48
|
+
|
|
49
|
+
// Prepare configuration
|
|
50
|
+
|
|
51
|
+
let config = route.options || route.config || {};
|
|
52
|
+
if (typeof config === 'function') {
|
|
53
|
+
config = config.call(realm.settings.bind, server);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
config = Config.enable(config); // Shallow clone
|
|
57
|
+
|
|
58
|
+
// Verify route level config (as opposed to the merged settings)
|
|
59
|
+
|
|
60
|
+
this._assert(method !== 'get' || !config.payload, 'Cannot set payload settings on HEAD or GET request');
|
|
61
|
+
this._assert(method !== 'get' || !config.validate || !config.validate.payload, 'Cannot validate HEAD or GET request payload');
|
|
62
|
+
|
|
63
|
+
// Rules
|
|
64
|
+
|
|
65
|
+
this._assert(!route.rules || !config.rules, 'Route rules can only appear once'); // XOR
|
|
66
|
+
const rules = (route.rules || config.rules);
|
|
67
|
+
const rulesConfig = internals.rules(rules, { method, path, vhost }, server);
|
|
68
|
+
delete config.rules;
|
|
69
|
+
|
|
70
|
+
// Handler
|
|
71
|
+
|
|
72
|
+
this._assert(route.handler || config.handler, 'Missing or undefined handler');
|
|
73
|
+
this._assert(!!route.handler ^ !!config.handler, 'Handler must only appear once'); // XOR
|
|
74
|
+
|
|
75
|
+
const handler = Config.apply('handler', route.handler || config.handler);
|
|
76
|
+
delete config.handler;
|
|
77
|
+
|
|
78
|
+
const handlerDefaults = Handler.defaults(method, handler, core);
|
|
79
|
+
|
|
80
|
+
// Apply settings in order: server <- handler <- realm <- route
|
|
81
|
+
|
|
82
|
+
const settings = internals.config([core.settings.routes, handlerDefaults, realm.settings, rulesConfig, config]);
|
|
83
|
+
this.settings = Config.apply('routeConfig', settings, method, path);
|
|
84
|
+
|
|
85
|
+
// Validate timeouts
|
|
86
|
+
|
|
87
|
+
const socketTimeout = (this.settings.timeout.socket === undefined ? 2 * 60 * 1000 : this.settings.timeout.socket);
|
|
88
|
+
this._assert(!this.settings.timeout.server || !socketTimeout || this.settings.timeout.server < socketTimeout, 'Server timeout must be shorter than socket timeout');
|
|
89
|
+
this._assert(!this.settings.payload.timeout || !socketTimeout || this.settings.payload.timeout < socketTimeout, 'Payload timeout must be shorter than socket timeout');
|
|
90
|
+
|
|
91
|
+
// Route members
|
|
92
|
+
|
|
93
|
+
this._core = core;
|
|
94
|
+
this.realm = realm;
|
|
95
|
+
|
|
96
|
+
this.settings.vhost = vhost;
|
|
97
|
+
this.settings.plugins = this.settings.plugins || {}; // Route-specific plugins settings, namespaced using plugin name
|
|
98
|
+
this.settings.app = this.settings.app || {}; // Route-specific application settings
|
|
99
|
+
|
|
100
|
+
// Path parsing
|
|
101
|
+
|
|
102
|
+
this._special = !!options.special;
|
|
103
|
+
this._analysis = this._core.router.analyze(this.path);
|
|
104
|
+
this.params = this._analysis.params;
|
|
105
|
+
this.fingerprint = this._analysis.fingerprint;
|
|
106
|
+
|
|
107
|
+
this.public = {
|
|
108
|
+
method: this.method,
|
|
109
|
+
path: this.path,
|
|
110
|
+
vhost,
|
|
111
|
+
realm,
|
|
112
|
+
settings: this.settings,
|
|
113
|
+
fingerprint: this.fingerprint,
|
|
114
|
+
auth: {
|
|
115
|
+
access: (request) => Auth.testAccess(request, this.public)
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Validation
|
|
120
|
+
|
|
121
|
+
this._setupValidation();
|
|
122
|
+
|
|
123
|
+
// Payload parsing
|
|
124
|
+
|
|
125
|
+
if (this.method === 'get') {
|
|
126
|
+
this.settings.payload = null;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
this.settings.payload.decoders = this._core.compression._decoders; // Reference the shared object to keep up to date
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this._assert(!this.settings.validate.payload || this.settings.payload.parse, 'Route payload must be set to \'parse\' when payload validation enabled');
|
|
133
|
+
this._assert(!this.settings.validate.state || this.settings.state.parse, 'Route state must be set to \'parse\' when state validation enabled');
|
|
134
|
+
this._assert(!this.settings.jsonp || typeof this.settings.jsonp === 'string', 'Bad route JSONP parameter name');
|
|
135
|
+
|
|
136
|
+
// Authentication configuration
|
|
137
|
+
|
|
138
|
+
this.settings.auth = (this._special ? false : this._core.auth._setupRoute(this.settings.auth, path));
|
|
139
|
+
|
|
140
|
+
// Cache
|
|
141
|
+
|
|
142
|
+
if (this.method === 'get' &&
|
|
143
|
+
typeof this.settings.cache === 'object' &&
|
|
144
|
+
(this.settings.cache.expiresIn || this.settings.cache.expiresAt)) {
|
|
145
|
+
|
|
146
|
+
this.settings.cache._statuses = new Set(this.settings.cache.statuses);
|
|
147
|
+
this._cache = new Catbox.Policy({ expiresIn: this.settings.cache.expiresIn, expiresAt: this.settings.cache.expiresAt });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// CORS
|
|
151
|
+
|
|
152
|
+
this.settings.cors = Cors.route(this.settings.cors);
|
|
153
|
+
|
|
154
|
+
// Security
|
|
155
|
+
|
|
156
|
+
this.settings.security = Security.route(this.settings.security);
|
|
157
|
+
|
|
158
|
+
// Handler
|
|
159
|
+
|
|
160
|
+
this.settings.handler = Handler.configure(handler, this);
|
|
161
|
+
this._prerequisites = Handler.prerequisitesConfig(this.settings.pre);
|
|
162
|
+
|
|
163
|
+
// Route lifecycle
|
|
164
|
+
|
|
165
|
+
this._extensions = {
|
|
166
|
+
onPreResponse: Ext.combine(this, 'onPreResponse')
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
if (this._special) {
|
|
170
|
+
this._cycle = [internals.drain, Handler.execute];
|
|
171
|
+
this.rebuild();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this._extensions.onPreAuth = Ext.combine(this, 'onPreAuth');
|
|
176
|
+
this._extensions.onCredentials = Ext.combine(this, 'onCredentials');
|
|
177
|
+
this._extensions.onPostAuth = Ext.combine(this, 'onPostAuth');
|
|
178
|
+
this._extensions.onPreHandler = Ext.combine(this, 'onPreHandler');
|
|
179
|
+
this._extensions.onPostHandler = Ext.combine(this, 'onPostHandler');
|
|
180
|
+
|
|
181
|
+
this.rebuild();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
_setupValidation() {
|
|
185
|
+
|
|
186
|
+
const validation = this.settings.validate;
|
|
187
|
+
if (this.method === 'get') {
|
|
188
|
+
validation.payload = null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this._assert(!validation.params || this.params.length, 'Cannot set path parameters validations without path parameters');
|
|
192
|
+
|
|
193
|
+
['headers', 'params', 'query', 'payload', 'state'].forEach((type) => {
|
|
194
|
+
|
|
195
|
+
validation[type] = Validation.compile(validation[type]);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
if (this.settings.response.schema !== undefined ||
|
|
199
|
+
this.settings.response.status) {
|
|
200
|
+
|
|
201
|
+
this.settings.response._validate = true;
|
|
202
|
+
|
|
203
|
+
const rule = this.settings.response.schema;
|
|
204
|
+
this.settings.response.status = this.settings.response.status || {};
|
|
205
|
+
const statuses = Object.keys(this.settings.response.status);
|
|
206
|
+
|
|
207
|
+
if (rule === true &&
|
|
208
|
+
!statuses.length) {
|
|
209
|
+
|
|
210
|
+
this.settings.response._validate = false;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
this.settings.response.schema = Validation.compile(rule);
|
|
214
|
+
for (const code of statuses) {
|
|
215
|
+
this.settings.response.status[code] = Validation.compile(this.settings.response.status[code]);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
rebuild(event) {
|
|
222
|
+
|
|
223
|
+
if (event) {
|
|
224
|
+
this._extensions[event.type].add(event);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (this._special) {
|
|
228
|
+
this._postCycle = (this._extensions.onPreResponse.nodes ? [this._extensions.onPreResponse] : []);
|
|
229
|
+
this._buildMarshalCycle();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Build lifecycle array
|
|
234
|
+
|
|
235
|
+
this._cycle = [];
|
|
236
|
+
|
|
237
|
+
// 'onRequest'
|
|
238
|
+
|
|
239
|
+
if (this.settings.jsonp) {
|
|
240
|
+
this._cycle.push(internals.parseJSONP);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (this.settings.state.parse) {
|
|
244
|
+
this._cycle.push(internals.state);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (this._extensions.onPreAuth.nodes) {
|
|
248
|
+
this._cycle.push(this._extensions.onPreAuth);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (this._core.auth._enabled(this, 'authenticate')) {
|
|
252
|
+
this._cycle.push(Auth.authenticate);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (this.method !== 'get') {
|
|
256
|
+
this._cycle.push(internals.payload);
|
|
257
|
+
|
|
258
|
+
if (this._core.auth._enabled(this, 'payload')) {
|
|
259
|
+
this._cycle.push(Auth.payload);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (this._core.auth._enabled(this, 'authenticate') &&
|
|
264
|
+
this._extensions.onCredentials.nodes) {
|
|
265
|
+
|
|
266
|
+
this._cycle.push(this._extensions.onCredentials);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (this._core.auth._enabled(this, 'access')) {
|
|
270
|
+
this._cycle.push(Auth.access);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (this._extensions.onPostAuth.nodes) {
|
|
274
|
+
this._cycle.push(this._extensions.onPostAuth);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (this.settings.validate.headers) {
|
|
278
|
+
this._cycle.push(Validation.headers);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (this.settings.validate.params) {
|
|
282
|
+
this._cycle.push(Validation.params);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (this.settings.jsonp) {
|
|
286
|
+
this._cycle.push(internals.cleanupJSONP);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (this.settings.validate.query) {
|
|
290
|
+
this._cycle.push(Validation.query);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (this.settings.validate.payload) {
|
|
294
|
+
this._cycle.push(Validation.payload);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (this.settings.validate.state) {
|
|
298
|
+
this._cycle.push(Validation.state);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (this._extensions.onPreHandler.nodes) {
|
|
302
|
+
this._cycle.push(this._extensions.onPreHandler);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
this._cycle.push(Handler.execute);
|
|
306
|
+
|
|
307
|
+
if (this._extensions.onPostHandler.nodes) {
|
|
308
|
+
this._cycle.push(this._extensions.onPostHandler);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
this._postCycle = [];
|
|
312
|
+
|
|
313
|
+
if (this.settings.response._validate &&
|
|
314
|
+
this.settings.response.sample !== 0) {
|
|
315
|
+
|
|
316
|
+
this._postCycle.push(Validation.response);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (this._extensions.onPreResponse.nodes) {
|
|
320
|
+
this._postCycle.push(this._extensions.onPreResponse);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
this._buildMarshalCycle();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
_buildMarshalCycle() {
|
|
327
|
+
|
|
328
|
+
this._marshalCycle = [Headers.type];
|
|
329
|
+
|
|
330
|
+
if (this.settings.cors) {
|
|
331
|
+
this._marshalCycle.push(Cors.headers);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (this.settings.security) {
|
|
335
|
+
this._marshalCycle.push(Security.headers);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
this._marshalCycle.push(Headers.entity);
|
|
339
|
+
|
|
340
|
+
if (this.method === 'get' ||
|
|
341
|
+
this.method === '*') {
|
|
342
|
+
|
|
343
|
+
this._marshalCycle.push(Headers.unmodified);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
this._marshalCycle.push(Headers.cache);
|
|
347
|
+
this._marshalCycle.push(Headers.state);
|
|
348
|
+
this._marshalCycle.push(Headers.content);
|
|
349
|
+
|
|
350
|
+
if (this._core.auth._enabled(this, 'response')) {
|
|
351
|
+
this._marshalCycle.push(Auth.response); // Must be last in case requires access to headers
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
_assert(condition, message) {
|
|
356
|
+
|
|
357
|
+
if (condition) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (this.method[0] !== '_') {
|
|
362
|
+
message = `${message}: ${this.method.toUpperCase()} ${this.path}`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
throw new Assert.AssertionError({
|
|
366
|
+
message,
|
|
367
|
+
actual: false,
|
|
368
|
+
expected: true,
|
|
369
|
+
operator: '==',
|
|
370
|
+
stackStartFunction: this._assert
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
internals.state = async function (request) {
|
|
377
|
+
|
|
378
|
+
request.state = {};
|
|
379
|
+
|
|
380
|
+
const req = request.raw.req;
|
|
381
|
+
const cookies = req.headers.cookie;
|
|
382
|
+
if (!cookies) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
var result = await request._core.states.parse(cookies);
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
Bounce.rethrow(err, 'system');
|
|
391
|
+
var parseError = err;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const { states, failed = [] } = result || parseError;
|
|
395
|
+
request.state = states || {};
|
|
396
|
+
|
|
397
|
+
// Clear cookies
|
|
398
|
+
|
|
399
|
+
for (const item of failed) {
|
|
400
|
+
if (item.settings.clearInvalid) {
|
|
401
|
+
request._clearState(item.name);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (!parseError) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
parseError.header = cookies;
|
|
410
|
+
|
|
411
|
+
return request._core.toolkit.failAction(request, request.route.settings.state.failAction, parseError, { tags: ['state', 'error'] });
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
internals.payload = async function (request) {
|
|
416
|
+
|
|
417
|
+
if (request.method === 'get' ||
|
|
418
|
+
request.method === 'head') { // When route.method is '*'
|
|
419
|
+
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (request._expectContinue) {
|
|
424
|
+
request.raw.res.writeContinue();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
const { payload, mime } = await Subtext.parse(request.raw.req, request._tap(), request.route.settings.payload);
|
|
429
|
+
|
|
430
|
+
request._isPayloadPending = !!(payload && payload._readableState);
|
|
431
|
+
request.mime = mime;
|
|
432
|
+
request.payload = payload;
|
|
433
|
+
}
|
|
434
|
+
catch (err) {
|
|
435
|
+
Bounce.rethrow(err, 'system');
|
|
436
|
+
|
|
437
|
+
if (request._isPayloadPending) {
|
|
438
|
+
await internals.drain(request);
|
|
439
|
+
request._isPayloadPending = false;
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
request._isPayloadPending = true;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
request.mime = err.mime;
|
|
446
|
+
request.payload = null;
|
|
447
|
+
|
|
448
|
+
return request._core.toolkit.failAction(request, request.route.settings.payload.failAction, err, { tags: ['payload', 'error'] });
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
internals.drain = async function (request) {
|
|
454
|
+
|
|
455
|
+
// Flush out any pending request payload not consumed due to errors
|
|
456
|
+
|
|
457
|
+
await Streams.drain(request.raw.req);
|
|
458
|
+
request._isPayloadPending = false;
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
internals.jsonpRegex = /^[\w\$\[\]\.]+$/;
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
internals.parseJSONP = function (request) {
|
|
466
|
+
|
|
467
|
+
const jsonp = request.query[request.route.settings.jsonp];
|
|
468
|
+
if (jsonp) {
|
|
469
|
+
if (internals.jsonpRegex.test(jsonp) === false) {
|
|
470
|
+
throw Boom.badRequest('Invalid JSONP parameter value');
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
request.jsonp = jsonp;
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
internals.cleanupJSONP = function (request) {
|
|
479
|
+
|
|
480
|
+
if (request.jsonp) {
|
|
481
|
+
delete request.query[request.route.settings.jsonp];
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
internals.config = function (chain) {
|
|
487
|
+
|
|
488
|
+
if (!chain.length) {
|
|
489
|
+
return {};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
let config = chain[0];
|
|
493
|
+
for (const item of chain) {
|
|
494
|
+
config = Hoek.applyToDefaultsWithShallow(config, item, ['bind', 'validate.headers', 'validate.payload', 'validate.params', 'validate.query', 'validate.state']);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return config;
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
internals.rules = function (rules, info, server) {
|
|
502
|
+
|
|
503
|
+
const configs = [];
|
|
504
|
+
|
|
505
|
+
let realm = server.realm;
|
|
506
|
+
while (realm) {
|
|
507
|
+
if (realm._rules) {
|
|
508
|
+
const source = (!realm._rules.settings.validate ? rules : Joi.attempt(rules, realm._rules.settings.validate.schema, realm._rules.settings.validate.options));
|
|
509
|
+
const config = realm._rules.processor(source, info);
|
|
510
|
+
if (config) {
|
|
511
|
+
configs.unshift(config);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
realm = realm.parent;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return internals.config(configs);
|
|
519
|
+
};
|
package/lib/security.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const internals = {};
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
exports.route = function (settings) {
|
|
7
|
+
|
|
8
|
+
if (!settings) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const security = settings;
|
|
13
|
+
if (security.hsts) {
|
|
14
|
+
if (security.hsts === true) {
|
|
15
|
+
security._hsts = 'max-age=15768000';
|
|
16
|
+
}
|
|
17
|
+
else if (typeof security.hsts === 'number') {
|
|
18
|
+
security._hsts = 'max-age=' + security.hsts;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
security._hsts = 'max-age=' + (security.hsts.maxAge || 15768000);
|
|
22
|
+
if (security.hsts.includeSubdomains || security.hsts.includeSubDomains) {
|
|
23
|
+
security._hsts = security._hsts + '; includeSubDomains';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (security.hsts.preload) {
|
|
27
|
+
security._hsts = security._hsts + '; preload';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (security.xframe) {
|
|
33
|
+
if (security.xframe === true) {
|
|
34
|
+
security._xframe = 'DENY';
|
|
35
|
+
}
|
|
36
|
+
else if (typeof security.xframe === 'string') {
|
|
37
|
+
security._xframe = security.xframe.toUpperCase();
|
|
38
|
+
}
|
|
39
|
+
else if (security.xframe.rule === 'allow-from') {
|
|
40
|
+
if (!security.xframe.source) {
|
|
41
|
+
security._xframe = 'SAMEORIGIN';
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
security._xframe = 'ALLOW-FROM ' + security.xframe.source;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
security._xframe = security.xframe.rule.toUpperCase();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return security;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
exports.headers = function (request) {
|
|
57
|
+
|
|
58
|
+
const response = request.response;
|
|
59
|
+
const security = response.request.route.settings.security;
|
|
60
|
+
|
|
61
|
+
if (security._hsts) {
|
|
62
|
+
response._header('strict-transport-security', security._hsts, { override: false });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (security._xframe) {
|
|
66
|
+
response._header('x-frame-options', security._xframe, { override: false });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (security.xss) {
|
|
70
|
+
response._header('x-xss-protection', '1; mode=block', { override: false });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (security.noOpen) {
|
|
74
|
+
response._header('x-download-options', 'noopen', { override: false });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (security.noSniff) {
|
|
78
|
+
response._header('x-content-type-options', 'nosniff', { override: false });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (security.referrer !== false) {
|
|
82
|
+
response._header('referrer-policy', security.referrer, { override: false });
|
|
83
|
+
}
|
|
84
|
+
};
|