hapii-js 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/lib/config.js ADDED
@@ -0,0 +1,446 @@
1
+ 'use strict';
2
+
3
+ const Os = require('os');
4
+
5
+ const Somever = require('@hapi/somever');
6
+ const Validate = require('@hapi/validate');
7
+
8
+
9
+ const internals = {};
10
+
11
+
12
+ exports.symbol = Symbol('hapi-response');
13
+
14
+
15
+ exports.apply = function (type, options, ...message) {
16
+
17
+ const result = internals[type].validate(options);
18
+
19
+ if (result.error) {
20
+ throw new Error(`Invalid ${type} options ${message.length ? '(' + message.join(' ') + ')' : ''} ${result.error.annotate()}`);
21
+ }
22
+
23
+ return result.value;
24
+ };
25
+
26
+
27
+ exports.enable = function (options) {
28
+
29
+ const settings = options ? Object.assign({}, options) : {}; // Shallow cloned
30
+
31
+ if (settings.security === true) {
32
+ settings.security = {};
33
+ }
34
+
35
+ if (settings.cors === true) {
36
+ settings.cors = {};
37
+ }
38
+
39
+ return settings;
40
+ };
41
+
42
+ exports.versionMatch = (version, range) => Somever.match(version, range, { includePrerelease: true });
43
+
44
+ internals.access = Validate.object({
45
+ entity: Validate.valid('user', 'app', 'any'),
46
+ scope: [false, Validate.array().items(Validate.string()).single().min(1)]
47
+ });
48
+
49
+
50
+ internals.auth = Validate.alternatives([
51
+ Validate.string(),
52
+ internals.access.keys({
53
+ mode: Validate.valid('required', 'optional', 'try'),
54
+ strategy: Validate.string(),
55
+ strategies: Validate.array().items(Validate.string()).min(1),
56
+ access: Validate.array().items(internals.access.min(1)).single().min(1),
57
+ payload: [
58
+ Validate.valid('required', 'optional'),
59
+ Validate.boolean()
60
+ ]
61
+ })
62
+ .without('strategy', 'strategies')
63
+ .without('access', ['scope', 'entity'])
64
+ ]);
65
+
66
+
67
+ internals.event = Validate.object({
68
+ method: Validate.array().items(Validate.function()).single(),
69
+ options: Validate.object({
70
+ before: Validate.array().items(Validate.string()).single(),
71
+ after: Validate.array().items(Validate.string()).single(),
72
+ bind: Validate.any(),
73
+ sandbox: Validate.valid('server', 'plugin'),
74
+ timeout: Validate.number().integer().min(1)
75
+ })
76
+ .default({})
77
+ });
78
+
79
+
80
+ internals.exts = Validate.array()
81
+ .items(internals.event.keys({ type: Validate.string().required() })).single();
82
+
83
+
84
+ internals.failAction = Validate.alternatives([
85
+ Validate.valid('error', 'log', 'ignore'),
86
+ Validate.function()
87
+ ])
88
+ .default('error');
89
+
90
+
91
+ internals.routeBase = Validate.object({
92
+ app: Validate.object().allow(null),
93
+ auth: internals.auth.allow(false),
94
+ bind: Validate.object().allow(null),
95
+ cache: Validate.object({
96
+ expiresIn: Validate.number(),
97
+ expiresAt: Validate.string(),
98
+ privacy: Validate.valid('default', 'public', 'private'),
99
+ statuses: Validate.array().items(Validate.number().integer().min(200)).min(1).single().default([200, 204]),
100
+ otherwise: Validate.string().default('no-cache')
101
+ })
102
+ .allow(false)
103
+ .default(),
104
+ compression: Validate.object()
105
+ .pattern(/.+/, Validate.object())
106
+ .default(),
107
+ cors: Validate.object({
108
+ origin: Validate.array().min(1).allow('ignore').default(['*']),
109
+ maxAge: Validate.number().default(86400),
110
+ headers: Validate.array().items(Validate.string()).default(['Accept', 'Authorization', 'Content-Type', 'If-None-Match']),
111
+ additionalHeaders: Validate.array().items(Validate.string()).default([]),
112
+ exposedHeaders: Validate.array().items(Validate.string()).default(['WWW-Authenticate', 'Server-Authorization']),
113
+ additionalExposedHeaders: Validate.array().items(Validate.string()).default([]),
114
+ credentials: Validate.boolean().when('origin', { is: 'ignore', then: false }).default(false),
115
+ preflightStatusCode: Validate.valid(200, 204).default(200)
116
+ })
117
+ .allow(false, true)
118
+ .default(false),
119
+ ext: Validate.object({
120
+ onPreAuth: Validate.array().items(internals.event).single(),
121
+ onCredentials: Validate.array().items(internals.event).single(),
122
+ onPostAuth: Validate.array().items(internals.event).single(),
123
+ onPreHandler: Validate.array().items(internals.event).single(),
124
+ onPostHandler: Validate.array().items(internals.event).single(),
125
+ onPreResponse: Validate.array().items(internals.event).single(),
126
+ onPostResponse: Validate.array().items(internals.event).single()
127
+ })
128
+ .default({}),
129
+ files: Validate.object({
130
+ relativeTo: Validate.string().pattern(/^([\/\.])|([A-Za-z]:\\)|(\\\\)/).default('.')
131
+ })
132
+ .default(),
133
+ json: Validate.object({
134
+ replacer: Validate.alternatives(Validate.function(), Validate.array()).allow(null).default(null),
135
+ space: Validate.number().allow(null).default(null),
136
+ suffix: Validate.string().allow(null).default(null),
137
+ escape: Validate.boolean().default(false)
138
+ })
139
+ .default(),
140
+ log: Validate.object({
141
+ collect: Validate.boolean().default(false)
142
+ })
143
+ .default(),
144
+ payload: Validate.object({
145
+ output: Validate.valid('data', 'stream', 'file').default('data'),
146
+ parse: Validate.boolean().allow('gunzip').default(true),
147
+ multipart: Validate.object({
148
+ output: Validate.valid('data', 'stream', 'file', 'annotated').required()
149
+ })
150
+ .default(false)
151
+ .allow(true, false),
152
+ allow: Validate.array().items(Validate.string()).single(),
153
+ override: Validate.string(),
154
+ protoAction: Validate.valid('error', 'remove', 'ignore').default('error'),
155
+ maxBytes: Validate.number().integer().positive().default(1024 * 1024),
156
+ maxParts: Validate.number().integer().positive().default(1000),
157
+ uploads: Validate.string().default(Os.tmpdir()),
158
+ failAction: internals.failAction,
159
+ timeout: Validate.number().integer().positive().allow(false).default(10 * 1000),
160
+ defaultContentType: Validate.string().default('application/json'),
161
+ compression: Validate.object()
162
+ .pattern(/.+/, Validate.object())
163
+ .default()
164
+ })
165
+ .default(),
166
+ plugins: Validate.object(),
167
+ response: Validate.object({
168
+ disconnectStatusCode: Validate.number().integer().min(400).default(499),
169
+ emptyStatusCode: Validate.valid(200, 204).default(204),
170
+ failAction: internals.failAction,
171
+ modify: Validate.boolean(),
172
+ options: Validate.object(),
173
+ ranges: Validate.boolean().default(true),
174
+ sample: Validate.number().min(0).max(100).when('modify', { then: Validate.forbidden() }),
175
+ schema: Validate.alternatives(Validate.object(), Validate.array(), Validate.function()).allow(true, false),
176
+ status: Validate.object().pattern(/\d\d\d/, Validate.alternatives(Validate.object(), Validate.array(), Validate.function()).allow(true, false))
177
+ })
178
+ .default(),
179
+ security: Validate.object({
180
+ hsts: Validate.alternatives([
181
+ Validate.object({
182
+ maxAge: Validate.number(),
183
+ includeSubdomains: Validate.boolean(),
184
+ includeSubDomains: Validate.boolean(),
185
+ preload: Validate.boolean()
186
+ }),
187
+ Validate.boolean(),
188
+ Validate.number()
189
+ ])
190
+ .default(15768000),
191
+ xframe: Validate.alternatives([
192
+ Validate.boolean(),
193
+ Validate.valid('sameorigin', 'deny'),
194
+ Validate.object({
195
+ rule: Validate.valid('sameorigin', 'deny', 'allow-from'),
196
+ source: Validate.string()
197
+ })
198
+ ])
199
+ .default('deny'),
200
+ xss: Validate.valid('enabled', 'disabled', false).default('disabled'),
201
+ noOpen: Validate.boolean().default(true),
202
+ noSniff: Validate.boolean().default(true),
203
+ referrer: Validate.alternatives([
204
+ Validate.boolean().valid(false),
205
+ Validate.valid('', 'no-referrer', 'no-referrer-when-downgrade',
206
+ 'unsafe-url', 'same-origin', 'origin', 'strict-origin',
207
+ 'origin-when-cross-origin', 'strict-origin-when-cross-origin')
208
+ ])
209
+ .default(false)
210
+ })
211
+ .allow(null, false, true)
212
+ .default(false),
213
+ state: Validate.object({
214
+ parse: Validate.boolean().default(true),
215
+ failAction: internals.failAction
216
+ })
217
+ .default(),
218
+ timeout: Validate.object({
219
+ socket: Validate.number().integer().positive().allow(false),
220
+ server: Validate.number().integer().positive().allow(false).default(false)
221
+ })
222
+ .default(),
223
+ validate: Validate.object({
224
+ headers: Validate.alternatives(Validate.object(), Validate.array(), Validate.function()).allow(null, true),
225
+ params: Validate.alternatives(Validate.object(), Validate.array(), Validate.function()).allow(null, true),
226
+ query: Validate.alternatives(Validate.object(), Validate.array(), Validate.function()).allow(null, false, true),
227
+ payload: Validate.alternatives(Validate.object(), Validate.array(), Validate.function()).allow(null, false, true),
228
+ state: Validate.alternatives(Validate.object(), Validate.array(), Validate.function()).allow(null, false, true),
229
+ failAction: internals.failAction,
230
+ errorFields: Validate.object(),
231
+ options: Validate.object().default(),
232
+ validator: Validate.object()
233
+ })
234
+ .default()
235
+ });
236
+
237
+
238
+ internals.server = Validate.object({
239
+ address: Validate.string().hostname(),
240
+ app: Validate.object().allow(null),
241
+ autoListen: Validate.boolean(),
242
+ cache: Validate.allow(null), // Validated elsewhere
243
+ compression: Validate.object({
244
+ minBytes: Validate.number().min(1).integer().default(1024)
245
+ })
246
+ .allow(false)
247
+ .default(),
248
+ debug: Validate.object({
249
+ request: Validate.array().items(Validate.string()).single().allow(false).default(['implementation']),
250
+ log: Validate.array().items(Validate.string()).single().allow(false)
251
+ })
252
+ .allow(false)
253
+ .default(),
254
+ host: Validate.string().hostname().allow(null),
255
+ info: Validate.object({
256
+ remote: Validate.boolean().default(false)
257
+ })
258
+ .default({}),
259
+ listener: Validate.any(),
260
+ load: Validate.object({
261
+ sampleInterval: Validate.number().integer().min(0).default(0)
262
+ })
263
+ .unknown()
264
+ .default(),
265
+ mime: Validate.object().empty(null).default(),
266
+ operations: Validate.object({
267
+ cleanStop: Validate.boolean().default(true)
268
+ })
269
+ .default(),
270
+ plugins: Validate.object(),
271
+ port: Validate.alternatives([
272
+ Validate.number().integer().min(0), // TCP port
273
+ Validate.string().pattern(/\//), // Unix domain socket
274
+ Validate.string().pattern(/^\\\\\.\\pipe\\/) // Windows named pipe
275
+ ])
276
+ .allow(null),
277
+ query: Validate.object({
278
+ parser: Validate.function()
279
+ })
280
+ .default(),
281
+ router: Validate.object({
282
+ isCaseSensitive: Validate.boolean().default(true),
283
+ stripTrailingSlash: Validate.boolean().default(false)
284
+ })
285
+ .default(),
286
+ routes: internals.routeBase.default(),
287
+ state: Validate.object(), // Cookie defaults
288
+ tls: Validate.alternatives([
289
+ Validate.object().allow(null),
290
+ Validate.boolean()
291
+ ]),
292
+ uri: Validate.string().pattern(/[^/]$/)
293
+ });
294
+
295
+
296
+ internals.vhost = Validate.alternatives([
297
+ Validate.string().hostname(),
298
+ Validate.array().items(Validate.string().hostname()).min(1)
299
+ ]);
300
+
301
+
302
+ internals.handler = Validate.alternatives([
303
+ Validate.function(),
304
+ Validate.object().length(1)
305
+ ]);
306
+
307
+
308
+ internals.route = Validate.object({
309
+ method: Validate.string().pattern(/^[a-zA-Z0-9!#\$%&'\*\+\-\.^_`\|~]+$/).required(),
310
+ path: Validate.string().required(),
311
+ rules: Validate.object(),
312
+ vhost: internals.vhost,
313
+
314
+ // Validated in route construction
315
+
316
+ handler: Validate.any(),
317
+ options: Validate.any(),
318
+ config: Validate.any() // Backwards compatibility
319
+ })
320
+ .without('config', 'options');
321
+
322
+
323
+ internals.pre = [
324
+ Validate.function(),
325
+ Validate.object({
326
+ method: Validate.alternatives(Validate.string(), Validate.function()).required(),
327
+ assign: Validate.string(),
328
+ mode: Validate.valid('serial', 'parallel'),
329
+ failAction: internals.failAction
330
+ })
331
+ ];
332
+
333
+
334
+ internals.routeConfig = internals.routeBase.keys({
335
+ description: Validate.string(),
336
+ id: Validate.string(),
337
+ isInternal: Validate.boolean(),
338
+ notes: [
339
+ Validate.string(),
340
+ Validate.array().items(Validate.string())
341
+ ],
342
+ pre: Validate.array().items(...internals.pre.concat(Validate.array().items(...internals.pre).min(1))),
343
+ tags: [
344
+ Validate.string(),
345
+ Validate.array().items(Validate.string())
346
+ ]
347
+ });
348
+
349
+
350
+ internals.cacheConfig = Validate.alternatives([
351
+ Validate.function(),
352
+ Validate.object({
353
+ name: Validate.string().invalid('_default'),
354
+ shared: Validate.boolean(),
355
+ provider: [
356
+ Validate.function(),
357
+ {
358
+ constructor: Validate.function().required(),
359
+ options: Validate.object({
360
+ partition: Validate.string().default('hapi-cache')
361
+ })
362
+ .unknown() // Catbox client validates other keys
363
+ .default({})
364
+ }
365
+ ],
366
+ engine: Validate.object()
367
+ })
368
+ .xor('provider', 'engine')
369
+ ]);
370
+
371
+
372
+ internals.cache = Validate.array().items(internals.cacheConfig).min(1).single();
373
+
374
+
375
+ internals.cachePolicy = Validate.object({
376
+ cache: Validate.string().allow(null).allow(''),
377
+ segment: Validate.string(),
378
+ shared: Validate.boolean()
379
+ })
380
+ .unknown(); // Catbox policy validates other keys
381
+
382
+
383
+ internals.method = Validate.object({
384
+ bind: Validate.object().allow(null),
385
+ generateKey: Validate.function(),
386
+ cache: internals.cachePolicy
387
+ });
388
+
389
+
390
+ internals.methodObject = Validate.object({
391
+ name: Validate.string().required(),
392
+ method: Validate.function().required(),
393
+ options: Validate.object()
394
+ });
395
+
396
+
397
+ internals.register = Validate.object({
398
+ once: true,
399
+ routes: Validate.object({
400
+ prefix: Validate.string().pattern(/^\/.+/),
401
+ vhost: internals.vhost
402
+ })
403
+ .default({})
404
+ });
405
+
406
+
407
+ internals.semver = Validate.string();
408
+
409
+
410
+ internals.plugin = internals.register.keys({
411
+ options: Validate.any(),
412
+ plugin: Validate.object({
413
+ register: Validate.function().required(),
414
+ name: Validate.string().when('pkg.name', { is: Validate.exist(), otherwise: Validate.required() }),
415
+ version: Validate.string(),
416
+ multiple: Validate.boolean().default(false),
417
+ dependencies: [
418
+ Validate.array().items(Validate.string()).single(),
419
+ Validate.object().pattern(/.+/, internals.semver)
420
+ ],
421
+ once: true,
422
+ requirements: Validate.object({
423
+ hapi: Validate.string(),
424
+ node: Validate.string()
425
+ })
426
+ .default(),
427
+ pkg: Validate.object({
428
+ name: Validate.string(),
429
+ version: Validate.string().default('0.0.0')
430
+ })
431
+ .unknown()
432
+ .default({})
433
+ })
434
+ .unknown()
435
+ })
436
+ .without('once', 'options')
437
+ .unknown();
438
+
439
+
440
+ internals.rules = Validate.object({
441
+ validate: Validate.object({
442
+ schema: Validate.alternatives(Validate.object(), Validate.array()).required(),
443
+ options: Validate.object()
444
+ .default({ allowUnknown: true })
445
+ })
446
+ });