Skip to content

Commit

Permalink
Merge 8bf521c into 2327a36
Browse files Browse the repository at this point in the history
  • Loading branch information
sjudson committed Aug 11, 2018
2 parents 2327a36 + 8bf521c commit 2391149
Show file tree
Hide file tree
Showing 11 changed files with 845 additions and 25 deletions.
43 changes: 43 additions & 0 deletions lib/authenticator.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,49 @@ Authenticator.prototype.authenticate = function(strategy, options, callback) {
return this._framework.authenticate(this, strategy, options, callback);
};

/**
* Middleware that uses the given `configurator` to setup a strategy, which it
* will use authenticate a request, with optional `options` and `callback`.
*
* Examples:
*
* function config(req, done) {
* var lstrategy = new LocalStrategy(function(username, password, done) {
* var db = dbs[req.params.directory];
*
* db.users.verify(username, password, function(err, user) {
* if (err) { return next(err); }
* if (!user) { return done(null, false); }
* return done(null, user);
* });
* }
*
* return done(null, lstrategy);
* }
*
* passport.authenticateWith(config, { successRedirect: '/', failureRedirect: '/login' })(req, res);
*
* passport.authenticateWith(config, function(err, user) {
* if (!user) { return res.redirect('/login'); }
* res.end('AuthenticateWithd!');
* })(req, res);
*
* passport.authenticateWith(config, { session: false })(req, res);
*
* app.get('/authenticate', passport.authenticateWith(config), function(req, res) {
* res.json(req.user);
* });
*
* @param {Strategy} configurator
* @param {Object} options
* @param {Function} callback
* @return {Function} middleware
* @api public
*/
Authenticator.prototype.authenticateWith = function(configurator, options, callback) {
return this._framework.authenticateWith(this, configurator, options, callback);
};

/**
* Middleware that will authorize a third-party account using the given
* `strategy` name, with optional `options`.
Expand Down
6 changes: 4 additions & 2 deletions lib/framework/connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
* Module dependencies.
*/
var initialize = require('../middleware/initialize')
, authenticate = require('../middleware/authenticate');
, authenticate = require('../middleware/authenticate')
, authenticateWith = require('../middleware/authenticateWith');

/**
* Framework support for Connect/Express.
Expand All @@ -22,7 +23,8 @@ exports = module.exports = function() {

return {
initialize: initialize,
authenticate: authenticate
authenticate: authenticate,
authenticateWith: authenticateWith
};
};

Expand Down
45 changes: 30 additions & 15 deletions lib/middleware/authenticate.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
* Module dependencies.
*/
var http = require('http')
, Strategy = require('passport-strategy')
, IncomingMessageExt = require('../http/request')
, AuthenticationError = require('../errors/authenticationerror');



