/** * OAUTH2 授权 * 登录,用户授权 *
* 作者:hugh zhuang * 邮箱:3378340995@qq.com * 日期:2018-10-08-下午3:29:34 * 版权:广州流辰信息技术有限公司 **/ import { OAUTH2_URL } from "@/api/baseUrl"; import request from "@/utils/request"; import requestState from "@/constants/state"; import { Message } from "element-ui"; import qs from "qs"; var AccessToken = function (data) { if (!(this instanceof AccessToken)) { return new AccessToken(data); } this.data = data; }; /** * 对返回结果的一层封装,如果遇见IBPS平台返回的错误,将返回一个错误 * 参见:IBPS返回码说明 */ var wrapper = function (callback) { return function (err, data, res) { callback = callback || function () {}; if (err) { err.name = "IBPSAPI" + err.name; return callback(err, data, res); } callback(null, data, res); }; }; /*! * 检查AccessToken是否有效,检查规则为当前时间和过期时间进行对比 * * Examples: * ``` * token.isValid(); * ``` */ AccessToken.prototype.isValid = function () { return ( !!this.data.access_token && new Date().getTime() < this.data.create_at + this.data.expires_in * 1000 ); }; /*! * 处理token,更新过期时间 */ var processToken = function (that, callback) { var createAt = new Date().getTime(); return function (err, data, res) { if (err) { return callback(err, data, res); } data.create_at = createAt; if (res.variables && res.variables.licJson) { data.licJson = JSON.parse(res.variables.licJson); } // 存储token that.saveToken(data.uid, data, function (err) { callback(err, new AccessToken(data)); }); }; }; /** * 根据clientId和clientSecret创建OAuth接口的构造函数 * 如需跨进程跨机器进行操作,access token需要进行全局维护 * 使用token的优先级是: * * 1. 使用当前缓存的token对象 * 2. 调用开发传入的获取token的异步方法,获得token之后使用(并缓存它)。 * Examples: * ``` * import IbpsOAuth from '@/utils/oauth2' * var oauthApi = new OAuth('clientId', 'clientSecret'); * ``` * @param {String} clientId 在平台上申请得到的clientId(或appid) * @param {String} clientSecret 在平台上申请得到的client secret(或app secret) * @param {Function} getToken 用于获取token的方法 * @param {Function} saveToken 用于保存token的方法 */ var OAuth = function (clientId, clientSecret, getToken, saveToken) { this.clientId = clientId; this.clientSecret = clientSecret; this.statistic = ""; this.username = ""; // token的获取和存储 this.store = {}; this.getToken = getToken || function (uid, callback) { callback(null, this.store[uid]); }; if (!saveToken && process.env.NODE_ENV === "production") { console.warn( "Please dont save oauth token into memory under production" ); } this.saveToken = saveToken || function (uid, token, callback) { this.store[uid] = token; callback(null); }; this.defaults = {}; }; /*! * request的封装 * * @param {String} url 路径 * @param {Object} opts urllib选项 * @param {Function} callback 回调函数 */ OAuth.prototype.request = function (opts, callback) { const options = {}; Object.assign(options, this.defaults); if (typeof opts === "function") { callback = opts; opts = {}; } for (const key in opts) { if (key !== "headers") { options[key] = opts[key]; } else { if (opts.headers) { options.headers = options.headers || {}; Object.assign(options.headers, opts.headers); } } } request(options) .then((response) => { const { state } = response; if ( state === requestState.UNSUPORT || state === requestState.WARNING ) { const err = new Error(response.message); err.state = state; err.cause = response.cause; callback(err); } else { callback(null, response.data, response); } }) .catch((error) => { callback(error); }); }; /** * 获取授权页面的URL地址 * @param {String} redirect 授权后要跳转的地址 * @param {String} state 开发者可提供的数据 * @param {String} scope 作用范围,值为snsapi_userinfo和snsapi_base,前者用于弹出,后者用于跳转 */ OAuth.prototype.getAuthorizeURL = function (redirect, state, scope) { const url = OAUTH2_URL() + ""; const info = { clientId: this.clientId, redirect_uri: redirect, response_type: "code", scope: scope || "snsapi_base", state: state || "", }; return url + "?" + qs.stringify(info) + "#ibps_redirect"; }; /** * 获取授权页面的URL地址 * @param {String} redirect 授权后要跳转的地址 * @param {String} state 开发者可提供的数据 * @param {String} scope 作用范围,值为snsapi_login,前者用于弹出,后者用于跳转 */ OAuth.prototype.getAuthorizeURLForWebsite = function (redirect, state, scope) { const url = OAUTH2_URL() + "/qrconnect"; const info = { clientId: this.clientId, redirect_uri: redirect, response_type: "code", scope: scope || "snsapi_login", state: state || "", }; return url + "?" + qs.stringify(info) + "#ibps_redirect"; }; /** * 根据授权获取到的code,换取access_token和uid * 获取uid之后,可以调用`IBPS.API`来获取更多用户信息 * Examples: * ``` * api.getAccessTokenByCode(code, callback); * ``` * Callback: * * - `error`, 获取access token出现异常时的异常对象 * - `result`, 成功时得到的响应结果 * * Result: * ``` * { * data: { * "access_token": "ACCESS_TOKEN", * "refresh_token": "REFRESH_TOKEN", * "uid": "uid", * "expires_in": 7200, * "scope": "SCOPE" * } * } * ``` * @param {String} code 授权获取到的code * @param {Function} callback 回调函数 */ OAuth.prototype.getAccessTokenByCode = function (code, callback) { localStorage.setItem("username", this.username); const args = { url: OAUTH2_URL() + "/authentication/apply", data: { client_id: this.clientId, client_secret: this.clientSecret, authorize_code: code, username: this.username, grant_type: "authorization_code", }, method: "post", }; this.request(args, wrapper(processToken(this, callback))); }; /** * 密码授权模式 * 根据用户名和密码,换取access token和uid * 获取uid之后,可以调用`IBPS.API`来获取更多用户信息 * Examples: * ``` * api.getAccessTokenByPassword(code, callback); * ``` * Callback: * * - `error`, 获取access token出现异常时的异常对象 * - `result`, 成功时得到的响应结果 * * Result: * ``` * { * data: { * "access_token": "ACCESS_TOKEN", * "refresh_token": "REFRESH_TOKEN", * "uid": "uid", * "expires_in": 7200, * "scope": "SCOPE" * } * } * ``` * ``` * @param {String} username 用户名 * @param {String} password 密码 * @param {Function} callback 回调函数 */ OAuth.prototype.getAccessTokenByPassword = function ( { username, password }, callback ) { const args = { url: OAUTH2_URL() + "/authentication/apply", data: { client_id: this.clientId, client_secret: this.clientSecret, username: username, password: password, grant_type: "password_credentials", }, method: "post", }; this.request(args, wrapper(processToken(this, callback))); }; /** * 根据refresh token,刷新access token,调用getAccessTokenByCode后才有效 * Examples: * ``` * api.refreshAccessToken(refreshToken, callback); * ``` * Callback: * * - `err`, 刷新access token出现异常时的异常对象 * - `result`, 成功时得到的响应结果 * * Result: * ``` * { * data: { * "access_token": "ACCESS_TOKEN", * "expires_in": 7200, * "refresh_token": "REFRESH_TOKEN", * "uid": "uid", * "remind_in": 7 * } * } * ``` * @param {String} refreshToken 刷新tonken * @param {Function} callback 回调函数 */ OAuth.prototype.refreshAccessToken = function (refreshToken, callback) { const username = localStorage.getItem("username"); const args = { url: OAUTH2_URL() + "/authentication/apply", data: { client_id: this.clientId, client_secret: this.clientSecret, grant_type: "refresh_token", username, refresh_token: refreshToken, }, method: "post", }; this.request(args, wrapper(processToken(this, callback))); }; /** * 获取用户信息 【私有方法】 * @param {*} options * @param {*} accessToken * @param {*} callback */ OAuth.prototype._getUser = function (options, accessToken, callback) { const args = { url: OAUTH2_URL() + "/user/context", data: { access_token: accessToken, uid: options.uid, lang: options.lang || "zh_CN", }, }; this.request(args, wrapper(callback)); }; /** * 根据uid,获取用户信息。 * 当access token无效时,自动通过refresh token获取新的access token。然后再获取用户信息 * Examples: * ``` * api.getUser(uid, callback); * api.getUser(options, callback); * ``` * * Options: * ``` * // 或 * { * "uid": "the user Id", // 必须 * "lang": "the lang code" // zh_CN 简体,zh_TW 繁体,en 英语 * } * ``` * Callback: * * - `err`, 获取用户信息出现异常时的异常对象 * - `result`, 成功时得到的响应结果 * * Result: * ``` * { * "uid": "uid", * "nickname": "NICKNAME", * "sex": "1", * "province": "PROVINCE" * "city": "CITY", * "country": "COUNTRY", * "headimgurl": "http://xxxxx.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46", * "privilege": [ * "PRIVILEGE1" * "PRIVILEGE2" * ] * } * ``` * @param {Object|String} options 传入uid或者参见Options * @param {Function} callback 回调函数 */ OAuth.prototype.getUser = function (options, callback) { if (typeof options !== "object") { options = { uid: options, }; } const that = this; this.getToken(options.uid, function (err, data) { if (err) { return callback(err); } // 没有token数据 if (!data) { const message = "No token for " + options.uid + ", please authorize first."; Message({ message: `${message}`, type: "error", showClose: true, dangerouslyUseHTMLString: true, duration: 5 * 1000, }); const error = new Error(message); err.state = "NoOAuthTokenError"; return callback(error); } const token = new AccessToken(data); if (token.isValid()) { that._getUser(options, token.data.access_token, callback); } else { that.refreshAccessToken( token.data.refresh_token, function (err, token) { if (err) { return callback(err); } that._getUser(options, token.data.access_token, callback); } ); } }); }; /** * 检验授权凭证(access_token)是否有效。 * Examples: * ``` * api.verifyToken(uid, accessToken, callback); * ``` * @param {String} uid 传入uid * @param {String} accessToken 待校验的access token * @param {Function} callback 回调函数 */ OAuth.prototype.verifyToken = function (uid, accessToken, callback) { const args = { url: OAUTH2_URL() + "/authentication/verify", params: { access_token: accessToken, uid: uid, }, method: "post", }; this.request(args, wrapper(callback)); }; /** * 用户名、密码方式登录。 * Examples: * ``` * api.userLogin(data, callback); * ``` * @param {Objcct} data * username : 用户名 * password : 密码 * @param {Function} callback 回调函数 */ OAuth.prototype.userLogin = function (data, callback) { const args = { url: OAUTH2_URL() + "/user/login/apply", data: data, method: "post", }; this.request(args, wrapper(callback)); }; /** * *申请授权 * Examples: * ``` * api.authorize(login, callback); * ``` * @param {Objcct} options * @param {Function} callback 回调函数 */ OAuth.prototype.authorize = function (data, callback) { const url = OAUTH2_URL() + "/authorize/apply"; if (typeof data !== "object") { data = { login_state: data, }; } data.client_id = this.clientId; const args = { url, data: data, method: "post", }; this.request(args, wrapper(callback)); }; /** * 根据code,获取用户信息。 * Examples: * ``` * api.getUserByCode(code, callback); * ``` * Callback: * * - `err`, 获取用户信息出现异常时的异常对象 * - `result`, 成功时得到的响应结果 * * Result: * ``` * { * "uid": "uid", * "nickname": "NICKNAME", * "sex": "1", * "province": "PROVINCE" * "city": "CITY", * "country": "COUNTRY", * "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46", * "privilege": [ * "PRIVILEGE1" * "PRIVILEGE2" * ] * } * ``` * @param {Object|String} options 授权获取到的code * @param {Function} callback 回调函数 */ OAuth.prototype.getUserByCode = function (options, callback) { const that = this; let lang; let code; if (typeof options === "string") { code = options; } else { lang = options.lang; code = options.code; } this.getAccessTokenByCode(code, function (err, result) { if (err) { return callback(err); } const uid = result.data.uid; that.getUser({ uid: uid, lang: lang }, callback); }); }; /** * 通过登录获取access_token * @param {*} options * @param {*} callback */ OAuth.prototype.getLoginCode = function (options, callback) { this.username = options.username; const that = this; /** * 用户登录 */ this.userLogin(options, function (err, data, res) { if (err) { return callback(err); } that.statistic = res.variables.statistic; //判斷是否有统计权限 // 没有token数据 if (!data) { const message = "没有传回用户信息"; Message({ message: `${message}`, type: "error", showClose: true, dangerouslyUseHTMLString: true, duration: 5 * 1000, }); const error = new Error(message); err.state = "NoOAuthTokenError"; return callback(error); } that.authorize(data, function (err1, data1) { if (err1) { return callback(err1); } that.getAccessTokenByCode(data1, callback); }); }); }; export default OAuth;