UNPKG

@hapi/hawk

Version:

HTTP Hawk Authentication Scheme

135 lines (95 loc) 4.36 kB
'use strict'; const Boom = require('@hapi/boom'); const Hoek = require('@hapi/hoek'); const Crypto = require('./crypto'); const Server = require('./server'); const internals = {}; exports.plugin = { pkg: require('../package.json'), requirements: { hapi: '>=18.4.0' }, register: function (server) { server.auth.scheme('hawk', internals.hawk); server.auth.scheme('bewit', internals.bewit); } }; internals.hawk = function (server, options) { Hoek.assert(options, 'Invalid hawk scheme options'); Hoek.assert(options.getCredentialsFunc, 'Missing required getCredentialsFunc method in hawk scheme configuration'); const settings = Hoek.clone(options); settings.hawk = settings.hawk || {}; const scheme = { authenticate: async function (request, h) { try { var result = await Server.authenticate(request.raw.req, settings.getCredentialsFunc, settings.hawk); } catch (err) { const { credentials, artifacts } = err; return h.unauthenticated(err, credentials ? { credentials, artifacts } : undefined); } if (server.auth.lookup(request.route).payload) { request.events.once('peek', (chunk) => { const payloadHash = Crypto.initializePayloadHash(request.auth.credentials.algorithm, request.headers['content-type']); payloadHash.update(chunk); request.events.on('peek', (chunk2) => payloadHash.update(chunk2)); request.events.once('finish', () => { request.plugins['hapi-auth-hawk'] = { payloadHash: Crypto.finalizePayloadHash(payloadHash) }; }); }); } return h.authenticated(result); }, payload: function (request, h) { if (!request.auth.artifacts.hash) { throw Boom.unauthorized(null, 'Hawk'); // Missing } const plugin = request.plugins['hapi-auth-hawk']; if (!plugin) { throw Boom.unauthorized('Payload is invalid'); } try { Server.authenticatePayloadHash(plugin.payloadHash, request.auth.artifacts); return h.continue; } catch (err) { throw Boom.unauthorized('Payload is invalid'); } }, response: function (request, h) { const response = request.response; const payloadHash = Crypto.initializePayloadHash(request.auth.credentials.algorithm, response.headers['content-type']); response.header('trailer', 'server-authorization'); response.header('transfer-encoding', 'chunked'); delete response.headers['content-length']; // Cannot not send a content-length header alongside transfer-encoding (https://tools.ietf.org/html/rfc7230#section-3.3.3) response.events.on('peek', (chunk) => { payloadHash.update(chunk); }); response.events.once('finish', () => { const header = Server.header(request.auth.credentials, request.auth.artifacts, { hash: Crypto.finalizePayloadHash(payloadHash) }); request.raw.res.addTrailers({ 'server-authorization': header }); }); return h.continue; } }; return scheme; }; internals.bewit = function (server, options) { Hoek.assert(options, 'Invalid bewit scheme options'); Hoek.assert(options.getCredentialsFunc, 'Missing required getCredentialsFunc method in bewit scheme configuration'); const settings = Hoek.clone(options); settings.hawk = settings.hawk || {}; const scheme = { authenticate: async function (request, h) { try { const { credentials, attributes } = await Server.authenticateBewit(request.raw.req, settings.getCredentialsFunc, settings.hawk); return h.authenticated({ credentials, attributes }); } catch (err) { const { credentials, attributes } = err; return h.unauthenticated(err, credentials ? { credentials, attributes } : undefined); } } }; return scheme; };