Authored by 陈峰

Merge branch 'feature/safe-bug' into 'gray'

Feature/safe bug



See merge request !16
... ... @@ -12,13 +12,16 @@ if (config.useOneapm) {
require('oneapm');
}
const _ = require('lodash');
const express = require('express');
const path = require('path');
const uuid = require('uuid');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const favicon = require('serve-favicon');
const session = require('client-sessions');
const CookieSession = require('client-sessions');
const MemcachedSession = require('yoho-express-session');
const memcached = require('connect-memcached');
const MemcachedStore = memcached(MemcachedSession);
const hbs = require('express-handlebars');
const multer = require('connect-multiparty');
const pkg = require('./package.json');
... ... @@ -54,21 +57,71 @@ app.use(bodyParser.urlencoded({extended: true}));
app.use(cookieParser());
app.use(multer());
app.use(session({
requestKey: 'session',
app.use(MemcachedSession({ // eslint-disable-line
proxy: true,
resave: false,
saveUninitialized: true,
unset: 'destroy',
secret: '82dd7e724f2c6870472c89dfa43cf48d',
name: 'yohoblk-session',
cookie: {
domain: 'yohoblk.com',
httpOnly: false
},
store: new MemcachedStore({
hosts: config.memcache.session,
prefix: 'yohoblk-session:',
reconnect: 5000,
timeout: 1000,
retries: 0
})
}));
app.use(CookieSession({ // eslint-disable-line
requestKey: 'session2',
cookieName: 'yohoblk-session',
secret: '82dd7e724f2c6870472c89dfa43cf48d',
domain: config.cookieDomain
cookie: {
domain: config.cookieDomain,
ephemeral: true
}
}));
app.use((req, res, next) => {
if (req.session) {
let sessionKeys = Object.keys(req.session || {});
let backSessionKeys = Object.keys(req.session2.sessionBack || {});
if (backSessionKeys.length > sessionKeys.length) {
let differences = _.difference(backSessionKeys, sessionKeys);
_.forEach(differences, d => {
req.session[d] = req.session2.sessionBack[d];
});
}
req.session2.sessionBack = req.session;
} else {
req.session = new MemcachedSession.Session(req);
req.session.cookie = new MemcachedSession.Cookie({
domain: 'yohoblk.com',
httpOnly: false
});
req.session = _.assign(req.session, req.session2.sessionBack);
}
if (typeof req.session.reset !== 'function') {
req.session.reset = function() {
req.session = null;
req.session2.reset();
};
}
next();
});
app.use((req, res, next) => {
req.user = {}; // 全局的用户数据
req.yoho = {}; // req和res绑定yoho对象,用于传递全局数据, 如req.yoho.channel等
if (!req.session || !req.session.uuid) {
req.session = {
uuid: uuid.v4()
};
if (!req.session) {
req.session = {};
}
next();
});
... ...
... ... @@ -382,10 +382,18 @@ const validate2 = (req, res, next) => {
let uid = req.user.uid;
let body = req.body;
if (!req.session.safeAccount) {
return res.send({
code: 400,
message: '修改失败,请重新验证身份'
});
}
if (type === 'password') {
let a = yield accountModel.changePwd(uid, body.password);
if (a.code === 200) {
req.session.safeAccount = false;
cookieHelper.setVal(res, body.type + '_STEP', 2);
res.send(a);
}
... ... @@ -401,6 +409,7 @@ const validate2 = (req, res, next) => {
cookieHelper.setVal(res, body.type + '_STEP', 2);
if (c.code === 200) {
req.session.safeAccount = false;
res.send({
code: 200,
data: {}
... ...
... ... @@ -82,6 +82,15 @@ const getUserInfoAPI = (req, res, next) => {
const sendCodePage = (req, res, next) => {
let inputInfo = req.inputInfo;
if (req.session.mobile && req.session.mobile !== inputInfo.phone) {
req.session.mobile = '';
req.session.captcha = '';
return res.json({
code: 400,
message: '验证码失效',
data: ''
});
}
BackService.sendCodeToUserAsync(inputInfo.type, inputInfo.phone, inputInfo.area)
.then(result => {
if (!(result.code && result.code === 200)) {
... ... @@ -122,8 +131,8 @@ const sendBackMobileAPI = (req, res, next) => {
let area = req.body.area || '86';
// 检查上次的发送短信号码
if (req.session.sendMobile && req.session.sendMobile !== mobile) {
req.session.sendMobile = '';
if (req.session.mobile && req.session.mobile !== mobile) {
req.session.mobile = '';
req.session.captcha = '';
return res.json({
code: 400,
... ... @@ -135,7 +144,7 @@ const sendBackMobileAPI = (req, res, next) => {
BackService.sendCodeToMobileAsync(area, mobile)
.then(result => {
// 记录发送短信的号码
req.session.sendMobile = mobile;
req.session.mobile = mobile;
res.json(result);
})
.catch(next);
... ...
... ... @@ -218,9 +218,6 @@ const bindCheck = (req, res, next) => {
} else if (result.code === 200 && result.data.is_register === 1) {
return UserService.getUserInfoAsync(area, mobile).then(user => {
// 绑定流程:code=201 已注册 绑定过其他第三方
req.session.thirdBind = {
mobile: mobile
};
return { code: 201, message: result.message, data: { user: user } };
});
} else if (result.code === 200 && result.data.is_register === 3) {
... ... @@ -251,8 +248,20 @@ const sendBindMsg = (req, res, next) => {
let mobile = req.body.mobile;
let area = req.body.area;
if (req.session.autouserinfoMobile && req.session.autouserinfoMobile !== mobile) {
req.session.autouserinfoMobile = '';
req.session.captcha = '';
return res.json({
code: 400,
message: '验证码失效'
});
}
BindService.sendBindMsgAsync(area, mobile).then(result => {
if (result && result.code) {
req.session.autouserinfoMobile = mobile;
req.session.thirdBind = {
mobile: mobile
};
res.json(result);
} else {
res.json({ code: 400, message: '', data: '' });
... ...
... ... @@ -241,7 +241,7 @@ const local = {
message: '格式错误'
});
}
if (req.session.sendMobile && req.session.sendMobile !== mobile) { // ajax中判断
req.session.sendMobile = '';
req.session.captcha = '';
... ... @@ -253,6 +253,7 @@ const local = {
LoginService.sendPasswordBySMS(area, mobile).then((result) => {
if (result && result.code === 200) {
req.session.captcha = '';
req.session.sendMobile = mobile;
}
... ...
... ... @@ -157,8 +157,8 @@ let sendBindMsg = (req, res, next) => {
// let checkNum = yield cache.get(`regCheckMobileNum_${mobile}`);
// 检查上次的发送短信号码
if (req.session.sendMobile && req.session.sendMobile !== mobile) {
req.session.sendMobile = '';
if (req.session.sendMobile_reg && req.session.sendMobile_reg !== mobile) {
req.session.sendMobile_reg = '';
req.session.captcha = '';
data.message = '验证码失效';
return res.json(data);
... ... @@ -180,7 +180,7 @@ let sendBindMsg = (req, res, next) => {
return cache.set(sendCodeKey, sendCodeTimes + 1, 3600).then(() => {
if (result.code) {
// 记录发送短信的号码
req.session.sendMobile = mobile;
req.session.sendMobile_reg = mobile;
return res.json(result);
} else {
data.message = '发送失败';
... ...
... ... @@ -21,10 +21,17 @@ const config = global.yoho.config;
const helpers = global.yoho.helpers;
const cookie = global.yoho.cookie;
const logger = global.yoho.logger;
const cache = global.yoho.cache;
let siteUrl = config.siteUrl.indexOf('//') === 0 ? 'http:' + config.siteUrl : config.siteUrl;
function getLoginStat(ip) {
let errorLoginKey = 'loginErrorIp:' + ip;
return cache.get(errorLoginKey);
}
// 本地登录
passport.use('local', new LocalStrategy({
usernameField: 'account',
... ... @@ -33,47 +40,67 @@ passport.use('local', new LocalStrategy({
}, (req, username, password, done) => {
let area = req.body.areaCode || '86';
let type = req.body.loginType;
let clientIp = req.yoho.clientIp;
let errorLoginKey = 'loginErrorIp:' + clientIp;
if (isNaN(_.parseInt(area)) || _.isEmpty(username) || _.isEmpty(password)) {
logger.info(`【Passport Loginbad params, area:${area} account:${username} password:${password}`);
return done({message: '登录参数错误'}, null);
}
return getLoginStat(clientIp).then((times) => {
let errLoginTimes = _.parseInt(times) || 0;
let verifyCode = req.body.captcha;
let verifyEmail = helpers.verifyEmail(username);
let verifyMobile = area === '86' ? helpers.verifyAreaMobile(area + '-' + username) : true;
if (errLoginTimes > 0 && type !== 'SMSLogin') {
if (!verifyCode || verifyCode !== req.session.captcha) {
return done({message: '验证码不正确或验证码过期', needCaptcha: true}, null);
}
}
if (!verifyEmail && !verifyMobile) {
logger.info(`【Passport Loginbad account, email:${verifyEmail} mobile:${verifyMobile}`);
return done({message: '登录账号格式错误'}, null);
}
let expire = req.cookies['LE' + md5('_LOGIN_EXPIRE')] || '';
if (isNaN(_.parseInt(area)) || _.isEmpty(username) || _.isEmpty(password)) {
logger.info(`【Passport Loginbad params, area:${area} account:${username} password:${password}`);
return done({message: '登录参数错误'}, null);
}
if (expire && expire < (new Date()).getTime() / 1000) {
return done({message: '页面停留时间过长,请刷新页面'}, null);
}
let verifyEmail = helpers.verifyEmail(username);
let verifyMobile = area === '86' ? helpers.verifyAreaMobile(area + '-' + username) : true;
let verifyCode = req.body.captcha;
if (!verifyEmail && !verifyMobile) {
logger.info(`【Passport Loginbad account, email:${verifyEmail} mobile:${verifyMobile}`);
return done({message: '登录账号格式错误'}, null);
}
if (verifyCode && verifyCode !== req.session.captcha) {
return done({message: '验证码不正确或验证码过期', needCaptcha: true}, null);
}
let expire = req.cookies['LE' + md5('_LOGIN_EXPIRE')] || '';
if (expire && expire < (new Date()).getTime() / 1000) {
return done({message: '页面停留时间过长,请刷新页面'}, null);
}
let shoppingKey = cookie.getShoppingKey(req);
return LoginService.signin(type, area, username, password, shoppingKey, req).then((result) => {
if (result.code && result.code === 200 && result.data.uid) {
done(null, result.data);
} else {
done({
message: '请输入正确的账号或密码',
needCaptcha: true
});
if (type !== 'SMSLogin' && verifyCode && verifyCode !== req.session.captcha) {
return done({message: '验证码不正确或验证码过期', needCaptcha: true}, null);
}
let shoppingKey = cookie.getShoppingKey(req);
return LoginService.signin(type, area, username, password, shoppingKey, req).then((result) => {
if (result.code && result.code === 200 && result.data.uid) {
cache.del(errorLoginKey).catch(() => {});
done(null, result.data);
} else {
cache.set(errorLoginKey, errLoginTimes + 1, 3600).catch(() => {});
done({
message: '请输入正确的账号或密码',
needCaptcha: true
});
}
}).catch(e => {
logger.error('call the signin service fail,', e);
done('登录失败,请稍后重试', null);
});
}).catch(e => {
logger.error('call the signin service fail,', e);
done('登录失败,请稍后重试', null);
return done('登录失败,请稍后重试', null);
});
}));
/**
... ...
... ... @@ -65,7 +65,9 @@
"winston-daily-rotate-file": "^1.1.4",
"xml2js": "^0.4.17",
"xss": "^0.2.13",
"yoho-node-lib": "0.2.17"
"connect-memcached": "^0.2.0",
"yoho-express-session": "^2.0.0",
"yoho-node-lib": "0.2.17"
},
"devDependencies": {
"autoprefixer": "^6.3.6",
... ...
... ... @@ -60,6 +60,15 @@ function disableSMSBtn() {
}
}
// 刷新图形验证码
function refreshImgCaptcha() {
var time = new Date(),
$captchaImg = $('.img-captcha'),
captchaImgSrc = $captchaImg.attr('src').split('?')[0];
$captchaImg.attr('src', captchaImgSrc + '?t=' + time.getTime());
}
// 发送短信验证码
function sendSMSCaptcha() {
return $.ajax({
... ... @@ -69,18 +78,15 @@ function sendSMSCaptcha() {
mobile: $phoneNumInput.val(),
area: $regionCodeText.text().replace('+', '')
}
}).then(function(ret) {
if (ret && ret.code === 400) {
errTip($imgCaptchaInput, ret.message);
refreshImgCaptcha();
ep.emit('img-captcha', false);
}
});
}
// 刷新图形验证码
function refreshImgCaptcha() {
var time = new Date(),
$captchaImg = $('.img-captcha'),
captchaImgSrc = $captchaImg.attr('src').split('?')[0];
$captchaImg.attr('src', captchaImgSrc + '?t=' + time.getTime());
}
// 异步验证图形码
function validateImgCaptchaAsync() {
return $.ajax({
... ... @@ -268,10 +274,17 @@ $smsCaptchaCtrl.on('click', function() {
return;
}
$smsCaptchaCtrl.addClass('disable');
disableSMSBtn();
sendSMSCaptcha();
validateImgCaptchaAsync().then(function(result) {
if (result.code === 200) {
$smsCaptchaCtrl.addClass('disable');
disableSMSBtn();
sendSMSCaptcha();
} else {
ep.emit('img-captcha', false);
errTip($imgCaptchaInput, '图形验证码错误');
refreshImgCaptcha();
}
});
});
// 统一设置用户信息
... ...
... ... @@ -735,7 +735,8 @@ function loginAsync() {
// 全部的本地验证-- 短信验证登录
function smsLoginAsync() {
return validateAccount()
.then(validateCaptchaImg)
// .then(validateCaptchaImg)
.then(validateCaptchaSms)
.then(function() {
return login();
... ...