@hapi/hawk
Version:
HTTP Hawk Authentication Scheme
135 lines (95 loc) • 4.36 kB
JavaScript
;
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;
};