sms.js 11.5 KB
/* eslint no-unused-vars: ["error", { "args": "none" }] */
'use strict';
const _ = require('lodash');
const co = Promise.coroutine;
const moment = require('moment');
const cookie = global.yoho.cookie;
const EventEmitter = require('events');
const utils = require(global.utils);
const RegServiceModel = require('../models/reg-service');
const PhoneServiceModel = require('../models/phone-service');
const AuthHelperModel = require('../models/auth-helper');
const LoginNewModel = require('../models/login-new');

// constrant
const CODE_REQUIRED = '请输入校验码';
const PASSWORD_REQUIRED = '请输入密码';
const PASSWORD_LENGTH_ERROR = '密码6-20位,请重新输入';
const BAD_PASSWORD = '密码格式不正确';
const LOGIN_SUCCSS = '登录成功';
const VERIFY_ERROR = '校验失败';

class SmsLogin {

    smsLoginPage(req, res, next) {
        _.set(req.session, 'smsLogin.step', 1);

        if (req.session.captchaValidCount == null) { // eslint-disable-line
            req.session.captchaValidCount = 5;
        }

        co(function* () {
            let bannerData = yield req.ctx(LoginNewModel).getTopBanner();
            let banner = _.get(bannerData, 'data[0].data[0].src', '');

            // 是否打开账号登录
            let openPassword = !_.get(req.app.locals.wap, 'close.passwordLogin', false);

            res.render('sms/sms-login-new', {
                module: 'passport',
                page: 'sms-login-new',
                width750: true,
                localCss: true,
                banner: banner,
                countrys: req.ctx(RegServiceModel).getAreaData(), // 地区信息列表
                backUrl: 'javascript:history.go(-1)', // eslint-disable-line
                loginUrl: '/passport/login',
                registerUrl: '/passport/reg/index', // 注册的URL链接
                aliLoginUrl: '/passport/login/alipay', // 支付宝快捷登录的URL链接
                weiboLoginUrl: '/passport/login/sina', // 微博登录的URL链接
                qqLoginUrl: '/passport/login/qq', // 腾讯QQ登录的URL链接
                wechatLoginUrl: '/passport/login/wechat', // 微信登录的URL链接
                internationalUrl: '/passport/international', // 国际号登录的URL链接
                phoneRetriveUrl: '/passport/back/mobile', // 通过手机号找回密码的URL链接
                emailRetriveUrl: '/passport/back/email', // 通过邮箱找回密码的URL链接
                isWechat: req.yoho.isWechat,
                openPassword
            });
        })().catch(next);
    }

    /**
     *  step1 的表单提交验证
     */
    indexCheck(req, res, next) {
        _.set(req.session, 'smsLogin.step', 1);

        let area = req.body.area = (req.body.area || '').trim();
        let mobile = req.body.mobile = (req.body.mobile || '').trim();
        let errorData = {
            code: 400,
            message: ''
        };

        let em = new EventEmitter();

        // 校验 成功
        em.on('resolve', () => {
            // 1. 将信息放入 session
            _.set(req.session, 'smsLogin.area', area);
            _.set(req.session, 'smsLogin.mobile', mobile);
            _.set(req.session, 'smsLogin.step', 2);
            req.session.captcha = null;

            req.ctx(PhoneServiceModel).sendSMS(mobile, area, 1);

            res.json({
                code: 200,
                redirect: '/passport/sms_login?step=2'
            });
        });

        // 校验 失败
        em.on('reject', error => {
            _.set(req.session, 'smsLogin.step', 1);


            res.json(error);
        });

        // 验证
        if ([area, mobile].some(val => val === '')) {
            return em.emit('reject', Object.assign(errorData, { message: '请填写手机号' }));
        }

        // congratulation~~
        em.emit('resolve');
    }

    tokenBefore(req, res, next) {

        let step = _.get(req.session, 'smsLogin.step');
        let count = _.get(req.session, 'smsLogin.count');
        let interval = _.get(req.session, 'smsLogin.interval');


        if (!req.xhr || step !== 2) {
            return next();
        }

        let now = Date.now();

        // 重发次数用完了, 回冻结5min
        //      1. 过了冻结期, count 重设为 5次
        //      2. 没过冻结期, end
        // 没有用完, 判断是否请求太频繁
        let during = moment.duration(interval - now, 'ms').minutes();
        let message = `请${during}分钟后再试`;

        if (!count) {
            if (interval > now) {
                return res.json({
                    code: 400,
                    message: message,
                    during: Math.ceil((interval - now) / 1000)
                });
            } else {
                _.set(req.session, 'smsLogin.count', 5);
            }
        } else if (interval > now) {
            return res.json({
                code: 429,
                message: message
            });
        }

        next();
    }



