login.js 10.5 KB
/**
 * 登录
 * @author: Bi Kai<kai.bi@yoho.cn>
 * @date: 2016/05/09
 */
'use strict';

const _ = require('lodash');
const passport = require('passport');
const uuid = require('uuid');
const md5 = require('md5');
const Promise = require('bluebird');
const co = Promise.coroutine;

const cookie = global.yoho.cookie;
const helpers = global.yoho.helpers;
const log = global.yoho.logger;
const config = global.yoho.config;
const cache = global.yoho.cache;
const LoginService = require('../models/login-service');
const PassportHelper = require('../models/passport-helper');
const safeRedirect = require('../../../doraemon/middleware/safe-redirect').safeRedirect;

const loginPageURL = `${config.siteUrl}/passport/login`;
const BlockRedirectFilter = /sign|login|passport/;

/**
 * 第三方登录回调
 */
const _doPassportCallback = (req, res, user) => {
    let shoppingKey = cookie.getShoppingKey(req);
    let refer = req.cookies.refer || config.siteUrl;

    refer = !BlockRedirectFilter.test(decodeURI(refer)) ? decodeURI(refer) : config.siteUrl;

    if (user.openId) {
        let signinByOpenID = LoginService.signinByOpenIDAsync(
            _.trim(user.nickname), _.trim(user.openId),
            _.trim(user.sourceType), _.trim(shoppingKey),
            _.trim(user.unionId)
        );

        return signinByOpenID.then((result) => {
            if (result.code !== 200) {
                return Promise.reject(result);
            }

            if (result.data['is_bind'] && result.data['is_bind'] === 'N') { //eslint-disable-line
                return helpers.urlFormat('/passport/thirdlogin/index', {
                    openId: user.unionId || user.openId,
                    sourceType: user.sourceType,
                    refer: refer
                });
            } else if (result.code === 200 && result.data.uid) {
                return LoginService.syncUserSession(result.data.uid, req, res).then(() => {
                    return refer;
                });
            }
        }).then((redirectTo) => {
            return res.redirect(redirectTo);
        });
    } else {
        return Promise.resolve(res.redirect(loginPageURL));
    }
};

const common = {
    /**
     * 获得跳转前的链接
     */
    beforeLogin: (req, res, next) => {
        let refer = req.query.refer;

        if (!refer) {
            refer = req.get('Referer');
        }
        refer && res.cookie('refer', encodeURI(refer), {
            domain: config.cookieDomain,
            httpOnly: true
        });
        next();
    },

    /**
     * 登录时,大于3次,需要图形验证码
     */
    needCaptcha: (req, res, next) => {
        let account = req.query.account;
        let result = {
            code: 400,
            message: '',
            data: ''
        };
        const MAX_ALLOW_ERROR_LOGIN = 3;

        if (account) {
            let errorLoginKey = 'account_errorlogin_' + account;

            cache.get(errorLoginKey).then(errloginTimes => {
                errloginTimes = parseInt(errloginTimes, 0) || 0;
                if (!isNaN(errloginTimes) && errloginTimes >= MAX_ALLOW_ERROR_LOGIN) {
                    result.data = {
                        needCaptcha: true
                    };
                }
                res.json(result);
            }).catch(next);
        } else {
            res.json(result);
        }
    }
};

/**
 * 本地登录
 */
const local = {
    loginPage: (req, res) => {
        // 设置登录有效时间30分钟, 防机器刷,cache不稳定,改为cookie
        res.cookie('LE' + md5('_LOGIN_EXPIRE'), (new Date()).getTime() / 1000 + 1800, {
            domain: config.cookieDomain,
            httpOnly: true
        });

        let loginMobile = _.trim(req.query.bindMobile || '');
        let loginCountryCode = '+' + _.trim(req.query.bindArea || '86');
        let countries = PassportHelper.getCountry();
        let defaultCountryName = '';

        if (loginCountryCode) {
            let area = countries.find((a) => {
                return a.areaCode === loginCountryCode;
            });

            defaultCountryName = area ? area.name : '';
        }

        res.display('login', {
            loginPage: true,
            defaultHeader: false,
            passport: {
                countryCode: loginCountryCode,
                countryName: {
                    text: defaultCountryName
                },
                country: {
                    list: countries
                },
                forgetPwd: helpers.urlFormat('/passport/back/index'),
                fastReg: helpers.urlFormat('/passport/reg'),
                weixinLogin: helpers.urlFormat('/passport/autosign/wechat'),
                qqLogin: helpers.urlFormat('/passport/autosign/qq'),
                weiboLogin: helpers.urlFormat('/passport/autosign/sina'),
                alipayLogin: helpers.urlFormat('/passport/autosign/alipay'),
                bindMobile: loginMobile
            },
            module: 'passport',
            page: 'login',
            title: '用户登录'
        });
    },
    login: (req, res, next) => {
        passport.authenticate('local', (err, user) => {
            if (err) {
                res.json({
                    code: 400,
                    message: err.message,
                    data: {
                        needCaptcha: err.needCaptcha
                    }
                });
            } else {
                // 同步用户数据
                co(function*() {
                    let isRemember = req.body.isRemember === 'true';
                    let refer = req.cookies.refer;

                    if (isRemember) {
                        yield LoginService.rememberAccountAsync({
                            area: req.body.areaCode || '86',
                            account: req.body.account,
                            password: req.body.password
                        }, req, res);
                    }

                    refer = !BlockRedirectFilter.test(decodeURI(refer)) ? decodeURI(refer) : config.siteUrl;

                    yield LoginService.syncUserSession(user.uid, req, res).then(() => {
                        res.json({
                            code: 200,
                            data: {
                                refer: safeRedirect(refer)
                            }
                        });
                    });
                })().catch(next);
            }
        })(req, res, next);
    },
    logout: (req, res) => {
        req.session.destroy();

        const clearAll = (v, k) => {
            res.clearCookie(k, {
                domain: config.cookieDomain
            });
        };

        _.forOwn(req.cookies, clearAll);

        res.redirect(config.siteUrl);
    }
};

