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

chore: not attempt strategy more than one time #987

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
158 changes: 92 additions & 66 deletions lib/middleware/authenticate.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/**
* Module dependencies.
*/
var http = require('http')
, IncomingMessageExt = require('../http/request')
, AuthenticationError = require('../errors/authenticationerror');

var http = require('http'),
IncomingMessageExt = require('../http/request'),
AuthenticationError = require('../errors/authenticationerror');

/**
* Authenticates requests.
Expand Down Expand Up @@ -73,9 +72,9 @@ module.exports = function authenticate(passport, name, options, callback) {
options = {};
}
options = options || {};

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
Expand All @@ -87,47 +86,57 @@ module.exports = function authenticate(passport, name, options, callback) {
// redirection (for example both Facebook and Twitter), since the first one to
// redirect will halt the chain.
if (!Array.isArray(name)) {
name = [ name ];
name = [name];
multi = false;
}

return function authenticate(req, res, next) {
req.login =
req.logIn = req.logIn || IncomingMessageExt.logIn;
req.logout =
req.logOut = req.logOut || IncomingMessageExt.logOut;
req.isAuthenticated = req.isAuthenticated || IncomingMessageExt.isAuthenticated;
req.isUnauthenticated = req.isUnauthenticated || IncomingMessageExt.isUnauthenticated;
req.login = req.logIn = req.logIn || IncomingMessageExt.logIn;
req.logout = req.logOut = req.logOut || IncomingMessageExt.logOut;
req.isAuthenticated =
req.isAuthenticated || IncomingMessageExt.isAuthenticated;
req.isUnauthenticated =
req.isUnauthenticated || IncomingMessageExt.isUnauthenticated;

req._sessionManager = passport._sm;

// accumulator for failures from each strategy in the chain
var failures = [];

var failureSet = {};

function allFailed() {
if (callback) {
if (!multi) {
return callback(null, false, failures[0].challenge, failures[0].status);
return callback(
null,
false,
failures[0].challenge,
failures[0].status
);
} else {
var challenges = failures.map(function(f) { return f.challenge; });
var statuses = failures.map(function(f) { return f.status; });
var challenges = failures.map(function (f) {
return f.challenge;
});
var statuses = failures.map(function (f) {
return f.status;
});
return callback(null, false, challenges, statuses);
}
}

// Strategies are ordered by priority. For the purpose of flashing a
// message, the first failure will be displayed.
var failure = failures[0] || {}
, challenge = failure.challenge || {}
, msg;
var failure = failures[0] || {},
challenge = failure.challenge || {},
msg;

if (options.failureFlash) {
var flash = options.failureFlash;
if (typeof flash == 'string') {
flash = { type: 'error', message: flash };
}
flash.type = flash.type || 'error';

var type = flash.type || challenge.type || 'error';
msg = flash.message || challenge.message || challenge;
if (typeof msg == 'string') {
Expand All @@ -147,41 +156,46 @@ module.exports = function authenticate(passport, name, options, callback) {
if (options.failureRedirect) {
return res.redirect(options.failureRedirect);
}

// When failure handling is not delegated to the application, the default
// is to respond with 401 Unauthorized. Note that the WWW-Authenticate
// header will be set according to the strategies in use (see
// actions#fail). If multiple strategies failed, each of their challenges
// will be included in the response.
var rchallenge = []
, rstatus, status;

var rchallenge = [],
rstatus,
status;

for (var j = 0, len = failures.length; j < len; j++) {
failure = failures[j];
challenge = failure.challenge;
status = failure.status;

rstatus = rstatus || status;
if (typeof challenge == 'string') {
rchallenge.push(challenge);
}
}

res.statusCode = rstatus || 401;
if (res.statusCode == 401 && rchallenge.length) {
res.setHeader('WWW-Authenticate', rchallenge);
}
if (options.failWithError) {
return next(new AuthenticationError(http.STATUS_CODES[res.statusCode], rstatus));
return next(
new AuthenticationError(http.STATUS_CODES[res.statusCode], rstatus)
);
}
res.end(http.STATUS_CODES[res.statusCode]);
}

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

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.
Expand All @@ -190,19 +204,22 @@ module.exports = function authenticate(passport, name, options, callback) {
strategy = layer;
} else {
prototype = passport._strategy(layer);
if (!prototype) { return next(new Error('Unknown authentication 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
// functions are bound via closure the the request/response pair. The end
// goal of the strategy is to invoke *one* of these action methods, in
// order to indicate successful or failed authentication, redirect to a
// third-party identity provider, etc.

/**
* Authenticate `user`, with optional `info`.
*
Expand All @@ -217,21 +234,21 @@ module.exports = function authenticate(passport, name, options, callback) {
* @param {Object} info
* @api public
*/
strategy.success = function(user, info) {
strategy.success = function (user, info) {
if (callback) {
return callback(null, user, info);
}

info = info || {};
var msg;

if (options.successFlash) {
var flash = options.successFlash;
if (typeof flash == 'string') {
flash = { type: 'success', message: flash };
}
flash.type = flash.type || 'success';

var type = flash.type || info.type || 'success';
msg = flash.message || info.message || info;
if (typeof msg == 'string') {
Expand All @@ -252,10 +269,12 @@ module.exports = function authenticate(passport, name, options, callback) {
req[options.assignProperty] = user;
return next();
}

req.logIn(user, options, function(err) {
if (err) { return next(err); }


req.logIn(user, options, function (err) {
if (err) {
return next(err);
}

function complete() {
if (options.successReturnToOrRedirect) {
var url = options.successReturnToOrRedirect;
Expand All @@ -270,10 +289,12 @@ module.exports = function authenticate(passport, name, options, callback) {
}
next();
}

if (options.authInfo !== false) {
passport.transformAuthInfo(info, req, function(err, tinfo) {
if (err) { return next(err); }
passport.transformAuthInfo(info, req, function (err, tinfo) {
if (err) {
return next(err);
}
req.authInfo = tinfo;
complete();
});
Expand All @@ -282,7 +303,7 @@ module.exports = function authenticate(passport, name, options, callback) {
}
});
};

/**
* Fail authentication, with optional `challenge` and `status`, defaulting
* to 401.
Expand All @@ -293,18 +314,23 @@ module.exports = function authenticate(passport, name, options, callback) {
* @param {Number} status
* @api public
*/
strategy.fail = function(challenge, status) {
strategy.fail = function (challenge, status) {
if (typeof challenge == 'number') {
status = challenge;
challenge = undefined;
}

// push this failure into the accumulator and attempt authentication
// using the next strategy
failures.push({ challenge: challenge, status: status });
attempt(i + 1);
if (!failureSet[i]) {
// to avoid single layer failed multiple times
// make following strategy attempt multiple times
failureSet[i] = 1;
attempt(i + 1);
}
};

/**
* Redirect to `url` with optional `status`, defaulting to 302.
*
Expand All @@ -315,7 +341,7 @@ module.exports = function authenticate(passport, name, options, callback) {
* @param {Number} status
* @api public
*/
strategy.redirect = function(url, status) {
strategy.redirect = function (url, status) {
// NOTE: Do not use `res.redirect` from Express, because it can't decide
// what it wants.
//
Expand All @@ -326,13 +352,13 @@ module.exports = function authenticate(passport, name, options, callback) {
// Express 4.x: res.redirect(status, url)
// - all versions (as of 4.8.7) continue to accept res.redirect(url, status)
// but issue deprecated versions

res.statusCode = status || 302;
res.setHeader('Location', url);
res.setHeader('Content-Length', '0');
res.end();
};

/**
* Pass without making a success or fail decision.
*
Expand All @@ -342,10 +368,10 @@ module.exports = function authenticate(passport, name, options, callback) {
*
* @api public
*/
strategy.pass = function() {
strategy.pass = function () {
next();
};

/**
* Internal error while performing authentication.
*
Expand All @@ -356,16 +382,16 @@ module.exports = function authenticate(passport, name, options, callback) {
* @param {Error} err
* @api public
*/
strategy.error = function(err) {
strategy.error = function (err) {
if (callback) {
return callback(err);
}

next(err);
};

// ----- END STRATEGY AUGMENTATION -----

strategy.authenticate(req, options);
})(0); // attempt
};
Expand Down