• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

node-webot / wechat-oauth / 56

07 Oct 2018 04:08PM UTC coverage: 92.258% (+0.05%) from 92.208%
56

Pull #52

travis-ci

web-flow
Merge branch 'master' into refine
Pull Request #52: refine the decryptMiniProgramUser

47 of 58 branches covered (81.03%)

8 of 9 new or added lines in 1 file covered. (88.89%)

143 of 155 relevant lines covered (92.26%)

13.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.08
/lib/oauth.js
1
'use strict';
2

3
var urllib = require('urllib');
3✔
4
var extend = require('util')._extend;
3✔
5
var querystring = require('querystring');
3✔
6

7
var wrapper = require('./util').wrapper;
3✔
8
var WxBizDataCrypt = require('./wx_biz_data_crypt');
3✔
9

10
var AccessToken = function (data) {
3✔
11
  if (!(this instanceof AccessToken)) {
39!
12
    return new AccessToken(data);
×
13
  }
14
  this.data = data;
39✔
15
};
16

17
/*!
18
 * 检查AccessToken是否有效,检查规则为当前时间和过期时间进行对比
19
 *
20
 * Examples:
21
 * ```
22
 * token.isValid();
23
 * ```
24
 */
25
AccessToken.prototype.isValid = function () {
3✔
26
  return !!this.data.access_token && (new Date().getTime()) < (this.data.create_at + this.data.expires_in * 1000);
24✔
27
};
28

29
/*!
30
 * 处理token,更新过期时间
31
 */
32
var processToken = function (that, callback) {
3✔
33
  var create_at = new Date().getTime();
30✔
34

35
  return function (err, data, res) {
30✔
36
    if (err) {
30✔
37
      return callback(err, data);
12✔
38
    }
39
    data.create_at = create_at;
18✔
40
    // 存储token
41
    that.saveToken(data.openid, data, function (err) {
18✔
42
      callback(err, new AccessToken(data));
18✔
43
    });
44
  };
45
};
46

47
/**
48
 * 根据appid和appsecret创建OAuth接口的构造函数
49
 * 如需跨进程跨机器进行操作,access token需要进行全局维护
50
 * 使用使用token的优先级是:
51
 *
52
 * 1. 使用当前缓存的token对象
53
 * 2. 调用开发传入的获取token的异步方法,获得token之后使用(并缓存它)。
54

55
 * Examples:
56
 * ```
57
 * var OAuth = require('wechat-oauth');
58
 * var api = new OAuth('appid', 'secret');
59
 * ```
60
 * @param {String} appid 在公众平台上申请得到的appid
61
 * @param {String} appsecret 在公众平台上申请得到的app secret
62
 * @param {Function} getToken 用于获取token的方法
63
 * @param {Function} saveToken 用于保存token的方法
64
 */
65
var OAuth = function (appid, appsecret, getToken, saveToken, isMiniProgram) {
3✔
66
  this.appid = appid;
54✔
67
  this.appsecret = appsecret;
54✔
68
  this.isMiniProgram = isMiniProgram;
54✔
69
  // token的获取和存储
70
  this.store = {};
54✔
71
  this.getToken = getToken || function (openid, callback) {
54✔
72
    callback(null, this.store[openid]);
9✔
73
  };
74
  if (!saveToken && process.env.NODE_ENV === 'production') {
54!
75
    console.warn('Please dont save oauth token into memory under production');
×
76
  }
77
  this.saveToken = saveToken || function (openid, token, callback) {
54✔
78
    this.store[openid] = token;
18✔
79
    callback(null);
18✔
80
  };
81
  this.defaults = {};
54✔
82
};
83

84
/**
85
 * 用于设置urllib的默认options
86
 *
87
 * Examples:
88
 * ```
89
 * oauth.setOpts({timeout: 15000});
90
 * ```
91
 * @param {Object} opts 默认选项
92
 */
93
OAuth.prototype.setOpts = function (opts) {
3✔
94
  this.defaults = opts;
×
95
};
96

97
/*!
98
 * urllib的封装
99
 *
100
 * @param {String} url 路径
101
 * @param {Object} opts urllib选项
102
 * @param {Function} callback 回调函数
103
 */
104
OAuth.prototype.request = function (url, opts, callback) {
3✔
105
  var options = {};
39✔
106
  extend(options, this.defaults);
39✔
107
  if (typeof opts === 'function') {
39!
108
    callback = opts;
×
109
    opts = {};
×
110
  }
111
  for (var key in opts) {
39✔
112
    if (key !== 'headers') {
78!
113
      options[key] = opts[key];
78✔
114
    } else {
115
      if (opts.headers) {
×
116
        options.headers = options.headers || {};
×
117
        extend(options.headers, opts.headers);
×
118
      }
119
    }
120
  }
121
  urllib.request(url, options, callback);
39✔
122
};
123

