controllers/sign.js
var validator = require('validator');
var EventProxy = require('eventproxy');
var config = require('../config');
var User = require('../proxy').User;
var mail = require('../common/mail');
var tools = require('../common/tools');
var cache = require('../common/cache');
var utility = require('utility');
var authMiddleWare = require('../middlewares/auth');
var uuid = require('node-uuid');
var speakeasy = require('speakeasy');
//sign up
exports.showSignup = function (req, res) {
res.render('sign/signin', {page: 'signup'});
};
exports.signup = function (req, res, next) {
var loginname = validator.trim(req.body.loginname).toLowerCase();
var email = validator.trim(req.body.email).toLowerCase();
var pass = validator.trim(req.body.pass);
var rePass = validator.trim(req.body.re_pass);
var captcha = validator.trim(req.body.captcha).toLowerCase();
var ep = new EventProxy();
ep.fail(next);
ep.on('prop_err', function (msg) {
res.status(422);
res.render('sign/signin', {error: msg, loginname: loginname, email: email, page: 'signup'});
});
// 验证信息的正确性
if ([loginname, pass, rePass, email, captcha].some(function (item) {
return item === '';
})) {
ep.emit('prop_err', '信息不完整。');
return;
}
var loginNameLength = getByteLen(loginname);
if (loginNameLength < 5) {
ep.emit('prop_err', '用户名至少需要5个字符。');
return;
}
if (!tools.validateId(loginname)) {
return ep.emit('prop_err', '用户名请用英文字母或数字');
}
if (!validator.isEmail(email)) {
return ep.emit('prop_err', '邮箱不合法。');
}
if (pass !== rePass) {
return ep.emit('prop_err', '两次密码输入不一致。');
}
if (captcha !== req.session.captcha) {
return ep.emit('prop_err', '验证码输入不正确。');
}
// END 验证信息的正确性
User.getUsersByQuery({
'$or': [
{'loginname': loginname},
{'email': email}
]
}, {}, function (err, users) {
if (err) {
return next(err);
}
if (users.length > 0) {
ep.emit('prop_err', '用户名或邮箱已被使用。');
return;
}
tools.bhash(pass, ep.done(function (passhash) {
// create gravatar
var avatarUrl = User.makeGravatar(email);
let isActive = true;
if (config.account_need_active || process.env.NODE_ENV === 'test') {
isActive = false;
}
User.newAndSave(loginname, loginname, passhash, email, avatarUrl, isActive, function (err, user) {
if (err) {
return next(err);
}
let msg = '欢迎加入 ' + config.name + '!';
// 发送激活邮件
if (config.account_need_active || process.env.NODE_ENV === 'test') {
mail.sendActiveMail(email, utility.md5(email + passhash + config.session_secret), loginname);
msg += '我们已给您的注册邮箱发送了一封邮件, 请点击里面的链接来认证您的帐号.';
}
authMiddleWare.gen_session(user, res);
res.render('sign/signin', {
success: msg,
page: 'signup'
});
});
}));
});
};
/**
* Show user login page.
*
* @param {HttpRequest} req
* @param {HttpResponse} res
*/
exports.showLogin = function (req, res) {
req.session._loginReferer = req.headers.referer;
res.render('sign/signin', {page: 'signin'});
};
/**
* define some page when login just jump to the home page
* @type {Array}
*/
var notJump = [
'/active_account', //active page
'/reset_pass', //reset password page, avoid to reset twice
'/signup', //regist page
'/search_pass' //serch pass page
];
/**
* Handle user login.
*
* @param {HttpRequest} req
* @param {HttpResponse} res
* @param {Function} next
*/
exports.login = function (req, res, next) {
var loginname = validator.trim(req.body.name).toLowerCase();
var pass = validator.trim(req.body.pass);
var ep = new EventProxy();
ep.fail(next);
if (!loginname || !pass) {
res.status(422);
return res.render('sign/signin', {error: '信息不完整。', page: 'signin'});
}
var getUser;
if (loginname.indexOf('@') !== -1) {
getUser = User.getUserByMail;
} else {
getUser = User.getUserByLoginName;
}
ep.on('login_error', function (login_error) {
res.status(403);
res.render('sign/signin', {error: '用户名或密码错误', page: 'signin'});
});
getUser(loginname, function (err, user) {
if (err) {
return next(err);
}
if (!user) {
return ep.emit('login_error');
}
var passhash = user.pass;
tools.bcompare(pass, passhash, ep.done(function (bool) {
if (!bool) {
return ep.emit('login_error');
}
if (!user.active) {
// 重新发送激活邮件
mail.sendActiveMail(user.email, utility.md5(user.email + passhash + config.session_secret), user.loginname);
res.status(403);
return res.render('sign/signin', {error: '此帐号还没有被激活,激活链接已发送到 ' + user.email + ' 邮箱,请查收。', page: 'signin'});
}
if (!!user.is_two_factor) {
return res.render('sign/two_factor', user);
}
// store session cookie
authMiddleWare.gen_session(user, res);
//check at some page just jump to home page
var refer = req.session._loginReferer || '/';
for (var i = 0, len = notJump.length; i !== len; ++i) {
if (refer.indexOf(notJump[i]) >= 0) {
refer = '/';
break;
}
}
res.redirect(refer);
}));
});
};
// 双因子验证
exports.two_factor = function (req, res, next) {
req.checkBody({
'name': {
notEmpty: {
options: [true],
errorMessage: '登录名不能为空'
}
},
'tfv': {
notEmpty: {
options: [true],
errorMessage: '验证码不能为空'
}
}
});
req.getValidationResult().then(function(result) {
if (!result.isEmpty()) {
return res.render('sign/two_factor', {error: '双因子验证码[参数]验证失败'});
}
var loginname = req.body.name;
var code = req.body.tfv;
User.getUserByLoginName(loginname, function (err, user) {
if (err) {
return next(err);
}
if (!user) {
return next(new Error('user is not exists'));
}
if (!user.is_two_factor || !user.two_factor_base32) {
return next(new Error('user two factor has some error'));
}
var verified = speakeasy.totp.verify({
secret: user.two_factor_base32,
encoding: 'base32',
token: code
});
if (verified) {
// store session cookie
authMiddleWare.gen_session(user, res);
res.redirect('/');
} else {
return res.render('sign/two_factor', {error: '双因子[验证码]验证失败'});
}
});
});
};
// sign out
exports.signout = function (req, res, next) {
req.session.destroy();
res.clearCookie(config.auth_cookie_name, {path: '/'});
res.redirect('/');
};
exports.activeAccount = function (req, res, next) {
var key = validator.trim(req.query.key);
var name = validator.trim(req.query.name);
User.getUserByLoginName(name, function (err, user) {
if (err) {
return next(err);
}
if (!user) {
return next(new Error('[ACTIVE_ACCOUNT] no such user: ' + name));
}
var passhash = user.pass;
if (!user || utility.md5(user.email + passhash + config.session_secret) !== key) {
return res.render('notify/notify', {error: '信息有误,帐号无法被激活。'});
}
if (user.active) {
return res.render('notify/notify', {error: '帐号已经是激活状态。'});
}
user.active = true;
user.save(function (err) {
if (err) {
return next(err);
}
res.render('notify/notify', {success: '帐号已被激活,请登录'});
});
});
};
exports.showSearchPass = function (req, res) {
res.render('sign/search_pass');
};
exports.updateSearchPass = function (req, res, next) {
var email = validator.trim(req.body.email).toLowerCase();
if (!validator.isEmail(email)) {
return res.render('sign/search_pass', {error: '邮箱不合法', email: email});
}
// 动态生成retrive_key和timestamp到users collection,之后重置密码进行验证
var retrieveKey = uuid.v4();
var retrieveTime = new Date().getTime();
User.getUserByMail(email, function (err, user) {
if (!user) {
res.render('sign/search_pass', {error: '没有这个电子邮箱。', email: email});
return;
}
user.retrieve_key = retrieveKey;
user.retrieve_time = retrieveTime;
user.save(function (err) {
if (err) {
return next(err);
}
// 发送重置密码邮件
mail.sendResetPassMail(email, retrieveKey, user.loginname);
res.render('notify/notify', {success: '我们已给您填写的电子邮箱发送了一封邮件,请在24小时内点击里面的链接来重置密码。'});
});
});
};
/**
* reset password
* 'get' to show the page, 'post' to reset password
* after reset password, retrieve_key&time will be destroy
* @param {http.req} req
* @param {http.res} res
* @param {Function} next
*/
exports.resetPass = function (req, res, next) {
var key = validator.trim(req.query.key || '');
var name = validator.trim(req.query.name || '');
User.getUserByNameAndKey(name, key, function (err, user) {
if (!user) {
res.status(403);
return res.render('notify/notify', {error: '信息有误,密码无法重置。'});
}
var now = new Date().getTime();
var oneDay = 1000 * 60 * 60 * 24;
if (!user.retrieve_time || now - user.retrieve_time > oneDay) {
res.status(403);
return res.render('notify/notify', {error: '该链接已过期,请重新申请。'});
}
return res.render('sign/reset', {name: name, key: key});
});
};
exports.updatePass = function (req, res, next) {
var psw = validator.trim(req.body.psw) || '';
var repsw = validator.trim(req.body.repsw) || '';
var key = validator.trim(req.body.key) || '';
var name = validator.trim(req.body.name) || '';
var ep = new EventProxy();
ep.fail(next);
if (psw !== repsw) {
return res.render('sign/reset', {name: name, key: key, error: '两次密码输入不一致。'});
}
User.getUserByNameAndKey(name, key, ep.done(function (user) {
if (!user) {
return res.render('notify/notify', {error: '错误的激活链接'});
}
tools.bhash(psw, ep.done(function (passhash) {
user.pass = passhash;
user.retrieve_key = null;
user.retrieve_time = null;
user.active = true; // 用户激活
user.save(function (err) {
if (err) {
return next(err);
}
return res.render('notify/notify', {success: '你的密码已重置。'});
});
}));
}));
};
function getByteLen(val) {
var len = 0;
for (var i = 0; i < val.length; i++) {
var a = val.charAt(i);
if (a.match(/[^\x00-\xff]/ig) != null) {
len += 2;
}
else {
len += 1;
}
}
return len;
}