@hapi/hapi
Advanced tools
Comparing version 18.4.0 to 19.0.0
102
lib/auth.js
@@ -17,12 +17,14 @@ 'use strict'; | ||
#core = null; | ||
#schemes = {}; | ||
#strategies = {}; | ||
api = {}; | ||
settings = { | ||
default: null // Strategy used as default if route has no auth settings | ||
}; | ||
constructor(core) { | ||
this._core = core; | ||
this._schemes = {}; | ||
this._strategies = {}; | ||
this.settings = { | ||
default: null // Strategy used as default if route has no auth settings | ||
}; | ||
this.api = {}; | ||
this.#core = core; | ||
} | ||
@@ -33,6 +35,6 @@ | ||
Hoek.assert(name, 'Authentication scheme must have a name'); | ||
Hoek.assert(!this._schemes[name], 'Authentication scheme name already exists:', name); | ||
Hoek.assert(!this.#schemes[name], 'Authentication scheme name already exists:', name); | ||
Hoek.assert(typeof scheme === 'function', 'scheme must be a function:', name); | ||
this._schemes[name] = scheme; | ||
this.#schemes[name] = scheme; | ||
} | ||
@@ -44,8 +46,8 @@ | ||
Hoek.assert(typeof options === 'object', 'options must be an object'); | ||
Hoek.assert(!this._strategies[name], 'Authentication strategy name already exists'); | ||
Hoek.assert(!this.#strategies[name], 'Authentication strategy name already exists'); | ||
Hoek.assert(scheme, 'Authentication strategy', name, 'missing scheme'); | ||
Hoek.assert(this._schemes[scheme], 'Authentication strategy', name, 'uses unknown scheme:', scheme); | ||
Hoek.assert(this.#schemes[scheme], 'Authentication strategy', name, 'uses unknown scheme:', scheme); | ||
server = server._clone(); | ||
const strategy = this._schemes[scheme](server, options); | ||
const strategy = this.#schemes[scheme](server, options); | ||
@@ -59,3 +61,3 @@ Hoek.assert(strategy.authenticate, 'Invalid scheme:', name, 'missing authenticate() method'); | ||
this._strategies[name] = { | ||
this.#strategies[name] = { | ||
methods: strategy, | ||
@@ -77,3 +79,3 @@ realm: server.realm | ||
const routes = this._core.router.table(); | ||
const routes = this.#core.router.table(); | ||
for (const route of routes) { | ||
@@ -87,3 +89,3 @@ route.rebuild(); | ||
Hoek.assert(name, 'Missing authentication strategy name'); | ||
const strategy = this._strategies[name]; | ||
const strategy = this.#strategies[name]; | ||
Hoek.assert(strategy, 'Unknown authentication strategy:', name); | ||
@@ -118,3 +120,3 @@ | ||
const strategy = this._strategies[auth.strategy]; | ||
const strategy = this.#strategies[auth.strategy]; | ||
Hoek.assert(strategy, 'Unknown authentication strategy:', auth.strategy); | ||
@@ -189,3 +191,3 @@ | ||
for (const name of options.strategies) { | ||
const strategy = this._strategies[name]; | ||
const strategy = this.#strategies[name]; | ||
Hoek.assert(strategy, 'Unknown authentication strategy', name, 'in', path); | ||
@@ -228,3 +230,3 @@ | ||
for (const name of config.strategies) { | ||
const strategy = this._strategies[name]; | ||
const strategy = this.#strategies[name]; | ||
if (strategy.methods[type]) { | ||
@@ -261,3 +263,3 @@ return true; | ||
for (const name of config.strategies) { | ||
const strategy = this._strategies[name]; | ||
const strategy = this.#strategies[name]; | ||
@@ -377,3 +379,3 @@ const bind = strategy.methods; | ||
const auth = request._core.auth; | ||
const strategy = auth._strategies[request.auth.strategy]; | ||
const strategy = auth.#strategies[request.auth.strategy]; | ||
Hoek.assert(strategy, 'Unknown authentication strategy:', request.auth.strategy); | ||
@@ -398,3 +400,3 @@ | ||
return (setting === 'optional' ? undefined : Boom.unauthorized('Missing payload authentication')); | ||
return setting === 'optional' ? undefined : Boom.unauthorized('Missing payload authentication'); | ||
} | ||
@@ -412,3 +414,3 @@ | ||
const strategy = auth._strategies[request.auth.strategy]; | ||
const strategy = auth.#strategies[request.auth.strategy]; | ||
Hoek.assert(strategy, 'Unknown authentication strategy:', request.auth.strategy); | ||
@@ -439,4 +441,4 @@ | ||
const prefix = value[0]; | ||
const type = (prefix === '+' ? 'required' : (prefix === '!' ? 'forbidden' : 'selection')); | ||
const clean = (type === 'selection' ? value : value.slice(1)); | ||
const type = prefix === '+' ? 'required' : (prefix === '!' ? 'forbidden' : 'selection'); | ||
const clean = type === 'selection' ? value : value.slice(1); | ||
scope[type] = scope[type] || []; | ||
@@ -460,15 +462,16 @@ scope[type].push(clean); | ||
result = result || {}; | ||
request.auth.isAuthenticated = !err; | ||
// Unauthenticated | ||
if (err) { | ||
if (err) { | ||
// Non-error response | ||
if (err instanceof Error === false) { | ||
request._log(['auth', 'unauthenticated', 'response', name], { statusCode: err.statusCode }); | ||
return err; // Non-error response | ||
return err; | ||
} | ||
// Missing authenticated | ||
if (err.isMissing) { | ||
// Try next strategy | ||
request._log(['auth', 'unauthenticated', 'missing', name], err); | ||
@@ -478,24 +481,25 @@ errors.push(err.output.headers['WWW-Authenticate']); | ||
} | ||
} | ||
if (config.mode === 'try') { | ||
request.auth.isAuthenticated = false; | ||
request.auth.strategy = name; | ||
request.auth.credentials = result.credentials; | ||
request.auth.artifacts = result.artifacts; | ||
request.auth.error = err; | ||
request._log(['auth', 'unauthenticated', 'try', name], err); | ||
return; | ||
} | ||
request.auth.strategy = name; | ||
request.auth.credentials = result.credentials; | ||
request.auth.artifacts = result.artifacts; | ||
request._log(['auth', 'unauthenticated', 'error', name], err); | ||
throw err; | ||
// Authenticated | ||
if (!err) { | ||
return; | ||
} | ||
// Authenticated | ||
// Unauthenticated | ||
const credentials = result.credentials; | ||
request.auth.strategy = name; | ||
request.auth.credentials = credentials; | ||
request.auth.artifacts = result.artifacts; | ||
request.auth.isAuthenticated = true; | ||
request.auth.error = err; | ||
if (config.mode === 'try') { | ||
request._log(['auth', 'unauthenticated', 'try', name], err); | ||
return; | ||
} | ||
request._log(['auth', 'unauthenticated', 'error', name], err); | ||
throw err; | ||
}; | ||
@@ -549,3 +553,3 @@ | ||
const count = typeof credentials.scope === 'string' ? | ||
(scope[type].indexOf(credentials.scope) !== -1 ? 1 : 0) : | ||
scope[type].indexOf(credentials.scope) !== -1 ? 1 : 0 : | ||
Hoek.intersect(scope[type], credentials.scope).length; | ||
@@ -552,0 +556,0 @@ |
@@ -17,16 +17,19 @@ 'use strict'; | ||
constructor() { | ||
decoders = { | ||
gzip: (options) => Zlib.createGunzip(options), | ||
deflate: (options) => Zlib.createInflate(options) | ||
}; | ||
this.encodings = ['identity', 'gzip', 'deflate']; | ||
this._encoders = { | ||
identity: null, | ||
gzip: (options) => Zlib.createGzip(options), | ||
deflate: (options) => Zlib.createDeflate(options) | ||
}; | ||
encodings = ['identity', 'gzip', 'deflate']; | ||
this._decoders = { | ||
gzip: (options) => Zlib.createGunzip(options), | ||
deflate: (options) => Zlib.createInflate(options) | ||
}; | ||
encoders = { | ||
identity: null, | ||
gzip: (options) => Zlib.createGzip(options), | ||
deflate: (options) => Zlib.createDeflate(options) | ||
}; | ||
#common = null; | ||
constructor() { | ||
this._updateCommons(); | ||
@@ -37,7 +40,7 @@ } | ||
this._common = new Map(); | ||
internals.common.forEach((header) => { | ||
this.#common = new Map(); | ||
this._common.set(header, Accept.encoding(header, this.encodings)); | ||
}); | ||
for (const header of internals.common) { | ||
this.#common.set(header, Accept.encoding(header, this.encodings)); | ||
} | ||
} | ||
@@ -47,5 +50,5 @@ | ||
Hoek.assert(this._encoders[encoding] === undefined, `Cannot override existing encoder for ${encoding}`); | ||
Hoek.assert(this.encoders[encoding] === undefined, `Cannot override existing encoder for ${encoding}`); | ||
Hoek.assert(typeof encoder === 'function', `Invalid encoder function for ${encoding}`); | ||
this._encoders[encoding] = encoder; | ||
this.encoders[encoding] = encoder; | ||
this.encodings.unshift(encoding); | ||
@@ -57,5 +60,5 @@ this._updateCommons(); | ||
Hoek.assert(this._decoders[encoding] === undefined, `Cannot override existing decoder for ${encoding}`); | ||
Hoek.assert(this.decoders[encoding] === undefined, `Cannot override existing decoder for ${encoding}`); | ||
Hoek.assert(typeof decoder === 'function', `Invalid decoder function for ${encoding}`); | ||
this._decoders[encoding] = decoder; | ||
this.decoders[encoding] = decoder; | ||
} | ||
@@ -66,3 +69,7 @@ | ||
const header = request.headers['accept-encoding']; | ||
const common = this._common.get(header); | ||
if (!header) { | ||
return 'identity'; | ||
} | ||
const common = this.#common.get(header); | ||
if (common) { | ||
@@ -113,3 +120,3 @@ return common; | ||
const encoder = this._encoders[encoding]; | ||
const encoder = this.encoders[encoding]; | ||
Hoek.assert(encoder !== undefined, `Unknown encoding ${encoding}`); | ||
@@ -116,0 +123,0 @@ return encoder(request.route.settings.compression[encoding]); |
@@ -16,3 +16,3 @@ 'use strict'; | ||
const result = Joi.validate(options, internals[type]); | ||
const result = internals[type].validate(options); | ||
@@ -29,3 +29,3 @@ if (result.error) { | ||
const settings = (options ? Object.assign({}, options) : {}); // Shallow cloned | ||
const settings = options ? Object.assign({}, options) : {}; // Shallow cloned | ||
@@ -68,3 +68,3 @@ if (settings.security === true) { | ||
internals.event = Joi.object({ | ||
method: Joi.array().items(Joi.func()).single(), | ||
method: Joi.array().items(Joi.function()).single(), | ||
options: Joi.object({ | ||
@@ -81,3 +81,4 @@ before: Joi.array().items(Joi.string()).single(), | ||
internals.exts = Joi.array().items(internals.event.keys({ type: Joi.string().required() })).single(); | ||
internals.exts = Joi.array() | ||
.items(internals.event.keys({ type: Joi.string().required() })).single(); | ||
@@ -87,3 +88,3 @@ | ||
Joi.valid('error', 'log', 'ignore'), | ||
Joi.func() | ||
Joi.function() | ||
]) | ||
@@ -130,7 +131,7 @@ .default('error'); | ||
files: Joi.object({ | ||
relativeTo: Joi.string().regex(/^([\/\.])|([A-Za-z]:\\)|(\\\\)/).default('.') | ||
relativeTo: Joi.string().pattern(/^([\/\.])|([A-Za-z]:\\)|(\\\\)/).default('.') | ||
}) | ||
.default(), | ||
json: Joi.object({ | ||
replacer: Joi.alternatives(Joi.func(), Joi.array()).allow(null).default(null), | ||
replacer: Joi.alternatives(Joi.function(), Joi.array()).allow(null).default(null), | ||
space: Joi.number().allow(null).default(null), | ||
@@ -152,3 +153,5 @@ suffix: Joi.string().allow(null).default(null), | ||
}) | ||
.allow(false), | ||
.default(false) | ||
.allow(true, false) | ||
.when('.', { is: true, then: Joi.object().strip() }), | ||
allow: Joi.array().items(Joi.string()).single(), | ||
@@ -170,13 +173,12 @@ override: Joi.string(), | ||
disconnectStatusCode: Joi.number().integer().min(400).default(499), | ||
emptyStatusCode: Joi.valid(200, 204).default(200), | ||
emptyStatusCode: Joi.valid(200, 204).default(204), | ||
failAction: internals.failAction, | ||
modify: Joi.boolean(), | ||
options: Joi.object().default(), | ||
options: Joi.object(), | ||
ranges: Joi.boolean().default(true), | ||
sample: Joi.number().min(0).max(100).when('modify', { is: true, then: Joi.forbidden() }), | ||
schema: Joi.alternatives(Joi.object(), Joi.array(), Joi.func()).allow(true, false), | ||
status: Joi.object().pattern(/\d\d\d/, Joi.alternatives(Joi.object(), Joi.array(), Joi.func()).allow(true, false)) | ||
sample: Joi.number().min(0).max(100).when('modify', { then: Joi.forbidden() }), | ||
schema: Joi.alternatives(Joi.object(), Joi.array(), Joi.function()).allow(true, false), | ||
status: Joi.object().pattern(/\d\d\d/, Joi.alternatives(Joi.object(), Joi.array(), Joi.function()).allow(true, false)) | ||
}) | ||
.default() | ||
.assert('options.stripUnknown', Joi.when('modify', { is: true, otherwise: false }), 'meet requirement of having peer modify set to true'), | ||
.default(), | ||
security: Joi.object({ | ||
@@ -227,10 +229,11 @@ hsts: Joi.alternatives([ | ||
validate: Joi.object({ | ||
headers: Joi.alternatives(Joi.object(), Joi.array(), Joi.func()).allow(null, true), | ||
params: Joi.alternatives(Joi.object(), Joi.array(), Joi.func()).allow(null, true), | ||
query: Joi.alternatives(Joi.object(), Joi.array(), Joi.func()).allow(null, false, true), | ||
payload: Joi.alternatives(Joi.object(), Joi.array(), Joi.func()).allow(null, false, true), | ||
state: Joi.alternatives(Joi.object(), Joi.array(), Joi.func()).allow(null, false, true), | ||
headers: Joi.alternatives(Joi.object(), Joi.array(), Joi.function()).allow(null, true), | ||
params: Joi.alternatives(Joi.object(), Joi.array(), Joi.function()).allow(null, true), | ||
query: Joi.alternatives(Joi.object(), Joi.array(), Joi.function()).allow(null, false, true), | ||
payload: Joi.alternatives(Joi.object(), Joi.array(), Joi.function()).allow(null, false, true), | ||
state: Joi.alternatives(Joi.object(), Joi.array(), Joi.function()).allow(null, false, true), | ||
failAction: internals.failAction, | ||
errorFields: Joi.object(), | ||
options: Joi.object().default() | ||
options: Joi.object().default(), | ||
validator: Joi.object() | ||
}) | ||
@@ -260,4 +263,3 @@ .default() | ||
load: Joi.object({ | ||
sampleInterval: Joi.number().integer().min(0).default(0), | ||
concurrent: Joi.number().integer().min(0).default(0) | ||
sampleInterval: Joi.number().integer().min(0).default(0) | ||
}) | ||
@@ -274,8 +276,8 @@ .unknown() | ||
Joi.number().integer().min(0), // TCP port | ||
Joi.string().regex(/\//), // Unix domain socket | ||
Joi.string().regex(/^\\\\\.\\pipe\\/) // Windows named pipe | ||
Joi.string().pattern(/\//), // Unix domain socket | ||
Joi.string().pattern(/^\\\\\.\\pipe\\/) // Windows named pipe | ||
]) | ||
.allow(null), | ||
query: Joi.object({ | ||
parser: Joi.func() | ||
parser: Joi.function() | ||
}) | ||
@@ -294,3 +296,3 @@ .default(), | ||
]), | ||
uri: Joi.string().regex(/[^/]$/) | ||
uri: Joi.string().pattern(/[^/]$/) | ||
}); | ||
@@ -306,3 +308,3 @@ | ||
internals.handler = Joi.alternatives([ | ||
Joi.func(), | ||
Joi.function(), | ||
Joi.object().length(1) | ||
@@ -313,3 +315,3 @@ ]); | ||
internals.route = Joi.object({ | ||
method: Joi.string().regex(/^[a-zA-Z0-9!#\$%&'\*\+\-\.^_`\|~]+$/).required(), | ||
method: Joi.string().pattern(/^[a-zA-Z0-9!#\$%&'\*\+\-\.^_`\|~]+$/).required(), | ||
path: Joi.string().required(), | ||
@@ -329,5 +331,5 @@ rules: Joi.object(), | ||
internals.pre = [ | ||
Joi.func(), | ||
Joi.function(), | ||
Joi.object({ | ||
method: Joi.alternatives(Joi.string(), Joi.func()).required(), | ||
method: Joi.alternatives(Joi.string(), Joi.function()).required(), | ||
assign: Joi.string(), | ||
@@ -348,3 +350,3 @@ mode: Joi.valid('serial', 'parallel'), | ||
], | ||
pre: Joi.array().items(internals.pre.concat(Joi.array().items(internals.pre).min(1))), | ||
pre: Joi.array().items(...internals.pre.concat(Joi.array().items(...internals.pre).min(1))), | ||
tags: [ | ||
@@ -358,3 +360,3 @@ Joi.string(), | ||
internals.cacheConfig = Joi.alternatives([ | ||
Joi.func(), | ||
Joi.function(), | ||
Joi.object({ | ||
@@ -364,5 +366,5 @@ name: Joi.string().invalid('_default'), | ||
provider: [ | ||
Joi.func(), | ||
Joi.function(), | ||
{ | ||
constructor: Joi.func().required(), | ||
constructor: Joi.function().required(), | ||
options: Joi.object({ | ||
@@ -394,3 +396,3 @@ partition: Joi.string().default('hapi-cache') | ||
bind: Joi.object().allow(null), | ||
generateKey: Joi.func(), | ||
generateKey: Joi.function(), | ||
cache: internals.cachePolicy | ||
@@ -402,3 +404,3 @@ }); | ||
name: Joi.string().required(), | ||
method: Joi.func().required(), | ||
method: Joi.function().required(), | ||
options: Joi.object() | ||
@@ -411,3 +413,3 @@ }); | ||
routes: Joi.object({ | ||
prefix: Joi.string().regex(/^\/.+/), | ||
prefix: Joi.string().pattern(/^\/.+/), | ||
vhost: internals.vhost | ||
@@ -425,3 +427,3 @@ }) | ||
plugin: Joi.object({ | ||
register: Joi.func().required(), | ||
register: Joi.function().required(), | ||
name: Joi.string().when('pkg.name', { is: Joi.exist(), otherwise: Joi.required() }), | ||
@@ -428,0 +430,0 @@ version: Joi.string(), |
209
lib/core.js
@@ -29,2 +29,3 @@ 'use strict'; | ||
const Toolkit = require('./toolkit'); | ||
const Validation = require('./validation'); | ||
@@ -51,6 +52,63 @@ | ||
actives = new WeakMap(); // Active requests being processed | ||
app = {}; | ||
auth = new Auth(this); | ||
caches = new Map(); // Cache clients | ||
compression = new Compression(); | ||
controlled = null; // Other servers linked to the phases of this server | ||
dependencies = []; // Plugin dependencies | ||
events = new Podium(internals.events); | ||
heavy = null; | ||
info = null; | ||
instances = new Set(); | ||
listener = null; | ||
methods = new Methods(this); // Server methods | ||
mime = null; | ||
onConnection = null; // Used to remove event listener on stop | ||
phase = 'stopped'; // 'stopped', 'initializing', 'initialized', 'starting', 'started', 'stopping', 'invalid' | ||
plugins = {}; // Exposed plugin properties by name | ||
registrations = {}; // Tracks plugin for dependency validation { name -> { version } } | ||
registring = 0; // > 0 while register() is waiting for plugin callbacks | ||
Request = class extends Request { }; | ||
requestCounter = { value: internals.counter.min, min: internals.counter.min, max: internals.counter.max }; | ||
root = null; | ||
router = null; | ||
settings = null; | ||
sockets = null; // Track open sockets for graceful shutdown | ||
started = false; | ||
states = null; | ||
toolkit = new Toolkit.Manager(); | ||
type = null; | ||
validator = null; | ||
extensionsSeq = 0; // Used to keep absolute order of extensions based on the order added across locations | ||
extensions = { | ||
server: { | ||
onPreStart: new Ext('onPreStart', this), | ||
onPostStart: new Ext('onPostStart', this), | ||
onPreStop: new Ext('onPreStop', this), | ||
onPostStop: new Ext('onPostStop', this) | ||
}, | ||
route: { | ||
onRequest: new Ext('onRequest', this), | ||
onPreAuth: new Ext('onPreAuth', this), | ||
onCredentials: new Ext('onCredentials', this), | ||
onPostAuth: new Ext('onPostAuth', this), | ||
onPreHandler: new Ext('onPreHandler', this), | ||
onPostHandler: new Ext('onPostHandler', this), | ||
onPreResponse: new Ext('onPreResponse', this) | ||
} | ||
}; | ||
decorations = { | ||
handler: new Map(), | ||
request: new Map(), | ||
server: new Map(), | ||
toolkit: new Map(), | ||
requestApply: null, | ||
public: { handler: [], request: [], server: [], toolkit: [] } | ||
}; | ||
constructor(options) { | ||
this.root = null; // Dispatch reference of the root server | ||
const { settings, type } = internals.setup(options); | ||
@@ -61,53 +119,14 @@ | ||
this.app = {}; | ||
this.auth = new Auth(this); | ||
this.caches = new Map(); // Cache clients | ||
this.compression = new Compression(); | ||
this.controlled = null; // Other servers linked to the phases of this server | ||
this.decorations = { handler: [], request: [], server: [], toolkit: [] }; // Public decoration names | ||
this.dependencies = []; // Plugin dependencies | ||
this.events = new Podium(internals.events); | ||
this.heavy = new Heavy(this.settings.load); | ||
this.instances = new Set(); | ||
this.methods = new Methods(this); // Server methods | ||
this.mime = new Mimos(this.settings.mime); | ||
this.onConnection = null; // Used to remove event listener on stop | ||
this.plugins = {}; // Exposed plugin properties by name | ||
this.queue = new internals.Queue(this.settings.load); | ||
this.registrations = {}; // Tracks plugin for dependency validation { name -> { version } } | ||
this.registring = 0; // > 0 while register() is waiting for plugin callbacks | ||
this.requestCounter = { value: internals.counter.min, min: internals.counter.min, max: internals.counter.max }; | ||
this.router = new Call.Router(this.settings.router); | ||
this.phase = 'stopped'; // 'stopped', 'initializing', 'initialized', 'starting', 'started', 'stopping', 'invalid' | ||
this.sockets = null; // Track open sockets for graceful shutdown | ||
this.actives = new WeakMap(); // Active requests being processed | ||
this.started = false; | ||
this.states = new Statehood.Definitions(this.settings.state); | ||
this.toolkit = new Toolkit(); | ||
this.extensionsSeq = 0; // Used to keep absolute order of extensions based on the order added across locations | ||
this.extensions = { | ||
server: { | ||
onPreStart: new Ext('onPreStart', this), | ||
onPostStart: new Ext('onPostStart', this), | ||
onPreStop: new Ext('onPreStop', this), | ||
onPostStop: new Ext('onPostStop', this) | ||
}, | ||
route: { | ||
onRequest: new Ext('onRequest', this), | ||
onPreAuth: new Ext('onPreAuth', this), | ||
onCredentials: new Ext('onCredentials', this), | ||
onPostAuth: new Ext('onPostAuth', this), | ||
onPreHandler: new Ext('onPreHandler', this), | ||
onPostHandler: new Ext('onPostHandler', this), | ||
onPreResponse: new Ext('onPreResponse', this) | ||
} | ||
}; | ||
this.Request = class extends Request { }; | ||
this._debug(); | ||
this._decorations = { handler: {}, request: {}, server: {}, toolkit: {}, requestApply: null }; | ||
this._initializeCache(); | ||
if (this.settings.routes.validate.validator) { | ||
this.validator = Validation.validator(this.settings.routes.validate.validator); | ||
} | ||
this.listener = this._createListener(); | ||
@@ -120,20 +139,23 @@ this._initializeListener(); | ||
const debug = this.settings.debug; | ||
if (!debug) { | ||
return; | ||
} | ||
// Subscribe to server log events | ||
if (this.settings.debug) { | ||
const debug = (request, event) => { | ||
const method = (event) => { | ||
const data = event.error || event.data; | ||
console.error('Debug:', event.tags.join(', '), data ? '\n ' + (data.stack || (typeof data === 'object' ? Hoek.stringify(data) : data)) : ''); | ||
}; | ||
const data = event.error || event.data; | ||
console.error('Debug:', event.tags.join(', '), data ? '\n ' + (data.stack || (typeof data === 'object' ? Hoek.stringify(data) : data)) : ''); | ||
}; | ||
if (this.settings.debug.log) { | ||
const filter = this.settings.debug.log.some((tag) => tag === '*') ? undefined : this.settings.debug.log; | ||
this.events.on({ name: 'log', filter }, (event) => debug(null, event)); | ||
} | ||
if (debug.log) { | ||
const filter = debug.log.some((tag) => tag === '*') ? undefined : debug.log; | ||
this.events.on({ name: 'log', filter }, method); | ||
} | ||
if (this.settings.debug.request) { | ||
const filter = this.settings.debug.request.some((tag) => tag === '*') ? undefined : this.settings.debug.request; | ||
this.events.on({ name: 'request', filter }, debug); | ||
} | ||
if (debug.request) { | ||
const filter = debug.request.some((tag) => tag === '*') ? undefined : debug.request; | ||
this.events.on({ name: 'request', filter }, (request, event) => method(event)); | ||
} | ||
@@ -173,2 +195,13 @@ } | ||
_counter() { | ||
const next = ++this.requestCounter.value; | ||
if (this.requestCounter.value > this.requestCounter.max) { | ||
this.requestCounter.value = this.requestCounter.min; | ||
} | ||
return next - 1; | ||
} | ||
_createCache(configs) { | ||
@@ -408,3 +441,6 @@ | ||
this.sockets.forEach((connection) => connection.destroy()); | ||
for (const connection of this.sockets) { | ||
connection.destroy(); | ||
} | ||
this.sockets.clear(); | ||
@@ -417,8 +453,7 @@ }; | ||
this.sockets.forEach((connection) => { | ||
for (const connection of this.sockets) { | ||
if (!this.actives.has(connection)) { | ||
connection.end(); | ||
} | ||
}); | ||
} | ||
} | ||
@@ -479,3 +514,5 @@ | ||
if (req.socket) { | ||
if (this.settings.operations.cleanStop && | ||
req.socket) { | ||
this.actives.set(req.socket, request); | ||
@@ -500,3 +537,3 @@ const env = { core: this, req }; | ||
this.queue.add(request); | ||
request._execute(); | ||
}; | ||
@@ -516,3 +553,3 @@ } | ||
if (socket.readable) { | ||
const request = this.actives.get(socket); | ||
const request = this.settings.operations.cleanStop && this.actives.get(socket); | ||
if (request) { | ||
@@ -661,43 +698,1 @@ const error = Boom.badRequest(); | ||
}; | ||
internals.Queue = class { | ||
constructor(options) { | ||
this.settings = options; | ||
this.active = 0; | ||
this.queue = []; | ||
} | ||
add(request) { | ||
if (this.settings.concurrent) { | ||
this.queue.push(request); | ||
this.next(); | ||
} | ||
else { | ||
request._execute(); | ||
} | ||
} | ||
next() { | ||
if (this.queue.length && | ||
this.active < this.settings.concurrent) { | ||
const request = this.queue.shift(); | ||
++this.active; | ||
request._execute(); | ||
} | ||
} | ||
release() { | ||
if (this.settings.concurrent) { | ||
--this.active; | ||
this.next(); | ||
} | ||
} | ||
}; |
@@ -12,10 +12,13 @@ 'use strict'; | ||
type = null; | ||
nodes = null; | ||
#core = null; | ||
#routes = []; | ||
#topo = new Topo.Sorter(); | ||
constructor(type, core) { | ||
this._topo = new Topo(); | ||
this._core = core; | ||
this._routes = []; | ||
this.#core = core; | ||
this.type = type; | ||
this.nodes = null; | ||
} | ||
@@ -31,3 +34,3 @@ | ||
group: event.realm.plugin, | ||
sort: this._core.extensionsSeq++ | ||
sort: this.#core.extensionsSeq++ | ||
}; | ||
@@ -43,10 +46,10 @@ | ||
this._topo.add(node, settings); | ||
this.#topo.add(node, settings); | ||
} | ||
this.nodes = this._topo.nodes; | ||
this.nodes = this.#topo.nodes; | ||
// Notify routes | ||
for (const route of this._routes) { | ||
for (const route of this.#routes) { | ||
route.rebuild(event); | ||
@@ -60,7 +63,7 @@ } | ||
for (const other of others) { | ||
merge.push(other._topo); | ||
merge.push(other.#topo); | ||
} | ||
this._topo.merge(merge); | ||
this.nodes = (this._topo.nodes.length ? this._topo.nodes : null); | ||
this.#topo.merge(merge); | ||
this.nodes = this.#topo.nodes.length ? this.#topo.nodes : null; | ||
} | ||
@@ -70,3 +73,3 @@ | ||
this._routes.push(route); | ||
this.#routes.push(route); | ||
} | ||
@@ -73,0 +76,0 @@ |
@@ -87,3 +87,3 @@ 'use strict'; | ||
const type = Object.keys(handler)[0]; | ||
const serverHandler = core._decorations.handler[type]; | ||
const serverHandler = core.decorations.handler.get(type); | ||
@@ -105,3 +105,3 @@ Hoek.assert(serverHandler, 'Unknown handler:', type); | ||
const type = Object.keys(handler)[0]; | ||
const serverHandler = route._core._decorations.handler[type]; | ||
const serverHandler = route._core.decorations.handler.get(type); | ||
@@ -108,0 +108,0 @@ Hoek.assert(serverHandler, 'Unknown handler:', type); |
@@ -106,3 +106,3 @@ 'use strict'; | ||
let header = await request._core.states.format(states); | ||
let header = await request._core.states.format(states, request); | ||
const existing = response.headers['set-cookie']; | ||
@@ -109,0 +109,0 @@ if (existing) { |
@@ -16,6 +16,9 @@ 'use strict'; | ||
methods = {}; | ||
#core = null; | ||
constructor(core) { | ||
this.core = core; | ||
this.methods = {}; | ||
this.#core = core; | ||
} | ||
@@ -65,3 +68,3 @@ | ||
settings.cache.generateFunc = (id, flags) => bound(...id.args, flags); | ||
const cache = this.core._cachePolicy(settings.cache, '#' + name); | ||
const cache = this.#core._cachePolicy(settings.cache, '#' + name); | ||
@@ -68,0 +71,0 @@ const func = function (...args) { |
'use strict'; | ||
const Querystring = require('querystring'); | ||
const Url = require('url'); | ||
@@ -12,2 +13,3 @@ | ||
const Response = require('./response'); | ||
const Toolkit = require('./toolkit'); | ||
const Transmit = require('./transmit'); | ||
@@ -37,2 +39,3 @@ | ||
this._states = {}; | ||
this._url = null; | ||
this._urlError = null; | ||
@@ -42,3 +45,2 @@ | ||
this.headers = req.headers; | ||
this.info = internals.info(this._core, req); | ||
this.jsonp = null; | ||
@@ -52,3 +54,3 @@ this.logs = []; | ||
this.path = null; | ||
this.payload = null; | ||
this.payload = undefined; | ||
this.plugins = options.plugins ? Object.assign({}, options.plugins) : {}; // Place for plugins to store state without conflicts with hapi, should be namespaced using plugin name (shallow cloned) | ||
@@ -63,7 +65,9 @@ this.pre = {}; // Pre raw values | ||
this.state = null; | ||
this.url = null; | ||
this.info = new internals.Info(this); | ||
this.auth = { | ||
isAuthenticated: false, | ||
isAuthorized: false, | ||
isInjected: options.auth ? true : false, | ||
credentials: options.auth ? options.auth.credentials : null, // Special keys: 'app', 'user', 'scope' | ||
@@ -76,6 +80,2 @@ artifacts: options.auth && options.auth.artifacts || null, // Scheme-specific artifacts | ||
if (options.auth) { | ||
this.auth.isInjected = true; | ||
} | ||
// Parse request url | ||
@@ -92,5 +92,4 @@ | ||
if (server._core._decorations.requestApply) { | ||
for (const property in server._core._decorations.requestApply) { | ||
const assignment = server._core._decorations.requestApply[property]; | ||
if (server._core.decorations.requestApply) { | ||
for (const [property, assignment] of server._core.decorations.requestApply.entries()) { | ||
request[property] = assignment(request); | ||
@@ -113,2 +112,15 @@ } | ||
get url() { | ||
if (this._urlError) { | ||
return null; | ||
} | ||
if (this._url) { | ||
return this._url; | ||
} | ||
return this._parseUrl(this.raw.req.url, this._core.settings.router); | ||
} | ||
_initializeUrl() { | ||
@@ -138,35 +150,70 @@ | ||
this._setUrl(url, stripTrailingSlash); | ||
this._urlError = null; | ||
} | ||
_setUrl(url, stripTrailingSlash) { | ||
_setUrl(source, stripTrailingSlash) { | ||
const base = url[0] === '/' ? `${this._core.info.protocol}://${this.info.host || `${this._core.info.host}:${this._core.info.port}`}` : ''; | ||
const url = this._parseUrl(source, { stripTrailingSlash, _fast: true }); | ||
this.query = this._parseQuery(url.searchParams); | ||
this.path = url.pathname; | ||
} | ||
url = new Url.URL(base + url); | ||
_parseUrl(source, options) { | ||
// Apply path modifications | ||
if (source[0] === '/') { | ||
let path = this._core.router.normalize(url.pathname); // pathname excludes query | ||
// Relative URL | ||
if (stripTrailingSlash && | ||
path.length > 1 && | ||
path[path.length - 1] === '/') { | ||
if (options._fast) { | ||
const url = { | ||
pathname: source, | ||
searchParams: '' | ||
}; | ||
path = path.slice(0, -1); | ||
const q = source.indexOf('?'); | ||
const h = source.indexOf('#'); | ||
if (q !== -1 && | ||
(h === -1 || q < h)) { | ||
url.pathname = source.slice(0, q); | ||
const query = h === -1 ? source.slice(q + 1) : source.slice(q + 1, h); | ||
url.searchParams = Querystring.parse(query); | ||
} | ||
else { | ||
url.pathname = h === -1 ? source : source.slice(0, h); | ||
} | ||
this._normalizePath(url, options); | ||
return url; | ||
} | ||
this._url = new Url.URL(`${this._core.info.protocol}://${this.info.host || `${this._core.info.host}:${this._core.info.port}`}${source}`); | ||
} | ||
else { | ||
url.pathname = path; | ||
// Absolute URI (proxied) | ||
// Parse query (must be done before this.url is set in case query parsing throws) | ||
this._url = new Url.URL(source); | ||
this.info.hostname = this._url.hostname; | ||
this.info.host = this._url.host; | ||
} | ||
this.query = this._parseQuery(url.searchParams); | ||
this._normalizePath(this._url, options); | ||
this._urlError = null; | ||
// Store request properties | ||
return this._url; | ||
} | ||
this.url = url; | ||
this.path = path; | ||
_normalizePath(url, options) { | ||
this.info.hostname = url.hostname; | ||
this.info.host = url.host; | ||
let path = this._core.router.normalize(url.pathname); | ||
if (options.stripTrailingSlash && | ||
path.length > 1 && | ||
path[path.length - 1] === '/') { | ||
path = path.slice(0, -1); | ||
} | ||
url.pathname = path; | ||
} | ||
@@ -176,13 +223,19 @@ | ||
let query = Object.create(null); | ||
// Flatten map | ||
let query = Object.create(null); | ||
for (let [key, value] of searchParams) { | ||
const entry = query[key]; | ||
if (entry !== undefined) { | ||
value = [].concat(entry, value); | ||
if (searchParams instanceof Url.URLSearchParams) { | ||
for (let [key, value] of searchParams) { | ||
const entry = query[key]; | ||
if (entry !== undefined) { | ||
value = [].concat(entry, value); | ||
} | ||
query[key] = value; | ||
} | ||
query[key] = value; | ||
} | ||
else { | ||
query = Object.assign(query, searchParams); | ||
} | ||
@@ -330,3 +383,3 @@ // Custom parser | ||
if (!response || | ||
response === this._core.toolkit.continue) { // Continue | ||
response === Toolkit.symbols.continue) { // Continue | ||
@@ -352,3 +405,3 @@ continue; | ||
if (response === this._core.toolkit.continue) { | ||
if (response === Toolkit.symbols.continue) { | ||
continue; | ||
@@ -422,3 +475,3 @@ } | ||
if (response && | ||
response !== this._core.toolkit.continue) { // Continue | ||
response !== Toolkit.symbols.continue) { // Continue | ||
@@ -432,3 +485,3 @@ this._setResponse(response); | ||
if (this.response === this._core.toolkit.close) { | ||
if (this.response === Toolkit.symbols.close) { | ||
this.raw.res.end(); // End the response in case it wasn't already closed | ||
@@ -462,3 +515,2 @@ } | ||
this._core.events.emit('response', this); | ||
this._core.queue.release(); | ||
} | ||
@@ -471,3 +523,3 @@ | ||
this.response !== response && | ||
(response.isBoom || this.response.source !== response.source)) { | ||
this.response.source !== response.source) { | ||
@@ -570,29 +622,46 @@ this.response._close(this); | ||
internals.info = function (core, req) { | ||
internals.Info = class { | ||
const host = req.headers.host ? req.headers.host.trim() : ''; | ||
const received = Date.now(); | ||
constructor(request) { | ||
const info = { | ||
received, | ||
remoteAddress: req.connection.remoteAddress, | ||
remotePort: req.connection.remotePort || '', | ||
referrer: req.headers.referrer || req.headers.referer || '', | ||
host, | ||
hostname: host.split(':')[0], | ||
id: `${received}:${core.info.id}:${core.requestCounter.value++}`, | ||
this._request = request; | ||
const req = request.raw.req; | ||
const host = req.headers.host ? req.headers.host.trim() : ''; | ||
const received = Date.now(); | ||
this.received = received; | ||
this.referrer = req.headers.referrer || req.headers.referer || ''; | ||
this.host = host; | ||
this.hostname = host.split(':')[0]; | ||
this.id = `${received}:${request._core.info.id}:${request._core._counter()}`; | ||
this._remoteAddress = null; | ||
this._remotePort = null; | ||
// Assigned later | ||
acceptEncoding: null, | ||
cors: null, | ||
responded: 0, | ||
completed: 0 | ||
}; | ||
this.acceptEncoding = null; | ||
this.cors = null; | ||
this.responded = 0; | ||
this.completed = 0; | ||
} | ||
if (core.requestCounter.value > core.requestCounter.max) { | ||
core.requestCounter.value = core.requestCounter.min; | ||
get remoteAddress() { | ||
if (!this._remoteAddress) { | ||
this._remoteAddress = this._request.raw.req.connection.remoteAddress; | ||
} | ||
return this._remoteAddress; | ||
} | ||
return info; | ||
get remotePort() { | ||
if (this._remotePort === null) { | ||
this._remotePort = this._request.raw.req.connection.remotePort || ''; | ||
} | ||
return this._remotePort; | ||
} | ||
}; | ||
@@ -599,0 +668,0 @@ |
@@ -111,3 +111,3 @@ 'use strict'; | ||
this._contentType = (typeof this.source === 'string' ? 'text/html' : 'application/json'); | ||
this._contentType = typeof this.source === 'string' ? 'text/html' : 'application/json'; | ||
} | ||
@@ -714,3 +714,11 @@ } | ||
this._prefix = '/**/' + variable + '('; // '/**/' prefix prevents CVE-2014-4671 security exploit | ||
this._data = (this._data === null || Buffer.isBuffer(this._data)) ? this._data : this._data.replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029'); | ||
if (this._data !== null && | ||
!Buffer.isBuffer(this._data)) { | ||
this._data = this._data | ||
.replace(/\u2028/g, '\\u2028') | ||
.replace(/\u2029/g, '\\u2029'); | ||
} | ||
this._suffix = ');'; | ||
@@ -717,0 +725,0 @@ } |
@@ -40,6 +40,6 @@ 'use strict'; | ||
const path = (realm.modifiers.route.prefix ? realm.modifiers.route.prefix + (route.path !== '/' ? route.path : '') : route.path); | ||
const path = realm.modifiers.route.prefix ? realm.modifiers.route.prefix + (route.path !== '/' ? route.path : '') : route.path; | ||
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); | ||
const vhost = (realm.modifiers.route.vhost || route.vhost); | ||
const vhost = realm.modifiers.route.vhost || route.vhost; | ||
@@ -68,3 +68,3 @@ // Set identifying members (assert) | ||
this._assert(!route.rules || !config.rules, 'Route rules can only appear once'); // XOR | ||
const rules = (route.rules || config.rules); | ||
const rules = route.rules || config.rules; | ||
const rulesConfig = internals.rules(rules, { method, path, vhost }, server); | ||
@@ -90,3 +90,3 @@ delete config.rules; | ||
const socketTimeout = (this.settings.timeout.socket === undefined ? 2 * 60 * 1000 : this.settings.timeout.socket); | ||
const socketTimeout = this.settings.timeout.socket === undefined ? 2 * 60 * 1000 : this.settings.timeout.socket; | ||
this._assert(!this.settings.timeout.server || !socketTimeout || this.settings.timeout.server < socketTimeout, 'Server timeout must be shorter than socket timeout'); | ||
@@ -133,3 +133,3 @@ this._assert(!this.settings.payload.timeout || !socketTimeout || this.settings.payload.timeout < socketTimeout, 'Payload timeout must be shorter than socket timeout'); | ||
else { | ||
this.settings.payload.decoders = this._core.compression._decoders; // Reference the shared object to keep up to date | ||
this.settings.payload.decoders = this._core.compression.decoders; // Reference the shared object to keep up to date | ||
} | ||
@@ -143,3 +143,3 @@ | ||
this.settings.auth = (this._special ? false : this._core.auth._setupRoute(this.settings.auth, path)); | ||
this.settings.auth = this._special ? false : this._core.auth._setupRoute(this.settings.auth, path); | ||
@@ -199,6 +199,7 @@ // Cache | ||
['headers', 'params', 'query', 'payload', 'state'].forEach((type) => { | ||
const validator = this._validator(); | ||
validation[type] = Validation.compile(validation[type]); | ||
}); | ||
for (const type of ['headers', 'params', 'query', 'payload', 'state']) { | ||
validation[type] = Validation.compile(validation[type], validator); | ||
} | ||
@@ -220,5 +221,5 @@ if (this.settings.response.schema !== undefined || | ||
else { | ||
this.settings.response.schema = Validation.compile(rule); | ||
this.settings.response.schema = Validation.compile(rule, validator); | ||
for (const code of statuses) { | ||
this.settings.response.status[code] = Validation.compile(this.settings.response.status[code]); | ||
this.settings.response.status[code] = Validation.compile(this.settings.response.status[code], validator); | ||
} | ||
@@ -236,3 +237,3 @@ } | ||
if (this._special) { | ||
this._postCycle = (this._extensions.onPreResponse.nodes ? [this._extensions.onPreResponse] : []); | ||
this._postCycle = this._extensions.onPreResponse.nodes ? [this._extensions.onPreResponse] : []; | ||
this._buildMarshalCycle(); | ||
@@ -382,2 +383,20 @@ return; | ||
} | ||
_validator() { | ||
if (this.settings.validate.validator) { | ||
return this.settings.validate.validator; | ||
} | ||
let realm = this.realm; | ||
while (realm) { | ||
if (realm.validator) { | ||
return realm.validator; | ||
} | ||
realm = realm.parent; | ||
} | ||
return this._core.validator; | ||
} | ||
}; | ||
@@ -437,2 +456,6 @@ | ||
if (request.payload !== undefined) { | ||
return internals.drain(request); | ||
} | ||
try { | ||
@@ -450,7 +473,3 @@ const { payload, mime } = await Subtext.parse(request.raw.req, request._tap(), request.route.settings.payload); | ||
await internals.drain(request); | ||
request._isPayloadPending = false; | ||
} | ||
else { | ||
request._isPayloadPending = true; | ||
} | ||
@@ -520,3 +539,3 @@ request.mime = err.mime; | ||
if (realm._rules) { | ||
const source = (!realm._rules.settings.validate ? rules : Joi.attempt(rules, realm._rules.settings.validate.schema, realm._rules.settings.validate.options)); | ||
const source = !realm._rules.settings.validate ? rules : Joi.attempt(rules, realm._rules.settings.validate.schema, realm._rules.settings.validate.options); | ||
const config = realm._rules.processor(source, info); | ||
@@ -523,0 +542,0 @@ if (config) { |
@@ -15,2 +15,4 @@ 'use strict'; | ||
const Route = require('./route'); | ||
const Toolkit = require('./toolkit'); | ||
const Validation = require('./validation'); | ||
@@ -37,5 +39,5 @@ | ||
this.app = core.app; | ||
this.auth = Object.create(this._core.auth); | ||
this.auth = this._core.auth; | ||
this.auth.strategy = this.auth._strategy.bind(this.auth, this); | ||
this.decorations = core.decorations; | ||
this.decorations = core.decorations.public; | ||
this.cache = internals.cache(this); | ||
@@ -67,3 +69,3 @@ this.events = core.events; | ||
}, | ||
parent: (parent ? parent.realm : null), | ||
parent: parent ? parent.realm : null, | ||
plugin: name, | ||
@@ -78,3 +80,4 @@ pluginOptions: {}, | ||
} | ||
} | ||
}, | ||
validator: null | ||
}; | ||
@@ -84,4 +87,4 @@ | ||
for (const method of core.decorations.server) { | ||
this[method] = core._decorations.server[method]; | ||
for (const [property, method] of core.decorations.server.entries()) { | ||
this[property] = method; | ||
} | ||
@@ -118,3 +121,3 @@ | ||
Hoek.assert(this._core.decorations[type], 'Unknown decoration type:', type); | ||
Hoek.assert(this._core.decorations.public[type], 'Unknown decoration type:', type); | ||
Hoek.assert(property, 'Missing decoration property name'); | ||
@@ -126,3 +129,3 @@ Hoek.assert(typeof property === 'string' || typeof property === 'symbol', 'Decoration property must be a string or a symbol'); | ||
const existing = this._core._decorations[type][property]; | ||
const existing = this._core.decorations[type].get(property); | ||
if (options.extend) { | ||
@@ -151,7 +154,7 @@ Hoek.assert(type !== 'handler', 'Cannot extent handler decoration:', propertyName); | ||
Hoek.assert(Request.reserved.indexOf(property) === -1, 'Cannot override built-in request interface decoration:', propertyName); | ||
Hoek.assert(!Request.reserved.includes(property), 'Cannot override built-in request interface decoration:', propertyName); | ||
if (options.apply) { | ||
this._core._decorations.requestApply = this._core._decorations.requestApply || {}; | ||
this._core._decorations.requestApply[property] = method; | ||
this._core.decorations.requestApply = this._core.decorations.requestApply || new Map(); | ||
this._core.decorations.requestApply.set(property, method); | ||
} | ||
@@ -166,3 +169,4 @@ else { | ||
Hoek.assert(this._core.toolkit.reserved.indexOf(property) === -1, 'Cannot override built-in toolkit decoration:', propertyName); | ||
Hoek.assert(!Toolkit.reserved.includes(property), 'Cannot override built-in toolkit decoration:', propertyName); | ||
this._core.toolkit.decorate(property, method); | ||
} | ||
@@ -174,6 +178,6 @@ else { | ||
if (typeof property === 'string') { | ||
Hoek.assert(Object.getOwnPropertyNames(internals.Server.prototype).indexOf(property) === -1, 'Cannot override the built-in server interface method:', propertyName); | ||
Hoek.assert(!Object.getOwnPropertyNames(internals.Server.prototype).includes(property), 'Cannot override the built-in server interface method:', propertyName); | ||
} | ||
else { | ||
Hoek.assert(Object.getOwnPropertySymbols(internals.Server.prototype).indexOf(property) === -1, 'Cannot override the built-in server interface method:', propertyName); | ||
Hoek.assert(!Object.getOwnPropertySymbols(internals.Server.prototype).includes(property), 'Cannot override the built-in server interface method:', propertyName); | ||
} | ||
@@ -187,4 +191,4 @@ | ||
this._core._decorations[type][property] = method; | ||
this._core.decorations[type].push(property); | ||
this._core.decorations[type].set(property, method); | ||
this._core.decorations.public[type].push(property); | ||
} | ||
@@ -228,7 +232,16 @@ | ||
expose(key, value) { | ||
expose(key, value, options = {}) { | ||
Hoek.assert(this.realm.plugin, 'Cannot call expose() outside of a plugin'); | ||
const plugin = this.realm.plugin; | ||
let plugin = this.realm.plugin; | ||
if (plugin[0] === '@' && | ||
options.scope !== true) { | ||
plugin = plugin.replace(/^\@([^\/]+)\//, ($0, $1) => { | ||
return !options.scope ? '' : `${$1}__`; | ||
}); | ||
} | ||
this._core.plugins[plugin] = this._core.plugins[plugin] || {}; | ||
@@ -305,3 +318,3 @@ | ||
settings.authority = settings.authority || (this._core.info.host + ':' + this._core.info.port); | ||
settings.authority = settings.authority || this._core.info.host + ':' + this._core.info.port; | ||
} | ||
@@ -534,2 +547,9 @@ | ||
validator(validator) { | ||
Hoek.assert(!this.realm.validator, 'Validator already set'); | ||
this.realm.validator = Validation.validator(validator); | ||
} | ||
start() { | ||
@@ -565,3 +585,3 @@ | ||
if (['initialized', 'starting', 'started'].indexOf(plugin._core.phase) !== -1) { | ||
if (['initialized', 'starting', 'started'].includes(plugin._core.phase)) { | ||
await Promise.all(clients.map((client) => client.start())); | ||
@@ -568,0 +588,0 @@ } |
@@ -21,3 +21,3 @@ 'use strict'; | ||
const team = new Teamwork(); | ||
const team = new Teamwork.Team(); | ||
stream[internals.team] = team; | ||
@@ -24,0 +24,0 @@ |
@@ -10,15 +10,34 @@ 'use strict'; | ||
const internals = { | ||
reserved: ['abandon', 'authenticated', 'close', 'context', 'continue', 'entity', 'redirect', 'realm', 'request', 'response', 'state', 'unauthenticated', 'unstate'] | ||
const internals = {}; | ||
exports.reserved = [ | ||
'abandon', | ||
'authenticated', | ||
'close', | ||
'context', | ||
'continue', | ||
'entity', | ||
'redirect', | ||
'realm', | ||
'request', | ||
'response', | ||
'state', | ||
'unauthenticated', | ||
'unstate' | ||
]; | ||
exports.symbols = { | ||
abandon: Symbol('abandon'), | ||
close: Symbol('close'), | ||
continue: Symbol('continue') | ||
}; | ||
exports = module.exports = internals.Manager = class { | ||
exports.Manager = class { | ||
constructor() { | ||
this.abandon = Symbol('abandon'); | ||
this.close = Symbol('close'); | ||
this.continue = Symbol('continue'); | ||
this.reserved = internals.reserved; | ||
this._toolkit = internals.toolkit(); | ||
} | ||
@@ -28,3 +47,3 @@ | ||
const h = new internals.Toolkit(request, this, options); | ||
const h = new this._toolkit(request, options); | ||
const bind = options.bind || null; | ||
@@ -47,3 +66,3 @@ | ||
var response = await internals.Manager.timed(operation, options); | ||
var response = await exports.timed(operation, options); | ||
} | ||
@@ -69,3 +88,3 @@ catch (err) { | ||
if (options.continue && | ||
response === this.continue) { | ||
response === exports.symbols.continue) { | ||
@@ -97,2 +116,7 @@ if (options.continue === 'undefined') { | ||
decorate(name, method) { | ||
this._toolkit.prototype[name] = method; | ||
} | ||
failAction(request, failAction, err, options) { | ||
@@ -116,21 +140,22 @@ | ||
} | ||
}; | ||
static timed(method, options) { | ||
if (!options.timeout) { | ||
return method; | ||
} | ||
exports.timed = function (method, options) { | ||
const timer = new Promise((resolve, reject) => { | ||
if (!options.timeout) { | ||
return method; | ||
} | ||
const handler = () => { | ||
const timer = new Promise((resolve, reject) => { | ||
reject(Boom.internal(`${options.name} timed out`)); | ||
}; | ||
const handler = () => { | ||
setTimeout(handler, options.timeout); | ||
}); | ||
reject(Boom.internal(`${options.name} timed out`)); | ||
}; | ||
return Promise.race([timer, method]); | ||
} | ||
setTimeout(handler, options.timeout); | ||
}); | ||
return Promise.race([timer, method]); | ||
}; | ||
@@ -156,75 +181,74 @@ | ||
internals.Toolkit = class { | ||
internals.toolkit = function () { | ||
constructor(request, manager, options) { | ||
const Toolkit = class { | ||
this.abandon = manager.abandon; | ||
this.close = manager.close; | ||
this.continue = manager.continue; | ||
this.context = options.bind; | ||
this.realm = options.realm; | ||
this.request = request; | ||
constructor(request, options) { | ||
if (options.auth) { | ||
this.authenticated = internals.authenticated; | ||
this.unauthenticated = internals.unauthenticated; | ||
} | ||
this.context = options.bind; | ||
this.realm = options.realm; | ||
this.request = request; | ||
for (const method of request._core.decorations.toolkit) { | ||
this[method] = request._core._decorations.toolkit[method]; | ||
this._auth = options.auth; | ||
} | ||
} | ||
response(result) { | ||
response(result) { | ||
Hoek.assert(!result || typeof result !== 'object' || typeof result.then !== 'function', 'Cannot wrap a promise'); | ||
Hoek.assert(result instanceof Error === false, 'Cannot wrap an error'); | ||
Hoek.assert(typeof result !== 'symbol', 'Cannot wrap a symbol'); | ||
Hoek.assert(!result || typeof result !== 'object' || typeof result.then !== 'function', 'Cannot wrap a promise'); | ||
Hoek.assert(result instanceof Error === false, 'Cannot wrap an error'); | ||
Hoek.assert(typeof result !== 'symbol', 'Cannot wrap a symbol'); | ||
return Response.wrap(result, this.request); | ||
} | ||
return Response.wrap(result, this.request); | ||
} | ||
redirect(location) { | ||
redirect(location) { | ||
return this.response('').redirect(location); | ||
} | ||
return this.response('').redirect(location); | ||
} | ||
entity(options) { | ||
entity(options) { | ||
Hoek.assert(options, 'Entity method missing required options'); | ||
Hoek.assert(options.etag || options.modified, 'Entity methods missing required options key'); | ||
Hoek.assert(options, 'Entity method missing required options'); | ||
Hoek.assert(options.etag || options.modified, 'Entity methods missing required options key'); | ||
this.request._entity = options; | ||
this.request._entity = options; | ||
const entity = Response.entity(options.etag, options); | ||
if (Response.unmodified(this.request, entity)) { | ||
return this.response().code(304).takeover(); | ||
const entity = Response.entity(options.etag, options); | ||
if (Response.unmodified(this.request, entity)) { | ||
return this.response().code(304).takeover(); | ||
} | ||
} | ||
} | ||
state(name, value, options) { | ||
state(name, value, options) { | ||
this.request._setState(name, value, options); | ||
} | ||
this.request._setState(name, value, options); | ||
} | ||
unstate(name, options) { | ||
unstate(name, options) { | ||
this.request._clearState(name, options); | ||
} | ||
}; | ||
this.request._clearState(name, options); | ||
} | ||
authenticated(data) { | ||
internals.authenticated = function (data) { | ||
Hoek.assert(this._auth, 'Method not supported outside of authentication'); | ||
Hoek.assert(data && data.credentials, 'Authentication data missing credentials information'); | ||
Hoek.assert(data && data.credentials, 'Authentication data missing credentials information'); | ||
return new internals.Auth(null, data); | ||
} | ||
return new internals.Auth(null, data); | ||
}; | ||
unauthenticated(error, data) { | ||
Hoek.assert(this._auth, 'Method not supported outside of authentication'); | ||
Hoek.assert(!data || data.credentials, 'Authentication data missing credentials information'); | ||
internals.unauthenticated = function (error, data) { | ||
return new internals.Auth(error, data); | ||
} | ||
}; | ||
Hoek.assert(!data || data.credentials, 'Authentication data missing credentials information'); | ||
Toolkit.prototype.abandon = exports.symbols.abandon; | ||
Toolkit.prototype.close = exports.symbols.close; | ||
Toolkit.prototype.continue = exports.symbols.continue; | ||
return new internals.Auth(error, data); | ||
return Toolkit; | ||
}; | ||
@@ -231,0 +255,0 @@ |
@@ -149,3 +149,3 @@ 'use strict'; | ||
response.statusCode === 200 && | ||
request.route.settings.response.emptyStatusCode === 204) { | ||
request.route.settings.response.emptyStatusCode !== 200) { | ||
@@ -206,3 +206,3 @@ response.code(204); | ||
return new Ammo.Stream(range); | ||
return new Ammo.Clip(range); | ||
}; | ||
@@ -245,3 +245,3 @@ | ||
const team = new Teamwork(); | ||
const team = new Teamwork.Team(); | ||
@@ -299,3 +299,3 @@ // Write payload | ||
err = err || new Boom(`Request ${event}`, { statusCode: request.route.settings.response.disconnectStatusCode }); | ||
err = err || new Boom.Boom(`Request ${event}`, { statusCode: request.route.settings.response.disconnectStatusCode }); | ||
const error = internals.error(request, Boom.boomify(err)); | ||
@@ -302,0 +302,0 @@ request._setResponse(error); |
@@ -11,4 +11,13 @@ 'use strict'; | ||
exports.compile = function (rule) { | ||
exports.validator = function (validator) { | ||
Hoek.assert(validator, 'Missing validator'); | ||
Hoek.assert(typeof validator.compile === 'function', 'Invalid validator compile method'); | ||
return validator; | ||
}; | ||
exports.compile = function (rule, validator) { | ||
// false - nothing allowed | ||
@@ -40,3 +49,4 @@ | ||
return Joi.compile(rule); | ||
Hoek.assert(validator, 'Cannot set uncompiled validation rules without configuring a validator'); | ||
return validator.compile(rule); | ||
}; | ||
@@ -147,3 +157,3 @@ | ||
if (request.route.settings.response.sample) { | ||
const currentSample = Math.ceil((Math.random() * 100)); | ||
const currentSample = Math.ceil(Math.random() * 100); | ||
if (currentSample > request.route.settings.response.sample) { | ||
@@ -215,3 +225,2 @@ return; | ||
catch (err) { | ||
return request._core.toolkit.failAction(request, request.route.settings.response.failAction, err, { tags: ['validation', 'response', 'error'] }); | ||
@@ -218,0 +227,0 @@ } |
@@ -5,5 +5,8 @@ { | ||
"homepage": "https://hapijs.com", | ||
"version": "18.4.0", | ||
"version": "19.0.0", | ||
"repository": "git://github.com/hapijs/hapi", | ||
"main": "lib/index.js", | ||
"files": [ | ||
"lib" | ||
], | ||
"keywords": [ | ||
@@ -16,27 +19,27 @@ "framework", | ||
"dependencies": { | ||
"@hapi/accept": "3.x.x", | ||
"@hapi/ammo": "3.x.x", | ||
"@hapi/boom": "7.x.x", | ||
"@hapi/bounce": "1.x.x", | ||
"@hapi/call": "5.x.x", | ||
"@hapi/catbox": "10.x.x", | ||
"@hapi/catbox-memory": "4.x.x", | ||
"@hapi/heavy": "6.x.x", | ||
"@hapi/hoek": "8.x.x", | ||
"@hapi/joi": "15.x.x", | ||
"@hapi/mimos": "4.x.x", | ||
"@hapi/podium": "3.x.x", | ||
"@hapi/shot": "4.x.x", | ||
"@hapi/somever": "2.x.x", | ||
"@hapi/statehood": "6.x.x", | ||
"@hapi/subtext": "6.x.x", | ||
"@hapi/teamwork": "3.x.x", | ||
"@hapi/topo": "3.x.x" | ||
"@hapi/accept": "4.x.x", | ||
"@hapi/ammo": "4.x.x", | ||
"@hapi/boom": "9.x.x", | ||
"@hapi/bounce": "2.x.x", | ||
"@hapi/call": "7.x.x", | ||
"@hapi/catbox": "11.x.x", | ||
"@hapi/catbox-memory": "5.x.x", | ||
"@hapi/heavy": "7.x.x", | ||
"@hapi/hoek": "9.x.x", | ||
"@hapi/joi": "17.x.x", | ||
"@hapi/mimos": "5.x.x", | ||
"@hapi/podium": "4.x.x", | ||
"@hapi/shot": "5.x.x", | ||
"@hapi/somever": "3.x.x", | ||
"@hapi/statehood": "7.x.x", | ||
"@hapi/subtext": "7.x.x", | ||
"@hapi/teamwork": "4.x.x", | ||
"@hapi/topo": "5.x.x" | ||
}, | ||
"devDependencies": { | ||
"@hapi/code": "6.x.x", | ||
"@hapi/inert": "5.x.x", | ||
"@hapi/joi-next-test": "npm:@hapi/joi@16.x.x", | ||
"@hapi/lab": "20.x.x", | ||
"@hapi/wreck": "15.x.x", | ||
"@hapi/code": "8.x.x", | ||
"@hapi/inert": "6.x.x", | ||
"@hapi/joi-legacy-test": "npm:@hapi/joi@15.x.x", | ||
"@hapi/lab": "22.x.x", | ||
"@hapi/wreck": "17.x.x", | ||
"@hapi/vision": "5.x.x", | ||
@@ -43,0 +46,0 @@ "handlebars": "4.x.x" |
<img src="https://raw.githubusercontent.com/hapijs/assets/master/images/hapi.png" width="400px" /> | ||
# @hapi/hapi | ||
# The Simple, Secure Framework Developers Trust | ||
@@ -8,1 +10,9 @@ | ||
### Visit the [hapi.dev](https://hapi.dev) Developer Portal for tutorials, documentation, and support | ||
## Useful resources | ||
- [Documentation and API](https://hapi.dev/) | ||
- [Version status](https://hapi.dev/resources/status/#hapi) (builds, dependencies, node versions, licenses, eol) | ||
- [Changelog](https://hapi.dev/resources/changelog/) | ||
- [Project policies](https://hapi.dev/policies/) | ||
- [Free and commercial support options](https://hapi.dev/support/) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
176766
4509
18
22
+ Added@hapi/accept@4.0.1(transitive)
+ Added@hapi/address@4.1.0(transitive)
+ Added@hapi/ammo@4.0.1(transitive)
+ Added@hapi/b64@5.0.0(transitive)
+ Added@hapi/boom@8.0.19.1.4(transitive)
+ Added@hapi/bounce@2.0.0(transitive)
+ Added@hapi/bourne@2.1.0(transitive)
+ Added@hapi/call@7.0.1(transitive)
+ Added@hapi/catbox@11.1.1(transitive)
+ Added@hapi/catbox-memory@5.0.1(transitive)
+ Added@hapi/content@5.0.2(transitive)
+ Added@hapi/cryptiles@5.1.0(transitive)
+ Added@hapi/file@2.0.0(transitive)
+ Added@hapi/formula@2.0.0(transitive)
+ Added@hapi/heavy@7.0.1(transitive)
+ Added@hapi/hoek@9.3.0(transitive)
+ Added@hapi/iron@6.0.0(transitive)
+ Added@hapi/joi@17.1.1(transitive)
+ Added@hapi/mimos@5.0.0(transitive)
+ Added@hapi/nigel@4.0.2(transitive)
+ Added@hapi/pez@5.1.0(transitive)
+ Added@hapi/pinpoint@2.0.1(transitive)
+ Added@hapi/podium@4.1.3(transitive)
+ Added@hapi/shot@5.0.5(transitive)
+ Added@hapi/somever@3.0.1(transitive)
+ Added@hapi/statehood@7.0.4(transitive)
+ Added@hapi/subtext@7.1.0(transitive)
+ Added@hapi/teamwork@4.0.05.1.1(transitive)
+ Added@hapi/topo@5.1.0(transitive)
+ Added@hapi/validate@1.1.3(transitive)
+ Added@hapi/vise@4.0.0(transitive)
- Removed@hapi/accept@3.2.4(transitive)
- Removed@hapi/address@2.1.4(transitive)
- Removed@hapi/ammo@3.1.2(transitive)
- Removed@hapi/b64@4.2.1(transitive)
- Removed@hapi/boom@7.4.11(transitive)
- Removed@hapi/bounce@1.3.2(transitive)
- Removed@hapi/bourne@1.3.2(transitive)
- Removed@hapi/call@5.1.3(transitive)
- Removed@hapi/catbox@10.2.3(transitive)
- Removed@hapi/catbox-memory@4.1.1(transitive)
- Removed@hapi/content@4.1.1(transitive)
- Removed@hapi/cryptiles@4.2.1(transitive)
- Removed@hapi/file@1.0.0(transitive)
- Removed@hapi/formula@1.2.0(transitive)
- Removed@hapi/heavy@6.2.2(transitive)
- Removed@hapi/iron@5.1.4(transitive)
- Removed@hapi/joi@15.1.116.1.8(transitive)
- Removed@hapi/mimos@4.1.1(transitive)
- Removed@hapi/nigel@3.1.1(transitive)
- Removed@hapi/pez@4.1.2(transitive)
- Removed@hapi/pinpoint@1.0.2(transitive)
- Removed@hapi/podium@3.4.3(transitive)
- Removed@hapi/shot@4.1.2(transitive)
- Removed@hapi/somever@2.1.1(transitive)
- Removed@hapi/statehood@6.1.2(transitive)
- Removed@hapi/subtext@6.1.3(transitive)
- Removed@hapi/teamwork@3.3.1(transitive)
- Removed@hapi/topo@3.1.6(transitive)
- Removed@hapi/vise@3.1.1(transitive)
Updated@hapi/accept@4.x.x
Updated@hapi/ammo@4.x.x
Updated@hapi/boom@9.x.x
Updated@hapi/bounce@2.x.x
Updated@hapi/call@7.x.x
Updated@hapi/catbox@11.x.x
Updated@hapi/catbox-memory@5.x.x
Updated@hapi/heavy@7.x.x
Updated@hapi/hoek@9.x.x
Updated@hapi/joi@17.x.x
Updated@hapi/mimos@5.x.x
Updated@hapi/podium@4.x.x
Updated@hapi/shot@5.x.x
Updated@hapi/somever@3.x.x
Updated@hapi/statehood@7.x.x
Updated@hapi/subtext@7.x.x
Updated@hapi/teamwork@4.x.x
Updated@hapi/topo@5.x.x