@jianghujs/jianghu
Version:
Progressive Enterprise Framework
316 lines (292 loc) • 9.83 kB
JavaScript
;
const _ = require('lodash');
const dayjs = require('dayjs');
async function getUserFromJwtAuthToken(authToken, jianghuKnex, xiaochengxuUserId, authTokenKey, appId) {
let user = {};
if (!authToken) {
return user;
}
let userSession;
// 如果是小程序用户,则不从 userSession 查数据,而是 mock 一个 userUession
if (xiaochengxuUserId) {
userSession = {
userId: xiaochengxuUserId,
deviceId: 'from_xiaochengxu',
operationAt: dayjs().format(),
};
} else {
if (authTokenKey === appId) {
userSession = await jianghuKnex('_user_session')
.where({ authToken })
.first();
}
if (authTokenKey !== appId) {
userSession = await jianghuKnex(`_${authTokenKey}_user_session`)
.where({ authToken })
.first();
}
}
if (userSession && userSession.userId) {
const userResult = await jianghuKnex('_view01_user')
.where({ userId: userSession.userId })
.first();
if (userResult) {
const userTmp = _.omit(userResult, [ 'clearTextPassword', 'password', 'md5Salt' ]);
user = userTmp;
user.deviceId = userSession.deviceId;
user.deviceType = userSession.deviceType;
user.loginTime = userSession.operationAt;
}
}
return user;
}
module.exports = {
async getUserInfo({
ctx,
config,
body,
jianghuKnex,
isGroupIdRequired,
appType,
xiaochengxuUserId = null,
mockBody = false,
}) {
if (mockBody) {
// 由于 userInfoUtil 针对的是 post 请求,所以需要构造一个结构一致的 body
body = {
appData: {
authToken: ctx.cookies.get(`${config.authTokenKey}_authToken`, {
httpOnly: false,
signed: false,
}),
},
};
}
const { authToken, actionData = {} } = body.appData;
// 取到 authToken 后不再需要保留在 actionData 中
// delete body.appData.authToken;
// 获取用户信息
const { authTokenKey, appId } = config;
const user = await getUserFromJwtAuthToken(authToken, jianghuKnex, xiaochengxuUserId, authTokenKey, appId);
const { userId, username } = user;
const groupId = isGroupIdRequired ? actionData.groupId : '';
// 如果有缓存,则直接返回缓存
if (userId && config.jianghuConfig.enableUserInfoCache && !isGroupIdRequired) {
const otherUserRuleData = await this.getUserRuleDataFromCache(
jianghuKnex,
userId
);
if (otherUserRuleData) {
return { userId, username, user, ...otherUserRuleData };
}
}
return {
userId,
username,
user,
...(await this.captureUserRuleData({
jianghuKnex,
appType,
userId,
groupId,
})),
};
},
async getUserRuleDataFromCache(jianghuKnex, userId) {
// const cache = await jianghuKnex('_cache').where({ userId: userId || 'visitor' }).first();
const cache = await jianghuKnex('_cache').where({ userId }).first();
if (cache && cache.content) {
return JSON.parse(cache.content);
}
return null;
},
/**
* 获取用户的 userGroupRoleList, allowResourceList, allowPageList, userAppList
* @param params
* @param params.jianghuKnex
* @param params.appType
* @param params.userId
* @param params.groupId
*/
async captureUserRuleData({ jianghuKnex, appType, userId, groupId = null }) {
let userGroupRoleList = [];
if (userId) {
// Tip: resource指定groupId后 ===> 只能取当前的groupId ===> params: { groupId }
userGroupRoleList = groupId
? await jianghuKnex('_user_group_role')
.where({ userId, groupId })
.select() :
await jianghuKnex('_user_group_role')
.where({ userId })
.select();
}
userGroupRoleList.push({ userId, groupId: 'public', roleId: '--' });
if (userId) {
userGroupRoleList.push({ userId, groupId: 'login', roleId: '--' });
}
const allowResourceList = await this.captureAllowResourceList({
jianghuKnex,
userGroupRoleList,
});
const allowPageList = await this.captureAllowPageList({
jianghuKnex,
userGroupRoleList,
});
const userAppList =
appType === 'multiApp'
? await this.captureAppList({ jianghuKnex, userId })
: [];
// Tip: 需要把 groupName 和 roleName 带出来, 而且public和login不需要带出来
let userGroupRoleListForShow = [];
if (userId) {
userGroupRoleListForShow = await jianghuKnex('_user_group_role')
.leftJoin('_role', '_user_group_role.roleId', '_role.roleId')
.leftJoin('_group', '_user_group_role.groupId', '_group.groupId')
.where('userId', userId)
.select(
'_user_group_role.userId AS userId',
'_user_group_role.groupId AS groupId',
'_group.groupName AS groupName',
'_user_group_role.roleId AS roleId',
'_role.roleName AS roleName'
);
}
return { userGroupRoleList: userGroupRoleListForShow, allowResourceList, allowPageList, userAppList };
},
async captureAppList({ jianghuKnex, userId }) {
if (!userId) {
return [];
}
return await jianghuKnex('_view02_user_app')
.where({ userId })
.select();
},
async captureAllowResourceList({
jianghuKnex,
userGroupRoleList,
}) {
const allResourceList = await jianghuKnex('_resource').select();
allResourceList.forEach(resource => {
const { pageId, actionId } = resource;
resource.resourceId = `${pageId}.${actionId}`;
});
const allUserGroupRoleResourceList = await jianghuKnex('_user_group_role_resource').select();
const allowResourceList = this.computeAllowList(
'resource',
allResourceList,
allUserGroupRoleResourceList,
userGroupRoleList
);
return allowResourceList.map(resource => {
return {
resourceId: resource.resourceId,
pageId: resource.pageId,
actionId: resource.actionId,
resourceType: resource.resourceType,
isPublic: resource.isPublic,
};
});
},
async captureAllowPageList({
jianghuKnex,
userGroupRoleList,
}) {
const allPageList = await jianghuKnex('_page').select();
const allUserGroupRolePageList = await jianghuKnex('_user_group_role_page').select();
const allowPageList = this.computeAllowList(
'page',
allPageList,
allUserGroupRolePageList,
userGroupRoleList
);
return allowPageList;
},
/**
* 计算用户有权限的数据项
*
* @param fieldKey 数据类型字段,如 'resource', 'page', 'uiLevel'
* @param allItemList 所有的数据列表,如 allResourceList, allPageList, allUiLevelList
* @param allRuleList 所有的规则列表,如 allUserGroupRoleResourceList, allUserGroupRolePageList, allUserGroupRoleUiLevelList
* @param userIdList
* @param groupIdList 用户有权限的 groupId 列表
* @param roleIdList 用户所属的 roleId 列表
* @param userGroupRoleList
*/
computeAllowList(
fieldKey,
allItemList,
allRuleList,
userGroupRoleList
) {
const idFieldKey = `${fieldKey}Id`;
const allowItemList = [];
const allItemMap = Object.fromEntries(
allItemList.map(obj => [ obj[idFieldKey], obj ])
);
if (!allItemList || !allRuleList) {
return allowItemList;
}
// 检查每个数据项有没有权限
allItemList.forEach(item => {
let resultAllowOrDeny = '';
let isPublic = false;
// 遍历并检查规则
allRuleList.forEach(rule => {
// deny 的优先级高于全部,一旦有 deny 则不再需要判断
if (resultAllowOrDeny === 'deny') {
return;
}
if (!this.checkResource(item[idFieldKey], rule[fieldKey])) {
return;
}
// 判断这条规则是否和当前用户匹配
if (!this.checkRule({ userGroupRoleList, rule })) {
return;
}
if (rule.group === 'public') {
isPublic = true;
}
resultAllowOrDeny = rule.allowOrDeny;
});
if (resultAllowOrDeny === 'allow') {
const allItem = allItemMap[item[idFieldKey]];
allowItemList.push({ ...allItem, isPublic });
}
});
return allowItemList;
},
/**
* userGroupRoleList 是否和当前 rule匹配
* @param userGroupRoleList.userGroupRoleList
* @param userGroupRoleList [{ userId, groupId, roleId }]
* @param rule {user:'*', group: 'login', role:'*'}
* @param userGroupRoleList.rule
*/
checkRule({ userGroupRoleList, rule }) {
const userGroupRoleListRule = userGroupRoleList
.filter(userGroupRole => this.checkResource(userGroupRole.userId, rule.user))
.filter(userGroupRole => this.checkResource(userGroupRole.groupId, rule.group))
.filter(userGroupRole => this.checkResource(userGroupRole.roleId, rule.role));
if (userGroupRoleListRule.length === 0) {
return false;
}
if (userGroupRoleListRule.length > 0) {
return true;
}
},
/**
* @description 判断资源是否符合规则,支持逗号及后缀通配符
* @return {Boolean} 检查结果
* @param {String} checkResource 待检查资源名,如 app1.student.res1
* @param {String} ruleResource 规则中的资源名,如 app1.normal.*,app1.student.res1
*/
checkResource(checkResource, ruleResource) {
const ruleParts = ruleResource.split(',');
return !!ruleParts.find(ruleValue => {
// 将后缀通配符转成正常正则
const ruleReg = '^' + ruleValue.replace(/\./g, '\\.').replace(/\|/g, '\\|').replace('*', '.*') + '$';
const regExp = new RegExp(ruleReg);
return regExp.test(checkResource);
});
},
};