jsbin/lib/handlers/session.js
2013-06-17 16:54:58 +01:00

394 lines
10 KiB
JavaScript

var utils = require('../utils'),
errors = require('../errors'),
Observable = utils.Observable;
var passport = require('passport'),
GitHubStrategy = require('passport-github').Strategy;
module.exports = Observable.extend({
constructor: function SessionHandler(sandbox) {
Observable.apply(this, arguments);
this.models = sandbox.models;
this.mailer = sandbox.mailer;
this.helpers = sandbox.helpers;
var methods = Object.getOwnPropertyNames(SessionHandler.prototype).filter(function (prop) {
return typeof this[prop] === 'function';
}, this);
utils.bindAll(this, methods);
},
loadUser: function (req, res, next) {
this.models.user.load(req.param('name'), function (err, user) {
if (err) {
return next(err);
}
if (user) {
req.user = user;
}
next();
});
},
loadUserFromSession: function (req, res, next) {
var name = req.session.user && req.session.user.name;
function notAuthorized() {
next(new errors.NotAuthorized());
}
if (!name) {
return notAuthorized();
}
this.models.user.load(name, function (err, user) {
if (err) {
return next(err);
}
if (!user) {
return notAuthorized();
}
req.user = user;
next();
});
},
loginUser: function (req, res, next) {
var _this = this,
model = this.models.user,
user = req.user,
key = req.param('key'),
helpers = this.helpers;
function failedLogin() {
return _this.respond(req, res, 401, {
ok: false,
message: 'No dice I\'m afraid, those details didn\'t work.',
step: '2.4'
});
}
function onValidate(err, didPasswordMatch) {
if (err) {
return next(err);
}
if (!didPasswordMatch) {
return failedLogin();
}
// Add the user data to the session.
_this.setUserSession(req, user);
// Update the login timestamp but don't wait for a response as it's
// non crucial.
model.touchLogin(user.name, function () {});
_this.respond(req, res, 200, {
ok: true,
key: '',
created: false
// message: 'Now logged in as ' + user.name
});
}
if (user) {
if (user.created.getTime()) {
model.valid(key, user.key, onValidate);
} else {
// No created timestamp so this is an old password. Validate it then
// update it to use the new algorithm.
if (model.validOldKey(key, user.key)) {
model.upgradeKey(user.name, key, function (err) {
onValidate(err, true);
});
} else {
onValidate(null, false);
}
}
} else {
failedLogin();
}
},
logoutUser: function (req, res, next) {
delete req.session.user;
res.flash(req.flash.INFO, 'You\'re now logged out');
res.redirect(303, this.redirectUrl(req));
},
createUser: function (req, res, next) {
var params = {
name: req.param('name'),
key: req.param('key'),
email: req.param('email', '')
}, _this = this;
if (req.user) {
return this.respond(req, res, 400, {
ok: false,
message: 'Too late I\'m afraid, that username is taken.',
step: '2.3'
});
}
if (!params.name || !params.key) {
return this.respond(req, res, 400, {
ok: false,
message: 'Missing username or password',
step: '2.3'
});
}
this.models.user.create(params, function (err, id) {
if (err) {
return next(err);
}
// Add the user data to the session.
_this.setUserSession(req, params);
return _this.respond(req, res, 200, {ok: true, created: true});
});
},
updateUser: function (req, res, next) {
var helpers = this.helpers,
requests = 0,
user = req.user,
email = req.param('email', '').trim(),
key = req.param('key', '').trim(),
_this = this;
if ('name' in req.params && req.user.name !== req.param('name')) {
return this.respond(req, res, 400, {
ok: false,
message: 'Sorry, changing your username isn\'t supported just yet. We\'re on it though!',
step: '6'
});
}
function onComplete(err) {
if (err) {
return next(err);
}
requests -= 1;
if (requests === 0) {
user.email = email;
_this.setUserSession(req, user);
_this.respond(req, res, 200, {
ok: true,
key: '',
avatar: req.session.user.avatar,
message: 'Account updated',
created: false
});
}
}
if (email && email !== user.email) {
requests += 1;
this.models.user.updateEmail(user.name, email, onComplete);
}
if (key) {
requests += 1;
this.models.user.updateKey(user.name, key, onComplete);
}
// Just return if nothing to update.
if (requests === 0) {
requests = 1;
onComplete();
}
},
requestToken: function (req, res, next) {
res.render('request', {
action: req.originalUrl,
csrf: req.session._csrf
});
},
// Generates a token to allow the user to reset their password.
forgotPassword: function (req, res, next) {
// Verify email.
var email = req.param('email', '').trim(),
helpers = this.helpers,
_this = this;
if (!email) {
res.statusCode = 400;
res.json({error: 'Please provide a valid email address'});
return;
}
this.models.user.loadByEmail(email, function (err, user) {
if (err) {
return next(err);
}
if (!user) {
res.statusCode = 404;
res.json({error: 'Unable to find a user for that email'});
return;
}
_this.models.forgotToken.createToken(user.name, function (err, token) {
if (err) {
return next(err);
}
var ctx = {
domain: helpers.set('url host').split(':')[0],
link: helpers.url('reset?token=' + token, true)
};
// Send email.
_this.mailer.forgotPassword(email, ctx, function (err, response) {
var flash = 'An email has been sent to ' + email;
if (err && err instanceof errors.MailerError) {
if (_this.mailer.isEnabled()) {
flash = 'Sorry, an error occurred when sending the reset email';
} else {
flash = 'Unable to send reset, email is not enabled in this version of JSBin';
}
} else if (err) {
return next(err);
}
if (req.ajax) {
res.json({});
} else {
res.flash(req.flash.INFO, flash);
res.redirect(303, helpers.url());
}
});
});
});
},
resetPassword: function (req, res, next) {
// Validate the token.
var token = req.param('token', '').trim(),
_this = this;
if (!token) {
// Nope.
return res.redirect(this.helpers.url());
}
this.models.forgotToken.loadUser(token, function (err, user) {
if (err) {
return next(err);
}
if (!user) {
return res.redirect(_this.helpers.url());
}
// Log the user in.
_this.setUserSession(req, user);
// Clear all their tokens.
_this.models.forgotToken.expireTokensByUser(user.name, function () {});
res.flash(req.flash.INFO, 'You\'re now logged in, you can change your password in the account menu');
res.redirect(303, _this.helpers.url());
});
},
setUserSession: function (req, user) {
req.session.user = {
avatar: utils.gravatar(user.email),
name: user.name,
email: user.email,
lastLogin: user.last_login || new Date()
};
},
// Routes all post requests to /sethome which needs some serious discussion
// as at the moment it uses the presence of params to determine which action
// to call. It is consistent with the PHP app though.
routeSetHome: function (req, res, next) {
var params = utils.extract(req.body, 'name', 'key', 'email'),
_this = this;
this.loadUserFromSession(req, res, function (err) {
if (!err) {
// User was loaded from the session (they're logged in).
_this.updateUser(req, res, next);
} else if (err instanceof errors.NotAuthorized) {
// This is a NotAuthorized error which means there is no session
// so this user is not logged in. Now we can try and load the user
// based on the "name" key.
_this.loadUser(req, res, function (err) {
if (err) {
return next(err);
}
if ('email' in params) {
// There's an email so this is a registration.
_this.createUser(req, res, next);
} else {
// No email, this is a login.
_this.loginUser(req, res, next);
}
});
} else {
return next(err);
}
});
},
respond: function (req, res, status, data) {
if (res.ajax) {
res.json(status, data);
} else {
var type = status && status < 400 ? res.flash.INFO : res.flash.ERROR,
redirect = type === req.flash.INFO ? this.redirectUrl(req) : 'back';
res.flash(type, data.message);
res.redirect(303, redirect);
}
},
redirectUrl: function (req, fallback) {
var url = req.param('_redirect', fallback || 'back');
// We only want to redirect to internal urls.
if (url.indexOf('http') > -1) {
url = 'home';
}
return url;
},
/**
* Github Auth
*/
github: passport.authenticate('github'),
githubPassportCallback: passport.authenticate('github', { failureRedirect: '/' }),
githubCallback: function(req, res, next) {
var githubUser = req.user;
this.models.user.load(req.user.profile.username, function (err, user) {
if (err) return next(err);
// if user but current user not signed in, don't allow them to connect github
// if user and the current user is signed in then link the accounts
// if no user then this is a new user, create them an account
// this.models.user.create({
// name: githubUser.profile.username,
// }, function (err, id) {});
console.log();
console.log();
console.log.apply(console, [].slice.call(arguments));
console.log();
console.log();
console.log(req.session.user);
console.log();
console.log();
res.redirect('/');
}.bind(this));
}
});