/**
* Authenticates requests.
*
Expand Down Expand Up @@ -61,13 +63,13 @@ var http = require('http')
*
* passport.authenticate('twitter');
*
* @param {String|Array} name
* @param {Strategy|String|Array} method
* @param {Object} options
* @param {Function} callback
* @return {Function}
* @api public
*/
module.exports = function authenticate(passport, name, options, callback) {
module.exports = function authenticate(passport, method, options, callback) {
if (typeof options == 'function') {
callback = options;
options = {};
Expand All @@ -76,18 +78,18 @@ module.exports = function authenticate(passport, name, options, callback) {

var multi = true;

// Cast `name` to an array, allowing authentication to pass through a chain of
// strategies. The first strategy to succeed, redirect, or error will halt
// the chain. Authentication failures will proceed through each strategy in
// series, ultimately failing if all strategies fail.
// Cast `method` to an array, allowing authentication to pass through a
// chain of strategies. The first strategy to succeed, redirect, or error
// will halt the chain. Authentication failures will proceed through each
// strategy in series, ultimately failing if all strategies fail.
//
// This is typically used on API endpoints to allow clients to authenticate
// using their preferred choice of Basic, Digest, token-based schemes, etc.
// It is not feasible to construct a chain of multiple strategies that involve
// redirection (for example both Facebook and Twitter), since the first one to
// redirect will halt the chain.
if (!Array.isArray(name)) {
name = [ name ];
if (!Array.isArray(method)) {
method = [ method ];
multi = false;
}

Expand Down Expand Up @@ -175,18 +177,22 @@ module.exports = function authenticate(passport, name, options, callback) {
}

(function attempt(i) {
var layer = name[i];
var layer = method[i];
// If no more strategies exist in the chain, authentication has failed.
if (!layer) { return allFailed(); }

// Get the strategy, which will be used as prototype from which to create
// a new instance. Action functions will then be bound to the strategy
// within the context of the HTTP request/response pair.
var prototype = passport._strategy(layer);
if (!prototype) { return next(new Error('Unknown authentication strategy "' + layer + '"')); }

var strategy = Object.create(prototype);

var strategy, prototype;
if (typeof layer.authenticate == 'function') {
strategy = layer;
} else { // typeof layer == 'string'
prototype = passport._strategy(layer);
if (!prototype) { return next(new Error('Unknown authentication strategy "' + layer + '"')); }

strategy = Object.create(prototype);
}

// ----- BEGIN STRATEGY AUGMENTATION -----
// Augment the new strategy instance with action functions. These action
Expand Down Expand Up @@ -242,7 +248,16 @@ module.exports = function authenticate(passport, name, options, callback) {
}
if (options.assignProperty) {
req[options.assignProperty] = user;
return next();
if (options.authInfo !== false) {
passport.transformAuthInfo(info, req, function(err, tinfo) {
if (err) { return next(err); }
req.authInfo = tinfo;
return next();
});
return;
} else {
return next();
}
}

req.logIn(user, options, function(err) {
Expand Down
62 changes: 62 additions & 0 deletions lib/middleware/authenticateWith.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Module dependencies.
*/
var authenticate = require('./authenticate')
, Strategy = require('passport-strategy')



/**
* Dynamically configures methods for authenticating requests.
*
* Employs the `configurator` to dynamically determine either a Strategy or
* list of strategy names with which to authenticate the request. This allows
* the authentication to be undertaken with context of the individual request.
* If the configurator returns a list of strategy names, those strategies must
* be preconfigured, as for the standard `authenticate` middleware.
*
* See the superior `authenticate` middleware for more information and details.
*
* @param {Function} configurator
* @param {Object} options
* @param {Function} callback
* @return {Function}
* @api public
*/
module.exports = function authenticateWith(passport, configurator, options, callback) {
if (typeof options == 'function') {
callback = options;
options = {};
}
options = options || {};

// The default configurator will cause `authenticate` to fail and handle
// that failure as per how it is set up.
configurator = configurator || function(req, done) { return done(); }

return function authenticateWith(req, res, next) {

function configured(err, method) {
if (err) { return next(err); }

// If configuration does not return a Strategy or name(s) of strategies,
// we will pass up an empty array, which `authenticate` will handle using
// the preferred failure mode. This provides the least behavioral
// divergence between this middleware and `authenticate`.
method = method || [];

return authenticate(passport, method, options, callback)(req, res, next);
}

try {
var arity = configurator.length;
if (arity == 3) {
configurator(req, options, configured);
} else { // arity == 2
configurator(req, configured);
}
} catch (ex) {
return next(ex);
}
};
};
70 changes: 65 additions & 5 deletions test/authenticator.middleware.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,67 @@ describe('Authenticator', function() {
});

});




describe('#authenticateWith', function() {

it('should have correct arity', function() {
var passport = new Authenticator();
expect(passport.authenticate).to.have.length(3);
});

describe('handling a request', function() {
function Strategy() {
}
Strategy.prototype.authenticate = function(req) {
var user = { id: '1', username: 'jaredhanson' };
this.success(user);
};

function config(req, done) {
return done(null, new Strategy());
};

var passport = new Authenticator();

var request, error;

before(function(done) {
chai.connect.use(passport.authenticateWith(config))
.req(function(req) {
request = req;

req.logIn = function(user, options, done) {
this.user = user;
done();
};
})
.next(function(err) {
error = err;
done();
})
.dispatch();
});

it('should not error', function() {
expect(error).to.be.undefined;
});

it('should set user', function() {
expect(request.user).to.be.an('object');
expect(request.user.id).to.equal('1');
expect(request.user.username).to.equal('jaredhanson');
});

it('should set authInfo', function() {
expect(request.authInfo).to.be.an('object');
expect(Object.keys(request.authInfo)).to.have.length(0);
});
});

});


describe('#authorize', function() {

it('should have correct arity', function() {
Expand Down Expand Up @@ -203,9 +262,10 @@ describe('Authenticator', function() {
expect(request.account.id).to.equal('1');
expect(request.account.username).to.equal('jaredhanson');
});

it('should not set authInfo', function() {
expect(request.authInfo).to.be.undefined;

it('should set authInfo', function() {
expect(request.authInfo).to.be.an('object');
expect(Object.keys(request.authInfo)).to.have.length(0);
});
});

Expand Down
49 changes: 49 additions & 0 deletions test/middleware/authenticate.success.info.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,5 +201,54 @@ describe('middleware/authenticate', function() {
expect(request.authInfo).to.be.undefined;
});
});

describe('success that assigns property and with info, but option that disables info', function() {
function Strategy() {
}
Strategy.prototype.authenticate = function(req) {
var user = { id: '1', username: 'jaredhanson' };
this.success(user, { clientId: '123', scope: 'read' });
};

var passport = new Passport();
passport.use('success', new Strategy());

var request, error;

before(function(done) {
chai.connect.use(authenticate(passport, 'success', { assignProperty: 'account', authInfo: false }))
.req(function(req) {
request = req;

req.logIn = function(user, options, done) {
this.user = user;
done();
};
})
.next(function(err) {
error = err;
done();
})
.dispatch();
});

it('should not error', function() {
expect(error).to.be.undefined;
});

it('should not set user', function() {
expect(request.user).to.be.undefined;
});

it('should set account', function() {
expect(request.account).to.be.an('object');
expect(request.account.id).to.equal('1');
expect(request.account.username).to.equal('jaredhanson');
});

it('should not set authInfo', function() {
expect(request.authInfo).to.be.undefined;
});
});

});

0 comments on commit 2391149

Please sign in to comment.