Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pack auth api #686

Merged
merged 2 commits into from
Mar 18, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified lib/auth/bewit.js
100644 → 100755
Empty file.
182 changes: 135 additions & 47 deletions lib/auth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,79 +14,156 @@ var Utils = require('../utils');
var internals = {};


exports = module.exports = internals.Auth = function (server, options) {
exports = module.exports = internals.Auth = function (server) {

Utils.assert(this.constructor === internals.Auth, 'Auth must be instantiated using new');
Utils.assert(options, 'Invalid options');
Utils.assert(!!options.scheme ^ !!options[Object.keys(options)[0]].scheme, 'Auth options must include either a top level strategy or object of strategies but not both');
Utils.assert(options.scheme || Object.keys(options).length, 'Number of authentication strategies must be greater than zero');

this.server = server;

// Move single strategy into default
// Load strategies

var settings = options.scheme ? { 'default': options } : options;
this._strategies = {};
this._extensions = [];
this._requiredByDefault = null; // Strategy name used as default if route has no auth settings

// Load strategies
return this;
};

this.strategies = {};
this.extensions = [];
for (var name in settings) {
if (settings.hasOwnProperty(name)) {
var strategy = settings[name];

Utils.assert(strategy.scheme, name + ' is missing a scheme');
Utils.assert(['oz', 'basic', 'hawk', 'cookie', 'bewit'].indexOf(strategy.scheme) !== -1 || strategy.scheme.indexOf('ext:') === 0, name + ' has an unknown scheme: ' + strategy.scheme);
Utils.assert(strategy.scheme.indexOf('ext:') !== 0 || strategy.implementation, name + ' has extension scheme missing implementation');
Utils.assert(!strategy.implementation || (typeof strategy.implementation === 'object' && typeof strategy.implementation.authenticate === 'function'), name + ' has invalid extension scheme implementation');

switch (strategy.scheme) {
case 'oz': this.strategies[name] = new Oz(this.server, strategy); break;
case 'hawk': this.strategies[name] = new Hawk(this.server, strategy); break;
case 'basic': this.strategies[name] = new Basic(this.server, strategy); break;
case 'cookie': this.strategies[name] = new Cookie(this.server, strategy); break;
case 'bewit': this.strategies[name] = new Bewit(this.server, strategy); break;
default: this.strategies[name] = strategy.implementation; break;
}

if (this.strategies[name].extend &&
typeof this.strategies[name].extend === 'function') {
internals.Auth.prototype.add = function (name, options) {

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 || ['oz', '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.requiredByDefault || !this._requiredByDefault, 'Cannot set default required strategy more than once: ' + name + ' (already set to: ' + this._requiredByDefault + ')');

options.scheme = options.scheme || 'ext';
switch (options.scheme) {
case 'oz': this._strategies[name] = new Oz(this.server, options); break;
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;
}

this.extensions.push(this.strategies[name]);
}
}
if (this._strategies[name].extend &&
typeof this._strategies[name].extend === 'function') {

this._extensions.push(this._strategies[name]);
}

return this;
if (options.requiredByDefault) {
this._requiredByDefault = name;
}
};


internals.Auth.authenticate = function (request, next) {
internals.Auth.prototype.addBatch = function (options) {

// Extend requests with loaded strategies
var self = this;

if (request.server.auth) {
for (var i = 0, il = request.server.auth.extensions.length; i < il; ++i) {
request.server.auth.extensions[i].extend(request);
}
Utils.assert(options && typeof options === 'object', 'Invalid auth options');

if (!Object.keys(options).length) {
return;
}

// Modes: required, optional, try, none
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) {

var self = this;

if (options === false) {
return false;
}

if (typeof options === 'string') {
options = { strategy: options };
}

if (!options) {
return false;
}

var config = request.route.auth;
if (config.mode === 'none') {
options.mode = options.mode || 'required';
Utils.assert(['required', 'optional', 'try'].indexOf(options.mode) !== -1, 'Unknown authentication mode: ' + options.mode);

Utils.assert(!options.entity || ['user', 'app', 'any'].indexOf(options.entity) !== -1, 'Unknown authentication entity type: ' + options.entity);
Utils.assert(!options.payload || ['required', 'optional'].indexOf(options.payload) !== -1, 'Unknown authentication payload mode: ' + options.entity);
Utils.assert(!(options.strategy && options.strategies), 'Route can only have a auth.strategy or auth.strategies (or use the default) but not both');
Utils.assert(!options.strategies || options.strategies.length, 'Cannot have empty auth.strategies array');
options.strategies = options.strategies || [options.strategy || 'default'];
delete options.strategy;

options.payload = options.payload || false;
var hasAuthenticatePayload = false;
options.strategies.forEach(function (strategy) {

Utils.assert(self._strategies[strategy], 'Unknown authentication strategy: ' + strategy);
hasAuthenticatePayload = hasAuthenticatePayload || typeof self._strategies[strategy].authenticatePayload === 'function';
Utils.assert(options.payload !== 'required' || hasAuthenticatePayload, 'Payload validation can only be required when all strategies support it');
});

Utils.assert(!options.payload || hasAuthenticatePayload, 'Payload authentication requires at least one strategy with payload support');

return options;
};


internals.Auth.prototype.routeConfig = function (request) {

var settings = request.route.auth;
if (settings) {
return settings;
}

if (this._requiredByDefault) {
return {
mode: 'required',
strategies: [this._requiredByDefault]
};
}

return false;
};


internals.Auth.authenticate = function (request, next) {

var auth = request.server._auth;
var config = auth.routeConfig(request);
if (!config) {
return next();
}

return request.server.auth.authenticate(request, next);
// Extend requests with loaded strategies

for (var i = 0, il = auth._extensions.length; i < il; ++i) {
auth._extensions[i].extend(request);
}

return auth.authenticate(request, next);
};


internals.Auth.prototype.authenticate = function (request, next) {

var self = this;

var config = request.route.auth;
var config = this.routeConfig(request);

var authErrors = [];
var strategyPos = 0;
Expand Down Expand Up @@ -116,7 +193,7 @@ internals.Auth.prototype.authenticate = function (request, next) {
return next(Boom.unauthorized('Missing authentication', authErrors));
}

var strategy = self.strategies[config.strategies[strategyPos++]]; // Increments counter after fetching current strategy
var strategy = self._strategies[config.strategies[strategyPos++]]; // Increments counter after fetching current strategy
return strategy.authenticate(request, validate);
};

Expand Down Expand Up @@ -158,7 +235,7 @@ internals.Auth.prototype.authenticate = function (request, next) {
// Authenticated

request.session = session;
request.session._strategy = self.strategies[config.strategies[strategyPos - 1]];
request.session._strategy = self._strategies[config.strategies[strategyPos - 1]];

// Check scope

Expand Down Expand Up @@ -222,9 +299,11 @@ internals.Auth.prototype.authenticate = function (request, next) {

internals.Auth.authenticatePayload = function (request, next) {

var config = request.route.auth;
var auth = request.server._auth;
var config = auth.routeConfig(request);

if (config.payload === 'none' ||
if (!config ||
!config.payload ||
!request.isAuthenticated) {

return next();
Expand All @@ -246,6 +325,15 @@ internals.Auth.authenticatePayload = function (request, next) {

internals.Auth.responseHeader = function (request, next) {

var auth = request.server._auth;
var config = auth.routeConfig(request);

if (!config ||
!request.isAuthenticated) {

return next();
}

if (!request.session ||
!request.session._strategy ||
typeof request.session._strategy.responseHeader !== 'function') {
Expand Down
4 changes: 1 addition & 3 deletions lib/auth/oz.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,7 @@ internals.Scheme.prototype._endpoint = function (name) {
var self = this;

var endpoint = {
auth: {
mode: 'none'
},
auth: false, // Override any defaults
handler: function (request) {

Oz.endpoints[name](request.raw.req, request.payload, self.settings, function (err, response) {
Expand Down
8 changes: 3 additions & 5 deletions lib/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ exports.server = {

router: {
isCaseSensitive: true, // Case-seinsitive paths
normalizeRequestPath: false, // Normalize incoming request path (Uppercase % encoding and decode non-reserved encoded characters)
routeDefaults: null // Default config applied to each new route on add
normalizeRequestPath: false // Normalize incoming request path (Uppercase % encoding and decode non-reserved encoded characters)
},

// State
Expand Down Expand Up @@ -67,11 +66,10 @@ exports.server = {
cache: null, // Always created (null defaults to exports.cache)

// Optional components
// false -> null, true -> defaults, {} -> override defaults

cors: false, // CORS headers on responses and OPTIONS requests (defaults: exports.cors)
cors: false, // CORS headers on responses and OPTIONS requests (defaults: exports.cors): false -> null, true -> defaults, {} -> override defaults
views: null, // Presentation engine (defaults: exports.views)
auth: null // Authentication
auth: {} // Authentication
};


Expand Down
22 changes: 18 additions & 4 deletions lib/pack.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internals.defaultPermissions = {
events: true,
views: true,
cache: true,
auth: true,
ext: false
};

Expand Down Expand Up @@ -56,10 +57,16 @@ exports = module.exports = internals.Pack = function (options) {
};


internals.Pack.prototype.server = function (host, port, options) {
internals.Pack.prototype.server = function (arg1, arg2, arg3) {

var server = new Server(host, port, options, this);
this._server(server);
[arg1, arg2, arg3].forEach(function (arg) { // Server arguments can appear in any order

if (typeof arg === 'object') {
Utils.assert(!arg.cache, 'Cannot configure server cache in a pack member');
}
});

this._server(new Server(arg1, arg2, arg3, this));
};


Expand Down Expand Up @@ -188,6 +195,13 @@ internals.Pack.prototype._register = function (plugin, permissions, options, cal
};
}

if (permissions.auth) {
methods.auth = function () {

self._applySync(selection.servers, Server.prototype.auth, arguments);
};
}

if (permissions.events) {
methods.events = self.events;
}
Expand Down Expand Up @@ -475,7 +489,7 @@ internals.Pack.prototype._provisionCache = function (options, type, name, segmen
}
else if (type === 'route') {
Utils.assert(!segment || segment.indexOf('//') === 0, 'Route cache segment must start with \'//\'');
segment = segment || name;
segment = segment || name; // name (path) already includes '/'
}
else if (type === 'plugin') {
Utils.assert(!segment || segment.indexOf('!!') === 0, 'Plugin cache segment must start with \'!!\'');
Expand Down
4 changes: 2 additions & 2 deletions lib/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,12 +451,12 @@ internals.Request.prototype._replyInterface = function (callback, withProperties
return response;
};

if (this.server.views ||
if (this.server._views ||
this._route.env.views) {

reply.view = function (template, context, options) {

var viewsManager = self._route.env.views || self.server.views;
var viewsManager = self._route.env.views || self.server._views;
response = Response.generate(new Response.View(viewsManager, template, context, options), process);
return response;
};
Expand Down
Loading