diff --git a/docs/Reference.md b/docs/Reference.md index 3936a3b0d..3d3e9e9b1 100755 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -22,7 +22,6 @@ - [`server.views(options)`](#serverviewsoptions) - [`server.cache(name, options)`](#servercachename-options) - [`server.auth(name, options)`](#serverauthname-options) - - [Basic authentication](#basic-authentication) - [Cookie authentication](#cookie-authentication) - [Hawk authentication](#hawk-authentication) - [Bewit authentication](#bewit-authentication) @@ -145,10 +144,6 @@ When creating a server instance, the following options configure the server's be - `app` - application-specific configuration which can later be accessed via `server.settings.app`. Provides a safe place to store application configuration without potential conflicts with **hapi**. Should not be used by plugins which should use `plugins[name]`. Note the difference between `server.settings.app` which is used to store configuration value and `server.app` which is meant for storing run-time state. -- `auth` - configures one or more authentication strategies. The `auth` key can be set to a single strategy object (the name will default to `'default'`), - or to an object with multiple strategies where the strategy name is the object key. The authentication strategies and their options are described in - [`server.auth()`](#serverauthname-options). - - `cache` - determines the type of server-side cache used. Every server includes a cache for storing and reusing request responses and helper results. By default a simple memory-based cache is used which has limited capacity and limited production environment suitability. In addition to the memory cache, a Redis, MongoDB, or Memcache cache can be configured. Actual caching is only utilized if helpers and plugins @@ -950,7 +945,7 @@ var cache = server.cache('countries', { expiresIn: 60 * 60 * 1000 }); Registers an authentication strategy where: -- `name` - is the strategy name (`'default'` is automatically assigned if a single strategy is registered via the server `auth` config). +- `name` - is the strategy name. - `options` - required strategy options. Each scheme comes with its own set of required options, in addition to the options shared by all schemes: - `scheme` - (required, except when `implementation` is used) the built-in scheme name. Available values: - `'basic'` - [HTTP Basic authentication](#basic-authentication) ([RFC 2617](http://tools.ietf.org/html/rfc2617)) @@ -961,55 +956,6 @@ Registers an authentication strategy where: - `defaultMode` - if `true`, the scheme is automatically assigned as a required strategy to any route without an `auth` config. Can only be assigned to a single server strategy. Value must be `true` (which is the same as `'required'`) or a valid authentication mode (`'required'`, `'optional'`, `'try'`). Defaults to `false`. -##### Basic authentication - -Basic authentication requires validating a username and password combination. The `'basic'` scheme takes the following options: - -- `scheme` - (required) set to `'basic'`. -- `validateFunc` - (required) a user lookup and password validation function with the signature `function(username, password, callback)` where: - - `username` - the username received from the client. - - `password` - the password received from the client. - - `callback` - a callback function with the signature `function(err, isValid, credentials)` where: - - `err` - an internal error. - - `isValid` - `true` if both the username was found and the password matched, otherwise `false`. - - `credentials` - a credentials object passed back to the application in `request.auth.credentials`. Typically, `credentials` are only - included when `isValid` is `true`, but there are cases when the application needs to know who tried to authenticate even when it fails - (e.g. with authentication mode `'try'`). -- `allowEmptyUsername` - (optional) if `true`, allows making requests with an empty username. Defaults to `false`. - -```javascript -var Bcrypt = require('bcrypt'); - -var users = { - john: { - username: 'john', - password: '$2a$10$iqJSHD.BGr0E2IxQwYgJmeP3NvhPrXAeLSaGCj6IR/XU5QtjVu5Tm', // 'secret' - name: 'John Doe', - id: '2133d32a' - } -}; - -var validate = function (username, password, callback) { - - var user = users[username]; - if (!user) { - return callback(null, false); - } - - Bcrypt.compare(password, user.password, function (err, isValid) { - - callback(err, isValid, { id: user.id, name: user.name }); - }); -}; - -server.auth('simple', { - scheme: 'basic', - validateFunc: validate -}); - -server.route({ method: 'GET', path: '/', config: { auth: 'simple' } }); -``` - ##### Cookie authentication Cookie authentication provides a simple cookie-based session management. The user has to be authenticated via other means, typically a web diff --git a/lib/auth/basic.js b/lib/auth/basic.js index f43f9ee50..39b245d67 100755 --- a/lib/auth/basic.js +++ b/lib/auth/basic.js @@ -12,7 +12,6 @@ var internals = {}; exports = module.exports = internals.Scheme = function (server, options) { Utils.assert(options, 'Invalid options'); - Utils.assert(options.scheme === 'basic', 'Wrong scheme'); Utils.assert(options.validateFunc, 'Missing required validateFunc method in configuration'); Utils.assert(server, 'Server is required'); diff --git a/lib/auth/bewit.js b/lib/auth/bewit.js index 6a94ac018..7f6e6e97b 100755 --- a/lib/auth/bewit.js +++ b/lib/auth/bewit.js @@ -13,7 +13,6 @@ var internals = {}; exports = module.exports = internals.Scheme = function (server, options) { Utils.assert(options, 'Invalid options'); - Utils.assert(options.scheme === 'bewit', 'Wrong scheme'); Utils.assert(options.getCredentialsFunc, 'Missing required getCredentialsFunc method in configuration'); Utils.assert(server, 'Server is required'); diff --git a/lib/auth/cookie.js b/lib/auth/cookie.js index 0424902d5..b5102ad44 100755 --- a/lib/auth/cookie.js +++ b/lib/auth/cookie.js @@ -13,7 +13,6 @@ exports = module.exports = internals.Scheme = function (server, options) { Utils.assert(server, 'Server is required'); Utils.assert(options, 'Invalid options'); - Utils.assert(options.scheme === 'cookie', 'Wrong scheme'); Utils.assert(!options.validateFunc || typeof options.validateFunc === 'function', 'Invalid validateFunc method in configuration'); Utils.assert(options.password, 'Missing required password in configuration'); Utils.assert(!options.appendNext || options.redirectTo, 'Cannot set \'appendNext\' without \'redirectTo\''); diff --git a/lib/auth/hawk.js b/lib/auth/hawk.js index b3e61bce1..fa92492a5 100755 --- a/lib/auth/hawk.js +++ b/lib/auth/hawk.js @@ -13,7 +13,6 @@ var internals = {}; exports = module.exports = internals.Scheme = function (server, options) { Utils.assert(options, 'Invalid options'); - Utils.assert(options.scheme === 'hawk', 'Wrong scheme'); Utils.assert(options.getCredentialsFunc, 'Missing required getCredentialsFunc method in configuration'); Utils.assert(server, 'Server is required'); diff --git a/lib/auth/index.js b/lib/auth/index.js index 291e9660d..ca6f2005f 100755 --- a/lib/auth/index.js +++ b/lib/auth/index.js @@ -18,35 +18,42 @@ var internals = {}; exports = module.exports = internals.Auth = function (server) { this.server = server; - - // Load strategies - + this._schemes = {}; this._strategies = {}; this._extensions = []; this._defaultStrategy = { // Strategy used as default if route has no auth settings name: null, mode: 'required' }; + + this.scheme('hawk', function (server, options) { return new Hawk(server, options); }); + this.scheme('basic', function (server, options) { return new Basic(server, options); }); + this.scheme('cookie', function (server, options) { return new Cookie(server, options); }); + this.scheme('bewit', function (server, options) { return new Bewit(server, options); }); +}; + + +internals.Auth.prototype.scheme = function (name, scheme) { + + Utils.assert(name, 'Authentication scheme must have a name'); + Utils.assert(!this._schemes[name], 'Authentication scheme name already exists:', name); + Utils.assert(typeof scheme === 'function', 'scheme must be a function:', name); + + this._schemes[name] = scheme; }; -internals.Auth.prototype.add = function (name, options) { +internals.Auth.prototype.strategy = function (name, scheme /*, mode, options */) { + + var mode = (arguments.length === 4 ? arguments[2] : null); + var options = (arguments.length === 4 ? arguments[3] : arguments[2]); Utils.assert(name, 'Authentication strategy must have a name'); Utils.assert(!this._strategies[name], 'Authentication strategy name already exists'); - Utils.assert(options && typeof options === 'object', 'Invalid strategy options'); - Utils.assert(!options.scheme || ['basic', 'hawk', 'cookie', 'bewit'].indexOf(options.scheme) !== -1, name, 'has an unknown scheme:', options.scheme); - Utils.assert(options.scheme || options.implementation, name + ' missing both scheme and extension implementation'); - Utils.assert(!options.implementation || (typeof options.implementation === 'object' && typeof options.implementation.authenticate === 'function'), name, 'has invalid extension scheme implementation'); - Utils.assert(!options.defaultMode || !this._defaultStrategy.name, 'Cannot set default required strategy more than once:', name, '- already set to:', this._defaultStrategy); - - switch (options.scheme) { - case 'hawk': this._strategies[name] = new Hawk(this.server, options); break; - case 'basic': this._strategies[name] = new Basic(this.server, options); break; - case 'cookie': this._strategies[name] = new Cookie(this.server, options); break; - case 'bewit': this._strategies[name] = new Bewit(this.server, options); break; - default: this._strategies[name] = options.implementation; break; - } + Utils.assert(scheme && this._schemes[scheme], name, 'has an unknown scheme:', scheme); + Utils.assert(!mode || !this._defaultStrategy.name, 'Cannot set default required strategy more than once:', name, '- already set to:', this._defaultStrategy); + + this._strategies[name] = this._schemes[scheme](this.server, options); if (this._strategies[name].extend && typeof this._strategies[name].extend === 'function') { @@ -54,35 +61,15 @@ internals.Auth.prototype.add = function (name, options) { this._extensions.push(this._strategies[name]); } - if (options.defaultMode) { + if (mode) { this._defaultStrategy.name = name; - this._defaultStrategy.mode = (typeof options.defaultMode === 'string' ? options.defaultMode : 'required'); + this._defaultStrategy.mode = (typeof mode === 'string' ? mode : 'required'); Utils.assert(['required', 'optional', 'try'].indexOf(this._defaultStrategy.mode) !== -1, 'Unknown default authentication mode:', this._defaultStrategy.mode); } }; -internals.Auth.prototype.addBatch = function (options) { - - var self = this; - - Utils.assert(options && typeof options === 'object', 'Invalid auth options'); - - if (!Object.keys(options).length) { - return; - } - - Utils.assert(!!options.scheme ^ !!options.implementation ^ !!options[Object.keys(options)[0]].scheme ^ !!options[Object.keys(options)[0]].implementation, 'Auth options must include either a top level strategy or object of strategies but not both'); - var settings = ((options.scheme || options.implementation) ? { 'default': options } : options); - - Object.keys(settings).forEach(function (strategy) { - - self.add(strategy, settings[strategy]); - }); -}; - - -internals.Auth.prototype.setupRoute = function (options) { +internals.Auth.prototype._setupRoute = function (options) { var self = this; @@ -127,7 +114,7 @@ internals.Auth.prototype.setupRoute = function (options) { }; -internals.Auth.prototype.routeConfig = function (request) { +internals.Auth.prototype._routeConfig = function (request) { if (request.route.auth) { return request.route.auth; @@ -152,8 +139,8 @@ internals.Auth.prototype.routeConfig = function (request) { internals.Auth.authenticate = function (request, next) { - var auth = request.server._auth; - var config = auth.routeConfig(request); + var auth = request.server.auth; + var config = auth._routeConfig(request); if (!config) { return next(); } @@ -164,15 +151,15 @@ internals.Auth.authenticate = function (request, next) { auth._extensions[i].extend(request); } - return auth.authenticate(request, next); + return auth._authenticate(request, next); }; -internals.Auth.prototype.authenticate = function (request, next) { +internals.Auth.prototype._authenticate = function (request, next) { var self = this; - var config = this.routeConfig(request); + var config = this._routeConfig(request); var authErrors = []; var strategyPos = 0; @@ -336,8 +323,8 @@ internals.Auth.prototype.authenticate = function (request, next) { internals.Auth.authenticatePayload = function (request, next) { - var auth = request.server._auth; - var config = auth.routeConfig(request); + var auth = request.server.auth; + var config = auth._routeConfig(request); if (!config || !config.payload || @@ -362,8 +349,8 @@ internals.Auth.authenticatePayload = function (request, next) { internals.Auth.responseHeader = function (request, response, next) { - var auth = request.server._auth; - var config = auth.routeConfig(request); + var auth = request.server.auth; + var config = auth._routeConfig(request); if (!config || !request.auth.isAuthenticated) { diff --git a/lib/defaults.js b/lib/defaults.js index ba780d4a0..644d3d9e4 100755 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -100,8 +100,7 @@ exports.server = { // Optional components cors: false, // CORS headers on responses and OPTIONS requests (defaults: exports.cors): false -> null, true -> defaults, {} -> override defaults - views: null, // Views engine - auth: {} // Authentication + views: null // Views engine }; diff --git a/lib/pack.js b/lib/pack.js index 0b403506e..881dec525 100755 --- a/lib/pack.js +++ b/lib/pack.js @@ -201,9 +201,15 @@ internals.Pack.prototype._register = function (plugin, options, callback, _depen self._applySync(selection.servers, Server.prototype.state, arguments); }, - auth: function () { + auth: { + scheme: function () { - self._applySync(selection.servers, Server.prototype.auth, arguments); + self._applyChildSync(selection.servers, 'auth', 'scheme', arguments); + }, + strategy: function () { + + self._applyChildSync(selection.servers, 'auth', 'strategy', arguments); + } }, ext: function () { @@ -589,6 +595,15 @@ internals.Pack.prototype._applySync = function (servers, func, args) { }; +internals.Pack.prototype._applyChildSync = function (servers, child, func, args) { + + for (var i = 0, il = servers.length; i < il; ++i) { + var obj = servers[i][child]; + obj[func].apply(obj, args); + } +}; + + internals.Pack.prototype._provisionCache = function (options, type, name, segment) { Utils.assert(options && typeof options === 'object', 'Invalid cache policy options'); diff --git a/lib/route.js b/lib/route.js index 1c82b8e26..316eabc64 100755 --- a/lib/route.js +++ b/lib/route.js @@ -84,7 +84,7 @@ exports = module.exports = internals.Route = function (options, server, env) { // Authentication configuration - this.settings.auth = this.server._auth.setupRoute(this.settings.auth); + this.settings.auth = this.server.auth._setupRoute(this.settings.auth); // Cache diff --git a/lib/schema.js b/lib/schema.js index 2070d3c67..321bf7bf8 100755 --- a/lib/schema.js +++ b/lib/schema.js @@ -56,7 +56,6 @@ internals.cache = Joi.object({ internals.serverSchema = { app: Joi.object().allow(null), - auth: Joi.object().allow(false, true), cache: [ Joi.string().allow(null), internals.cache, diff --git a/lib/server.js b/lib/server.js index 2f6d543ff..abca77e32 100755 --- a/lib/server.js +++ b/lib/server.js @@ -86,7 +86,7 @@ module.exports = internals.Server = function (/* host, port, options */) { // Server facilities this._started = false; - this._auth = new Auth(this); // Required before _router + this.auth = new Auth(this); // Required before _router this._router = new Router(this); // Server load @@ -192,12 +192,6 @@ module.exports = internals.Server = function (/* host, port, options */) { Http.globalAgent.maxSockets = this.settings.maxSockets; } - // Authentication - - if (this.settings.auth) { - this._auth.addBatch(this.settings.auth); - } - // Server information this.info = { @@ -448,12 +442,6 @@ internals.Server.prototype.state = function (name, options) { }; -internals.Server.prototype.auth = function (name, options) { - - this._auth.add(name, options); -}; - - internals.Server.prototype.views = function (options) { Utils.assert(!this._views, 'Cannot set server views manager more than once'); diff --git a/test/integration/auth.js b/test/integration/auth.js index 019a9bc2d..bcdd801cb 100755 --- a/test/integration/auth.js +++ b/test/integration/auth.js @@ -23,19 +23,44 @@ var it = Lab.test; describe('Auth', function () { - it('throws when setting defaultMode to invalid value', function (done) { + it('throws when setting mode to invalid value', function (done) { var server = new Hapi.Server(); expect(function () { - server.auth('password', { - scheme: 'basic', - validateFunc: function () { }, - defaultMode: 'boom' + server.auth.strategy('password', 'basic', 'boom', { + validateFunc: function () { } }); }).to.throw('Unknown default authentication mode: boom'); done(); }); + it('cannot create a route with a strategy not configured on the server', function (done) { + + var config = { + auth: { + mode: 'required', + strategies: ['missing'] + } + }; + + var server = new Hapi.Server(); + server.auth.strategy('test', 'basic', { + validateFunc: function (username, password, callback) { + + return callback(null, password === 'password', { id: 'steve', password: 'password' }); + } + }); + + var a = function () { + + var handler = function (request) { }; + server.route({ method: 'GET', path: '/', handler: handler, config: config }); + }; + + expect(a).to.throw(Error, 'Unknown authentication strategy: missing'); + done(); + }); + var basicHeader = function (username, password) { return 'Basic ' + (new Buffer(username + ':' + password, 'utf8')).toString('base64'); @@ -62,16 +87,8 @@ describe('Auth', function () { return callback(null, false); }; - var config = { - debug: false, - auth: { - scheme: 'basic', - validateFunc: loadUser, - defaultMode: 'required' - } - }; - - var server = new Hapi.Server(config); + var server = new Hapi.Server({ debug: false }); + server.auth.strategy('default', 'basic', 'required', { validateFunc: loadUser }); var basicHandler = function (request, reply) { @@ -197,15 +214,12 @@ describe('Auth', function () { it('allow missing username', function (done) { - var config = { - auth: { - scheme: 'basic', - validateFunc: function (username, password, callback) { callback(null, true, {}); }, - allowEmptyUsername: true - } - }; + var server1 = new Hapi.Server(); + server1.auth.strategy('default', 'basic', { + validateFunc: function (username, password, callback) { callback(null, true, {}); }, + allowEmptyUsername: true + }); - var server1 = new Hapi.Server(config); server1.route({ method: 'GET', path: '/', handler: function (request, reply) { reply('ok'); }, config: { auth: true } }); server1.inject({ method: 'GET', url: '/', headers: { authorization: basicHeader('', 'abcd') } }, function (res) { @@ -301,8 +315,7 @@ describe('Auth', function () { it('should not ask for credentials if no server auth configured', function (done) { - var config = {}; - var server = new Hapi.Server(config); + var server = new Hapi.Server(); server.route({ path: '/noauth', method: 'GET', @@ -324,13 +337,8 @@ describe('Auth', function () { it('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); + var server = new Hapi.Server(); + server.auth.strategy('default', 'basic', { validateFunc: loadUser }); server.route({ path: '/noauth', method: 'GET', @@ -360,19 +368,9 @@ describe('Auth', function () { it('should throw if server has strategies route refers to nonexistent strategy', function (done) { - var config = { - auth: { - 'a': { - scheme: 'basic', - validateFunc: loadUser - }, - 'b': { - scheme: 'basic', - validateFunc: loadUser - } - } - }; - var server = new Hapi.Server(config); + var server = new Hapi.Server(); + server.auth.strategy('a', 'basic', { validateFunc: loadUser }); + server.auth.strategy('b', 'basic', { validateFunc: loadUser }); var fn = function () { @@ -471,14 +469,8 @@ describe('Auth', function () { } }; - var config = { - auth: { - scheme: 'hawk', - getCredentialsFunc: getCredentials - } - }; - - var server = new Hapi.Server(config); + var server = new Hapi.Server(); + server.auth.strategy('default', 'hawk', { getCredentialsFunc: getCredentials }); var hawkHandler = function (request, reply) { @@ -709,17 +701,14 @@ describe('Auth', function () { var request = { method: 'POST', url: '/hawk', headers: { authorization: hawkHeader('john', '/hawk').field, custom: 'example.com:8080' } }; - var config = { - auth: { - scheme: 'hawk', - getCredentialsFunc: getCredentials, - hawk: { - hostHeaderName: 'custom' - } + var server = new Hapi.Server(); + server.auth.strategy('default', 'hawk', { + getCredentialsFunc: getCredentials, + hawk: { + hostHeaderName: 'custom' } - }; + }); - var server = new Hapi.Server(config); server.route({ method: 'POST', path: '/hawk', handler: hawkHandler, config: { auth: true } }); server.inject(request, function (res) { @@ -911,14 +900,8 @@ describe('Auth', function () { } }; - var config = { - auth: { - scheme: 'bewit', - getCredentialsFunc: getCredentials - } - }; - - var server = new Hapi.Server(config); + var server = new Hapi.Server(); + server.auth.strategy('default', 'bewit', { getCredentialsFunc: getCredentials }) var bewitHandler = function (request, reply) { @@ -996,17 +979,14 @@ describe('Auth', function () { 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, - hawk: { - hostHeaderName: 'custom' - } + var server = new Hapi.Server(); + server.auth.strategy('default', 'bewit', { + getCredentialsFunc: getCredentials, + hawk: { + hostHeaderName: 'custom' } - }; + }); - var server = new Hapi.Server(config); server.route({ method: 'GET', path: '/bewit', handler: bewitHandler, config: { auth: true } }); server.inject(request, function (res) { @@ -1051,40 +1031,6 @@ describe('Auth', function () { }); }); - describe('Ext', function () { - - it('returns a reply on successful ext any', function (done) { - - var config = { - auth: { - implementation: { - - authenticate: function (request, callback) { - - callback(null, { credentials: {} }); - } - } - } - }; - - var handler = function (request, reply) { - - reply('Success'); - }; - - var server = new Hapi.Server(config); - server.route({ method: 'POST', path: '/ext', handler: handler, config: { auth: true } }); - - 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 = { @@ -1139,20 +1085,9 @@ describe('Auth', function () { } }; - var config = { - auth: { - 'hawk': { - scheme: 'hawk', - getCredentialsFunc: getCredentials - }, - 'basic': { - scheme: 'basic', - validateFunc: loadUser - } - } - }; - - var server = new Hapi.Server(config); + var server = new Hapi.Server(); + server.auth.strategy('hawk', 'hawk', { getCredentialsFunc: getCredentials }); + server.auth.strategy('basic', 'basic', { validateFunc: loadUser }); var handler = function (request, reply) { @@ -1300,8 +1235,8 @@ describe('Auth', function () { describe('Cookie', function (done) { - var config = { - scheme: 'cookie', + var server = new Hapi.Server(); + server.auth.strategy('default', 'cookie', { password: 'password', ttl: 60 * 1000, cookie: 'special', @@ -1313,9 +1248,7 @@ describe('Auth', function () { return callback(null, session.user === 'valid', override); } - }; - - var server = new Hapi.Server({ auth: config }); + }); server.route({ method: 'GET', path: '/login/{user}', @@ -1406,15 +1339,13 @@ describe('Auth', function () { it('authenticates a request', function (done) { - var config = { - scheme: 'cookie', + var server = new Hapi.Server(); + server.auth.strategy('default', 'cookie', { password: 'password', ttl: 60 * 1000, cookie: 'special', clearInvalid: true - }; - - var server = new Hapi.Server({ auth: config }); + }); server.route({ method: 'GET', path: '/login/{user}', @@ -1458,15 +1389,13 @@ describe('Auth', function () { it('sends to login page (uri without query)', function (done) { - var config = { - scheme: 'cookie', + var server = new Hapi.Server(); + server.auth.strategy('default', 'cookie', { password: 'password', ttl: 60 * 1000, redirectTo: 'http://example.com/login', appendNext: true - }; - - var server = new Hapi.Server({ auth: config }); + }); server.route({ method: 'GET', path: '/', handler: function (request, reply) { @@ -1485,15 +1414,13 @@ describe('Auth', function () { it('sends to login page (uri with query)', function (done) { - var config = { - scheme: 'cookie', + var server = new Hapi.Server(); + server.auth.strategy('default', 'cookie', { password: 'password', ttl: 60 * 1000, redirectTo: 'http://example.com/login?mode=1', appendNext: true - }; - - var server = new Hapi.Server({ auth: config }); + }); server.route({ method: 'GET', path: '/', handler: function (request, reply) { @@ -1512,15 +1439,13 @@ describe('Auth', function () { it('sends to login page and does not append the next query when appendNext is false', function (done) { - var config = { - scheme: 'cookie', + var server = new Hapi.Server(); + server.auth.strategy('default', 'cookie', { password: 'password', ttl: 60 * 1000, redirectTo: 'http://example.com/login?mode=1', appendNext: false - }; - - var server = new Hapi.Server({ auth: config }); + }); server.route({ method: 'GET', path: '/', handler: function (request, reply) { @@ -1539,15 +1464,13 @@ describe('Auth', function () { it('does not redirect on try', function (done) { - var config = { - scheme: 'cookie', + var server = new Hapi.Server(); + server.auth.strategy('default', 'cookie', { password: 'password', ttl: 60 * 1000, redirectTo: 'http://example.com/login', appendNext: true - }; - - var server = new Hapi.Server({ auth: config }); + }); server.route({ method: 'GET', path: '/', config: { auth: { mode: 'try' } }, handler: function (request, reply) { diff --git a/test/integration/pack/--auth/index.js b/test/integration/pack/--auth/index.js index bfffa4dbb..d6add3dff 100755 --- a/test/integration/pack/--auth/index.js +++ b/test/integration/pack/--auth/index.js @@ -16,17 +16,10 @@ exports.register = function (plugin, options, next) { return callback(null, false); }; - plugin.auth('basic', { - scheme: 'basic', - validateFunc: loadUser, - defaultMode: true - }); - - plugin.auth('special', { - implementation: { - authenticate: function () { } - } - }); + plugin.auth.strategy('basic', 'basic', 'required', { validateFunc: loadUser }); + + plugin.auth.scheme('special', function () { return { authenticate: function () { } } }); + plugin.auth.strategy('special', 'special', {}); return next(); }; diff --git a/test/unit/auth.js b/test/unit/auth.js deleted file mode 100755 index a2ecffd1d..000000000 --- a/test/unit/auth.js +++ /dev/null @@ -1,432 +0,0 @@ -// Load modules - -var Lab = require('lab'); -var Hapi = require('../..'); -var Auth = require('../../lib/auth'); - - -// 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; - - -describe('Auth', function () { - - describe('#constructor', function () { - - it('throws an error when constructed without options', function (done) { - - var fn = function () { - - var auth = new Auth(); - auth.addBatch(null); - }; - - expect(fn).to.throw(Error, 'Invalid auth options'); - done(); - }); - - it('throws an error when constructed without a scheme', function (done) { - - var fn = function () { - - var auth = new Auth(); - auth.addBatch({ scheme: null }); - }; - - expect(fn).to.throw('Cannot read property \'scheme\' of null'); - done(); - }); - - it('does not throws an error if no strategies are defined', function (done) { - - var a = function () { - - var auth = new Auth(); - auth.addBatch({}); - }; - - expect(a).to.not.throw; - done(); - }); - - it('does not throw an error if strategies are defined but not used', function (done) { - - var server = { - settings: {}, - route: function () { } - }; - - var scheme = { - 'test': { - scheme: 'basic', - validateFunc: function () { } - } - }; - - var a = function () { - - var auth = new Auth(server); - auth.addBatch(scheme); - }; - - expect(a).to.not.throw(Error); - done(); - }); - - it('cannot create a route with a strategy not configured on the server', function (done) { - - var config = { - auth: { - mode: 'required', - strategies: ['missing'] - } - }; - - var options = { - auth: { - 'test': { - scheme: 'basic', - validateFunc: function (username, password, callback) { - - return callback(null, password === 'password', { id: 'steve', password: 'password' }); - } - } - } - }; - - var server = new Hapi.Server(options); - - var a = function () { - - var handler = function (request) { }; - server.route({ method: 'GET', path: '/', handler: handler, config: config }); - }; - - expect(a).to.throw(Error, 'Unknown authentication strategy: missing'); - done(); - }); - }); - - describe('#authenticate', function () { - - var test = function (scheme) { - - it('does not throw an error when credentials exist and entity is any (' + scheme.scheme + ')', function (done) { - - var options = { - auth: scheme - }; - - var server = new Hapi.Server('localhost', 0, options); - - var request = { - auth: { - credentials: {} - }, - _bench: new Hapi.utils.Bench(), - route: { - auth: { - entity: 'any', - strategies: ['default'] - } - }, - log: function () { }, - server: server - }; - - server._auth.authenticate(request, function (err) { - - expect(err).to.not.exist; - done(); - }); - }); - - it('does not throw an error when credentials exist and entity defaults to any (' + scheme.scheme + ')', function (done) { - - var options = { - auth: scheme - }; - - var server = new Hapi.Server('localhost', 0, options); - - var request = { - auth: { - credentials: {} - }, - _bench: new Hapi.utils.Bench(), - route: { - auth: { - strategies: ['default'] - } - }, - log: function () { }, - server: server - }; - - server._auth.authenticate(request, function (err) { - - expect(err).to.not.exist; - done(); - }); - }); - - it('does not throw an error when credentials exist with a user and user entity specified (' + scheme.scheme + ')', function (done) { - - var options = { - auth: scheme - }; - - var server = new Hapi.Server('localhost', 0, options); - - var request = { - auth: { - credentials: { - user: 'test' - } - }, - _bench: new Hapi.utils.Bench(), - route: { - auth: { - entity: 'user', - strategies: ['default'] - } - }, - log: function () { }, - server: server - }; - - server._auth.authenticate(request, function (err) { - - expect(err).to.not.exist; - done(); - }); - }); - - it('throws an error when credentials exist without a user and user entity is specified (' + scheme.scheme + ')', function (done) { - - var options = { - auth: scheme - }; - - var server = new Hapi.Server('localhost', 0, options); - - var request = { - auth: { - credentials: {} - }, - _bench: new Hapi.utils.Bench(), - route: { - auth: { - entity: 'user', - strategies: ['default'] - } - }, - log: function () { }, - server: server - }; - - server._auth.authenticate(request, function (err) { - - expect(err).to.exist; - expect(err).to.be.instanceOf(Error); - done(); - }); - }); - - it('throws an error when credentials exist without a app and app entity is specified (' + scheme.scheme + ')', function (done) { - - var options = { - auth: scheme - }; - - var server = new Hapi.Server('localhost', 0, options); - - var request = { - auth: { - credentials: { - user: 'test' - } - }, - _bench: new Hapi.utils.Bench(), - route: { - auth: { - entity: 'app', - strategies: ['default'] - } - }, - log: function () { }, - server: server - }; - - server._auth.authenticate(request, function (err) { - - expect(err).to.exist; - expect(err).to.be.instanceOf(Error); - done(); - }); - }); - - it('does not throw an error when credentials exist with a app and app entity is specified (' + scheme.scheme + ')', function (done) { - - var options = { - auth: scheme - }; - - var server = new Hapi.Server('localhost', 0, options); - - var request = { - auth: { - credentials: { - app: 'test' - } - }, - _bench: new Hapi.utils.Bench(), - route: { - auth: { - entity: 'app', - strategies: ['default'] - } - }, - log: function () { }, - server: server - }; - - server._auth.authenticate(request, function (err) { - - expect(err).to.not.exist; - done(); - }); - }); - - it('throws an error when an unknown entity is specified (' + scheme.scheme + ')', function (done) { - - var options = { - auth: scheme - }; - - var server = new Hapi.Server('localhost', 0, options); - - var request = { - auth: { - credentials: { - user: 'test' - } - }, - _bench: new Hapi.utils.Bench(), - route: { - auth: { - entity: 'wrongEntity', - strategies: ['default'] - } - }, - log: function () { }, - server: server, - host: 'localhost' - }; - - server._auth.authenticate(request, function (err) { - - expect(err).to.exist; - expect(err).to.be.instanceOf(Error); - done(); - }); - }); - - it('throws an error when missing credentials and bad request (' + scheme.scheme + ')', function (done) { - - var server = { - settings: {}, - route: function () { } - }; - - var request = { - _bench: new Hapi.utils.Bench(), - route: { - auth: { - mode: 'required', - entity: 'user', - strategies: ['default'] - } - }, - auth: { - credentials: {} - }, - log: function () { }, - raw: { - res: { - setHeader: function () { } - }, - req: { - headers: { - host: 'localhost' - }, - url: 'http://localhost/test' - } - }, - server: server - }; - - var auth = new Auth(server); - auth.addBatch(scheme); - - auth.authenticate(request, function (err) { - - expect(err).to.exist; - expect(err).to.be.instanceOf(Error); - done(); - }); - }); - }; - - var basic = { - scheme: 'basic', - validateFunc: function () { } - }; - - test(basic); - - var hawk = { - scheme: 'hawk', - getCredentialsFunc: function () { } - }; - - test(hawk); - - it('returns error on bad ext scheme callback', function (done) { - - var server = new Hapi.Server({ - debug: false, - auth: { - implementation: { - authenticate: function (request, callback) { - - return callback(null, null); - } - } - } - }); - - var handler = function (request, reply) { - - reply('ok'); - }; - - server.route({ method: 'GET', path: '/', handler: handler, config: { auth: 'default' } }); - server.inject({ url: '/', method: 'GET' }, function (res) { - - expect(res.statusCode).to.equal(500); - done(); - }); - }); - }); -}); \ No newline at end of file diff --git a/test/unit/server.js b/test/unit/server.js index 6173d7aa9..fc0aea7fb 100755 --- a/test/unit/server.js +++ b/test/unit/server.js @@ -184,16 +184,6 @@ describe('Server', function () { done(); }); - it('does not throw an error when enabling auth', function (done) { - - var fn = function () { - - var server = new Hapi.Server({ auth: { scheme: 'basic', validateFunc: function () { } } }); - }; - expect(fn).to.not.throw(Error); - done(); - }); - describe('#start', function () { it('does not throw an error', function (done) {