/** * 个人中心---账户安全 * @author gaohongwei <hongwei.gao@yoho.cn> * @date: 2016/8/30 */ 'use strict'; const Promise = require('bluebird'); const co = Promise.coroutine; const helpers = global.yoho.helpers; const logger = global.yoho.logger; const _ = require('lodash'); const crypto = global.yoho.crypto; const AccountApi = require('./account-api'); module.exports = class extends global.yoho.BaseModel { constructor(ctx) { super(ctx); } // 时间转换为时间戳 datetimeToUnix(datetime) { let tmpDatetime = datetime.replace(/:/g, '-'); tmpDatetime = tmpDatetime.replace(/ /g, '-'); let arr = tmpDatetime.split('-'); let now = new Date(Date.UTC(arr[0], arr[1] - 1, arr[2], arr[3] - 8, arr[4], arr[5])); return parseInt(now.getTime() / 1000, 10); } /** * 根据输入的mobile获取area * @param type $mobile * @return int */ handleMobile(mobile) { let res = {}; // 国际号 if (mobile.indexOf('-') > 0) { let areaTmp = mobile.split('-'); res.area = areaTmp[0]; res.mobile = areaTmp[1]; } else { res.area = 86; res.mobile = mobile; } return res; } /** * 获得标题文案 * @param type ischeckMobile * @param type ischeckEmail * @param type checkType */ getTitles(ischeckMobile, ischeckEmail, checkType) { let subTitle, enTitle, pageKey; if (checkType === 'mobile') { subTitle = ischeckMobile ? '修改手机' : '验证手机'; enTitle = ischeckMobile ? 'CHANGE TELEPHONE' : 'VERIFICATION TELEPHONE'; pageKey = 'mobile'; } else if (checkType === 'userpwd') { subTitle = '修改密码'; enTitle = 'CHANGE PASSWORD'; pageKey = 'userpwd'; } else { subTitle = ischeckEmail ? '修改邮箱' : '验证邮箱'; enTitle = ischeckEmail ? 'CHANGE EMAIL' : 'VERIFICATION EMAIL'; pageKey = 'email'; } return { subTitle: subTitle, enTitle: enTitle, pageKey: pageKey }; } /** * 第一部页面form结构-step1 * @param type $data 用户验证相关信息 * @param type $ischeckMobile * @param type $ischeckEmail * @param type $firstCheck * @return string */ getFormDataStep1(data, ischeckMobile, ischeckEmail, firstCheck) { // 都没验证 let formData1 = [ { inputTxt: '请输入登录密码', key: 'password', type: 'password', name: 'password' } ]; let formData2 = [ {// 只验证手机号 inputTxt: '已验证的手机号', isVerify: true, verifyAccount: data.mobile.slice(0, 3) + '****' + data.mobile.slice(7), realAccount: data.mobile } ]; let formData3 = [ {// 只验证邮箱 inputTxt: '已验证邮箱', isVerify: true, verifyAccount: data.email.slice(0, 2) + '****' + data.email.slice(6), realAccount: data.email } ]; let formData, verifyType, checkEmailFlag, checkMobileFlag; // 只验证手机号 if (ischeckMobile && !ischeckEmail) { formData = formData2; verifyType = 3; checkEmailFlag = false; checkMobileFlag = true; } else if (ischeckEmail && !ischeckMobile) { // 只验证邮箱 formData = formData3; verifyType = 2; checkEmailFlag = true; checkMobileFlag = false; } else if (ischeckMobile && ischeckEmail) { // 都验证 formData = (firstCheck === 'mobile') ? formData2 : formData3; verifyType = (firstCheck === 'mobile') ? 3 : 2; checkEmailFlag = (firstCheck === 'mobile') ? false : true; checkMobileFlag = (firstCheck === 'mobile') ? true : false; } else { // 没有验证 formData = formData1; verifyType = 1; checkEmailFlag = false; checkMobileFlag = false; } return { formData: formData, verifyType: verifyType, checkEmailFlag: checkEmailFlag, checkMobileFlag: checkMobileFlag }; } /** * 第二步-formData * @param type $ischeckEmail * @param type $ischeckMobile * @param type $checkType * @return array */ getFormDataStep2(ischeckEmail, ischeckMobile, checkType) { let formData = []; switch (checkType) { case 'userpwd': formData = [ { inputTxt: '输入新密码', key: 'newPwd', type: 'password', name: 'newPwd' }, { inputTxt: '确认新密码', key: 'confirm_password', type: 'password', name: 'confirm_password' } ]; break; case 'email': formData = [ { inputTxt: ischeckEmail ? '新的邮箱' : '我的邮箱', key: 'email', type: 'text', name: 'email' } ]; break; case 'mobile': formData = [ { inputTxt: ischeckMobile ? '输入新的手机号码' : '请输入手机号码', key: 'mobilevalue', type: 'text', name: 'mobile' } ]; break; default: formData = []; break; } return {formData: formData}; } /** * 个人中心-判断身份验证状态 * @param type uid * @param type checkType * @param type step page步骤 * @return type */ auditCheckStatus(uid, checkType, step) { let that = this; return co(function*() { let accountDataModel = new AccountApi(that.ctx); let res = yield accountDataModel.getVerifyInfo(uid), ret = {status: false}; if (res.data) { let data = res.data, ischeckMobile = (data.mobileVerify === 'N') ? false : true, ischeckEmail = (data.emailVerify === 'N') ? false : true; let firstCheck = '', // 优先验证标识 titleInfo = that.getTitles(ischeckMobile, ischeckEmail, checkType), formJson = {}; if (ischeckMobile && ischeckEmail) { firstCheck = (that.datetimeToUnix(data.mobileVerifyTime) <= that.datetimeToUnix(data.emailVerifyTime)) ? 'mobile' : 'email'; } let verifyType = 1, checkEmailFlag = false, checkMobileFlag = false; if (step === 1) { formJson = that.getFormDataStep1(data, ischeckMobile, ischeckEmail, firstCheck); verifyType = formJson.verifyType; checkEmailFlag = formJson.checkEmailFlag; checkMobileFlag = formJson.checkMobileFlag; delete formJson.verifyType; delete formJson.checkEmailFlag; delete formJson.checkMobileFlag; } else if (step === 2) { formJson = that.getFormDataStep2(ischeckEmail, ischeckMobile, checkType); } ret = { status: true, ischeckMobile: ischeckMobile, ischeckEmail: ischeckEmail, email: data.email, mobile: data.mobile, subTitle: titleInfo.subTitle, enTitle: titleInfo.enTitle, pageKey: titleInfo.pageKey, formData: formJson.formData, verifyType: verifyType, checkEmailFlag: checkEmailFlag, checkMobileFlag: checkMobileFlag }; } return ret; })(); } /** * 校验进入第二步 * @param type $checkCode * @param type $uid * @param type MaxTime * @return boolean */ checkCode(ckCode, uid, time) { time = parseInt(`0${time}`, 10) || 86400000; try { // checkCode里空格用+替换 let code = decodeURIComponent(ckCode); let checkStr = crypto.decrypt('yoho9646abcdefgh', code); let checkInfo = checkStr.split('_'), checkUid = checkInfo[0], timeDiff = Date.parse(new Date()) - checkInfo[1]; // 时间差,秒 24h 86400000 if (checkStr.indexOf('completeverify') > 0 && String(checkUid) === String(uid) && timeDiff <= time) { return true; } else { return false; } } catch (e) { // eslint-disable-line logger.error(`account checkCode decrypt error [checkCode]:${ckCode}`); return false; } } /** * 个人中心-账号安全-index * */ getAccountInfo(uid) { let that = this; return co(function*() { let accountDataModel = new AccountApi(that.ctx); let resq = [{ icon: 'ok', type: '登录密码', tip: '互联网帐号存在被盗风险,建议您定期更改密码以保护帐号安全。', red: 'true', url: helpers.urlFormat('/home/account/userpwd'), isValid: true }, { icon: 'warning', type: '邮箱验证', tip: '验证后,可用于找回登录密码。', url: helpers.urlFormat('/home/account/email') }, { icon: 'warning', type: '手机验证', tip: '验证后,可用于找回登录密码。', url: helpers.urlFormat('/home/account/mobile') }]; let verifyResult = yield accountDataModel.getVerifyInfo(uid); if (verifyResult.data) { let verifyData = verifyResult.data; resq[1].icon = verifyData.emailVerify === 'N' ? 'warning' : 'ok'; resq[1].tip = verifyData.emailVerify === 'N' ? '验证后,可用于找回登录密码。' : '您验证的邮箱:' + verifyData.email.slice(0, 2) + '****' + verifyData.email.slice(6); resq[1].isValid = verifyData.emailVerify === 'N' ? false : true; resq[2].icon = verifyData.mobileVerify === 'N' ? 'warning' : 'ok'; resq[2].isValid = verifyData.mobileVerify === 'N' ? false : true; resq[2].tip = verifyData.mobileVerify === 'N' ? '验证后,可用于找回登录密码。' : '您验证的手机:' + verifyData.mobile.slice(0, 3) + '****' + verifyData.mobile.slice(7); } return resq; })(); } /** * 个人中心-修改密码身份验证-page1/2/3 */ userPwd(params) { let that = this; return co(function*() { let step = params.step ? parseInt(params.step, 10) : 1, ckCode = params.checkCode || '', success = params.success || false, progress = 'progress' + step, uid = params.uid; // 第二步验证信息校验 if (step === 2 && ckCode !== '') { let checkFlag = that.checkCode(ckCode, uid); if (!checkFlag) { // res.redirect(helpers.urlFormat('/home/account/userpwd', {step: 1})); return { code: 400, url: '/home/account/userpwd', params: {step: 1} }; } } // 验证信息 let verifyInfo = yield that.auditCheckStatus(uid, 'userpwd', step); if (!verifyInfo.status) { return { meValidatePage: true }; } let data = { subTitle: verifyInfo.subTitle, enTitle: verifyInfo.enTitle, verifyType: verifyInfo.verifyType, // verifyType 1:登录密码验证 2:邮箱验证 3:手机验证 progressCur: progress, progress: [ { progressName: '1.验证身份' }, { progressName: '2.修改登录密码' }, { progressName: '3.完成' } ] }; // form变化1-验证身份 2-修改 3-成功 if (step === 1) { data.progress[0].iscur = true; data.returnInfo = false; data.formInfo = { ischeckEmail: verifyInfo.checkEmailFlag, formData: verifyInfo.formData, mobileCode: verifyInfo.checkMobileFlag }; } else if (step === 2) { data.progress[1].iscur = true; data.returnInfo = false; data.formInfo = { formData: verifyInfo.formData }; } else if (step === 3) { data.progress[2].iscur = true; data.returnInfo = true; data.resClass = success ? 'res-success' : 'res-error'; data.complete = { resInfo: '恭喜你,您已经成功修改了登录密码!' }; } return { userpwd: data, meValidatePage: true }; })(); } /** * 个人中心-邮箱验证身份-page1/2/3 */ userEmail(params) { let that = this; return co(function*() { let step = params.step ? parseInt(params.step, 10) : 1, ckCode = params.checkCode || '', success = params.success || false, progress = 'progress' + step, uid = params.uid; // 第二步验证信息校验 if (step === 2) { let checkFlag = that.checkCode(ckCode, uid); if (!checkFlag) { // res.redirect(helpers.urlFormat('/home/account/email', {step: 1})); return { code: 400, url: '/home/account/email', params: {step: 1} }; } } // 验证信息 let verifyInfo = yield that.auditCheckStatus(uid, 'email', step); if (!verifyInfo.status) { return { meValidatePage: true }; } let data = { subTitle: verifyInfo.subTitle, enTitle: verifyInfo.enTitle, verifyType: verifyInfo.verifyType, // verifyType 1:登录密码验证 2:邮箱验证 3:手机验证 progressCur: progress, progress: [ { progressName: '1.验证身份' }, { progressName: '2.' + verifyInfo.subTitle }, { progressName: '3.完成' } ] }; // form变化1-验证身份 2-修改 3-成功 if (step === 1) { data.progress[0].iscur = true; data.returnInfo = false; data.formInfo = { ischeckEmail: verifyInfo.checkEmailFlag, formData: verifyInfo.formData, mobileCode: verifyInfo.checkMobileFlag }; } else if (step === 2) { data.progress[1].iscur = true; data.returnInfo = false; data.formInfo = { formData: verifyInfo.formData, mobileCode: false }; } else if (step === 3) { data.progress[2].iscur = true; data.returnInfo = true; data.resClass = success === 'true' ? 'res-success' : 'res-error'; data.complete = { resInfo: success === 'true' ? '恭喜您,您已经成功' + verifyInfo.subTitle + '!' : '抱歉,' + verifyInfo.subTitle + '失败' }; } return { email: data, meValidatePage: true }; })(); } /** * 个人中心-手机验证身份-page1/2/3 */ userMobile(params) { let that = this; return co(function*() { let step = params.step ? parseInt(params.step, 10) : 1, ckCode = params.checkCode || '', success = params.success || false, progress = 'progress' + step, uid = params.uid; // 第二步验证信息校验 if (step === 2) { let checkFlag = that.checkCode(ckCode, uid); if (!checkFlag) { // res.redirect(helpers.urlFormat('/home/account/mobile', {step: 1})); return { code: 400, url: '/home/account/mobile', params: {step: 1} }; } } // 验证信息 let verifyInfo = yield that.auditCheckStatus(uid, 'mobile', step); if (!verifyInfo.status) { return { meValidatePage: true }; } let data = { subTitle: verifyInfo.subTitle, enTitle: verifyInfo.enTitle, verifyType: verifyInfo.verifyType, // verifyType 1:登录密码验证 2:邮箱验证 3:手机验证 progressCur: progress, progress: [ { progressName: '1.验证身份' }, { progressName: '2.' + verifyInfo.subTitle }, { progressName: '3.完成' } ] }; // form变化1-验证身份 2-修改 3-成功 if (step === 1) { data.progress[0].iscur = true; data.returnInfo = false; data.formInfo = { ischeckEmail: verifyInfo.checkEmailFlag, formData: verifyInfo.formData, mobileCode: verifyInfo.checkMobileFlag }; } else if (step === 2) { data.progress[1].iscur = true; data.returnInfo = false; data.formInfo = { formData: verifyInfo.formData, mobileCode: true }; } else if (step === 3) { data.progress[2].iscur = true; data.returnInfo = true; data.resClass = success ? 'res-success' : 'res-error'; data.complete = { resInfo: '恭喜你,您已成功' + verifyInfo.subTitle + '!今后您可以使用该手机号+密码进行登录' }; } return { mobile: data, meValidatePage: true }; })(); } /** * 个人中心-邮箱验证身份-邮件发送成功过渡页 */ sendEmailSuccess(params) { let that = this; return co(function*() { let checkType = params.checkType || 'userpwd', uid = params.uid, email = params.email || '', emailDomain = '', type = params.email || 1;// 1:身份验证 2:修改邮箱 // 验证信息 let verifyInfo = yield that.auditCheckStatus(uid, checkType); if (!verifyInfo.status) { return { meValidatePage: true }; } emailDomain = 'http://' + ((email.split[1] === 'gmail.com') ? 'mail.google.com' : 'mail.' + email.split('@')[1]); let data = { subTitle: verifyInfo.subTitle, enTitle: verifyInfo.enTitle, progressCur: (type === 1) ? 'progress1' : 'progress2', progress: [ { progressName: '1.验证身份' }, { progressName: '2.' + verifyInfo.subTitle }, { progressName: '3.完成' } ], returnInfo: true, sendEmail: { emailInfo: email.slice(0, 2) + '****' + email.slice(6), emailUrl: emailDomain } }; if (type === 1) { data.progress[0].iscur = true; } else { data.progress[1].iscur = true; } let resqData = {meValidatePage: true}; resqData.email = data; return resqData; })(); } /** * 点击邮箱验证链接方法--修改验证邮箱 */ mailResult(params) { let that = this; return co(function*() { let code = params.code; let accountDataModel = new AccountApi(that.ctx); let check = yield accountDataModel.checkEmailCode(code); if (check.code === 200) { let data = yield accountDataModel.modifyVerifyEmail(code); if (data.code === 200) { // res.redirect(helpers.urlFormat('/home/account/email', // {step: 3, success: true})); return { code: 400, url: '/home/account/email', params: {step: 3, success: true} }; } } // res.redirect(helpers.urlFormat('/home/account/email', // {step: 3, success: false})); return { code: 400, url: '/home/account/email', params: {step: 3, success: false} }; })(); } /** * 身份验证-登录密码验证Ajax */ verifyPassword(req) { let that = this; return co(function*() { let password = _.trim(req.body.password || ''), uid = req.user.uid, accountDataModel = new AccountApi(that.ctx); let resqData = yield accountDataModel.verifyPwd(uid, password); if (resqData.code === 200) { let ckCode = crypto.encryption('yoho9646abcdefgh', uid + '_' + Date.parse(new Date()) + '_' + password + 'completeverify'); resqData.data = encodeURIComponent(ckCode); } return resqData; })(); } /** * 分-验证密码正确性-ajax */ checkPassword(req) { let that = this; return co(function*() { let password = _.trim(req.body.password || ''), uid = req.user.uid, resqData = {code: 400}, accountDataModel = new AccountApi(that.ctx); resqData = yield accountDataModel.verifyPwd(uid, password); return resqData; })(); } /** * 分-验证图形验证码-ajax */ checkVerifyCode(req) { return co(function*() { let captchaCode = _.trim(req.body.verifyCode || '').toLowerCase(), resqData = {}; if (captchaCode && captchaCode !== req.session.captcha) { resqData.code = 400; resqData.message = '图形验证码不正确'; } else { resqData.code = 200; resqData.message = ''; } return resqData; })(); } /** * 手机身份验证-校验手机号 */ identityMobile(req) { let that = this; return co(function*() { let mobile = req.body.mobile || '', resqData = {code: 400}, uid = req.user.uid, check = false, userId; let mobileInfo = that.handleMobile(mobile), accountDataModel = new AccountApi(that.ctx); let userInfo = yield accountDataModel.getUserInfoByMobile(mobileInfo.area, mobile); userId = 'uid' in userInfo.data ? userInfo.data.uid : 0; if (userId === uid) { check = true; } if (check) { resqData = { code: 200, message: '', data: '' }; } else { resqData = { code: 400, message: '手机号错误', data: '' }; } return resqData; })(); } /** * 向验证手机号发送短信-ajax */ sendMobileMsg(req, uid) { let that = this; return co(function*() { let mobile = req.body.mobile || '', _code = req.body.checkCode, resqData = {code: 400}; let accountDataModel = new AccountApi(that.ctx); // 发送验证码前置数据校验 if (!_code) { let verifyInfo = yield accountDataModel.getVerifyInfo(uid); mobile = _.get(verifyInfo, 'data.mobile', ''); if (!mobile) { return Object.assign(resqData, { message: '数据验证错误' }); } } else if (!that.checkCode(_code, uid)) { return Object.assign(resqData, { message: '数据验证错误' }); } let mobileInfo = that.handleMobile(mobile); resqData = yield accountDataModel.sendMobileMsg(uid, mobileInfo.mobile, mobileInfo.area); return resqData; })(); } /** * 校验短信验证码-ajax */ checkMobileMsg(req, uid) { let that = this; return co(function*() { let mobile = req.body.mobile || '', code = req.body.code || '', _code = req.body.checkCode, resqData = {code: 400}; let accountDataModel = new AccountApi(that.ctx); // 校验验证码前置数据校验 if (!_code) { let verifyInfo = yield accountDataModel.getVerifyInfo(uid); mobile = _.get(verifyInfo, 'data.mobile', ''); if (!mobile) { return Object.assign(resqData, { message: '数据验证错误' }); } } else if (!that.checkCode(_code, uid)) { return Object.assign(resqData, { message: '数据验证错误' }); } if (mobile === '') { Object.assign(resqData, { message: '手机号为空', data: '' }); return resqData; } if (code === '') { Object.assign(resqData, { message: '验证码为空', data: '' }); return resqData; } let mobileInfo = that.handleMobile(mobile); resqData = yield accountDataModel.checkVerifyMsg(mobileInfo.area, mobileInfo.mobile, code); if (resqData.code === 200) { let ckCode = crypto.encryption('yoho9646abcdefgh', uid + '_' + Date.parse(new Date()) + '_' + mobileInfo.mobile + mobileInfo.area + 'completeverify'); Object.assign(resqData, { code: 200, data: encodeURIComponent(ckCode) }); } return resqData; })(); } /** * 身份验证时,发送邮件-ajax */ sendEmail(req) { let that = this; return co(function*() { let uid = req.user.uid, checkType = req.body.checkType || 'userpwd', email = req.body.email || '', resqData = {code: 400}; let accountDataModel = new AccountApi(that.ctx), verifyInfo = yield accountDataModel.getVerifyInfo(uid); email = _.get(verifyInfo, 'data.email', ''); if (!email) { return Object.assign(resqData, { message: '数据验证错误' }); } let ckCode = crypto.encryption('yoho9646abcdefgh', uid + '_' + Date.parse(new Date()) + '_' + email + checkType + 'completeverify'); let callback = 'home/account/' + checkType + '?step=2&checkCode=' + encodeURIComponent(ckCode); // callback拼接于邮箱域名处; resqData = yield accountDataModel.sendVerifyEmailForNext(email, callback); return resqData; })(); } /** * 分-修改邮箱前,校验邮箱-ajax */ checkEmail(req) { let that = this; return co(function*() { let uid = req.user.uid, email = req.body.email || '', resqData = {code: 400}; let accountDataModel = new AccountApi(that.ctx); resqData = yield accountDataModel.checkVerifyEmail(uid, email); return resqData; })(); } /** * 修改密码 */ modifyPwd(req, params) { let that = this; return co(function*() { let uid = params.uid, newPwd = params.newPwd || '', _code = params.checkCode; let accountDataModel = new AccountApi(that.ctx); // 前置数据校验 if (!_code || !that.checkCode(_code, uid, 600000)) { return { code: 400, message: '数据验证错误' }; } let resqData = yield accountDataModel.modifyPwd(uid, newPwd); return resqData; })(); } /** * 修改验证手机号 */ modifyMobile(req, uid) { let that = this; return co(function*() { let mobile = req.body.mobile || '', code = req.body.code || '', _code = req.body.checkCode, resqData = {code: 400}; let accountDataModel = new AccountApi(that.ctx); // 校验验证码前置数据校验 // 校验checkCode,有效时间10分钟(checkCode在调改接口前获取,考虑网络延时,服务器间的时间差,设置10分钟) if (!_code || !that.checkCode(_code, uid, 600000)) { return Object.assign(resqData, { message: '数据验证错误' }); } if (mobile === '') { resqData = { code: 400, message: '手机号为空', data: '' }; return resqData; } if (code === '') { resqData = { code: 400, message: '验证码为空', data: '' }; return resqData; } let mobileInfo = that.handleMobile(mobile); let checkFlag = yield accountDataModel.checkVerifyMobile(uid, mobileInfo.mobile, mobileInfo.area); if (checkFlag.code === 200) { resqData = yield accountDataModel.modifyVerifyMobile(uid, mobileInfo.area, mobileInfo.mobile); } else { resqData = { code: checkFlag.data, message: checkFlag.message, data: '' }; } return resqData; })(); } /** * 分-检查手机号是否可修改-ajax */ checkMobile(params, uid) { let that = this; return co(function*() { let mobile = params.mobile || '', resqData = {code: 400}; let mobileInfo = that.handleMobile(mobile), accountDataModel = new AccountApi(that.ctx); resqData = yield accountDataModel.checkVerifyMobile(uid, mobileInfo.mobile, mobileInfo.area); return resqData; })(); } /** * 修改验证邮箱校验并发送邮件-ajax * */ modifyEmail(req) { let that = this; return co(function*() { let uid = req.user.uid, email = req.body.email || '', _code = req.body.checkCode, resqData = {code: 400}; let accountDataModel = new AccountApi(that.ctx); // 前置数据校验 if (!_code || !that.checkCode(_code, uid, 600000)) { return Object.assign(resqData, { message: '数据验证错误' }); } let check = yield accountDataModel.checkVerifyEmail(uid, email); if (check.code === 200) { resqData = yield accountDataModel.sendVerifyEmail(uid, email); } return resqData; })(); } };