Authored by ccbikai

Merge branch 'feature/wechat-auth' into release/sale

@@ -132,8 +132,7 @@ Session.vim @@ -132,8 +132,7 @@ Session.vim
132 *~ 132 *~
133 # auto-generated tag files 133 # auto-generated tag files
134 tags 134 tags
135 -  
136 - 135 +
137 ### VS Code ### 136 ### VS Code ###
138 .vscode/ 137 .vscode/
139 138
@@ -39,7 +39,9 @@ app.set('view engine', '.hbs'); @@ -39,7 +39,9 @@ app.set('view engine', '.hbs');
39 app.use(favicon(path.join(__dirname, '/public/favicon.ico'))); 39 app.use(favicon(path.join(__dirname, '/public/favicon.ico')));
40 app.use(express.static(path.join(__dirname, 'public'))); 40 app.use(express.static(path.join(__dirname, 'public')));
41 app.use(bodyParser.json()); 41 app.use(bodyParser.json());
42 -app.use(bodyParser.urlencoded({extended: false})); 42 +app.use(bodyParser.urlencoded({
  43 + extended: false
  44 +}));
43 app.use(cookieParser()); 45 app.use(cookieParser());
44 app.use(session({ 46 app.use(session({
45 proxy: true, 47 proxy: true,
@@ -56,8 +58,8 @@ app.use(session({ @@ -56,8 +58,8 @@ app.use(session({
56 }, 58 },
57 store: new MemcachedStore({ 59 store: new MemcachedStore({
58 hosts: config.memcache.session, 60 hosts: config.memcache.session,
59 - prefix: 'qinsessionsession:', // 兼容 PHP SESSION  
60 - key: 'yohobuy_session' // 兼容 PHP SESSION 61 + prefix: 'qinsessionsession:', // 兼容 PHP SESSION
  62 + key: 'yohobuy_session' // 兼容 PHP SESSION
61 }) 63 })
62 })); 64 }));
63 65
  1 +/**
  2 + * passport 验证策略注册
  3 + * @author: jiangfeng<jeff.jiang@yoho.cn>
  4 + * @date: 2016/5/31
  5 + */
  6 +
  7 +'use strict';
  8 +const passport = require('passport');
  9 +const WeixinStrategy = require('passport-weixin-plus');
  10 +
  11 +const config = require('../../config/common');
  12 +
  13 +let siteUrl = config.siteUrl.indexOf('//') === 0 ? 'http:' + config.siteUrl : config.siteUrl;
  14 +
  15 +/**
  16 + * wechat登录
  17 + */
  18 +passport.use(new WeixinStrategy({
  19 + authorizationURL: 'https://open.weixin.qq.com/connect/oauth2/authorize',
  20 + tokenURL: 'https://api.weixin.qq.com/sns/oauth2/access_token',
  21 + clientID: config.thirdLogin.wechat.appID,
  22 + clientSecret: config.thirdLogin.wechat.appSecret,
  23 + callbackURL: `${siteUrl}/passport/login/wechat/callback`,
  24 + requireState: false,
  25 + scope: 'snsapi_userinfo'
  26 +}, (accessToken, refreshToken, profile, done) => {
  27 + done(null, profile);
  28 +}));
  1 +/**
  2 + * 登录
  3 + * @author: Bi Kai<kai.bi@yoho.cn>
  4 + * @date: 2016/05/09
  5 + */
  6 +'use strict';
  7 +
  8 +const library = '../../../library';
  9 +const passport = require('passport');
  10 +const cookie = require(`${library}/cookie`);
  11 +const helpers = require(`${library}/helpers`);
  12 +const log = require(`${library}/logger`);
  13 +const config = require('../../../config/common');
  14 +const AuthHelper = require('../models/auth-helper');
  15 +
  16 +const loginPage = `${config.siteUrl}/passport/login/index`;
  17 +
  18 +function doPassportCallback(openId, nickname, sourceType, req, res) {
  19 + let shoppingKey = cookie.getShoppingKey(req);
  20 + let refer = req.cookies.refer;
  21 +
  22 + if (refer) {
  23 + refer = decodeURI(req.cookies.refer);
  24 + } else {
  25 + refer = `${config.siteUrl}/home`;
  26 + }
  27 +
  28 + if (openId && nickname) {
  29 + AuthHelper.signinByOpenID(nickname, openId, sourceType, shoppingKey).then((result) => {
  30 + if (result.data['is_bind'] && result.data['is_bind'] === 'N') { //eslint-disable-line
  31 + return helpers.urlFormat('/passport/bind/index', {
  32 + openId: openId,
  33 + sourceType: sourceType,
  34 + refer: refer
  35 + });
  36 + } else if (result.code === 200 && result.data.uid) {
  37 + return AuthHelper.syncUserSession(result.data.uid, req, res).then(() => {
  38 + return refer;
  39 + });
  40 + }
  41 + }).then((redirectTo) => {
  42 + console.log('redirectTo=', redirectTo);
  43 + return res.redirect(redirectTo);
  44 + }).catch((e) => {
  45 + log.error('频道页面渲染错误:' + JSON.stringify(e));
  46 + return res.send('error');
  47 + });
  48 + }
  49 +}
  50 +
  51 +const wechat = {
  52 + beforeLogin: (req, res, next) => {
  53 + let refer = req.query.refer;
  54 +
  55 + if (!refer) {
  56 + refer = req.get('Referer');
  57 + }
  58 + refer && res.cookie('refer', encodeURI(refer));
  59 + next();
  60 + },
  61 + login: (req, res, next) => {
  62 + return passport.authenticate('weixin')(req, res, next);
  63 + },
  64 + callback: (req, res, next) => {
  65 + passport.authenticate('weixin', (err, user) => {
  66 + if (err) {
  67 + log.error(`wechat authenticate error : ${JSON.stringify(err)}`);
  68 + return res.redirect(loginPage);
  69 + }
  70 + let nickname = user.displayName || user._json.nickname;
  71 + let openId = user.id || user._json.unionid;
  72 +
  73 + doPassportCallback(openId, nickname, 'wechat', req, res);
  74 + })(req, res, next);
  75 + }
  76 +};
  77 +
  78 +exports.wechat = wechat;
  1 +/**
  2 + * sub app channel
  3 + * @author: Bi Kai<kai.bi@yoho.cn>
  4 + * @date: 2016/05/09
  5 + */
  6 +'use strict';
  7 +var express = require('express'),
  8 + path = require('path'),
  9 + hbs = require('express-handlebars');
  10 +
  11 +var passport = require('passport');
  12 +
  13 +var app = express();
  14 +
  15 +// set view engin
  16 +var doraemon = path.join(__dirname, '../../doraemon/views'); // parent view root
  17 +
  18 +app.on('mount', function(parent) {
  19 + delete parent.locals.settings; // 不继承父 App 的设置
  20 + Object.assign(app.locals, parent.locals);
  21 +});
  22 +
  23 +app.set('views', path.join(__dirname, 'views/action'));
  24 +app.engine('.hbs', hbs({
  25 + extname: '.hbs',
  26 + defaultLayout: 'layout',
  27 + layoutsDir: doraemon,
  28 + partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],
  29 + helpers: require(`${global.library}/helpers`)
  30 +}));
  31 +
  32 +
  33 +require('./auth');
  34 +app.use(passport.initialize());
  35 +app.use(passport.session());
  36 +
  37 +// router
  38 +app.use(require('./router'));
  39 +
  40 +module.exports = app;
  1 +'use strict';
  2 +
  3 +const library = '../../../library';
  4 +const API = require(`${library}/api`).API;
  5 +const sign = require(`${library}/sign`);
  6 +const api = new API();
  7 +
  8 +class Auth {
  9 +
  10 + static signinByOpenID(nickname, openId, sourceType, shoppingKey) {
  11 + let param = {
  12 + nickname: nickname,
  13 + openId: openId,
  14 + source_type: sourceType, // esline-disable-line
  15 + method: 'app.passport.signinByOpenID',
  16 + shoppingKey: shoppingKey
  17 + };
  18 +
  19 + if (shoppingKey) {
  20 + param.shopping_key = shoppingKey;
  21 + }
  22 +
  23 + return api.get('', sign.apiSign(param));
  24 + }
  25 +
  26 + static profile(uid) {
  27 + let param = {
  28 + uid: uid,
  29 + method: 'app.passport.profile'
  30 + };
  31 +
  32 + return api.get('', sign.apiSign(param));
  33 + }
  34 +
  35 + static syncUserSession(uid, req, res) {
  36 + return Auth.profile(uid).then((userInfo) => {
  37 + let token = sign.makeToken(uid);
  38 + let data = userInfo.data;
  39 +
  40 + if (data) {
  41 + let uidCookie = `${data.profile_name}::${data.uid}::${data.vip_info.title}::${token}`;
  42 +
  43 + res.cookie('_UID', uidCookie, {
  44 + domain: 'yohobuy.com'
  45 + });
  46 + }
  47 + req.session._TOKEN = token; // esline-disable-line
  48 + req.session._LOGIN_UID = uid; // esline-disable-line
  49 + res.cookie('token', token, {
  50 + domain: 'yohobuy.com'
  51 + }); // esline-disable-line
  52 + }).catch(console.log);
  53 + }
  54 +}
  55 +
  56 +module.exports = Auth;
  1 +/**
  2 + * router of sub app channel
  3 + * @author: Bi Kai<kai.bi@yoho.cn>
  4 + * @date: 2016/05/09
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +const express = require('express');
  10 +const cRoot = './controllers';
  11 +const login = require(cRoot + '/login');
  12 +
  13 +const router = express.Router(); // eslint-disable-line
  14 +
  15 +router.get('/login/wechat', login.wechat.beforeLogin, login.wechat.login); // 登录
  16 +router.get('/login/wechat/callback', login.wechat.callback);
  17 +
  18 +module.exports = router;
@@ -4,30 +4,30 @@ @@ -4,30 +4,30 @@
4 * @date: 2016/05/06 4 * @date: 2016/05/06
5 */ 5 */
6 6
7 - var express = require('express'),  
8 - path = require('path'),  
9 - hbs = require('express-handlebars'); 7 +var express = require('express'),
  8 + path = require('path'),
  9 + hbs = require('express-handlebars');
10 10
11 - var app = express(); 11 +var app = express();
12 12
13 - // set view engin  
14 - var doraemon = path.join(__dirname, '../../doraemon/views'); // parent view root 13 +// set view engin
  14 +var doraemon = path.join(__dirname, '../../doraemon/views'); // parent view root
15 15
16 - app.on('mount', function(parent) {  
17 - delete parent.locals.settings; // 不继承父 App 的设置  
18 - Object.assign(app.locals, parent.locals);  
19 - }); 16 +app.on('mount', function(parent) {
  17 + delete parent.locals.settings; // 不继承父 App 的设置
  18 + Object.assign(app.locals, parent.locals);
  19 +});
20 20
21 - app.set('views', path.join(__dirname, 'views/action'));  
22 - app.engine('.hbs', hbs({  
23 - extname: '.hbs',  
24 - defaultLayout: 'layout',  
25 - layoutsDir: doraemon,  
26 - partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],  
27 - helpers: require(`${global.library}/helpers`)  
28 - })); 21 +app.set('views', path.join(__dirname, 'views/action'));
  22 +app.engine('.hbs', hbs({
  23 + extname: '.hbs',
  24 + defaultLayout: 'layout',
  25 + layoutsDir: doraemon,
  26 + partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],
  27 + helpers: require(`${global.library}/helpers`)
  28 +}));
29 29
30 - // router  
31 - app.use(require('./router')); 30 +// router
  31 +app.use(require('./router'));
32 32
33 - module.exports = app; 33 +module.exports = app;
@@ -47,6 +47,12 @@ module.exports = { @@ -47,6 +47,12 @@ module.exports = {
47 colorize: 'all', 47 colorize: 'all',
48 prettyPrint: true 48 prettyPrint: true
49 } 49 }
  50 + },
  51 + thirdLogin: {
  52 + wechat: {
  53 + appID: 'wx75e5a7c0c88e45c2',
  54 + appSecret: 'ce21ae4a3f93852279175a167e54509b'
  55 + }
50 } 56 }
51 }; 57 };
52 58
@@ -13,4 +13,5 @@ module.exports = app => { @@ -13,4 +13,5 @@ module.exports = app => {
13 13
14 // 业务模块 14 // 业务模块
15 app.use('/product', require('./apps/product')); 15 app.use('/product', require('./apps/product'));
  16 + app.use('/passport', require('./apps/passport'));
16 }; 17 };
@@ -3,6 +3,8 @@ @@ -3,6 +3,8 @@
3 * @param {[object]} req 3 * @param {[object]} req
4 * @return {[string]} 4 * @return {[string]}
5 */ 5 */
  6 +'use strict';
  7 +
