/** * Created by PengLing on 2017/3/27. */ 'use strict'; const pathToRegexp = require('path-to-regexp'); const util = require('util'); const moment = require('moment'); class ExcludesUrls { constructor(opts) { this.allUrls = undefined; this.reload(opts); } sanitizePath (path) { if (!path) return '/'; const p = '/' + path.replace(/^\/+/i, '').replace(/\/+$/, '').replace(/\/{2,}/, '/'); return p; } reload (opts) { // load all url if (!this.allUrls) { this.allUrls = opts; let that = this; this.allUrls.forEach(function (url, i, arr) { if (typeof url === "string") { url = { p: url, o: '*' }; arr[i] = url; } const keys = []; let eachPath = url.p; url.p = (!eachPath || eachPath === '(.*)' || util.isRegExp(eachPath)) ? eachPath : that.sanitizePath(eachPath); url.pregexp = pathToRegexp(eachPath, keys); }); } } isExcluded (path, method) { return this.allUrls.some(function (url) { return !url.auth && url.pregexp.test(path) && (url.o === '*' || url.o.indexOf(method) !== -1); }); } } /** * 判断Url是否不鉴权 * @param {*} opts {exclude: [*] or []},'*'或['*']:跳过所有路由; []:所有路由都要验证 * @param {*} path 当前request的path * @param {*} method 当前request的method */ let isPathExcluded = function (opts, path, method) { let excludeAll = Boolean(opts.exclude && opts.exclude.length && opts.exclude[0] == '*'); let excludes = null; if (!excludeAll) { let excludeOpts = opts.exclude || []; excludeOpts.push({ p: '/login', o: 'POST' }); excludeOpts.push({ p: '/logout', o: 'PUT' }); excludes = new ExcludesUrls(excludeOpts); } let excluded = excludeAll || excludes.isExcluded(path, method); return excluded; }; let authorizeToken = async function (ctx, token) { let startTime = moment() let rslt = null; const tokenFormatRegexp = /^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$/g; if (token && tokenFormatRegexp.test(token)) { try { const expired = await ctx.redis.hget(token, 'expired'); // const authorizeRes = await ctx.app.fs.emisRequest.get('authorize', { // query: { token } // }) // const { userInfo, expired } = authorizeRes; // TODO 从项企 clickhouse 数据库中查 token 并更新 if (expired && moment().valueOf() <= moment(expired).valueOf()) { const userInfo = JSON.parse(await ctx.redis.hmget(token, 'userInfo')); const { pomsUserInfo: pomsUser } = userInfo // const pomsUser = await ctx.app.fs.dc.models.User.findOne({ // where: { // pepUserId: userInfo.id // } // }) || {} rslt = { 'authorized': userInfo.authorized, 'resources': (userInfo || {}).resources || [], }; ctx.fs.api.userId = pomsUser.id; ctx.fs.api.userInfo = pomsUser; ctx.fs.api.pepUserId = userInfo.id; ctx.fs.api.pepUserInfo = userInfo; ctx.fs.api.token = token; } } catch (err) { const { error } = err.response || {}; ctx.fs.logger.log('[anxinyun]', '[AUTH] failed', (error || {}).message || `cannot GET /users/${token}`); } } return rslt; }; let isResourceAvailable = function (resources, options) { let authCode = null; // authorize user by authorization attribute const { authAttr, method, path } = options; for (let prop in authAttr) { let keys = []; let re = pathToRegexp(prop.replace(/\:[A-Za-z_\-]+\b/g, '(\\d+)'), keys); if (re.test(`${method}${path}`)) { authCode = authAttr[prop]; break; } } return !authCode || (resources || []).some(code => code === authCode); }; function factory (app, opts) { return async function auth (ctx, next) { const { path, method, header, query } = ctx; ctx.fs.logger.log('[AUTH] start', path, method); ctx.fs.api = ctx.fs.api || {}; ctx.fs.port = opts.port; ctx.redis = app.redis; ctx.redisTools = app.redisTools; let error = null; if (path) { if (!isPathExcluded(opts, path, method)) { const user = await authorizeToken(ctx, header.token || query.token); if (user && user.authorized) { // if (!isResourceAvailable(user.resources, { authAttr: app.fs.auth.authAttr, path, method })) { // error = { status: 403, name: 'Forbidden' } // } else { // error = { status: 401, name: 'Unauthorized' } // } } else { error = { status: 401, name: 'Unauthorized' } } } } else { error = { status: 401, name: 'Unauthorized' }; } if (error) { ctx.fs.logger.log('[AUTH] failed', path, method); ctx.status = error.status; ctx.body = error.name; } else { ctx.fs.logger.log('[AUTH] passed', path, method); await next(); } } } module.exports = factory;