124
/**
125
 * 获取授权页面的URL地址
126
 * @param {String} redirect 授权后要跳转的地址
127
 * @param {String} state 开发者可提供的数据
128
 * @param {String} scope 作用范围,值为snsapi_userinfo和snsapi_base,前者用于弹出,后者用于跳转
129
 */
130
OAuth.prototype.getAuthorizeURL = function (redirect, state, scope) {
3✔
131
  var url = 'https://open.weixin.qq.com/connect/oauth2/authorize';
9✔
132
  var info = {
9✔
133
    appid: this.appid,
134
    redirect_uri: redirect,
135
    response_type: 'code',
136
    scope: scope || 'snsapi_base',
15✔
137
    state: state || ''
12✔
138
  };
139

140
  return url + '?' + querystring.stringify(info) + '#wechat_redirect';
9✔
141
};
142

143
/**
144
 * 获取授权页面的URL地址
145
 * @param {String} redirect 授权后要跳转的地址
146
 * @param {String} state 开发者可提供的数据
147
 * @param {String} scope 作用范围,值为snsapi_login,前者用于弹出,后者用于跳转
148
 */
149
OAuth.prototype.getAuthorizeURLForWebsite = function (redirect, state, scope) {
3✔
150
  var url = 'https://open.weixin.qq.com/connect/qrconnect';
9✔
151
  var info = {
9✔
152
    appid: this.appid,
153
    redirect_uri: redirect,
154
    response_type: 'code',
155
    scope: scope || 'snsapi_login',
15✔
156
    state: state || ''
12✔
157
  };
158

159
  return url + '?' + querystring.stringify(info) + '#wechat_redirect';
9✔
160
};
161

162
/**
163
 * 根据授权获取到的code,换取access token和openid
164
 * 获取openid之后,可以调用`wechat.API`来获取更多信息
165
 * Examples:
166
 * ```
167
 * api.getAccessToken(code, callback);
168
 * ```
169
 * Callback:
170
 *
171
 * - `err`, 获取access token出现异常时的异常对象
172
 * - `result`, 成功时得到的响应结果
173
 *
174
 * Result:
175
 * ```
176
 * {
177
 *  data: {
178
 *    "access_token": "ACCESS_TOKEN",
179
 *    "expires_in": 7200,
180
 *    "refresh_token": "REFRESH_TOKEN",
181
 *    "openid": "OPENID",
182
 *    "scope": "SCOPE"
183
 *  }
184
 * }
185
 * ```
186
 * @param {String} code 授权获取到的code
187
 * @param {Function} callback 回调函数
188
 */
189
OAuth.prototype.getAccessToken = function (code, callback) {
3✔
190
  var url = 'https://api.weixin.qq.com/sns/oauth2/access_token';
15✔
191
  var info = {
15✔
192
    appid: this.appid,
193
    secret: this.appsecret,
194
    code: code,
195
    grant_type: 'authorization_code'
196
  };
197
  var args = {
15✔
198
    data: info,
199
    dataType: 'json'
200
  };
201
  this.request(url, args, wrapper(processToken(this, callback)));
15✔
202
};
203

204
/**
205
 * 根据授权获取到的code,换取小程序的session key和openid(以及有条件下的unionid)
206
 * 获取openid之后,可以调用`wechat.API`来获取更多信息
207
 * Examples:
208
 * ```
209
 * api.getSessionKey(code, callback);
210
 * ```
211
 * Callback:
212
 *
213
 * - `err`, 获取session key出现异常时的异常对象
214
 * - `result`, 成功时得到的响应结果
215
 *
216
 * Result:
217
 * ```
218
 * {
219
 *  data: {
220
 *    "session_key": "SESSION_KEY",
221
 *    "openid": "OPENID",
222
 *    "unionid": "UNIONID"
223
 *  }
224
 * }
225
 * ```
226
 * @param {String} code 授权获取到的code
227
 * @param {Function} callback 回调函数
228
 */
229
OAuth.prototype.getSessionKey = function(code, callback) {
3✔
230
  var url = 'https://api.weixin.qq.com/sns/jscode2session';
6✔
231
  var info = {
6✔
232
    appid: this.appid,
233
    secret: this.appsecret,
234
    js_code: code,
235
    grant_type: 'authorization_code',
236
  };
237
  var args = {
6✔
238
    data: info,
239
    dataType: 'json'
240
  };
241
  this.request(url, args, wrapper(processToken(this, callback)));
6✔
242
};
243