6 exports.getUid = (req) => { 8 exports.getUid = (req) => {
7 const cookie = req.cookies._UID; 9 const cookie = req.cookies._UID;
8 let _uid = 0; 10 let _uid = 0;
@@ -21,3 +23,7 @@ exports.getUid = (req) => { @@ -21,3 +23,7 @@ exports.getUid = (req) => {
21 23
22 return _uid; 24 return _uid;
23 }; 25 };
  26 +
  27 +exports.getShoppingKey = (req) => {
  28 + return req.cookies['_SPK'] ? req.cookies['_SPK'] : ''; // eslint-disable-line
  29 +};
@@ -11,12 +11,12 @@ const FileTransport = require('winston-daily-rotate-file'); @@ -11,12 +11,12 @@ const FileTransport = require('winston-daily-rotate-file');
11 11
12 require('influxdb-winston'); 12 require('influxdb-winston');
13 13
14 -const logger = new (winston.Logger)({ 14 +const logger = new(winston.Logger)({
15 transports: [ 15 transports: [
16 - new (FileTransport)(config.loggers.infoFile),  
17 - new (FileTransport)(config.loggers.errorFile),  
18 - new (winston.transports.UdpTransport)(config.loggers.udp),  
19 - new (winston.transports.Console)(config.loggers.console) 16 + new(FileTransport)(config.loggers.infoFile),
  17 + new(FileTransport)(config.loggers.errorFile),
  18 + new(winston.transports.UdpTransport)(config.loggers.udp),
  19 + new(winston.transports.Console)(config.loggers.console)
20 ], 20 ],
21 exitOnError: false 21 exitOnError: false
22 }); 22 });
@@ -72,7 +72,7 @@ exports.apiSign = (params) => { @@ -72,7 +72,7 @@ exports.apiSign = (params) => {
72 // 检查签名,APP 访问 H5 页面的时候需要检查 72 // 检查签名,APP 访问 H5 页面的时候需要检查
73 exports.checkSign = (params) => { 73 exports.checkSign = (params) => {
74 const // eslint-disable-line camelcase 74 const // eslint-disable-line camelcase
75 - clientSecret = params.client_secret; 75 + clientSecret = params.client_secret;
76 76
77 let sortedParams; 77 let sortedParams;
78 78
@@ -94,3 +94,7 @@ exports.webSign = (params) => { @@ -94,3 +94,7 @@ exports.webSign = (params) => {
94 94
95 return params.key === md5(md5(webPrivateKey) + params.uid); 95 return params.key === md5(md5(webPrivateKey) + params.uid);
96 }; 96 };
  97 +
  98 +exports.makeToken = (string) => {
  99 + return md5(md5(string + '#@!@#'));
  100 +};
@@ -43,6 +43,8 @@ @@ -43,6 +43,8 @@
43 "moment": "^2.13.0", 43 "moment": "^2.13.0",
44 "morgan": "^1.7.0", 44 "morgan": "^1.7.0",
45 "oneapm": "^1.2.20", 45 "oneapm": "^1.2.20",
  46 + "passport": "^0.3.2",
  47 + "passport-weixin-plus": "0.0.4",
46 "request-promise": "^3.0.0", 48 "request-promise": "^3.0.0",
47 "serve-favicon": "^2.3.0", 49 "serve-favicon": "^2.3.0",
48 "uuid": "^2.0.2", 50 "uuid": "^2.0.2",