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

Make passport.authenticate() callbacks more useful #423

Closed
wants to merge 1 commit into from
Closed
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
312 changes: 165 additions & 147 deletions lib/middleware/authenticate.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,20 @@ var http = require('http')
* otherwise. An optional `info` argument will be passed, containing additional
* details provided by the strategy's verify callback.
*
* app.get('/protected', function(req, res, next) {
* passport.authenticate('local', function(err, user, info) {
* if (err) { return next(err) }
* if (!user) { return res.redirect('/signin') }
* res.redirect('/account');
* })(req, res, next);
* });
* app.get('/protected',
* passport.authenticate('local',
* function(err, user, info, status, req, res, next) {
* if (err) { return next(err) }
* if (!user) { return res.redirect('/signin') }
* res.redirect('/account');
* }
* )
* );
*
* Note that if a callback is supplied, it becomes the application's
* responsibility to log-in the user, establish a session, and otherwise perform
* the desired operations.
* Note that if a callback is supplied, all other passport response handling
* is bypassed, and all options are ignored. This feature should be reserved
* for performance-critical applications where the base passport options are
* undesireable.
*
* Examples:
*
Expand All @@ -59,7 +62,152 @@ module.exports = function authenticate(passport, name, options, callback) {
options = {};
}
options = options || {};

function failed(challenges, statuses, req, res, next) {
// Strategies are ordered by priority. For the purpose of flashing a
// message, the first failure will be displayed.
var msg;
var failures = 1;
var challenge = challenges;
var status = statuses;
if (Array.isArray(challenges)) {
failures = challenge.length;
challenge = challenges[0];
status = statuses[0];
} else if (!challenge) {
challenge = {};
}

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') {
req.flash(type, msg);
}
}
if (options.failureMessage) {
msg = options.failureMessage;
if (typeof msg == 'boolean') {
msg = challenge.message || challenge;
}
if (typeof msg == 'string') {
req.session.messages = req.session.messages || [];
req.session.messages.push(msg);
}
}
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 = typeof challenge == 'string' ? [challenge] : [];
var rstatus = status;

// starting at j = 1 because we've handled the first case already and
// if failures = 1, statuses is not an array.
for (var j = 1, len = failures; j < len; j++) {

rstatus = rstatus || statuses[j];
if (typeof challenges[j] == 'string') {
rchallenge.push(challenges[j]);
}
}

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));
}
res.end(http.STATUS_CODES[res.statusCode]);
}

function succeeded(user, info, req, res, next) {
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') {
req.flash(type, msg);
}
}
if (options.successMessage) {
msg = options.successMessage;
if (typeof msg == 'boolean') {
msg = info.message || info;
}
if (typeof msg == 'string') {
req.session.messages = req.session.messages || [];
req.session.messages.push(msg);
}
}
if (options.assignProperty) {
req[options.assignProperty] = user;
return next();
}

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

function complete() {
if (options.successReturnToOrRedirect) {
var url = options.successReturnToOrRedirect;
if (req.session && req.session.returnTo) {
url = req.session.returnTo;
delete req.session.returnTo;
}
return res.redirect(url);
}
if (options.successRedirect) {
return res.redirect(options.successRedirect);
}
next();
}

if (options.authInfo !== false) {
passport.transformAuthInfo(info, req, function(err, tinfo) {
if (err) { return next(err); }
req.authInfo = tinfo;
complete();
});
} else {
complete();
}
});
}
function defaultCallback(err, user, info, status, req, res, next) {
if (err) {
next(err);
} else if (user) {
succeeded(user, info, req, res, next);
} else {
failed(info, status, req, res, next);
}
}

if (typeof callback != 'function') {
callback = defaultCallback;
}

var multi = true;

// Cast `name` to an array, allowing authentication to pass through a chain of
Expand All @@ -82,76 +230,12 @@ module.exports = function authenticate(passport, name, options, callback) {
var failures = [];

function allFailed() {
if (callback) {
if (!multi) {
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; });
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;

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') {
req.flash(type, msg);
}
}
if (options.failureMessage) {
msg = options.failureMessage;
if (typeof msg == 'boolean') {
msg = challenge.message || challenge;
}
if (typeof msg == 'string') {
req.session.messages = req.session.messages || [];
req.session.messages.push(msg);
}
}
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;

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));
if (multi) {
var challenges = failures.map(function(f) { return f.challenge; });
var statuses = failures.map(function(f) { return f.status; });
return callback(null, false, challenges, statuses, req, res, next);
}
res.end(http.STATUS_CODES[res.statusCode]);
callback(null, false, failures[0].challenge, failures[0].status, req, res, next);
}

(function attempt(i) {
Expand Down Expand Up @@ -190,69 +274,7 @@ module.exports = function authenticate(passport, name, options, callback) {
* @api public
*/
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') {
req.flash(type, msg);
}
}
if (options.successMessage) {
msg = options.successMessage;
if (typeof msg == 'boolean') {
msg = info.message || info;
}
if (typeof msg == 'string') {
req.session.messages = req.session.messages || [];
req.session.messages.push(msg);
}
}
if (options.assignProperty) {
req[options.assignProperty] = user;
return next();
}

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

function complete() {
if (options.successReturnToOrRedirect) {
var url = options.successReturnToOrRedirect;
if (req.session && req.session.returnTo) {
url = req.session.returnTo;
delete req.session.returnTo;
}
return res.redirect(url);
}
if (options.successRedirect) {
return res.redirect(options.successRedirect);
}
next();
}

if (options.authInfo !== false) {
passport.transformAuthInfo(info, req, function(err, tinfo) {
if (err) { return next(err); }
req.authInfo = tinfo;
complete();
});
} else {
complete();
}
});
callback(null, user, info, null, req, res, next);
};

/**
Expand Down Expand Up @@ -329,11 +351,7 @@ module.exports = function authenticate(passport, name, options, callback) {
* @api public
*/
strategy.error = function(err) {
if (callback) {
return callback(err);
}

next(err);
callback(err, null, null, null, req, res, next);
};

// ----- END STRATEGY AUGMENTATION -----
Expand Down