244
/**
245
 * 根据refresh token,刷新access token,调用getAccessToken后才有效
246
 * Examples:
247
 * ```
248
 * api.refreshAccessToken(refreshToken, callback);
249
 * ```
250
 * Callback:
251
 *
252
 * - `err`, 刷新access token出现异常时的异常对象
253
 * - `result`, 成功时得到的响应结果
254
 *
255
 * Result:
256
 * ```
257
 * {
258
 *  data: {
259
 *    "access_token": "ACCESS_TOKEN",
260
 *    "expires_in": 7200,
261
 *    "refresh_token": "REFRESH_TOKEN",
262
 *    "openid": "OPENID",
263
 *    "scope": "SCOPE"
264
 *  }
265
 * }
266
 * ```
267
 * @param {String} refreshToken refreshToken
268
 * @param {Function} callback 回调函数
269
 */
270
OAuth.prototype.refreshAccessToken = function (refreshToken, callback) {
3✔
271
  var url = 'https://api.weixin.qq.com/sns/oauth2/refresh_token';
9✔
272
  var info = {
9✔
273
    appid: this.appid,
274
    grant_type: 'refresh_token',
275
    refresh_token: refreshToken
276
  };
277
  var args = {
9✔
278
    data: info,
279
    dataType: 'json'
280
  };
281
  this.request(url, args, wrapper(processToken(this, callback)));
9✔
282
};
283

284
OAuth.prototype._getUser = function (options, accessToken, callback) {
3✔
285
  var url = 'https://api.weixin.qq.com/sns/userinfo';
6✔
286
  var info = {
6✔
287
    access_token: accessToken,
288
    openid: options.openid,
289
    lang: options.lang || 'en'
12✔
290
  };
291
  var args = {
6✔
292
    data: info,
293
    dataType: 'json'
294
  };
295
  this.request(url, args, wrapper(callback));
6✔
296
};
297

298
/**
299
 * 根据服务器保存的sessionKey对从小程序客户端获取的加密用户数据进行解密
300
 * Examples:
301
 * ```
302
 * api.decryptMiniProgramUser({encryptedData, iv}, callback);
303
 * ```
304
 * Callback:
305
 *
306
 * - `err`, 解密用户信息出现异常时的异常对象
307
 * - `result`, 成功时得到的响应结果
308
 *
309
 * Result:
310
 * ```
311
 *{
312
 *    "openId": "OPENID",
313
 *    "nickName": "NICKNAME",
314
 *    "gender": "GENDER",
315
 *    "city": "CITY",
316
 *    "province": "PROVINCE",
317
 *    "country": "COUNTRY",
318
 *    "avatarUrl": "AVATARURL",
319
 *    "unionId": "UNIONID",
320
 *    "watermark":
321
 *    {
322
 *        "appid":"APPID",
323
 *        "timestamp":TIMESTAMP
324
 *    }
325
 *}
326
 * ```
327
 * @param {Object} options 需要解密的对象
328
 * @param {String} options.encryptedData 从小程序中获得的加密过的字符串
329
 * @param {String} options.iv 从小程序中获得的加密算法初始向量
330
 */
331
OAuth.prototype.decryptMiniProgramUser = function (options) {
3✔
332
  var decrypter = new WxBizDataCrypt(this.appid, options.sessionKey);
3✔
333
  return decrypter.decryptData(options.encryptedData, options.iv);
3✔
334
};
335

336
/**
337
 * 根据openid,获取用户信息。
338
 * 当access token无效时,自动通过refresh token获取新的access token。然后再获取用户信息
339
 * Examples:
340
 * ```
341
 * api.getUser(openid, callback);
342
 * api.getUser(options, callback);
343
 * ```
344
 *
345
 * Options:
346
 * ```
347
 * // 或
348
 * {
349
 *  "openid": "the open Id", // 必须
350
 *  "lang": "the lang code" // zh_CN 简体,zh_TW 繁体,en 英语
351
 * }
352
 * ```
353
 * Callback:
354
 *
355
 * - `err`, 获取用户信息出现异常时的异常对象
356
 * - `result`, 成功时得到的响应结果
357
 *
358
 * Result:
359
 * ```
360
 * {
361
 *  "openid": "OPENID",
362
 *  "nickname": "NICKNAME",
363
 *  "sex": "1",
364
 *  "province": "PROVINCE"
365
 *  "city": "CITY",
366
 *  "country": "COUNTRY",
367
 *  "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
368
 *  "privilege": [
369
 *    "PRIVILEGE1"
370
 *    "PRIVILEGE2"
371
 *  ]
372
 * }
373
 * ```
374
 * @param {Object|String} options 传入openid或者参见Options
375
 * @param {Function} callback 回调函数
376
 */
