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

Auth api refactor #715

Merged
merged 9 commits into from
Mar 27, 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
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