UNPKG

ibird-token

Version:

The token manager of ibird.

346 lines (314 loc) 10.6 kB
'use strict'; /** * 主模块 * Created by yinfxs on 2017/4/7. */ const path = require('path'); const uuid = require('uuid'); const utility = require('ibird-utils'); const fs = require('fs-extra'); const Redis = require('./utils/redis'); const moment = require('moment'); const tokenUtils = require('./utils/token'); moment.locale('zh-cn'); const app = { mode: 1 }; const cache = { mode: 1, expires_in: { access_token: 86400, // 默认一天,1天 refresh_token: 604800 // 默认一周,7天 } }; module.exports = app; /** * 内存模式 * @type {number} */ app.MODE_MEMORY = 1; /** * Redis模式 * @type {number} */ app.MODE_REDIS = 2; /** * 自定义模式 * @type {number} */ app.MODE_USER = 3; /** * 访问令牌的Key * @type {string} */ app.TOKENKEY = 'access_token'; /** * 访问令牌在Cookie中的Key * @type {string} */ app.COOKIETOKEN = `IBIRD_${app.TOKENKEY}`.toUpperCase(); /** * 用户ID在Cookie中的Key * @type {string} */ app.COOKIEUSERID = `IBIRD_USERID`; /** * 初始化配置 * * @param obj 配置对象 * */ app.config = (obj = {}) => { cache.mode = [app.MODE_USER, app.MODE_MEMORY, app.MODE_REDIS].indexOf(obj.mode) >= 0 ? obj.mode : app.MODE_MEMORY; const expires_in = obj.expires_in || {}; cache.expires_in = (typeof expires_in.access_token === 'number') && (typeof expires_in.refresh_token === 'number') ? expires_in : cache.expires_in; cache.expires_in.access_token = Math.abs(cache.expires_in.access_token); cache.expires_in.refresh_token = Math.abs(cache.expires_in.refresh_token); if (app.taskInterval) clearInterval(app.taskInterval); switch (cache.mode) { case app.MODE_MEMORY: cache.token = { access_token: {}, refresh_token: {} }; app.taskfn(); break; case app.MODE_REDIS: if (!obj.redis) throw new Error(`Redis模式下必须提供Redis的连接参数(redis)`); const redis = Redis(obj.redis); app.redis = redis; cache.redis = redis; break; case app.MODE_USER: if (!obj.token || (typeof obj.get !== 'function') || (typeof obj.set !== 'function') || (typeof obj.remove !== 'function') || (typeof obj.refresh !== 'function')) { throw new Error(`自定义模式下必须提供令牌的设置/获取/删除/刷新函数(get、set、remove、refresh)`); } cache.set = obj.set; cache.get = obj.get; cache.remove = obj.remove; cache.refresh = obj.refresh; break; } Object.assign(app, { mode: cache.mode, condition: obj.condition || false, ignoreURLs: obj.ignoreURLs || [/signin/, /token/], fakeTokens: obj.fakeTokens || [], expiredMiddleware: obj.expiredMiddleware, client: obj.client || {}, useridKey: obj.useridKey || '_id' }); }; /** * 授权 * @param condition */ app.authorization = async (condition) => { if (!(condition instanceof Promise) && (typeof condition !== 'function') && (typeof condition !== 'boolean')) throw Error(`授权条件必须是函数或布尔类型`); try { let data = null; if (condition instanceof Promise) { data = await condition; } else if (typeof condition === 'function') { data = await condition(); } else { if (!condition) throw new Error('未满足授权条件'); } const time = moment().unix(); const token = { access_token: uuid.v1().replace(/-/g, ''), refresh_token: new Buffer(uuid.v1()).toString('base64'), expires_in: cache.expires_in.access_token, created: time, updated: time, data }; switch (cache.mode) { case app.MODE_MEMORY: cache.token.access_token[token.access_token] = token; cache.token.refresh_token[token.refresh_token] = token; break; case app.MODE_REDIS: await app.redis.pipeline() .set(token.access_token, JSON.stringify(token, null, 0), 'EX', cache.expires_in.access_token) .set(token.refresh_token, JSON.stringify(token, null, 0), 'EX', cache.expires_in.refresh_token) .exec(); break; case app.MODE_USER: await cache.set(token); break; } return Promise.resolve(token); } catch (e) { return Promise.reject(e); } }; /** * 鉴权 * @param access_token */ app.authentication = async (access_token) => { let token = null; if (Array.isArray(app.fakeTokens) && app.fakeTokens.length > 0) { if (app.fakeTokens.indexOf(access_token) >= 0) return {}; } try { switch (cache.mode) { case app.MODE_MEMORY: const access_cache = cache.token.access_token; token = access_cache[access_token]; if (token) return token; break; case app.MODE_REDIS: token = await app.redis.get(access_token); if (token) { token = utility.parse(token); return token; } break; case app.MODE_USER: token = await cache.get(access_token); if (token) { const access_expires = moment(token.created, 'X').add(cache.expires_in.access_token, 's'); if (moment().isBefore(access_expires)) return token; } break; } throw new Error(`令牌已失效`); } catch (e) { return Promise.reject(e); } }; /** * 通过刷新令牌刷新访问令牌 * @param refresh_token */ app.refresh = async (refresh_token) => { if (!refresh_token) return Promise.reject(new Error(`参数异常(refresh_token)`)); let token = null; try { switch (cache.mode) { case app.MODE_MEMORY: const access_cache = cache.token.access_token; const refresh_cache = cache.token.refresh_token; token = refresh_cache[refresh_token]; if (!token) throw new Error(`刷新令牌已过期`); token.updated = moment().unix(); token.access_token = uuid.v1().replace(/-/g, ''); access_cache[token.access_token] = token; refresh_cache[token.refresh_token] = token; cache.token = { access_token: access_cache, refresh_token: refresh_cache }; break; case app.MODE_REDIS: token = await app.redis.get(refresh_token); if (!token) throw new Error(`刷新令牌已过期`); token = utility.parse(token); token.updated = moment().unix(); token.access_token = uuid.v1(); await app.redis.pipeline() .set(token.access_token, JSON.stringify(token, null, 0), 'EX', cache.expires_in.access_token) .set(token.refresh_token, JSON.stringify(token, null, 0), 'EX', cache.expires_in.refresh_token) .exec(); break; case app.MODE_USER: token = await cache.refresh(refresh_token); break; } return Promise.resolve(token); } catch (e) { return Promise.reject(e); } }; /** * 立即删除令牌 * @param access_token,访问令牌 */ app.remove = async (access_token) => { if (!access_token || !cache.mode) return; let token = null; try { switch (cache.mode) { case app.MODE_MEMORY: const access_cache = cache.token.access_token; const refresh_cache = cache.token.refresh_token; token = access_cache[access_token]; if (!token) return; cache.token = { access_token: utility.omit(access_cache, token.access_token), refresh_token: utility.omit(refresh_cache, token.refresh_token) }; break; case app.MODE_REDIS: token = await app.redis.get(access_token); if (token) { token = utility.parse(token); } else { return; } await app.redis.pipeline().del(token.access_token).del(token.refresh_token).exec(); break; case app.MODE_USER: await cache.remove(access_token); break; } } catch (e) { return; } }; // 从ctx中解析token app.parse = (ctx) => { return tokenUtils(ctx); } /** * 内存模式的自动更新任务 */ app.taskfn = () => { app.taskInterval = setInterval(() => { if (app.mode != app.MODE_MEMORY) return; if (Object.keys(cache.token.access_token) == 0) return; const access_cache = {}; const refresh_cache = {}; const _access = cache.token.access_token; const _refresh = cache.token.refresh_token; const now = moment(); for (const access_token in _access) { const token = _access[access_token] || {}; const access_expires = moment(token.updated, 'X').add(cache.expires_in.access_token, 's'); if (now.isBefore(access_expires)) access_cache[token.access_token] = token; } for (const refresh_token in _refresh) { const token = _refresh[refresh_token] || {}; const refresh_expires = moment(token.created, 'X').add(cache.expires_in.refresh_token, 's'); if (now.isBefore(refresh_expires)) refresh_cache[token.refresh_token] = token; } cache.token = { access_token: access_cache, refresh_token: refresh_cache }; }, 1500); }; /** * 自动配置 */ app.config(); /** * 导出中间件 * @param app */ app.middleware = (app) => { require('./middleware/token')(app); }; /** * 导出内置路由 * @param app */ app.route = (router) => { require('./route/token')(router); require('./route/signin')(router); require('./route/signout')(router); require('./route/refresh')(router); require('./route/validity')(router); };