    // AJAX 获取验证码
    token(req, res, next) {
        let area = _.get(req.session, 'smsLogin.area');
        let mobile = _.get(req.session, 'smsLogin.mobile');

        req.ctx(PhoneServiceModel).sendSMS(mobile, area, 1).then(result => {
            if (result.code === 200) {

                _.set(req.session, 'smsLogin.step', 2);
                _.set(req.session, 'smsLogin.area', area);
                _.set(req.session, 'smsLogin.mobile', mobile);

                --req.session.smsLogin.count;

                if (!req.session.smsLogin.count) {
                    _.set(req.session, 'smsLogin.interval', Date.now() + 5 * 60 * 1000);
                } else {
                    _.set(req.session, 'smsLogin.interval', Date.now() + 60 * 1000);
                }

                result.redirect = '/passport/sms_login?step=2';
                res.json(result);
                return;
            }

            res.json(result);
        });
    }

    checkBefore(req, res, next) {
        let code = req.query.code = (req.query.code || '').trim();
        let step = _.get(req.session, 'smsLogin.step');

        if (!req.xhr && step !== 2) {
            return next();
        }

        if (!code) {
            return res.json({
                code: 404,
                message: CODE_REQUIRED
            });
        }

        next();
    }

    // AJAX 校验验证码 in step2
    check(req, res, next) {
        const code = req.query.code;
        const mobile = _.get(req.session, 'smsLogin.mobile', '');
        const area = _.get(req.session, 'smsLogin.area', '');
        const shopping_key = cookie.getShoppingKey(req); // eslint-disable-line
        let from = req.cookies.from || 'yohobuy';

        if (!mobile || !area) {
            res.json({
                code: 401,
                message: VERIFY_ERROR
            });
            return;
        }

        Promise.all([
            req.ctx(PhoneServiceModel).checkUserPhoneExist(mobile, area),
            req.ctx(PhoneServiceModel).verifySMS(mobile, area, code, 1)
        ])
            .then(result => {
                let r1 = result[0] || {};
                let r2 = result[1] || {};
                let redirect;

                // 验证码 校验异常
                if (r2.code !== 200) {
                    res.json(r2);
                    return;
                }

                // 检测 手机号 是否注册 异常
                if (r1.code !== 200) {
                    res.json(r1);
                    return;
                }

                // 校验失败
                if (r2.data.is_pass !== 'Y') {
                    res.json({
                        code: 401,
                        message: '短信验证码错误'
                    });

                    return;
                }

                // 手机号码 没注册
                if (r1.data.is_register !== 'Y') {
                    redirect = '/passport/sms_login?step=3';
                    _.set(req.session, 'smsLogin.step', 3);

                    res.json({
                        code: 200,
                        redirect,
                        newer: true,
                        registerCode: r1.data.code
                    });

                    return;
                }

                // 手机号码已注册 --> 直接登录
                req.ctx(PhoneServiceModel).autoSignin({
                    profile: mobile,
                    code: r2.data.code,
                    area,
                    shopping_key,
                    from
                })
                    .then(info => {
                        if (info.code !== 200) {
                            return Promise.reject(info);
                        }

                        res.cookie('_LOGIN_TYPE', 5, {
                            domain: 'm.yohobuy.com'
                        });
                        return req.ctx(AuthHelperModel).syncUserSession(info.data.uid, req, res, info.data.session_key);
                    })
                    .then((authData) => {
                        res.json({
                            code: 200,
                            message: LOGIN_SUCCSS,
                            redirect: _.get(authData, 'refer') || utils.refererLimit(req.cookies.refer)
                        });

                        delete req.session.smsLogin;
                    })
                    .catch(error => {
                        res.json(error);
                    });

            })
            .catch(next);
    }

    // AJAX 短信登录 设置密码 in step3
    password(req, res, next) {
        let step = _.get(req.session, 'smsLogin.step');

        if (step !== 3) {
            return next();
        }


        let data = {
            code: '400',
            message: BAD_PASSWORD
        };

        let mobile = _.get(req.session, 'smsLogin.mobile');
        let area = _.get(req.session, 'smsLogin.area');
        let password = (req.body.password || '').trim();
        let registerCode = req.body.registerCode || '';
        let clientIp = req.yoho.clientIp || '';
        let from = req.cookies.from || 'yohobuy';

        if (!password) {
            data.message = PASSWORD_REQUIRED;
            return res.json(data);
        }

        if (password.length < 6 || password.length > 20) {
            data.message = PASSWORD_LENGTH_ERROR;
            return res.json(data);
        }

        if (!/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$/.test(password)) {
            return res.json(data);
        }

        // 购物车key
        let shoppingKey = cookie.getShoppingKey(req);


        // 验证注册的标识码是否有效
        let resultCopy = null;

        req.ctx(RegServiceModel).regMobileAes(area, mobile, password,
            shoppingKey, registerCode, '0', clientIp, from).then(result => {
            if (!result.code || result.code !== 200) {
                return res.send(result);
            }
            if (!result.data || !result.data.uid) {
                return res.send(result);
            }
            resultCopy = result;

            return req.ctx(AuthHelperModel).syncUserSession(result.data.uid, req, res, result.data.session_key);
        }).then((authData) => {
            if (!resultCopy) {
                return;
            }
            res.json({
                code: 200,
                message: LOGIN_SUCCSS,
                redirect: _.get(authData, 'refer') || utils.refererLimit(req.cookies.refer)
            });
            delete req.session.smsLogin;
        }).catch(next);
    }
}

module.exports = new SmsLogin();