/**
 * 微信登录
 */
const wechat = {
    login: (req, res, next) => {
        req.session = req.session || {};
        req.session.authState = uuid.v4();
        return passport.authenticate('wechat', {
            state: req.session.authState
        })(req, res, next);
    },
    callback: (req, res, next) => {
        if (req.session && req.session.authState && req.session.authState === req.query.state) {
            passport.authenticate('wechat', (err, user) => {
                if (err) {
                    log.error(`wechat authenticate error : ${JSON.stringify(err)}`);
                    return res.redirect(loginPageURL);
                } else {
                    _doPassportCallback(req, res, {
                        openId: user._json.openid,
                        unionId: user._json.unionid || user.id,
                        nickname: user._json.nickname || user.displayName,
                        sourceType: 'wechat',
                        rawUser: user
                    }).catch(next);
                }
            })(req, res, next);
        } else {
            return next(new Error(`session:${req.session.authState},query:${req.query.state}`));
        }
    }
};

/**
 * 新浪登录
 */
const sina = {
    login: (req, res, next) => {
        req.session = req.session || {};
        req.session.authState = uuid.v4();
        return passport.authenticate('sina', {
            state: req.session.authState
        })(req, res, next);
    },
    callback: (req, res, next) => {
        if (req.session && req.session.authState && req.session.authState === req.query.state) {
            passport.authenticate('sina', (err, user) => {
                if (err) {
                    log.error(`sina authenticate error : ${JSON.stringify(err)}`);
                    return res.redirect(loginPageURL);
                }
                let nickname = user.screen_name;
                let openId = user.id;

                _doPassportCallback(req, res, {
                    openId: openId,
                    nickname: nickname,
                    sourceType: 'sina'
                }).catch(next);
            })(req, res, next);
        } else {
            return next(new Error('Auth State Mismatch'));
        }
    }
};

/**
 * QQ登录
 */
const qq = {
    login: (req, res) => {
        let authState = req.session.authState = uuid.v4();

        return res.redirect(`//www.yohobuy.com/passport/autosign/qq?type=yohoblk&state=${authState}`);
    },
    callback: (req, res, next) => {
        if (req.session && req.session.authState && req.session.authState === req.query.state) {
            if (req.query.err) {
                log.error(`qq authenticate error : ${JSON.stringify(req.query.err)}`);
                return res.redirect(loginPageURL);
            }

            _doPassportCallback(req, res, {
                openId: req.query.openid,
                nickname: req.query.nickname,
                sourceType: 'qq'
            }).catch(next);
        } else {
            return next(new Error('Auth State Mismatch'));
        }
    }
};

/**
 * 支付宝登录
 */
const alipay = {
    login: (req, res, next) => {
        return passport.authenticate('alipay')(req, res, next);
    },
    callback: (req, res, next) => {
        passport.authenticate('alipay', (err, user) => {
            if (err) {
                log.error(`alipay authenticate error : ${JSON.stringify(err)}`);
                return res.redirect(loginPageURL);
            }

            let nickname = user.realName;
            let openId = user.userId;

            _doPassportCallback(req, res, {
                openId: openId,
                nickname: nickname,
                sourceType: 'alipay'
            }).catch(next);
        })(req, res, next);
    }
};

module.exports = {
    common,
    local,
    wechat,
    qq,
    sina,
    alipay
};