You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
162 lines
5.3 KiB
162 lines
5.3 KiB
/**
|
|
* 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;
|
|
|