@tachybase/module-acl 1.6.21 → 1.6.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,14 @@
1
1
  module.exports = {
2
2
  "react": "18.3.1",
3
- "@tachybase/client": "1.6.21",
4
- "@tachybase/schema": "1.6.11",
3
+ "@tachybase/client": "1.6.23",
4
+ "@tachybase/schema": "1.6.16",
5
5
  "antd": "5.22.5",
6
6
  "@ant-design/icons": "6.1.0",
7
7
  "react-i18next": "16.2.1",
8
- "@tego/client": "1.6.11",
9
- "@tego/server": "1.6.11",
8
+ "@tego/client": "1.6.16",
9
+ "@tego/server": "1.6.16",
10
10
  "async-mutex": "0.5.0",
11
11
  "lodash": "4.17.21",
12
- "@tachybase/test": "1.6.11",
12
+ "@tachybase/test": "1.6.16",
13
13
  "ahooks": "3.9.0"
14
14
  };
@@ -33,12 +33,32 @@ async function setCurrentRole(ctx, next) {
33
33
  const attachRoles = ctx.state.attachRoles || [];
34
34
  const cache = ctx.cache;
35
35
  const repository = ctx.db.getRepository("users.roles", ctx.state.currentUser.id);
36
- const roles = await cache.wrap(
37
- `roles:${ctx.state.currentUser.id}`,
38
- () => repository.find({
36
+ const roles = await cache.wrap(`roles:${ctx.state.currentUser.id}`, async () => {
37
+ const userRoles2 = await repository.find({
39
38
  raw: true
40
- })
41
- );
39
+ });
40
+ const rolesUsers = await ctx.db.getRepository("rolesUsers").find({
41
+ filter: {
42
+ userId: ctx.state.currentUser.id
43
+ },
44
+ raw: true
45
+ });
46
+ const rolesUsersMap = new Map(
47
+ rolesUsers.map((roleUser) => {
48
+ var _a2;
49
+ const item = ((_a2 = roleUser == null ? void 0 : roleUser.toJSON) == null ? void 0 : _a2.call(roleUser)) || roleUser;
50
+ return [item.roleName, item];
51
+ })
52
+ );
53
+ return userRoles2.map((role) => {
54
+ var _a2;
55
+ const item = ((_a2 = role == null ? void 0 : role.toJSON) == null ? void 0 : _a2.call(role)) || role;
56
+ return {
57
+ ...item,
58
+ rolesUsers: rolesUsersMap.get(item.name)
59
+ };
60
+ });
61
+ });
42
62
  if (!roles.length && !attachRoles.length) {
43
63
  ctx.state.currentRole = void 0;
44
64
  return ctx.throw(401, {
@@ -32,9 +32,21 @@ __export(with_acl_meta_exports, {
32
32
  module.exports = __toCommonJS(with_acl_meta_exports);
33
33
  var import_server = require("@tego/server");
34
34
  var import_lodash = __toESM(require("lodash"));
35
+ function escapeRegExp(value) {
36
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
37
+ }
38
+ function createAssociationMatcher(association) {
39
+ const parts = association.split(".").filter(Boolean);
40
+ if (!parts.length) {
41
+ return () => false;
42
+ }
43
+ const quotedAssociation = parts.map((part) => `"?${escapeRegExp(part)}"?`).join(String.raw`\s*(?:\.|->)\s*"?`);
44
+ const associationPattern = new RegExp(`(^|[^A-Za-z0-9_])${quotedAssociation}(?=\\s*(?:\\.|->)|[^A-Za-z0-9_]|$)`);
45
+ return (whereCase) => associationPattern.test(whereCase);
46
+ }
35
47
  function createWithACLMetaMiddleware() {
36
48
  return async (ctx, next) => {
37
- var _a, _b, _c;
49
+ var _a, _b, _c, _d, _e;
38
50
  await next();
39
51
  const dataSourceKey = ctx.get("x-data-source");
40
52
  const dataSource = ctx.tego.dataSourceManager.dataSources.get(dataSourceKey);
@@ -81,7 +93,12 @@ function createWithACLMetaMiddleware() {
81
93
  params: {},
82
94
  resourceName: ctx.action.resourceName,
83
95
  resourceOf: ctx.action.resourceOf,
84
- mergeParams() {
96
+ mergeParams(params) {
97
+ this.params = import_lodash.default.mergeWith({}, this.params, params, (value, srcValue) => {
98
+ if (Array.isArray(value) && Array.isArray(srcValue)) {
99
+ return srcValue;
100
+ }
101
+ });
85
102
  }
86
103
  },
87
104
  state: {
@@ -141,7 +158,7 @@ function createWithACLMetaMiddleware() {
141
158
  ...params,
142
159
  context: actionCtx
143
160
  });
144
- const actionSql = ctx.db.sequelize.queryInterface.queryGenerator.selectQuery(
161
+ const actionSql = db.sequelize.queryInterface.queryGenerator.selectQuery(
145
162
  Model.getTableName(),
146
163
  {
147
164
  where: (() => {
@@ -193,7 +210,10 @@ function createWithACLMetaMiddleware() {
193
210
  },
194
211
  Model
195
212
  );
196
- const whereCase = actionSql.match(/WHERE (.*?);/)[1];
213
+ const whereCase = (_e = (_d = actionSql.match(/\bWHERE\s+([\s\S]*)$/i)) == null ? void 0 : _d[1]) == null ? void 0 : _e.replace(/;\s*$/, "");
214
+ if (!whereCase) {
215
+ continue;
216
+ }
197
217
  conditions.push({
198
218
  whereCase,
199
219
  action,
@@ -203,18 +223,21 @@ function createWithACLMetaMiddleware() {
203
223
  let include = conditions.map((condition) => condition.include).flat();
204
224
  const whereCases = conditions.map((condition) => condition.whereCase);
205
225
  include = include.filter((inc) => {
206
- return conditions.some((condition) => {
207
- return whereCases.some((whereCase) => whereCase.includes(inc.association));
208
- });
226
+ if (!(inc == null ? void 0 : inc.association)) {
227
+ return false;
228
+ }
229
+ const matchesAssociation = createAssociationMatcher(inc.association);
230
+ return whereCases.some((whereCase) => matchesAssociation(whereCase));
209
231
  });
210
232
  const results = await collection.model.findAll({
211
233
  where: {
212
234
  [primaryKeyField]: ids
213
235
  },
236
+ raw: true,
214
237
  attributes: [
215
238
  primaryKeyField,
216
239
  ...conditions.map((condition) => {
217
- return [ctx.db.sequelize.literal(`CASE WHEN ${condition.whereCase} THEN 1 ELSE 0 END`), condition.action];
240
+ return [db.sequelize.literal(`CASE WHEN ${condition.whereCase} THEN 1 ELSE 0 END`), condition.action];
218
241
  })
219
242
  ],
220
243
  include
@@ -223,7 +246,10 @@ function createWithACLMetaMiddleware() {
223
246
  if (allAllowed.includes(action)) {
224
247
  return [action, ids];
225
248
  }
226
- return [action, results.filter((item) => Boolean(item.get(action))).map((item) => item.get(primaryKeyField))];
249
+ return [
250
+ action,
251
+ Array.from(new Set(results.filter((item) => Boolean(item[action])).map((item) => item[primaryKeyField])))
252
+ ];
227
253
  }).reduce((acc, [action, ids2]) => {
228
254
  acc[action] = ids2;
229
255
  return acc;
@@ -211,7 +211,8 @@ class PluginACL extends import_server.Plugin {
211
211
  await this.app.db.getRepository("dataSourcesRoles").updateOrCreate({
212
212
  values: {
213
213
  roleName: model.get("name"),
214
- dataSourceKey: "main"
214
+ dataSourceKey: "main",
215
+ strategy: model.get("strategy")
215
216
  },
216
217
  filterKeys: ["roleName", "dataSourceKey"],
217
218
  transaction
@@ -248,14 +249,26 @@ class PluginACL extends import_server.Plugin {
248
249
  await this.writeResourceToACL(resource, transaction);
249
250
  });
250
251
  this.app.db.on("collections.afterDestroy", async (model, options) => {
252
+ var _a;
251
253
  const { transaction } = options;
254
+ const resourceName = model.get("name");
252
255
  await this.app.db.getRepository("dataSourcesRolesResources").destroy({
253
256
  filter: {
254
- name: model.get("name"),
257
+ name: resourceName,
255
258
  dataSourceKey: "main"
256
259
  },
257
260
  transaction
258
261
  });
262
+ const aclRoles = (_a = this.acl) == null ? void 0 : _a.roles;
263
+ if (!aclRoles || typeof aclRoles.values !== "function") {
264
+ return;
265
+ }
266
+ for (const role of aclRoles.values()) {
267
+ if (!role || typeof role.revokeResource !== "function") {
268
+ continue;
269
+ }
270
+ role.revokeResource(resourceName);
271
+ }
259
272
  });
260
273
  this.app.db.on("fields.afterCreate", async (model, options) => {
261
274
  const { transaction } = options;
@@ -330,7 +343,7 @@ class PluginACL extends import_server.Plugin {
330
343
  });
331
344
  }
332
345
  this.app.on("afterInstallPlugin", async (plugin) => {
333
- if (plugin.getName() !== "user") {
346
+ if (!["user", "users"].includes(plugin.getName())) {
334
347
  return;
335
348
  }
336
349
  const User = this.db.getCollection("users");
@@ -355,7 +368,7 @@ class PluginACL extends import_server.Plugin {
355
368
  });
356
369
  });
357
370
  this.app.on("beforeInstallPlugin", async (plugin) => {
358
- if (plugin.getName() !== "user") {
371
+ if (!["user", "users"].includes(plugin.getName())) {
359
372
  return;
360
373
  }
361
374
  const roles = this.app.db.getRepository("roles");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tachybase/module-acl",
3
3
  "displayName": "Access control",
4
- "version": "1.6.21",
4
+ "version": "1.6.23",
5
5
  "description": "Based on roles, resources, and actions, access control can precisely manage interface configuration permissions, data operation permissions, menu access permissions, and plugin permissions.",
6
6
  "keywords": [
7
7
  "Users & permissions"
@@ -20,10 +20,10 @@
20
20
  "dependencies": {},
21
21
  "devDependencies": {
22
22
  "@ant-design/icons": "^6.1.0",
23
- "@tachybase/schema": "1.6.11",
24
- "@tachybase/test": "1.6.11",
25
- "@tego/client": "1.6.11",
26
- "@tego/server": "1.6.11",
23
+ "@tachybase/schema": "1.6.16",
24
+ "@tachybase/test": "1.6.16",
25
+ "@tego/client": "1.6.16",
26
+ "@tego/server": "1.6.16",
27
27
  "@types/jsonwebtoken": "^8.5.9",
28
28
  "ahooks": "^3.9.0",
29
29
  "antd": "5.22.5",
@@ -33,7 +33,7 @@
33
33
  "react": "18.3.1",
34
34
  "react-dom": "18.3.1",
35
35
  "react-i18next": "16.2.1",
36
- "@tachybase/client": "1.6.21"
36
+ "@tachybase/client": "1.6.23"
37
37
  },
38
38
  "description.zh-CN": "基于角色、资源和操作的权限控制,可以精确控制界面配置权限、数据操作权限、菜单访问权限、插件权限。",
39
39
  "displayName.zh-CN": "权限控制",