Authored by 陈轩

注册 安全

... ... @@ -15,7 +15,47 @@ const RegService = require('../models/reg-service');
const AuthHelper = require('../models/auth-helper');
const captchaService = require('../models/captcha-service');
/*
session 结构
phoneReg: {
step //当前步骤
captcha // step1 的校验码
count: 5 // 默认可以重发5次, 当count: 0, 冻结30min,之后解冻
expire // 解冻时间
}
*/
/**
* 步骤校验
* step: 预期步骤
*/
let guardStep = function(step) {
return (req, res, next) => {
let curStep = _.get(req.session, 'phoneReg.step');
if (curStep !== step) {
if (req.xhr) {
return res.json({
code: 400,
refer: '/reg.html'
});
} else {
return res.redirect('/reg.html');
}
}
return next();
};
};
/**
* Step1: 输入手机号码 + 验证码
*/
let index = (req, res) => {
if (req.user.uid) {
return res.redirect(req.get('refer') || '/');
}
// 设置注册有效时间30分钟, 防机器刷
// req.session.REG_EXPIRE = Date.now() + 1800000;
let refer = req.query.refer;
... ... @@ -24,6 +64,13 @@ let index = (req, res) => {
domain: 'yohobuy.com'
});
// session init
_.set(req.session, 'phoneReg.step', 1);
if (req.session.phoneReg.count == null) { // eslint-disable-line
req.session.phoneReg.count = 5;
}
res.render('reg/index', {
module: 'passport',
page: 'reg',
... ... @@ -36,6 +83,8 @@ let index = (req, res) => {
countrys: RegService.getAreaData() // 地区信息列表
});
};
let verifyMobile = (req, res, next) => {
let data = {
code: 400,
... ... @@ -45,14 +94,13 @@ let verifyMobile = (req, res, next) => {
let mobile = +req.body.phoneNum;
let area = +(req.body.areaCode || 86);
let captcha = +(req.body.captcha);
let captcha = (req.body.captcha || '').trim();
if (captcha !== req.session.regCaptch) {
if (captcha !== _.get(req.session, 'phoneReg.captcha')) {
return res.json({
code: 111,
message: '校验码不正确'
code: 400,
message: '校验码不正确',
refreshCaptcha: true
});
}
... ... @@ -70,6 +118,7 @@ let verifyMobile = (req, res, next) => {
// return res.json(data);
// }
// 向手机发送注册验证码
RegService.sendCodeToMobile(area, mobile).then((result) => {
if (!result.code) {
... ... @@ -80,6 +129,8 @@ let verifyMobile = (req, res, next) => {
if (result.code === 200) {
let token = sign.makeToken(mobile);
_.set(req.session, 'phoneReg.step', 2); // go step 2
result.data = helpers.urlFormat('/passport/reg/code', {
token: token,
phoneNum: mobile,
... ... @@ -90,6 +141,11 @@ let verifyMobile = (req, res, next) => {
return res.json(result);
}).catch(next);
};
/**
* Step2: 校验 手机验证码
*/
let codeAction = (req, res, next) => {
let token = req.query.token;
let mobile = +req.query.phoneNum;
... ... @@ -115,6 +171,36 @@ let codeAction = (req, res, next) => {
serviceUrl: 'http://chat8.live800.com/live800/chatClient/chatbox.jsp?companyID=620092&configID=149091&jid=8732423409&info=' // 在线客服
});
};
let sendCodeBusyBoy = (req, res, next) => {
let count = _.get(req.session, 'phoneReg.count');
let expire = _.get(req.session, 'phoneReg.expire');
if (count) {
return next();
} else {
/*
如果 count === 0
1. 没过解冻期
2. 过了解冻期, count reset to 5
*/
let now = Date.now();
if (now > expire) {
_.set(req.session, 'phoneReg.count', 5);
return next();
} else {
return res.json({
code: 400,
message: '错误次数太多, 5分钟稍后再试'
});
}
}
};
let sendCode = (req, res, next) => {
let data = {
code: 400,
... ... @@ -141,9 +227,23 @@ let sendCode = (req, res, next) => {
// 向手机发送注册验证码
RegService.sendCodeToMobile(area, mobile).then((result) => {
return result.code ? res.json(result) : res.json(data);
let code = _.get(result, 'code');
if (code) {
--req.session.phoneReg.count;
// count is 0, will freeze;
if (!req.session.phoneReg.count) {
_.set(req.session, 'phoneReg.expire', Date.now() + 5 * 60 * 1000);
}
return res.json(result);
} else {
return res.json(data);
}
}).catch(next);
};
let verifyCode = (req, res, next) => {
let data = {
code: 400,
... ... @@ -175,22 +275,31 @@ let verifyCode = (req, res, next) => {
return res.json(data);
}
// 返回跳转到设置密码的链接
if (result.code === 200) {
let resultCode = _.get(result, 'code');
let token = sign.makeToken(mobile);
switch (resultCode) {
case 200:
_.set(req.session, 'phoneReg.step', 3); // go step 3
result.data = helpers.urlFormat('/passport/reg/password', {
token: token,
phoneNum: mobile,
areaCode: area
});
} else if (result.code === 404) {
result.message = '验证码错误'; // 统一验证提示
break;
case 404:
default:
result = data;
}
return res.json(result);
}).catch(next);
};
/**
* Step3: set Password
*/
let passwordAction = (req, res, next) => {
let token = req.query.token;
let mobile = +req.query.phoneNum;
... ... @@ -215,6 +324,7 @@ let passwordAction = (req, res, next) => {
token: token // 访问令牌
});
};
let setPassword = (req, res, next) => {
let data = {
code: 400,
... ... @@ -270,6 +380,8 @@ let setPassword = (req, res, next) => {
refer = '/home';
}
delete req.session.phoneNum;
return res.json({
code: 200,
message: '注册成功',
... ... @@ -287,7 +399,7 @@ let setPassword = (req, res, next) => {
const genCaptcha = (req, res) => {
let captcha = captchaService.generateCaptcha(90, 52, 4);
req.session.regCaptch = captcha.token;
_.set(req.session, 'phoneReg.captcha', captcha.text);
res.type('png')
.set('Cache-Control', 'no-cache')
... ... @@ -296,6 +408,8 @@ const genCaptcha = (req, res) => {
};
module.exports = {
guardStep,
sendCodeBusyBoy,
index,
verifyMobile,
code: codeAction,
... ...
... ... @@ -86,11 +86,11 @@ router.post('/passport/bind/changeMobile', bind.changeMobile);
router.get('/passport/reg/index', reg.index);
router.get('/passport/reg/captcha.png', reg.genCaptcha);
router.post('/passport/reg/verifymobile', reg.verifyMobile);
router.get('/passport/reg/code', reg.code);
router.post('/passport/reg/sendcode', reg.sendCode);
router.post('/passport/reg/verifycode', reg.verifyCode);
router.get('/passport/reg/password', reg.password);
router.post('/passport/reg/setpassword', reg.setPassword);
router.get('/passport/reg/code', reg.guardStep(2), reg.code);
router.post('/passport/reg/sendcode', reg.guardStep(2), reg.sendCodeBusyBoy, reg.sendCode);
router.post('/passport/reg/verifycode', reg.guardStep(2), reg.verifyCode);
router.get('/passport/reg/password', reg.guardStep(3), reg.password);
router.post('/passport/reg/setpassword', reg.guardStep(3), reg.setPassword);
/**
* 密码找回
... ...
... ... @@ -49,6 +49,7 @@ function checkEnableNext() {
* 刷新 校验码
*/
function refreshCaptcha() {
$captcha.val('').focus();
$captchaPNG.attr('src', ['//m.yohobuy.com/passport/reg/captcha.png', '?t=', Date.now()].join(''));
}
... ... @@ -101,6 +102,8 @@ $btnNext.on('touchstart', function() {
if (data.code === 200) {
location.href = data.data;
} else {
data.refreshCaptcha && refreshCaptcha();
showErrTip(data.message);
requested = false;
}
... ...
... ... @@ -3,12 +3,11 @@
结构:
div.passport-captch
input
div
div.passport-captcha-img
img
button
emmet:
div.passport-captch>input+div>img+button
div.passport-captcha>input+div.passport-captcha-img>img
*/
... ...