internals.writeHead = function (response) { const res = response.request.raw.res; const headers = Object.keys(response.headers); let i = 0; try { for (; i < headers.length; ++i) { const header = headers[i]; const value = response.headers[header]; if (value !== undefined) { res.setHeader(header, value); } } } catch (err) { for (--i; i >= 0; --i) { res.removeHeader(headers[i]); // Undo headers } throw Boom.boomify(err); } if (response.settings.message) { res.statusMessage = response.settings.message; } try { res.writeHead(response.statusCode); } catch (err) { throw Boom.boomify(err); } };
static wrap(result, request) { if (result instanceof internals.Response || typeof result === 'symbol') { return result; } if (result instanceof Error) { return Boom.boomify(result); } return new internals.Response(result, request); }
_prepare() { this._passThrough(); if (!this._processors.prepare) { return this; } try { return this._processors.prepare(this); } catch (err) { throw Boom.boomify(err); } }
internals.end = function (env, event, err) { const { request, stream, team } = env; if (!team) { // Used instead of cleaning up emitter listeners return; } env.team = null; if (request.raw.res.finished) { if (event !== 'aborted') { request.info.responded = Date.now(); } team.attend(); return; } if (err) { request.raw.res.destroy(); Response.drain(stream); } err = err || new Boom(`Request ${event}`, { statusCode: request.route.settings.response.disconnectStatusCode }); const error = internals.error(Boom.boomify(err), request); request._setResponse(error); if (request.raw.res[Config.symbol]) { request.raw.res.statusCode = error.statusCode; request.raw.res[Config.symbol].result = error.source; // Force injected response to error } if (event) { request._log(['response', 'error', event]); } else { request._log(['response', 'error'], err); } request.raw.res.end(); // Triggers injection promise resolve team.attend(); };
async _marshal() { let source = this.source; // Processor marshal if (this._processors.marshal) { try { source = await this._processors.marshal(this); } catch (err) { throw Boom.boomify(err); } } // Stream source if (source instanceof Stream) { if (typeof source._read !== 'function') { throw Boom.badImplementation('Stream must have a readable interface'); } if (source._readableState.objectMode) { throw Boom.badImplementation('Cannot reply with stream in object mode'); } this._payload = source; return; } // Plain source (non string or null) const jsonify = (this.variety === 'plain' && source !== null && typeof source !== 'string'); if (!jsonify && this.settings.stringify) { throw Boom.badImplementation('Cannot set formatting options on non object response'); } let payload = source; if (jsonify) { const options = this.settings.stringify || {}; const space = options.space || this.request.route.settings.json.space; const replacer = options.replacer || this.request.route.settings.json.replacer; const suffix = options.suffix || this.request.route.settings.json.suffix || ''; const escape = this.request.route.settings.json.escape || false; try { if (replacer || space) { payload = JSON.stringify(payload, replacer, space); } else { payload = JSON.stringify(payload); } } catch (err) { throw Boom.boomify(err); } if (suffix) { payload = payload + suffix; } if (escape) { payload = Hoek.escapeJson(payload); } } this._payload = new internals.Response.Payload(payload, this.settings); }
exports.authenticate = async function (req, credentialsFunc, options) { options = options || {}; // Default options options.timestampSkewSec = options.timestampSkewSec || 60; // 60 seconds // Application time const now = Utils.now(options.localtimeOffsetMsec); // Measure now before any other processing // Convert node Http request object to a request configuration object const request = Utils.parseRequest(req, options); // Parse HTTP Authorization header const attributes = Utils.parseAuthorizationHeader(request.authorization); // Construct artifacts container const artifacts = { method: request.method, host: request.host, port: request.port, resource: request.url, ts: attributes.ts, nonce: attributes.nonce, hash: attributes.hash, ext: attributes.ext, app: attributes.app, dlg: attributes.dlg, mac: attributes.mac, id: attributes.id }; // Verify required header attributes if (!attributes.id || !attributes.ts || !attributes.nonce || !attributes.mac) { throw Boom.badRequest('Missing attributes', { decorate: { artifacts } }); } // Fetch Hawk credentials try { var credentials = await credentialsFunc(attributes.id); } catch (err) { throw Boom.boomify(err, { decorate: { artifacts } }); } if (!credentials) { throw Object.assign(Utils.unauthorized('Unknown credentials'), { artifacts }); } const result = { credentials, artifacts }; if (!credentials.key || !credentials.algorithm) { throw new Boom('Invalid credentials', { decorate: result }); } if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) { throw new Boom('Unknown algorithm', { decorate: result }); } // Calculate MAC const mac = Crypto.calculateMac('header', credentials, artifacts); if (!Cryptiles.fixedTimeComparison(mac, attributes.mac)) { throw Object.assign(Utils.unauthorized('Bad mac'), result); } // Check payload hash if (options.payload || options.payload === '') { if (!attributes.hash) { throw Object.assign(Utils.unauthorized('Missing required payload hash'), result); } const hash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, request.contentType); if (!Cryptiles.fixedTimeComparison(hash, attributes.hash)) { throw Object.assign(Utils.unauthorized('Bad payload hash'), result); } } // Check nonce if (options.nonceFunc) { try { await options.nonceFunc(credentials.key, attributes.nonce, attributes.ts); } catch (err) { throw Object.assign(Utils.unauthorized('Invalid nonce'), result); } } // Check timestamp staleness if (Math.abs((attributes.ts * 1000) - now) > (options.timestampSkewSec * 1000)) { const tsm = Crypto.timestampMessage(credentials, options.localtimeOffsetMsec); throw Object.assign(Utils.unauthorized('Stale timestamp', tsm), result); } // Successful authentication return result; };