hapi
Version:
HTTP Server framework
1,393 lines (1,008 loc) • 53.7 kB
JavaScript
// Load modules
var Crypto = require('crypto');
var Lab = require('lab');
var Hawk = require('hawk');
var Stream = require('stream');
var Hapi = require('../..');
// Declare internals
var internals = {};
// Test shortcuts
var expect = Lab.expect;
var before = Lab.before;
var after = Lab.after;
var describe = Lab.experiment;
var it = Lab.test;
var itx = function () { };
describe('Auth', function () {
var basicHeader = function (username, password) {
return 'Basic ' + (new Buffer(username + ':' + password, 'utf8')).toString('base64');
};
describe('Basic', function () {
var loadUser = function (username, password, callback) {
if (username === 'john') {
return callback(null, password === '12345', {
user: 'john',
scope: [],
tos: '1.0.0'
});
}
else if (username === 'jane') {
return callback(Hapi.error.internal('boom'));
}
else if (username === 'invalid1') {
return callback(null, true, 'bad');
}
return callback(null, false);
};
var config = {
auth: {
scheme: 'basic',
validateFunc: loadUser,
defaultMode: 'required'
}
};
var server = new Hapi.Server(config);
var basicHandler = function (request) {
request.reply('Success');
};
var doubleHandler = function (request) {
var options = { method: 'POST', url: '/basic', headers: { authorization: basicHeader('john', '12345') }, credentials: request.auth.credentials };
server.inject(options, function (res) {
request.reply(res.result);
});
};
server.route([
{ method: 'POST', path: '/basic', handler: basicHandler, config: { auth: true } },
{ method: 'POST', path: '/basicOptional', handler: basicHandler, config: { auth: { mode: 'optional' } } },
{ method: 'POST', path: '/basicScope', handler: basicHandler, config: { auth: { scope: 'x' } } },
{ method: 'POST', path: '/basicTos', handler: basicHandler, config: { auth: { tos: '1.1.x' } } },
{ method: 'POST', path: '/double', handler: doubleHandler }
]);
it('returns a reply on successful auth', function (done) {
var request = { method: 'POST', url: '/basic', headers: { authorization: basicHeader('john', '12345') } };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result).to.equal('Success');
done();
});
});
it('returns a reply on successful double auth', function (done) {
var request = { method: 'POST', url: '/double', headers: { authorization: basicHeader('john', '12345') } };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result).to.equal('Success');
done();
});
});
it('returns a reply on failed optional auth', function (done) {
var request = { method: 'POST', url: '/basicOptional' };
server.inject(request, function (res) {
expect(res.result).to.equal('Success');
done();
});
});
it('returns an error on bad password', function (done) {
var request = { method: 'POST', url: '/basic', headers: { authorization: basicHeader('john', 'abcd') } };
server.inject(request, function (res) {
expect(res.result.code).to.equal(401);
done();
});
});
it('returns an error on bad header format', function (done) {
var request = { method: 'POST', url: '/basic', headers: { authorization: 'basic' } };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result.code).to.equal(400);
expect(res.result.isMissing).to.equal(undefined);
done();
});
});
it('returns an error on bad header format', function (done) {
var request = { method: 'POST', url: '/basic', headers: { authorization: 'basic' } };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result.code).to.equal(400);
expect(res.result.isMissing).to.equal(undefined);
done();
});
});
it('returns an error on bad header internal syntax', function (done) {
var request = { method: 'POST', url: '/basic', headers: { authorization: 'basic 123' } };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result.code).to.equal(400);
expect(res.result.isMissing).to.equal(undefined);
done();
});
});
it('returns an error on missing username', function (done) {
var request = { method: 'POST', url: '/basic', headers: { authorization: basicHeader('', '') } };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result.code).to.equal(400);
done();
});
});
it('returns an error on unknown user', function (done) {
var request = { method: 'POST', url: '/basic', headers: { authorization: basicHeader('doe', '12345') } };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result.code).to.equal(401);
done();
});
});
it('returns an error on internal user lookup error', function (done) {
var request = { method: 'POST', url: '/basic', headers: { authorization: basicHeader('jane', '12345') } };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result.code).to.equal(500);
done();
});
});
it('returns an error on non-object credentials error', function (done) {
var request = { method: 'POST', url: '/basic', headers: { authorization: basicHeader('invalid1', '12345') } };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result.code).to.equal(500);
done();
});
});
it('returns an error on insufficient tos', function (done) {
var request = { method: 'POST', url: '/basicTos', headers: { authorization: basicHeader('john', '12345') } };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result.code).to.equal(403);
done();
});
});
it('returns an error on insufficient scope', function (done) {
var request = { method: 'POST', url: '/basicScope', headers: { authorization: basicHeader('john', '12345') } };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result.code).to.equal(403);
done();
});
});
itx('should not ask for credentials if no server auth configured', function (done) {
var config = {};
var server = new Hapi.Server(config);
server.route({
path: '/noauth',
method: 'GET',
config: {
handler: function (req) {
req.reply('Success');
}
}
});
server.inject('/noauth', function (res) {
expect(res.result).to.exist;
expect(res.statusCode).to.equal(200);
done();
});
});
itx('should ask for credentials if server has one default strategy', function (done) {
var config = {
auth: {
scheme: 'basic',
validateFunc: loadUser
}
};
var server = new Hapi.Server(config);
server.route({
path: '/noauth',
method: 'GET',
config: {
auth: 'default',
handler: function (req) {
req.reply('Success');
}
}
});
var validOptions = { method: 'GET', url: '/noauth', headers: { authorization: basicHeader('john', '12345') } };
server.inject(validOptions, function (res) {
expect(res.result).to.exist;
expect(res.statusCode).to.equal(200);
server.inject('/noauth', function (res) {
expect(res.result).to.exist;
expect(res.statusCode).to.equal(401);
done();
});
});
});
itx('should throw if server has strategies route refers to nonexistent strategy', function (done) {
var config = {
auth: {
'default': {
scheme: 'basic',
validateFunc: loadUser
},
'b': {
scheme: 'basic',
validateFunc: loadUser
}
}
};
var server = new Hapi.Server(config);
var fn = function () {
server.route({
path: '/noauth',
method: 'GET',
config: {
auth: {
strategy: 'hello'
},
handler: function (req) {
req.reply('Success');
}
}
});
};
expect(fn).to.throw();
done();
});
it('cannot add a route that has payload validation required', function (done) {
var fn = function () {
server.route({ method: 'POST', path: '/basicPayload', handler: basicHandler, config: { auth: { mode: 'required', payload: 'required' }, payload: 'raw' } });
};
expect(fn).to.throw(Error);
done();
});
it('cannot add a route that has payload validation as optional', function (done) {
var fn = function () {
server.route({ method: 'POST', path: '/basicPayload', handler: basicHandler, config: { auth: { mode: 'required', payload: 'optional' }, payload: 'raw' } });
};
expect(fn).to.throw(Error);
done();
});
it('can add a route that has payload validation as none', function (done) {
var fn = function () {
server.route({ method: 'POST', path: '/basicPayload', handler: basicHandler, config: { auth: { mode: 'required', payload: false }, payload: 'raw' } });
};
expect(fn).to.not.throw(Error);
done();
});
});
describe('Hawk', function () {
var credentials = {
'john': {
cred: {
id: 'john',
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
algorithm: 'sha256'
}
},
'jane': {
err: Hapi.error.internal('boom')
},
'joan': {
cred: {
id: 'joan',
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
algorithm: 'sha256'
}
}
};
var getCredentials = function (id, callback) {
if (credentials[id]) {
return callback(credentials[id].err, credentials[id].cred);
}
else {
return callback(null, null);
}
};
var hawkHeader = function (id, path) {
if (credentials[id] && credentials[id].cred) {
return Hawk.client.header('http://example.com:8080' + path, 'POST', { credentials: credentials[id].cred });
}
else {
return '';
}
};
var config = {
auth: {
scheme: 'hawk',
getCredentialsFunc: getCredentials
}
};
var server = new Hapi.Server(config);
var hawkHandler = function (request) {
request.reply('Success');
};
var hawkErrorHandler = function (request) {
request.reply(new Error());
};
var hawkStreamHandler = function (request) {
var TestStream = function () {
Stream.Readable.call(this);
};
Hapi.utils.inherits(TestStream, Stream.Readable);
TestStream.prototype._read = function (size) {
var self = this;
if (this.isDone) {
return;
}
this.isDone = true;
setTimeout(function () {
self.push('hi');
}, 2);
setTimeout(function () {
self.push(null);
}, 5);
};
var stream = new TestStream();
request.reply(stream);
};
server.route([
{ method: 'POST', path: '/hawk', handler: hawkHandler, config: { auth: 'default' } },
{ method: 'POST', path: '/hawkValidate', handler: hawkHandler, config: { auth: 'default', validate: { query: { } } } },
{ method: 'POST', path: '/hawkError', handler: hawkErrorHandler, config: { auth: 'default' } },
{ method: 'POST', path: '/hawkStream', handler: hawkStreamHandler, config: { auth: 'default' } },
{ method: 'POST', path: '/hawkOptional', handler: hawkHandler, config: { auth: { mode: 'optional' } } },
{ method: 'POST', path: '/hawkScope', handler: hawkHandler, config: { auth: { scope: 'x' } } },
{ method: 'POST', path: '/hawkTos', handler: hawkHandler, config: { auth: { tos: '2.0.0' } } },
{ method: 'POST', path: '/hawkPayload', handler: hawkHandler, config: { auth: { mode: 'required', payload: 'required' }, payload: 'raw' } },
{ method: 'POST', path: '/hawkPayloadOptional', handler: hawkHandler, config: { auth: { mode: 'required', payload: 'optional' }, payload: 'raw' } },
{ method: 'POST', path: '/hawkPayloadNone', handler: hawkHandler, config: { auth: { mode: 'required', payload: false }, payload: 'raw' } },
{ method: 'POST', path: '/hawkOptionalPayload', handler: hawkHandler, config: { auth: { mode: 'optional', payload: 'required' }, payload: 'raw' } }
]);
it('returns a reply on successful auth', function (done) {
var request = { method: 'POST', url: 'http://example.com:8080/hawk', headers: { authorization: hawkHeader('john', '/hawk').field } };
server.inject(request, function (res) {
expect(res.statusCode).to.equal(200);
expect(res.result).to.equal('Success');
done();
});
});
it('returns a reply on failed optional auth', function (done) {
var request = { method: 'POST', url: 'http://example.com:8080/hawkOptional' };
server.inject(request, function (res) {
expect(res.result).to.equal('Success');
done();
});
});
it('includes authorization header in response when the response is a stream', function (done) {
var authHeader = hawkHeader('john', '/hawkStream');
var request = { method: 'POST', url: 'http://example.com:8080/hawkStream', headers: { authorization: authHeader.field } };
server.inject(request, function (res) {
expect(res.statusCode).to.equal(200);
expect(res.raw.res._trailer).to.contain('Hawk');
var options = {
payload: res.payload
};
getCredentials('john', function (err, cred) {
var header = Hawk.server.header(cred, authHeader.artifacts, options);
var trailerAuth = res.raw.res._trailer.split(':')[1];
trailerAuth = trailerAuth.substr(1, trailerAuth.lastIndexOf('"'));
expect(res.headers.trailer).to.contain('Server-Authorization');
expect(header).to.equal(trailerAuth);
done();
});
});
});
it('includes valid authorization header in response when the response is text', function (done) {
var authHeader = hawkHeader('john', '/hawk');
var request = { method: 'POST', url: 'http://example.com:8080/hawk', headers: { authorization: authHeader.field } };
server.inject(request, function (res) {
expect(res.headers['server-authorization']).to.contain('Hawk');
expect(res.statusCode).to.equal(200);
var options = {
payload: res.payload,
contentType: res.headers['content-type']
};
getCredentials('john', function (err, cred) {
var header = Hawk.server.header(cred, authHeader.artifacts, options);
expect(header).to.equal(res.headers['server-authorization']);
done();
});
});
});
it('includes valid authorization header in response when the request fails validation', function (done) {
var authHeader = hawkHeader('john', '/hawkValidate?a=1');
var request = { method: 'POST', url: 'http://example.com:8080/hawkValidate?a=1', headers: { authorization: authHeader.field } };
server.inject(request, function (res) {
expect(res.headers['server-authorization']).to.exist;
expect(res.headers['server-authorization']).to.contain('Hawk');
expect(res.statusCode).to.equal(400);
var options = {
payload: res.payload,
contentType: res.headers['content-type']
};
getCredentials('john', function (err, cred) {
authHeader.artifacts.credentials = cred;
var header = Hawk.server.header(cred, authHeader.artifacts, options);
expect(header).to.equal(res.headers['server-authorization']);
done();
});
});
});
it('doesn\'t include authorization header in response when the response is an error', function (done) {
var request = { method: 'POST', url: 'http://example.com:8080/hawkError', headers: { authorization: hawkHeader('john', '/hawkError').field } };
server.inject(request, function (res) {
expect(res.statusCode).to.equal(500);
expect(res.headers.authorization).to.not.exist;
done();
});
});
it('returns an error on bad auth header', function (done) {
var request = { method: 'POST', url: 'http://example.com:8080/hawk', headers: { authorization: hawkHeader('john', 'abcd').field } };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result.code).to.equal(401);
done();
});
});
it('returns an error on bad header format', function (done) {
var request = { method: 'POST', url: 'http://example.com:8080/hawk', headers: { authorization: 'junk' } };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result.code).to.equal(401);
done();
});
});
it('returns an error on bad scheme', function (done) {
var request = { method: 'POST', url: 'http://example.com:8080/hawk', headers: { authorization: 'junk something' } };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result.code).to.equal(401);
done();
});
});
it('returns an error on insufficient tos', function (done) {
var request = { method: 'POST', url: 'http://example.com:8080/hawkTos', headers: { authorization: hawkHeader('john', '/hawkTos').field } };
server.inject(request, function (res) {
expect(res.result.code).to.equal(403);
done();
});
});
it('returns an error on insufficient scope', function (done) {
var request = { method: 'POST', url: 'http://example.com:8080/hawkScope', headers: { authorization: hawkHeader('john', '/hawkScope').field } };
server.inject(request, function (res) {
expect(res.result.code).to.equal(403);
done();
});
});
it('returns a reply on successful auth when using a custom host header key', function (done) {
var request = { method: 'POST', url: '/hawk', headers: { authorization: hawkHeader('john', '/hawk').field, custom: 'example.com:8080' } };
var config = {
auth: {
scheme: 'hawk',
getCredentialsFunc: getCredentials,
hostHeaderName: 'custom'
}
};
var server = new Hapi.Server(config);
server.route({ method: 'POST', path: '/hawk', handler: hawkHandler, config: { auth: 'default' } });
server.inject(request, function (res) {
expect(res.statusCode).to.equal(200);
expect(res.result).to.equal('Success');
done();
});
});
it('returns a reply on successful auth and payload validation', function (done) {
var payload = 'application text formatted payload';
var authHeader = Hawk.client.header('http://example.com:8080/hawkPayload', 'POST', { credentials: credentials.john.cred, payload: payload, contentType: 'application/text' });
var request = { method: 'POST', url: 'http://example.com:8080/hawkPayload', headers: { authorization: authHeader.field, 'content-type': 'application/text' }, payload: payload };
server.inject(request, function (res) {
expect(res.statusCode).to.equal(200);
expect(res.result).to.equal('Success');
done();
});
});
it('returns an error with payload validation when the payload is tampered with', function (done) {
var payload = 'Here is my payload';
var authHeader = Hawk.client.header('http://example.com:8080/hawkPayload', 'POST', { credentials: credentials.john.cred, payload: payload });
payload += 'HACKED';
var request = { method: 'POST', url: 'http://example.com:8080/hawkPayload', headers: { authorization: authHeader.field }, payload: payload };
server.inject(request, function (res) {
expect(res.statusCode).to.equal(401);
expect(res.result.message).to.equal('Payload is invalid');
done();
});
});
it('returns an error with payload validation when the payload is tampered with and the route has optional validation', function (done) {
var payload = 'Here is my payload';
var authHeader = Hawk.client.header('http://example.com:8080/hawkPayloadOptional', 'POST', { credentials: credentials.john.cred, payload: payload });
payload += 'HACKED';
var request = { method: 'POST', url: 'http://example.com:8080/hawkPayloadOptional', headers: { authorization: authHeader.field }, payload: payload };
server.inject(request, function (res) {
expect(res.statusCode).to.equal(401);
expect(res.result.message).to.equal('Payload is invalid');
done();
});
});
it('returns a reply on successful auth and payload validation when validation is optional', function (done) {
var payload = 'Here is my payload';
var authHeader = Hawk.client.header('http://example.com:8080/hawkPayloadOptional', 'POST', { credentials: credentials.john.cred, payload: payload });
var request = { method: 'POST', url: 'http://example.com:8080/hawkPayloadOptional', headers: { authorization: authHeader.field }, payload: payload };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result).to.equal('Success');
done();
});
});
it('returns a reply on successful auth when payload validation is optional and no payload hash exists', function (done) {
var payload = 'Here is my payload';
var authHeader = Hawk.client.header('http://example.com:8080/hawkPayloadOptional', 'POST', { credentials: credentials.john.cred });
var request = { method: 'POST', url: 'http://example.com:8080/hawkPayloadOptional', headers: { authorization: authHeader.field }, payload: payload };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result).to.equal('Success');
done();
});
});
it('returns a reply on successful auth and when payload validation is disabled', function (done) {
var payload = 'Here is my payload';
var authHeader = Hawk.client.header('http://example.com:8080/hawkPayloadNone', 'POST', { credentials: credentials.john.cred, payload: payload });
var request = { method: 'POST', url: 'http://example.com:8080/hawkPayloadNone', headers: { authorization: authHeader.field }, payload: payload };
server.inject(request, function (res) {
expect(res.statusCode).to.equal(200);
expect(res.result).to.equal('Success');
done();
});
});
it('returns a reply on successful auth when the payload is tampered with and the route has disabled validation', function (done) {
var payload = 'Here is my payload';
var authHeader = Hawk.client.header('http://example.com:8080/hawkPayloadNone', 'POST', { credentials: credentials.john.cred, payload: payload });
payload += 'HACKED';
var request = { method: 'POST', url: 'http://example.com:8080/hawkPayloadNone', headers: { authorization: authHeader.field }, payload: payload };
server.inject(request, function (res) {
expect(res.statusCode).to.equal(200);
expect(res.result).to.equal('Success');
done();
});
});
it('returns a reply on successful auth when auth is optional and when payload validation is required', function (done) {
var payload = 'Here is my payload';
var authHeader = Hawk.client.header('http://example.com:8080/hawkOptionalPayload', 'POST', { credentials: credentials.john.cred, payload: payload });
var request = { method: 'POST', url: 'http://example.com:8080/hawkOptionalPayload', headers: { authorization: authHeader.field }, payload: payload };
server.inject(request, function (res) {
expect(res.statusCode).to.equal(200);
expect(res.result).to.equal('Success');
done();
});
});
it('returns an error with payload validation when the payload is tampered with and the route has optional auth', function (done) {
var payload = 'Here is my payload';
var authHeader = Hawk.client.header('http://example.com:8080/hawkOptionalPayload', 'POST', { credentials: credentials.john.cred, payload: payload });
payload += 'HACKED';
var request = { method: 'POST', url: 'http://example.com:8080/hawkOptionalPayload', headers: { authorization: authHeader.field }, payload: payload };
server.inject(request, function (res) {
expect(res.statusCode).to.equal(401);
expect(res.result.message).to.equal('Payload is invalid');
done();
});
});
});
describe('Bewit', function () {
var credentials = {
'john': {
cred: {
id: 'john',
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
algorithm: 'sha256'
}
},
'jane': {
err: Hapi.error.internal('boom')
}
};
var getCredentials = function (id, callback) {
if (credentials[id]) {
return callback(credentials[id].err, credentials[id].cred);
}
else {
return callback(null, null);
}
};
var getBewit = function (id, path) {
if (credentials[id] && credentials[id].cred) {
return Hawk.uri.getBewit('http://example.com:8080' + path, { credentials: credentials[id].cred, ttlSec: 60 });
}
else {
return '';
}
};
var config = {
auth: {
scheme: 'bewit',
getCredentialsFunc: getCredentials
}
};
var server = new Hapi.Server(config);
var bewitHandler = function (request) {
request.reply('Success');
};
server.route([
{ method: 'GET', path: '/bewit', handler: bewitHandler, config: { auth: 'default' } },
{ method: 'GET', path: '/bewitOptional', handler: bewitHandler, config: { auth: { mode: 'optional' } } },
{ method: 'GET', path: '/bewitScope', handler: bewitHandler, config: { auth: { scope: 'x' } } },
{ method: 'GET', path: '/bewitTos', handler: bewitHandler, config: { auth: { tos: '2.0.0' } } }
]);
it('returns a reply on successful auth', function (done) {
var bewit = getBewit('john', '/bewit');
server.inject('http://example.com:8080/bewit?bewit=' + bewit, function (res) {
expect(res.result).to.equal('Success');
done();
});
});
it('returns an error reply on failed optional auth', function (done) {
var bewit = getBewit('john', '/abc');
server.inject('http://example.com:8080/bewitOptional?bewit=' + bewit, function (res) {
expect(res.result.code).to.equal(401);
done();
});
});
it('returns an error on bad bewit', function (done) {
var bewit = getBewit('john', '/abc');
server.inject('http://example.com:8080/bewit?bewit=' + bewit, function (res) {
expect(res.result.code).to.equal(401);
done();
});
});
it('returns an error on bad bewit format', function (done) {
server.inject('http://example.com:8080/bewit?bewit=junk', function (res) {
expect(res.result.code).to.equal(400);
done();
});
});
it('returns an error on insufficient tos', function (done) {
var bewit = getBewit('john', '/bewitTos');
server.inject('http://example.com:8080/bewitTos?bewit=' + bewit, function (res) {
expect(res.result.code).to.equal(403);
done();
});
});
it('returns an error on insufficient scope', function (done) {
var bewit = getBewit('john', '/bewitScope');
server.inject('http://example.com:8080/bewitScope?bewit=' + bewit, function (res) {
expect(res.result.code).to.equal(403);
done();
});
});
it('returns a reply on successful auth when using a custom host header key', function (done) {
var bewit = getBewit('john', '/bewit');
var request = { method: 'GET', url: '/bewit?bewit=' + bewit, headers: { custom: 'example.com:8080' } };
var config = {
auth: {
scheme: 'bewit',
getCredentialsFunc: getCredentials,
hostHeaderName: 'custom'
}
};
var server = new Hapi.Server(config);
server.route({ method: 'GET', path: '/bewit', handler: bewitHandler, config: { auth: 'default' } });
server.inject(request, function (res) {
expect(res.statusCode).to.equal(200);
expect(res.result).to.equal('Success');
done();
});
});
it('cannot add a route that has payload validation required', function (done) {
var fn = function () {
server.route({ method: 'POST', path: '/bewitPayload', handler: bewitHandler, config: { auth: { mode: 'required', payload: 'required' }, payload: 'raw' } });
};
expect(fn).to.throw(Error);
done();
});
it('cannot add a route that has payload validation as optional', function (done) {
var fn = function () {
server.route({ method: 'POST', path: '/bewitPayload', handler: bewitHandler, config: { auth: { mode: 'required', payload: 'optional' }, payload: 'raw' } });
};
expect(fn).to.throw(Error);
done();
});
it('can add a route that has payload validation as none', function (done) {
var fn = function () {
server.route({ method: 'POST', path: '/bewitPayload', handler: bewitHandler, config: { auth: { mode: 'required', payload: false }, payload: 'raw' } });
};
expect(fn).to.not.throw(Error);
done();
});
});
describe('Ext', function () {
it('returns a reply on successful ext any', function (done) {
var config = {
auth: {
implementation: {
authenticate: function (request, callback) {
callback(null, {});
}
}
}
};
var handler = function (request) {
request.reply('Success');
};
var server = new Hapi.Server(config);
server.route({ method: 'POST', path: '/ext', handler: handler, config: { auth: 'default' } });
var request = { method: 'POST', url: '/ext' };
server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result).to.equal('Success');
done();
});
});
});
describe('Multiple', function () {
var credentials = {
'john': {
cred: {
id: 'john',
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
algorithm: 'sha256'
}
},
'jane': {
err: Hapi.error.internal('boom')
}
};
var getCredentials = function (id, callback) {
if (credentials[id]) {
return callback(credentials[id].err, credentials[id].cred);
}
else {
return callback(null, null);
}
};
var loadUser = function (username, password, callback) {
if (username === 'john') {
return callback(null, password === '12345', {
user: 'john',
scope: [],
tos: '1.0.0'
});
}
else if (username === 'jane') {
return callback(Hapi.error.internal('boom'));
}
else if (username === 'invalid1') {
return callback(null, true, 'bad');
}
return callback(null, false);
};
var hawkHeader = function (id, path) {
if (credentials[id] && credentials[id].cred) {
return Hawk.client.header('http://example.com:8080' + path, 'POST', { credentials: credentials[id].cred }).field;
}
else {
return '';
}
};
var config = {
auth: {
'default': {
scheme: 'hawk',
getCredentialsFunc: getCredentials
},
'hawk': {
scheme: 'hawk',
getCredentialsFunc: getCredentials
},
'basic': {
scheme: 'basic',
validateFunc: loadUser
}
}
};
var server = new Hapi.Server(config);
var handler = function (request) {
request.reply('Success');
};
server.route([
{ method: 'POST', path: '/multiple', handler: handler, config: { auth: { strategies: ['basic', 'hawk'] } } },
{ method: 'POST', path: '/multipleOptional', handler: handler, config: { auth: { mode: 'optional' } } },
{ method: 'POST', path: '/multipleScope', handler: handler, config: { auth: { scope: 'x' } } },
{ method: 'POST', path: '/multipleTos', handler: handler, config: { auth: { tos: '2.0.0' } } },
{ method: 'POST', path: '/multiplePayload', handler: handler, config: { auth: { strategies: ['basic', 'hawk'], payload: 'optional' }, payload: 'raw' } }
]);
it('returns a reply on successful auth of first auth strategy', function (done) {
var request = { method: 'POST', url: 'http://example.com:8080/multiple', headers: { authorization: basicHeader('john', '12345') } };
server.inject(request, function (res) {
expect(res.result).to.equal('Success');
done();
});
});
it('returns a reply on successful auth of second auth strategy', function (done) {
var request = { method: 'POST', url: 'http://example.com:8080/multiple', headers: { authorization: hawkHeader('john', '/multiple') } };
server.inject(request, function (res) {
expect(res.result).to.equal('Success');
done();
});
});
it('returns an error when the auth strategies fail', function (done) {
var request = { method: 'POST', url: 'http://example.com:8080/multiple', headers: { authorization: 'Basic fail' } };
server.inject(request, function (res) {
expect(res.statusCode).to.equal(400);
done();
});
});
it('returns a 401 response when missing the authorization header', function (done) {
var request = { method: 'POST', url: 'http://example.com:8080/multiple'};
server.inject(request, function (res) {
expect(res.statusCode).to.equal(401);
done();
});
});
it('returns a WWW-Authenticate header that has all challenge options when missing the authorization header', function (done) {
var request = { method: 'POST', url: 'http://example.com:8080/multiple' };
server.inject(request, function (res) {
expect(res.headers['www-authenticate']).to.contain('Hawk');
expect(res.headers['www-authenticate']).to.contain('Basic');
done();
});
});
it('returns a 400 error when the authorization header has both Basic and Hawk and both are wrong', function (done) {
var request = { method: 'POST', url: 'http://example.com:8080/multiple', headers: { authorization: 'Basic fail; Hawk fail' } };
server.inject(request, function (res) {
expect(res.statusCode).to.equal(400);
done();
});
});
it('returns a 400 response when the authorization header has both Basic and Hawk and the second one is correct', function (done) {
var request = { method: 'POST', url: 'http://example.com:8080/multiple', headers: { authorization: 'Basic fail; ' + hawkHeader('john', '/multiple') } };
server.inject(request, function (res) {
expect(res.statusCode).to.equal(400);
done();
});
});
it('returns full error message on bad auth header', function (done) {
var request = { method: 'POST', url: 'http://example.com:8080/multiple', headers: { authorization: hawkHeader('john', 'abcd') } };
server.inject(request, function (res) {
expect(res.result.code).to.equal(401);
expect(res.result.message).to.equal('Bad mac');
done();
});
});
it('cannot add a route that has payload validation required when one of the server strategies doesn\'t support it', function (done) {
var fn = function () {
server.route({ method: 'POST', path: '/multiplePayload', handler: handler, config: { auth: { strategies: ['basic', 'hawk'], payload: 'required' } } });
};
expect(fn).to.throw(Error);
done();
});
it('returns an error with payload validation when the payload is tampered with and the route has optional auth', function (done) {
var payload = 'Here is my payload';
var authHeader = Hawk.client.header('http://example.com:8080/multiplePayload', 'POST', { credentials: credentials.john.cred, payload: payload });
payload += 'HACKED';
var request = { method: 'POST', url: 'http://example.com:8080/multiplePayload', headers: { authorization: authHeader.field }, payload: payload };
server.inject(request, function (res) {
expect(res.statusCode).to.equal(401);
expect(res.result.message).to.equal('Payload is invalid');
done();
});
});
it('returns a successful reply with payload validation as optional when the payload is valid', function (done) {
var payload = 'Here is my payload';
var authHeader = Hawk.client.header('http://example.com:8080/multiplePayload', 'POST', { credentials: credentials.john.cred, payload: payload });
var request = { method: 'POST', url: 'http://example.com:8080/multiplePayload', headers: { authorization: authHeader.field }, payload: payload };
server.inject(request, function (res) {
expect(res.statusCode).to.equal(200);
expect(res.result).to.equal('Success');
done();
});
});
});
describe('Cookie', function (done) {
var config = {
scheme: 'cookie',
password: 'password',
ttl: 60 * 1000,
cookie: 'special',
clearInvalid: true,
validateFunc: function (session, callback) {
var override = Hapi.utils.clone(session);
override.something = 'new';
return callback(null, session.user === 'valid', override);
}
};
var server = new Hapi.Server({ auth: config });
server.route({
method: 'GET', path: '/login/{user}',
config: {
auth: { mode: 'try' },
handler: function () {
this.auth.session.set({ user: this.params.user });
return this.reply(this.params.user);
}
}
});
server.route({
method: 'GET', path: '/resource', handler: function () {
expect(this.auth.credentials.something).to.equal('new');
return this.reply('resource');
},
config: { auth: 'default' }
});
server.route({
method: 'GET', path: '/logout', handler: function () {
this.auth.session.clear();
return this.reply('logged-out');
}, config: { auth: 'default' }
});
it('authenticates a request', function (done) {
server.inject('/login/valid', function (res) {
expect(res.result).to.equal('valid');
var header = res.headers['set-cookie'];
expect(header.length).to.equal(1);
expect(header[0]).to.contain('Max-Age=60');
var cookie = header[0].match(/(?:[^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)\s*=\s*(?:([^\x00-\x20\"\,\;\\\x7F]*))/);
server.inject({ method: 'GET', url: '/resource', headers: { cookie: 'special=' + cookie[1] } }, function (res) {
expect(res.statusCode).to.equal(200);
expect(res.result).to.equal('resource');
done();
});
});
});
it('ends a session', function (done) {
server.inject('/login/valid', function (res) {
expect(res.result).to.equal('valid');
var header = res.headers['set-cookie'];
expect(header.length).to.equal(1);
expect(header[0]).to.contain('Max-Age=60');
var cookie = header[0].match(/(?:[^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)\s*=\s*(?:([^\x00-\x20\"\,\;\\\x7F]*))/);
server.inject({ method: 'GET', url: '/logout', headers: { cookie: 'special=' + cookie[1] } }, function (res) {
expect(res.statusCode).to.equal(200);
expect(res.result).to.equal('logged-out');
expect(res.headers['set-cookie'][0]).to.equal('special=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; Path=/');
done();
});
});
});
it('fails a request with invalid session', function (done) {
server.inject('/login/invalid', function (res) {
expect(res.result).to.equal('invalid');
var header = res.headers['set-cookie'];
expect(header.length).to.equal(1);
expect(header[0]).to.contain('Max-Age=60');
var cookie = header[0].match(/(?:[^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)\s*=\s*(?:([^\x00-\x20\"\,\;\\\x7F]*))/);
server.inject({ method: 'GET', url: '/resource', headers: { cookie: 'special=' + cookie[1] } }, function (res) {
expect(res.headers['set-cookie'][0]).to.equal('special=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; Path=/');
expect(res.statusCode).to.equal(401);
done();
});
});
});
it('authenticates a request', function (done) {
var config = {
scheme: 'cookie',
password: 'password',
ttl: 60 * 1000,
cookie: 'special',
clearInvalid: true
};
var server = new Hapi.Server({ auth: config });
server.route({
method: 'GET', path: '/login/{user}',
config: {
auth: { mode: 'try' },
handler: function () {
this.auth.session.set({ user: this.params.user });
return this.reply(this.params.user);
}
}
});
server.route({
method: 'GET', path: '/resource', handler: function () {
expect(this.auth.credentials.user).to.equal('steve');
return this.reply('resource');
},
config: { auth: 'default' }
});
server.inject('/login/steve', function (res) {
expect(res.result).to.equal('steve');
var header = res.headers['set-cookie'];
expect(header.length).to.equal(1);
expect(header[0]).to.contain('Max-Age=60');
var cookie = header[0].match(/(?:[^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)\s*=\s*(?:([^\x00-\x20\"\,\;\\\x7F]*))/);
server.inject({ method: 'GET', url: '/resource', headers: { cookie: 'special=' + cookie[1] } }, function (res) {
expect(res.statusCode).to.equal(200);
expect(res.result).to.equal('resource');
done();
});
});
});
describe('redirection', function (done) {
it('sends to login page (uri without query)', function (done) {
var config = {
scheme: 'cookie',