Skip to content

Commit

Permalink
Merge pull request #715 from spumko/feature/authRefactor
Browse files Browse the repository at this point in the history
Auth api refactor
  • Loading branch information
geek committed Mar 27, 2013
2 parents 0ef949c + 3a4bf13 commit 8c2d859
Show file tree
Hide file tree
Showing 19 changed files with 303 additions and 257 deletions.
10 changes: 7 additions & 3 deletions examples/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,16 @@ internals.hashPassword = function (password) {

internals.users = {
john: {
id: 'john',
password: internals.hashPassword('john')
user: 'john'
}
};


internals.passwords = {
john: 'john'
};


internals.credentials = {
'john': {
id: 'john',
Expand All @@ -41,7 +45,7 @@ internals.credentials = {

internals.loadUser = function (username, callback) {

callback(null, internals.users[username]);
callback(null, internals.users[username], internals.hashPassword(internals.passwords[username]));
};


Expand Down
48 changes: 18 additions & 30 deletions lib/auth/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,55 +53,43 @@ internals.Scheme.prototype.authenticate = function (request, callback) {
return callback(Boom.badRequest('Bad header internal syntax', 'Basic'));
}

var credentials = {
username: credentialsParts[0],
password: credentialsParts[1]
};
var username = credentialsParts[0];
var password = credentialsParts[1];

this.settings.loadUserFunc(credentials.username, function (err, user) {
this.settings.loadUserFunc(username, function (err, credentials, hashedPassword) {

if (err) {
request.log(['auth', 'user'], err);
return callback(err, null, true);
return callback(err, null, { log: { tags: ['auth', 'basic'], data: err } });
}

if (!user) {
request.log(['auth', 'error', 'user', 'unknown']);
return callback(Boom.unauthorized('Bad username or password', 'Basic'), null, true);
if (!credentials) {
return callback(Boom.unauthorized('Bad username or password', 'Basic'), null, { log: { tags: ['auth', 'error', 'basic', 'user', 'unknown'] } });
}

if (!user.hasOwnProperty('password') ||
!user.id ||
user.id !== credentials.username) {
if (typeof credentials !== 'object') {
return callback(Boom.internal('Bad credentials object received for Basic auth validation'), null, { log: { tags: ['auth', 'error', 'basic', 'credentials', 'invalid'] } });
}

if (hashedPassword === null ||
hashedPassword === undefined ||
typeof hashedPassword !== 'string') {

request.log(['auth', 'error', 'user', 'invalid']);
return callback(Boom.internal('Bad user object received for Basic auth validation'), null, true);
return callback(Boom.internal('Bad password received for Basic auth validation'), null, { log: { tags: ['auth', 'error', 'basic', 'password', 'invalid'] } });
}

if (typeof self.settings.hashPasswordFunc === 'function') {
credentials.password = self.settings.hashPasswordFunc(credentials.password);
password = self.settings.hashPasswordFunc(password);
}

// Check password

if (!Cryptiles.fixedTimeComparison(user.password, credentials.password)) {
request.log(['auth', 'error', 'user', 'password']);
return callback(Boom.unauthorized('Bad username or password', 'Basic'), null, true);
if (!Cryptiles.fixedTimeComparison(hashedPassword, password)) {
return callback(Boom.unauthorized('Bad username or password', 'Basic'), null, { log: { tags: ['auth', 'error', 'basic', 'password'] } });
}

// Authenticated

var session = {
id: user.id,
app: '',
scope: user.scope,
user: user.id,
ext: Utils.clone(user) // ext.tos
};

delete session.ext.password;

return callback(null, session);
return callback(null, credentials);
});
};

Expand Down
6 changes: 1 addition & 5 deletions lib/auth/bewit.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ internals.Scheme.prototype.authenticate = function (request, callback) {

Hawk.uri.authenticate(request.raw.req, this.settings.getCredentialsFunc, { hostHeaderName: this.settings.hostHeaderName }, function (err, credentials, bewit) {

if (credentials) {
credentials.authExt = bewit.ext;
}

return callback(err, credentials);
return callback(err, credentials, { artifacts: bewit });
});
};
24 changes: 13 additions & 11 deletions lib/auth/cookie.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ internals.Scheme.prototype.authenticate = function (request, callback) {
request.clearState(self.settings.cookie);
}

request.log(['auth', 'validate'], err);
return unauthenticated(Boom.unauthorized('Invalid cookie'), session, true);
return unauthenticated(Boom.unauthorized('Invalid cookie'), session, { log: { tags: ['auth', 'validate'], data: err}});
}

if (override) {
Expand All @@ -78,10 +77,10 @@ internals.Scheme.prototype.authenticate = function (request, callback) {
});
};

var unauthenticated = function (err, session, wasLogged) {
var unauthenticated = function (err, session, options) {

if (!self.settings.redirectTo) {
return callback(err, session, wasLogged);
return callback(err, session, options);
}

var uri = self.settings.redirectTo;
Expand All @@ -96,7 +95,7 @@ internals.Scheme.prototype.authenticate = function (request, callback) {
uri += self.settings.appendNext + '=' + encodeURIComponent(request.url.path);
}

return callback(new Redirection(uri), session, wasLogged);
return callback(new Redirection(uri), session, options);
};

validate();
Expand All @@ -109,13 +108,16 @@ internals.Scheme.prototype.extend = function (request) {

// Decorate request

request.setSession = function (session) {
Utils.assert(!request.session, 'Conflicting use of request.session');

request.setState(self.settings.cookie, session);
};
request.session = {
set: function (session) {

request.clearSession = function () {
request.setState(self.settings.cookie, session);
},
clear: function () {

request.clearState(self.settings.cookie);
};
request.clearState(self.settings.cookie);
}
}
};
15 changes: 5 additions & 10 deletions lib/auth/hawk.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,23 @@ internals.Scheme.prototype.authenticate = function (request, callback) {

Hawk.server.authenticate(request.raw.req, this.settings.getCredentialsFunc, { hostHeaderName: this.settings.hostHeaderName }, function (err, credentials, artifacts) {

if (credentials) {
credentials.artifacts = artifacts;
delete credentials.artifacts.credentials; // Needed to prevent utils.clone from creating a maximum call stack error
}

return callback(err, credentials);
return callback(err, credentials, { artifacts: artifacts });
});
};


internals.Scheme.prototype.authenticatePayload = function (payload, credentials, contentType, callback) {
internals.Scheme.prototype.authenticatePayload = function (request, callback) {

var isValid = Hawk.server.authenticatePayload(payload, credentials, credentials.artifacts.hash, contentType);
var isValid = Hawk.server.authenticatePayload(request.rawBody, request.auth.credentials, request.auth.artifacts.hash, request.raw.req.headers['content-type']);

return callback(isValid ? null : Boom.unauthorized('Payload is invalid'));
};


internals.Scheme.prototype.responseHeader = function (request, callback) {

var artifacts = Utils.clone(request.session.artifacts);
artifacts.credentials = request.session;
var artifacts = Utils.clone(request.auth.artifacts);
artifacts.credentials = request.auth.credentials;

var options = {
contentType: request.response._headers['Content-Type']
Expand Down
75 changes: 41 additions & 34 deletions lib/auth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,12 @@ internals.Auth.prototype.authenticate = function (request, next) {
var authErrors = [];
var strategyPos = 0;

request.isAuthenticated = false;

var authenticate = function () {

// Injection

if (request.session) {
return validate(null, request.session);
if (request.auth.credentials) {
return validate(null, request.auth.credentials);
}

// Authenticate
Expand All @@ -185,7 +183,8 @@ internals.Auth.prototype.authenticate = function (request, next) {
if (config.mode === 'optional' ||
config.mode === 'try') {

request.session = null;
request.auth.isAuthenticated = false;
request.auth.credentials = null;
request.log(['auth', 'unauthenticated']);
return next();
}
Expand All @@ -197,16 +196,21 @@ internals.Auth.prototype.authenticate = function (request, next) {
return strategy.authenticate(request, validate);
};

var validate = function (err, session, wasLogged) {
var validate = function (err, credentials, options) {

options = options || {};

// Unauthenticated

if (!err && !session) {
return next(Boom.internal('Authentication response missing both error and session'));
if (!err && !credentials) {
return next(Boom.internal('Authentication response missing both error and credentials'));
}

if (err) {
if (!wasLogged) {
if (options.log) {
request.log(options.log.tags, options.log.data);
}
else {
request.log(['auth', 'unauthenticated'], err);
}

Expand All @@ -215,7 +219,9 @@ internals.Auth.prototype.authenticate = function (request, next) {
err.response.code !== 401) { // An actual error (not just missing authentication)

if (config.mode === 'try') {
request.session = session;
request.auth.isAuthenticated = false;
request.auth.credentials = credentials;
request.auth.artifacts = options.artifacts;
request.log(['auth', 'unauthenticated', 'try'], err);
return next();
}
Expand All @@ -234,25 +240,26 @@ internals.Auth.prototype.authenticate = function (request, next) {

// Authenticated

request.session = session;
request.session._strategy = self._strategies[config.strategies[strategyPos - 1]];
request.auth.credentials = credentials;
request.auth.artifacts = options.artifacts;
request.auth._strategy = self._strategies[config.strategies[strategyPos - 1]];

// Check scope

if (config.scope &&
(!session.scope || session.scope.indexOf(config.scope) === -1)) {
(!credentials.scope || credentials.scope.indexOf(config.scope) === -1)) {

request.log(['auth', 'error', 'scope'], { got: session.scope, need: config.scope });
request.log(['auth', 'error', 'scope'], { got: credentials.scope, need: config.scope });
return next(Boom.forbidden('Insufficient scope (\'' + config.scope + '\' expected)'));
}

// Check TOS

var tos = (config.hasOwnProperty('tos') ? config.tos : null);
if (tos &&
(!session.ext || !session.ext.tos || session.ext.tos < tos)) {
(!credentials.tos || credentials.tos < tos)) {

request.log(['auth', 'error', 'tos'], { min: tos, received: session.ext && session.ext.tos });
request.log(['auth', 'error', 'tos'], { min: tos, received: credentials.tos });
return next(Boom.forbidden('Insufficient TOS accepted'));
}

Expand All @@ -264,32 +271,32 @@ internals.Auth.prototype.authenticate = function (request, next) {

if (entity === 'any') {
request.log(['auth']);
request.isAuthenticated = true;
request.auth.isAuthenticated = true;
return next();
}

// Entity: 'user'

if (entity === 'user') {
if (!session.user) {
request.log(['auth', 'error'], 'User session required');
return next(Boom.forbidden('Application session cannot be used on a user endpoint'));
if (!credentials.user) {
request.log(['auth', 'error'], 'User credentials required');
return next(Boom.forbidden('Application credentials cannot be used on a user endpoint'));
}

request.log(['auth']);
request.isAuthenticated = true;
request.auth.isAuthenticated = true;
return next();
}

// Entity: 'app'

if (session.user) {
request.log(['auth', 'error'], 'App session required');
return next(Boom.forbidden('User session cannot be used on an application endpoint'));
if (credentials.user) {
request.log(['auth', 'error'], 'App credentials required');
return next(Boom.forbidden('User credentials cannot be used on an application endpoint'));
}

request.log(['auth']);
request.isAuthenticated = true;
request.auth.isAuthenticated = true;
return next();
};

Expand All @@ -304,19 +311,19 @@ internals.Auth.authenticatePayload = function (request, next) {

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

return next();
}

if (config.payload === 'optional' &&
(!request.session.artifacts.hash ||
typeof request.session._strategy.authenticatePayload !== 'function')) {
(!request.auth.artifacts.hash ||
typeof request.auth._strategy.authenticatePayload !== 'function')) {

return next();
}

request.session._strategy.authenticatePayload(request.rawBody, request.session, request.raw.req.headers['content-type'], function (err) {
request.auth._strategy.authenticatePayload(request, function (err) {

return next(err);
});
Expand All @@ -329,14 +336,14 @@ internals.Auth.responseHeader = function (request, next) {
var config = auth.routeConfig(request);

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

return next();
}

if (!request.session ||
!request.session._strategy ||
typeof request.session._strategy.responseHeader !== 'function') {
if (!request.auth.credentials ||
!request.auth._strategy ||
typeof request.auth._strategy.responseHeader !== 'function') {

return next();
}
Expand All @@ -348,7 +355,7 @@ internals.Auth.responseHeader = function (request, next) {
return next();
}

request.session._strategy.responseHeader(request, function (err) {
request.auth._strategy.responseHeader(request, function (err) {

return next(err);
});
Expand Down
Loading

0 comments on commit 8c2d859

Please sign in to comment.