377
OAuth.prototype.getUser = function (options, callback) {
3✔
378
  if (typeof options !== 'object') {
30✔
379
    options = {
18✔
380
      openid: options
381
    };
382
  }
383
  var that = this;
30✔
384
  this.getToken(options.openid, function (err, data) {
30✔
385
    if (err) {
30✔
386
      return callback(err);
3✔
387
    }
388
    // 没有token数据
389
    if (!data) {
27✔
390
      var error = new Error('No token for ' + options.openid + ', please authorize first.');
6✔
391
      error.name = 'NoOAuthTokenError';
6✔
392
      return callback(error);
6✔
393
    }
394
    var token = new AccessToken(data);
21✔
395
    if (token.isValid()) {
21✔
396
      that._getUser(options, token.data.access_token, callback);
15✔
397
    } else {
398
      that.refreshAccessToken(token.data.refresh_token, function (err, token) {
6✔
399
        if (err) {
6✔
400
          return callback(err);
3✔
401
        }
402
        that._getUser(options, token.data.access_token, callback);
3✔
403
      });
404
    }
405
  });
406
};
407

408
/**
409
 * 检验授权凭证(access_token)是否有效。
410
 * Examples:
411
 * ```
412
 * api.verifyToken(openid, accessToken, callback);
413
 * ```
414
 * @param {String} openid 传入openid
415
 * @param {String} accessToken 待校验的access token
416
 * @param {Function} callback 回调函数
417
 */
418
OAuth.prototype.verifyToken = function (openid, accessToken, callback) {
3✔
419
  var url = 'https://api.weixin.qq.com/sns/auth';
3✔
420
  var info = {
3✔
421
    access_token: accessToken,
422
    openid: openid
423
  };
424
  var args = {
3✔
425
    data: info,
426
    dataType: 'json'
427
  };
428
  this.request(url, args, wrapper(callback));
3✔
429
};
430

431
/**
432
 * 根据code,获取用户信息。注意,当OAuth为MiniProgram类型时,返回的用户对象会有所不同,请查看官方文档确定数据结构以便解析。
433
 * Examples:
434
 * ```
435
 * api.getUserByCode(code, callback);
436
 * ```
437
 * Callback:
438
 *
439
 * - `err`, 获取用户信息出现异常时的异常对象
440
 * - `result`, 成功时得到的响应结果
441
 *
442
 * Result:
443
 * ```
444
 * {
445
 *  "openid": "OPENID",
446
 *  "nickname": "NICKNAME",
447
 *  "sex": "1",
448
 *  "province": "PROVINCE"
449
 *  "city": "CITY",
450
 *  "country": "COUNTRY",
451
 *  "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
452
 *  "privilege": [
453
 *    "PRIVILEGE1"
454
 *    "PRIVILEGE2"
455
 *  ]
456
 * }
457
 * ```
458
 * @param {Object|String} options 授权获取到的code
459
 * @param {Function} callback 回调函数
460
 */
461
OAuth.prototype.getUserByCode = function (options, callback) {
3✔
462
  var that = this;
12✔
463

464
  var lang, code;
12✔
465
  if (typeof options === 'string') {
12✔
466
    code = options;
9✔
467
  } else {
468
    lang = options.lang;
3✔
469
    code = options.code;
3✔
470
  }
471

472
  if (this.isMiniProgram) {
12✔
473
    this.getSessionKey(code, function (err, result) {
6✔
474
      if (err) {
6✔
475
        return callback(err);
3✔
476
      }
477
      var sessionKey = result.data.session_key;
3✔
478
      var user;
3✔
479
      try {
3✔
480
        user = that.decryptMiniProgramUser({
3✔
481
          sessionKey,
482
          encryptedData: options.encryptedData,
483
          iv: options.iv,
484
        });
485
      } catch (ex) {
NEW
486
        return callback(ex);
×
487
      }
488

489
      callback(null, user);
3✔
490
    });
491
  } else {
492
    this.getAccessToken(code, function (err, result) {
6✔
493
      if (err) {
6!
494
        return callback(err);
×
495
      }
496
      var openid = result.data.openid;
6✔
497
      that.getUser({openid: openid, lang: lang}, callback);
6✔
498
    });
499
  }
500
};
501

502
module.exports = OAuth;
3✔
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2023 Coveralls, Inc