@ozdao/martyrs 0.2.563 → 0.2.565
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.
- package/dist/abac-BPl9Bmf9.js +1527 -0
- package/dist/builder.js +51 -39
- package/dist/{common.schema-GFSlNJo7.js → common.schema-DswiUXKB.js} +1 -1
- package/dist/community.server.js +48 -9
- package/dist/core.server.js +6 -4
- package/dist/{crud-C7FSTUes.js → crud-q1ye5IhV.js} +7 -7
- package/dist/events.server.js +3 -3
- package/dist/gallery.server.js +2 -2
- package/dist/inventory.server.js +4 -6
- package/dist/{main-CmjWiDVF.js → main-B9o1iBAZ.js} +1279 -1287
- package/dist/marketplace.server.js +1 -1
- package/dist/martyrs/src/components/Button/Button.vue2.js +33 -42
- package/dist/martyrs/src/components/Button/Button.vue2.js.map +1 -1
- package/dist/martyrs/src/components/EditImages/{EditImages.vue.js → EditImages.vue2.js} +2 -2
- package/dist/martyrs/src/components/EditImages/EditImages.vue2.js.map +1 -0
- package/dist/martyrs/src/components/Feed/Feed.vue.js +1 -1
- package/dist/martyrs/src/components/FieldPhone/FieldPhone.vue.js +1 -1
- package/dist/martyrs/src/components/FieldPhone/FieldPhone.vue.js.map +1 -1
- package/dist/martyrs/src/components/Loader/Loader.vue.js +1 -2
- package/dist/martyrs/src/components/Loader/Loader.vue.js.map +1 -1
- package/dist/martyrs/src/components/Menu/{Menu.vue.js → Menu.vue2.js} +2 -2
- package/dist/martyrs/src/components/Menu/Menu.vue2.js.map +1 -0
- package/dist/martyrs/src/components/Tab/{Tab.vue.js → Tab.vue2.js} +2 -2
- package/dist/martyrs/src/components/Tab/Tab.vue2.js.map +1 -0
- package/dist/martyrs/src/components/Tree/Tree.vue.js +6 -3
- package/dist/martyrs/src/components/Tree/Tree.vue.js.map +1 -1
- package/dist/martyrs/src/modules/auth/auth.client.js +10 -7
- package/dist/martyrs/src/modules/auth/auth.client.js.map +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/EnterPassword.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/Invite.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/Profile.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/ProfileBlogposts.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/ResetPassword.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/SignIn.vue.js +12 -12
- package/dist/martyrs/src/modules/auth/views/components/pages/SignIn.vue.js.map +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/SignUp.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/router/auth.router.js +116 -0
- package/dist/martyrs/src/modules/auth/views/router/auth.router.js.map +1 -0
- package/dist/martyrs/src/modules/auth/views/router/users.router.js +180 -0
- package/dist/martyrs/src/modules/auth/views/router/users.router.js.map +1 -0
- package/dist/martyrs/src/modules/backoffice/components/partials/Sidebar.vue.js +3 -3
- package/dist/martyrs/src/modules/backoffice/components/partials/Sidebar.vue.js.map +1 -1
- package/dist/martyrs/src/modules/core/locales/en.js +45 -0
- package/dist/martyrs/src/modules/core/locales/en.js.map +1 -1
- package/dist/martyrs/src/modules/core/locales/ru.js +45 -0
- package/dist/martyrs/src/modules/core/locales/ru.js.map +1 -1
- package/dist/martyrs/src/modules/core/views/classes/i18n.manager.js +9 -0
- package/dist/martyrs/src/modules/core/views/classes/i18n.manager.js.map +1 -1
- package/dist/martyrs/src/modules/core/views/components/sections/{Filters.vue.js → Filters.vue2.js} +2 -2
- package/dist/martyrs/src/modules/core/views/components/sections/Filters.vue2.js.map +1 -0
- package/dist/martyrs/src/modules/core/views/components/sections/SectionPageTitle.vue.js +1 -1
- package/dist/martyrs/src/modules/core/views/mixins/mixins.js +1 -2
- package/dist/martyrs/src/modules/core/views/mixins/mixins.js.map +1 -1
- package/dist/martyrs/src/modules/core/views/router/addRoutes.js +6 -1
- package/dist/martyrs/src/modules/core/views/router/addRoutes.js.map +1 -1
- package/dist/martyrs/src/modules/events/components/pages/EditEvent.vue.js +1 -1
- package/dist/martyrs/src/modules/events/components/pages/Event.vue.js +1 -1
- package/dist/martyrs/src/modules/events/components/pages/EventsBackoffice.vue.js +1 -1
- package/dist/martyrs/src/modules/gallery/components/sections/BackofficeGallery.vue.js +1 -1
- package/dist/martyrs/src/modules/inventory/components/pages/InventoryEdit.vue.js +2 -2
- package/dist/martyrs/src/modules/inventory/components/pages/InventoryEdit.vue.js.map +1 -1
- package/dist/martyrs/src/modules/marketplace/views/components/pages/Marketplace.vue.js +1 -1
- package/dist/martyrs/src/modules/marketplace/views/store/marketplace.js +0 -16
- package/dist/martyrs/src/modules/marketplace/views/store/marketplace.js.map +1 -1
- package/dist/martyrs/src/modules/notifications/components/elements/NotificationBadge.vue.js +4 -4
- package/dist/martyrs/src/modules/notifications/components/elements/NotificationBadge.vue.js.map +1 -1
- package/dist/martyrs/src/modules/notifications/components/pages/Notifications.vue.js +1 -1
- package/dist/martyrs/src/modules/orders/components/elements/FieldSubscribeNewsletter.vue.js +3 -0
- package/dist/martyrs/src/modules/orders/components/elements/FieldSubscribeNewsletter.vue.js.map +1 -1
- package/dist/martyrs/src/modules/orders/components/pages/OrderCreateBackoffice.vue.js +1 -1
- package/dist/martyrs/src/modules/orders/components/pages/Orders.vue.js +1 -1
- package/dist/martyrs/src/modules/orders/components/sections/FormDelivery.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/blocks/CardOrganization.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/blocks/CardOrganization.vue.js.map +1 -1
- package/dist/martyrs/src/modules/organizations/components/pages/Organization.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/pages/OrganizationBackoffice.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/pages/OrganizationEdit.vue.js +2 -2
- package/dist/martyrs/src/modules/organizations/components/pages/OrganizationEdit.vue.js.map +1 -1
- package/dist/martyrs/src/modules/organizations/components/pages/Organizations.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/sections/Organizations.vue.js +1 -1
- package/dist/martyrs/src/modules/products/components/blocks/CardCategory.vue.js +1 -1
- package/dist/martyrs/src/modules/products/components/blocks/CardCategory.vue.js.map +1 -1
- package/dist/martyrs/src/modules/products/components/blocks/CardProduct.vue.js +15 -2
- package/dist/martyrs/src/modules/products/components/blocks/CardProduct.vue.js.map +1 -1
- package/dist/martyrs/src/modules/products/components/pages/Categories.vue.js +9 -6
- package/dist/martyrs/src/modules/products/components/pages/Categories.vue.js.map +1 -1
- package/dist/martyrs/src/modules/products/components/pages/CategoryEdit.vue.js +4 -3
- package/dist/martyrs/src/modules/products/components/pages/CategoryEdit.vue.js.map +1 -1
- package/dist/martyrs/src/modules/products/components/pages/Product.vue.js +11 -2
- package/dist/martyrs/src/modules/products/components/pages/Product.vue.js.map +1 -1
- package/dist/martyrs/src/modules/products/components/pages/ProductEdit.vue.js +2 -2
- package/dist/martyrs/src/modules/products/components/pages/Products.vue.js +2 -2
- package/dist/martyrs/src/modules/products/components/sections/EditVariants.vue.js +1 -1
- package/dist/martyrs/src/modules/products/components/sections/SectionProduct.vue.js +11 -8
- package/dist/martyrs/src/modules/products/components/sections/SectionProduct.vue.js.map +1 -1
- package/dist/martyrs/src/modules/rents/views/components/pages/Gant/GanttToolbar.vue.js +1 -1
- package/dist/martyrs/src/modules/rents/views/components/pages/Rents.vue.js +1 -1
- package/dist/martyrs/src/modules/rents/views/components/pages/RentsEdit.vue.js +210 -60
- package/dist/martyrs/src/modules/rents/views/components/pages/RentsEdit.vue.js.map +1 -1
- package/dist/martyrs/src/modules/spots/components/pages/Map.vue.js +3 -3
- package/dist/martyrs/src/modules/spots/components/pages/Map.vue.js.map +1 -1
- package/dist/martyrs/src/modules/spots/components/pages/SpotEdit.vue.js +1 -1
- package/dist/martyrs.css +1 -1
- package/dist/martyrs.es.js +1 -1
- package/dist/music.server.js +11 -12
- package/dist/node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/utils.js +1 -1
- package/dist/node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/renderer/utils.js +1 -1
- package/dist/notifications.server.js +0 -3
- package/dist/orders.server.js +5 -6
- package/dist/organizations.server.js +9 -10
- package/dist/products.server.js +27 -26
- package/dist/{queryProcessor-CBQgZycY.js → queryProcessor-C_5Iipam.js} +4 -1
- package/dist/rents.server.js +2 -3
- package/dist/spots.server.js +1 -1
- package/dist/style.css +38 -23
- package/dist/{web-cNKIl_cL.js → web-BF3ijvEr.js} +1 -1
- package/package.json +1 -1
- package/src/builder/modes/ssr.rspack.dev.js +4 -3
- package/src/builder/rspack/rspack.config.api.js +15 -4
- package/src/builder/rspack/rspack.config.base.js +3 -3
- package/src/builder/rspack/rspack.config.ssr.client.js +28 -28
- package/src/builder/templates/page.js +2 -2
- package/src/components/Button/Button.vue +50 -37
- package/src/components/FieldPhone/FieldPhone.vue +1 -1
- package/src/components/Loader/Loader.vue +1 -1
- package/src/components/Tree/Tree.vue +6 -3
- package/src/modules/PROCESS.md +0 -0
- package/src/modules/TASKS.MD +17 -0
- package/src/modules/auth/auth.client.js +11 -7
- package/src/modules/auth/views/components/pages/SignIn.vue +1 -1
- package/src/modules/auth/views/router/auth.router.js +94 -0
- package/src/modules/auth/views/router/users.router.js +153 -0
- package/src/modules/backoffice/components/partials/Sidebar.vue +7 -7
- package/src/modules/community/community.server.js +8 -0
- package/src/modules/community/policies/blog.policies.js +55 -0
- package/src/modules/community/routes/blog.routes.js +1 -1
- package/src/modules/community/routes/comments.routes.js +1 -1
- package/src/modules/community/routes/reactions.routes.js +1 -4
- package/src/modules/core/controllers/classes/abac/abac.adapter.express.js +206 -124
- package/src/modules/core/controllers/classes/abac/abac.adapter.ws.js +203 -50
- package/src/modules/core/controllers/classes/abac/abac.core.js +127 -36
- package/src/modules/core/controllers/classes/abac/abac.fields.js +144 -179
- package/src/modules/core/controllers/classes/abac/abac.js +201 -10
- package/src/modules/core/controllers/classes/abac/abac.policies.js +147 -57
- package/src/modules/core/controllers/classes/crud/crud.policies.js +5 -5
- package/src/modules/core/controllers/policies/core.policies.js +5 -2
- package/src/modules/core/controllers/utils/queryProcessor.js +4 -1
- package/src/modules/core/core.server.js +1 -0
- package/src/modules/core/locales/en.js +45 -0
- package/src/modules/core/locales/ru.js +45 -0
- package/src/modules/core/models/schemas/common.schema.js +1 -1
- package/src/modules/core/views/classes/i18n.manager.js +13 -0
- package/src/modules/core/views/components/sections/filters/FilterPrice.vue +81 -0
- package/src/modules/core/views/mixins/mixins.js +1 -2
- package/src/modules/core/views/router/addRoutes.js +6 -1
- package/src/modules/events/routes/events.routes.js +1 -1
- package/src/modules/inventory/components/pages/InventoryEdit.vue +3 -3
- package/src/modules/inventory/policies/inventory.policies.js +1 -1
- package/src/modules/inventory/routes/inventory.routes.js +1 -1
- package/src/modules/marketplace/marketplace.router.js +66 -0
- package/src/modules/marketplace/views/components/layouts/Marketplace.vue +363 -0
- package/src/modules/marketplace/views/components/pages/Catalog.vue +73 -0
- package/src/modules/marketplace/views/store/marketplace.js +0 -16
- package/src/modules/music/controllers/stream.controller.js +1 -1
- package/src/modules/music/music.server.js +1 -1
- package/src/modules/music/policies/music.policies.js +3 -2
- package/src/modules/music/router/library.router.js +26 -0
- package/src/modules/music/router/music.router.js +176 -0
- package/src/modules/notifications/components/elements/NotificationBadge.vue +5 -6
- package/src/modules/notifications/notifications.server.js +1 -3
- package/src/modules/orders/components/elements/FieldSubscribeNewsletter.vue +5 -0
- package/src/modules/orders/orders.server.js +0 -1
- package/src/modules/organizations/components/blocks/CardOrganization.vue +2 -2
- package/src/modules/organizations/components/pages/DepartmentEdit.vue +2 -2
- package/src/modules/organizations/components/pages/OrganizationEdit.vue +2 -2
- package/src/modules/organizations/policies/organizations.policies.js +12 -6
- package/src/modules/organizations/routes/organizations.routes.js +1 -3
- package/src/modules/products/components/blocks/CardCategory.vue +1 -1
- package/src/modules/products/components/blocks/CardProduct.vue +16 -2
- package/src/modules/products/components/pages/Categories.vue +9 -6
- package/src/modules/products/components/pages/CategoryEdit.vue +8 -4
- package/src/modules/products/components/pages/Product.vue +11 -5
- package/src/modules/products/components/sections/SectionProduct.vue +11 -7
- package/src/modules/products/controllers/categories.controller.js +32 -27
- package/src/modules/products/routes/categories.routes.js +1 -1
- package/src/modules/rents/controllers/routes/rents.routes.js +1 -1
- package/src/modules/rents/views/components/pages/RentsEdit.vue +208 -49
- package/src/modules/spots/components/pages/Map.vue +2 -2
- package/dist/abac-DYoheWuc.js +0 -1031
- package/dist/core.abac-DUPBnlk6.js +0 -298
- package/dist/core.logger-C3q8A9dl.js +0 -51
- package/dist/martyrs/src/components/EditImages/EditImages.vue.js.map +0 -1
- package/dist/martyrs/src/components/Menu/Menu.vue.js.map +0 -1
- package/dist/martyrs/src/components/Tab/Tab.vue.js.map +0 -1
- package/dist/martyrs/src/modules/auth/auth.router.js +0 -342
- package/dist/martyrs/src/modules/auth/auth.router.js.map +0 -1
- package/dist/martyrs/src/modules/core/views/components/sections/Filters.vue.js.map +0 -1
- package/src/modules/auth/auth.router.js +0 -262
- package/src/modules/core/controllers/classes/abac/v2/abac-core-fixed.js +0 -313
- package/src/modules/core/controllers/classes/abac/v2/abac-express-fixed.js +0 -276
- package/src/modules/core/controllers/classes/abac/v2/abac-fields-fixed.js +0 -425
- package/src/modules/core/controllers/classes/abac/v2/abac-main-fixed.js +0 -295
- package/src/modules/core/controllers/classes/abac/v2/abac-policies-fixed.js +0 -316
- package/src/modules/core/controllers/classes/abac/v2/abac-ws-fixed.js +0 -237
- package/src/modules/core/controllers/classes/core.abac.js +0 -310
- package/src/modules/core/controllers/classes/core.crud.js +0 -89
- package/src/modules/governance/reactcode/eslint.config.js +0 -28
package/dist/abac-DYoheWuc.js
DELETED
|
@@ -1,1031 +0,0 @@
|
|
|
1
|
-
import { C as CacheNamespaced } from "./core.cache-DALYFDdy.js";
|
|
2
|
-
import { L as LoggerNamespaced } from "./core.logger-C3q8A9dl.js";
|
|
3
|
-
class ABACCore {
|
|
4
|
-
constructor(abac) {
|
|
5
|
-
this.abac = abac;
|
|
6
|
-
this.runningPolicies = /* @__PURE__ */ new Map();
|
|
7
|
-
}
|
|
8
|
-
// Нормализация контекста
|
|
9
|
-
normalizeContext(input) {
|
|
10
|
-
const context = {
|
|
11
|
-
user: null,
|
|
12
|
-
action: null,
|
|
13
|
-
resource: null,
|
|
14
|
-
currentResource: null,
|
|
15
|
-
data: {},
|
|
16
|
-
req: null,
|
|
17
|
-
socket: null,
|
|
18
|
-
params: {},
|
|
19
|
-
_cache: /* @__PURE__ */ new Map(),
|
|
20
|
-
_abac: this.abac,
|
|
21
|
-
// Добавляем флаг для пропуска field policies (например, для админов)
|
|
22
|
-
skipFieldPolicies: input.skipFieldPolicies || false
|
|
23
|
-
};
|
|
24
|
-
if (input.req) {
|
|
25
|
-
context.user = input.user || input.req.userId;
|
|
26
|
-
context.data = {
|
|
27
|
-
...input.req.body,
|
|
28
|
-
...input.req.query,
|
|
29
|
-
params: input.req.params
|
|
30
|
-
};
|
|
31
|
-
context.params = input.req.params;
|
|
32
|
-
context.req = input.req;
|
|
33
|
-
} else if (input.socket) {
|
|
34
|
-
context.user = input.user || input.socket.userId;
|
|
35
|
-
context.socket = input.socket;
|
|
36
|
-
context.data = input.data || {};
|
|
37
|
-
}
|
|
38
|
-
return Object.assign(context, input);
|
|
39
|
-
}
|
|
40
|
-
// Выполнение политики с кэшированием
|
|
41
|
-
async executePolicyWithCache(policyName, policyFn, context) {
|
|
42
|
-
const contextCacheKey = `${policyName}_${context.action}`;
|
|
43
|
-
if (context._cache.has(contextCacheKey)) {
|
|
44
|
-
return context._cache.get(contextCacheKey);
|
|
45
|
-
}
|
|
46
|
-
const globalCacheKey = `policy_${policyName}_${context.user}_${context.resource}_${context.action}`;
|
|
47
|
-
if (this.abac.options.cacheEnabled) {
|
|
48
|
-
const cached = await this.abac.cache.get(globalCacheKey);
|
|
49
|
-
if (cached !== void 0) {
|
|
50
|
-
context._cache.set(contextCacheKey, cached);
|
|
51
|
-
return cached;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
const result = await policyFn(context);
|
|
55
|
-
context._cache.set(contextCacheKey, result);
|
|
56
|
-
if (this.abac.options.cacheEnabled) {
|
|
57
|
-
await this.abac.cache.setWithTags(globalCacheKey, result, [
|
|
58
|
-
`user_${context.user}`,
|
|
59
|
-
`resource_${context.resource}`,
|
|
60
|
-
`policy_${policyName}`
|
|
61
|
-
]);
|
|
62
|
-
}
|
|
63
|
-
return result;
|
|
64
|
-
}
|
|
65
|
-
// Выполнение политик с ограничением параллельности
|
|
66
|
-
async executePoliciesLimited(policies, context, stopOnDeny = false) {
|
|
67
|
-
const results = [];
|
|
68
|
-
const limit = this.abac.options.concurrencyLimit;
|
|
69
|
-
const batches = [];
|
|
70
|
-
for (let i = 0; i < policies.length; i += limit) {
|
|
71
|
-
batches.push(policies.slice(i, i + limit));
|
|
72
|
-
}
|
|
73
|
-
for (const batch of batches) {
|
|
74
|
-
const batchPromises = batch.map(async ([name, policy]) => {
|
|
75
|
-
try {
|
|
76
|
-
const policyFn = typeof policy === "function" ? policy : policy.fn;
|
|
77
|
-
const result = await this.executePolicyWithCache(name, policyFn, context);
|
|
78
|
-
return { name, result: this.normalizeResult(result, name) };
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.error(`Error in policy ${name}:`, error);
|
|
81
|
-
return {
|
|
82
|
-
name,
|
|
83
|
-
result: {
|
|
84
|
-
allow: !this.abac.options.strictMode,
|
|
85
|
-
reason: `POLICY_ERROR_${name.toUpperCase()}`
|
|
86
|
-
},
|
|
87
|
-
error
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
const batchResults = await Promise.all(batchPromises);
|
|
92
|
-
results.push(...batchResults);
|
|
93
|
-
if (stopOnDeny) {
|
|
94
|
-
const shouldStop = batchResults.some((r) => !r.result.allow || r.result.force);
|
|
95
|
-
if (shouldStop) break;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return results;
|
|
99
|
-
}
|
|
100
|
-
// Нормализация результата политики
|
|
101
|
-
normalizeResult(result, policyName) {
|
|
102
|
-
if (this.abac.options.strictMode && result === void 0) {
|
|
103
|
-
return { allow: false, force: false, reason: `UNDEFINED_IN_STRICT_MODE_${policyName.toUpperCase()}` };
|
|
104
|
-
}
|
|
105
|
-
if (result && typeof result === "object" && ("allow" in result || "force" in result)) {
|
|
106
|
-
return {
|
|
107
|
-
allow: result.allow !== void 0 ? !!result.allow : true,
|
|
108
|
-
force: !!result.force,
|
|
109
|
-
reason: result.reason || `POLICY_${policyName.toUpperCase()}`
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
if (result === true) {
|
|
113
|
-
return { allow: true, force: false, reason: `ALLOWED_BY_${policyName.toUpperCase()}` };
|
|
114
|
-
}
|
|
115
|
-
if (result === false) {
|
|
116
|
-
return { allow: false, force: false, reason: `DENIED_BY_${policyName.toUpperCase()}` };
|
|
117
|
-
}
|
|
118
|
-
return { allow: !this.abac.options.strictMode, force: false, reason: `NEUTRAL_${policyName.toUpperCase()}` };
|
|
119
|
-
}
|
|
120
|
-
// Основной метод проверки доступа
|
|
121
|
-
async checkAccess(rawContext, customPolicies = {}) {
|
|
122
|
-
const startTime = Date.now();
|
|
123
|
-
const context = this.normalizeContext(rawContext);
|
|
124
|
-
if (context.isServiceRequest) {
|
|
125
|
-
return { allow: true, reason: "SERVICE_REQUEST_ALLOWED" };
|
|
126
|
-
}
|
|
127
|
-
if (!context.user && !context.options?.allowUnauthenticated) {
|
|
128
|
-
return { allow: false, reason: "UNAUTHENTICATED_ACCESS_DENIED" };
|
|
129
|
-
}
|
|
130
|
-
if (!context.currentResource && (context.data?._id || context.data?.params?._id || context.data?.url)) {
|
|
131
|
-
await this.loadResource(context);
|
|
132
|
-
}
|
|
133
|
-
const result = await this.abac.policies.evaluate(context, customPolicies);
|
|
134
|
-
if (this.abac.options.enableAudit) {
|
|
135
|
-
await this.audit(context, result, Date.now() - startTime);
|
|
136
|
-
}
|
|
137
|
-
console.error("result logging:", result);
|
|
138
|
-
return result;
|
|
139
|
-
}
|
|
140
|
-
// Загрузка ресурса
|
|
141
|
-
async loadResource(context) {
|
|
142
|
-
const resourceModel = this.abac.getResourceModel(context.resource);
|
|
143
|
-
if (!resourceModel) return;
|
|
144
|
-
try {
|
|
145
|
-
let currentResource;
|
|
146
|
-
const id = context.data._id || context.data.params?._id;
|
|
147
|
-
if (id) {
|
|
148
|
-
currentResource = await resourceModel.findById(id);
|
|
149
|
-
} else if (context.data.url) {
|
|
150
|
-
currentResource = await resourceModel.findOne({ url: context.data.url });
|
|
151
|
-
}
|
|
152
|
-
if (currentResource) {
|
|
153
|
-
context.currentResource = currentResource;
|
|
154
|
-
context.resourceModel = resourceModel;
|
|
155
|
-
}
|
|
156
|
-
} catch (error) {
|
|
157
|
-
console.error("Resource loading error:", error);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
// Аудит
|
|
161
|
-
async audit(context, result, duration) {
|
|
162
|
-
try {
|
|
163
|
-
let info = JSON.stringify({
|
|
164
|
-
type: "ACCESS_CHECK",
|
|
165
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
166
|
-
user: context.user,
|
|
167
|
-
resource: context.resource,
|
|
168
|
-
action: context.action,
|
|
169
|
-
result: result.allow,
|
|
170
|
-
reason: result.reason,
|
|
171
|
-
duration,
|
|
172
|
-
metadata: context.auditMetadata || {}
|
|
173
|
-
});
|
|
174
|
-
await this.abac.logger.log("info", info);
|
|
175
|
-
console.error("Audit logging:", info);
|
|
176
|
-
} catch (error) {
|
|
177
|
-
console.error("Audit logging error:", error);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
class ABACPolicies {
|
|
182
|
-
constructor(abac) {
|
|
183
|
-
this.abac = abac;
|
|
184
|
-
this.global = /* @__PURE__ */ new Map();
|
|
185
|
-
this.resources = /* @__PURE__ */ new Map();
|
|
186
|
-
this.extensions = /* @__PURE__ */ new Map();
|
|
187
|
-
this.priorities = {
|
|
188
|
-
static: [],
|
|
189
|
-
dynamic: [],
|
|
190
|
-
extensions: []
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
registerGlobalPolicy(name, policyFn, metadata = {}) {
|
|
194
|
-
if (typeof policyFn !== "function") {
|
|
195
|
-
throw new Error(`Global policy "${name}" must be a function`);
|
|
196
|
-
}
|
|
197
|
-
const policy = {
|
|
198
|
-
fn: policyFn,
|
|
199
|
-
type: metadata.type || "dynamic",
|
|
200
|
-
...metadata
|
|
201
|
-
};
|
|
202
|
-
this.global.set(name, policy);
|
|
203
|
-
this.updatePriorities();
|
|
204
|
-
return this.abac;
|
|
205
|
-
}
|
|
206
|
-
registerResourcePolicy(resourceName, policyFn, options = {}) {
|
|
207
|
-
if (typeof policyFn !== "function") {
|
|
208
|
-
throw new Error(`Resource policy for "${resourceName}" must be a function`);
|
|
209
|
-
}
|
|
210
|
-
this.resources.set(resourceName, {
|
|
211
|
-
fn: policyFn,
|
|
212
|
-
modelName: options.modelName || options.model || resourceName,
|
|
213
|
-
...options
|
|
214
|
-
});
|
|
215
|
-
return this.abac;
|
|
216
|
-
}
|
|
217
|
-
registerExtension(moduleName, extensionFn) {
|
|
218
|
-
if (typeof extensionFn !== "function") {
|
|
219
|
-
throw new Error(`Extension "${moduleName}" must be a function`);
|
|
220
|
-
}
|
|
221
|
-
this.extensions.set(moduleName, extensionFn);
|
|
222
|
-
this.updatePriorities();
|
|
223
|
-
return this.abac;
|
|
224
|
-
}
|
|
225
|
-
updatePriorities() {
|
|
226
|
-
this.priorities = {
|
|
227
|
-
static: [],
|
|
228
|
-
dynamic: [],
|
|
229
|
-
extensions: []
|
|
230
|
-
};
|
|
231
|
-
for (const [name, policy] of this.global) {
|
|
232
|
-
const type = policy.type || "dynamic";
|
|
233
|
-
this.priorities[type].push([name, policy]);
|
|
234
|
-
}
|
|
235
|
-
this.priorities.extensions = Array.from(this.extensions.entries());
|
|
236
|
-
}
|
|
237
|
-
async evaluate(context, customPolicies = {}) {
|
|
238
|
-
const core = this.abac.core;
|
|
239
|
-
let hasForceAllow = false;
|
|
240
|
-
let hasForceDisallow = false;
|
|
241
|
-
let hasDeny = false;
|
|
242
|
-
let denyReason = "";
|
|
243
|
-
let allowReason = "";
|
|
244
|
-
const processResult = (result) => {
|
|
245
|
-
if (result.force) {
|
|
246
|
-
if (result.allow) {
|
|
247
|
-
hasForceAllow = true;
|
|
248
|
-
allowReason = result.reason;
|
|
249
|
-
} else {
|
|
250
|
-
hasForceDisallow = true;
|
|
251
|
-
denyReason = result.reason;
|
|
252
|
-
}
|
|
253
|
-
} else if (!result.allow) {
|
|
254
|
-
hasDeny = true;
|
|
255
|
-
if (!denyReason) denyReason = result.reason;
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
const checkForceFlags = () => {
|
|
259
|
-
if (hasForceDisallow) {
|
|
260
|
-
return {
|
|
261
|
-
allow: false,
|
|
262
|
-
reason: denyReason || "FORCE_DENIED_BY_POLICY"
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
if (hasForceAllow) {
|
|
266
|
-
return {
|
|
267
|
-
allow: true,
|
|
268
|
-
reason: allowReason || "FORCE_ALLOWED_BY_POLICY"
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
return null;
|
|
272
|
-
};
|
|
273
|
-
for (const type of ["static", "dynamic"]) {
|
|
274
|
-
const policies = [...this.priorities[type]];
|
|
275
|
-
if (type === "dynamic") {
|
|
276
|
-
for (const [name, fn] of Object.entries(customPolicies)) {
|
|
277
|
-
policies.push([name, { fn, type: "dynamic" }]);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
const results = await core.executePoliciesLimited(
|
|
281
|
-
policies,
|
|
282
|
-
context,
|
|
283
|
-
type === "static"
|
|
284
|
-
// Для static останавливаемся на deny
|
|
285
|
-
);
|
|
286
|
-
for (const { result, error } of results) {
|
|
287
|
-
if (error) continue;
|
|
288
|
-
processResult(result);
|
|
289
|
-
}
|
|
290
|
-
const forceResult = checkForceFlags();
|
|
291
|
-
if (forceResult) return forceResult;
|
|
292
|
-
}
|
|
293
|
-
const resourcePolicy = this.resources.get(context.resource);
|
|
294
|
-
if (resourcePolicy) {
|
|
295
|
-
const results = await core.executePoliciesLimited(
|
|
296
|
-
[[`RESOURCE_${context.resource}`, resourcePolicy]],
|
|
297
|
-
context
|
|
298
|
-
);
|
|
299
|
-
if (!results[0].error) {
|
|
300
|
-
processResult(results[0].result);
|
|
301
|
-
}
|
|
302
|
-
const forceResult = checkForceFlags();
|
|
303
|
-
if (forceResult) return forceResult;
|
|
304
|
-
}
|
|
305
|
-
if (hasDeny) {
|
|
306
|
-
return {
|
|
307
|
-
allow: false,
|
|
308
|
-
reason: denyReason || "DENIED_BY_POLICY"
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
const extensionResults = await core.executePoliciesLimited(
|
|
312
|
-
this.priorities.extensions,
|
|
313
|
-
context
|
|
314
|
-
);
|
|
315
|
-
for (const { result } of extensionResults) {
|
|
316
|
-
if (result.allow) {
|
|
317
|
-
return { allow: true, reason: result.reason };
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
const defaultAllow = !this.abac.options.defaultDeny;
|
|
321
|
-
return {
|
|
322
|
-
allow: defaultAllow,
|
|
323
|
-
reason: defaultAllow ? "DEFAULT_ALLOW" : "DEFAULT_DENY"
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
// Проверка конкретных политик
|
|
327
|
-
async checkPolicies(rawContext, policyNames = [], customPolicies = {}) {
|
|
328
|
-
const context = this.abac.core.normalizeContext(rawContext);
|
|
329
|
-
const policies = this.getPoliciesByNames(policyNames, customPolicies);
|
|
330
|
-
const results = await this.abac.core.executePoliciesLimited(
|
|
331
|
-
Object.entries(policies),
|
|
332
|
-
context,
|
|
333
|
-
this.abac.options.strictMode
|
|
334
|
-
);
|
|
335
|
-
for (const { result } of results) {
|
|
336
|
-
if (result.force) return { allow: result.allow, reason: result.reason };
|
|
337
|
-
if (!result.allow && this.abac.options.strictMode) {
|
|
338
|
-
return { allow: false, reason: result.reason };
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
return { allow: true, reason: "POLICIES_PASSED" };
|
|
342
|
-
}
|
|
343
|
-
getPoliciesByNames(names, customPolicies = {}) {
|
|
344
|
-
const policies = {};
|
|
345
|
-
for (const name of names) {
|
|
346
|
-
if (this.global.has(name)) {
|
|
347
|
-
policies[name] = this.global.get(name);
|
|
348
|
-
} else if (this.extensions.has(name)) {
|
|
349
|
-
policies[name] = { fn: this.extensions.get(name), type: "extension" };
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
Object.assign(policies, customPolicies);
|
|
353
|
-
return policies;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
class ABACFields {
|
|
357
|
-
constructor(abac) {
|
|
358
|
-
this.abac = abac;
|
|
359
|
-
this.configs = /* @__PURE__ */ new Map();
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
* Регистрация field policies
|
|
363
|
-
*/
|
|
364
|
-
registerFieldsPolicy(resourceName, config) {
|
|
365
|
-
const normalized = {};
|
|
366
|
-
for (const [pattern, conf] of Object.entries(config)) {
|
|
367
|
-
const { actions, ...baseSettings } = conf;
|
|
368
|
-
const base = {
|
|
369
|
-
actions: baseSettings.actions || "*",
|
|
370
|
-
access: baseSettings.access || "allow",
|
|
371
|
-
validator: baseSettings.validator || null,
|
|
372
|
-
transform: baseSettings.transform || null,
|
|
373
|
-
rule: baseSettings.rule || "remove",
|
|
374
|
-
force: baseSettings.force || false,
|
|
375
|
-
pattern
|
|
376
|
-
};
|
|
377
|
-
if (actions && typeof actions === "object") {
|
|
378
|
-
normalized[pattern] = {
|
|
379
|
-
base,
|
|
380
|
-
actions: Object.entries(actions).reduce((acc, [action, override]) => {
|
|
381
|
-
acc[action] = { ...base, ...override };
|
|
382
|
-
return acc;
|
|
383
|
-
}, {})
|
|
384
|
-
};
|
|
385
|
-
} else {
|
|
386
|
-
normalized[pattern] = { base };
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
this.configs.set(resourceName, normalized);
|
|
390
|
-
return this;
|
|
391
|
-
}
|
|
392
|
-
/**
|
|
393
|
-
* Проверка доступа к полям
|
|
394
|
-
*/
|
|
395
|
-
async checkFields(context, data, action = null) {
|
|
396
|
-
const normalizedContext = this.abac.core.normalizeContext(context);
|
|
397
|
-
if (normalizedContext.skipFieldPolicies) {
|
|
398
|
-
return {
|
|
399
|
-
allowed: data,
|
|
400
|
-
denied: [],
|
|
401
|
-
errors: [],
|
|
402
|
-
transformed: data
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
const { resource } = normalizedContext;
|
|
406
|
-
const fieldAction = action || normalizedContext.action;
|
|
407
|
-
const config = normalizedContext.options?.fieldsConfig || this.configs.get(resource);
|
|
408
|
-
if (!config) {
|
|
409
|
-
return { allowed: data, denied: [], errors: [], transformed: data };
|
|
410
|
-
}
|
|
411
|
-
if (this.abac.policies && this.abac.policies.priorities.extensions.length > 0) {
|
|
412
|
-
for (const [name, extensionFn] of this.abac.policies.priorities.extensions) {
|
|
413
|
-
try {
|
|
414
|
-
await extensionFn(normalizedContext);
|
|
415
|
-
} catch (error) {
|
|
416
|
-
console.error(`Extension ${name} error:`, error);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
const result = {
|
|
421
|
-
allowed: JSON.parse(JSON.stringify(data)),
|
|
422
|
-
denied: [],
|
|
423
|
-
errors: [],
|
|
424
|
-
transformed: null
|
|
425
|
-
};
|
|
426
|
-
const rules = this._collectRules(data, config, fieldAction);
|
|
427
|
-
const forced = rules.filter((r) => r.rule.force);
|
|
428
|
-
const regular = rules.filter((r) => !r.rule.force);
|
|
429
|
-
for (const { path, value, rule } of [...forced, ...regular]) {
|
|
430
|
-
const processed = /* @__PURE__ */ new Set();
|
|
431
|
-
if (processed.has(path)) continue;
|
|
432
|
-
processed.add(path);
|
|
433
|
-
const hasAccess = await this._checkFieldAccess(
|
|
434
|
-
rule.access,
|
|
435
|
-
normalizedContext,
|
|
436
|
-
path,
|
|
437
|
-
value
|
|
438
|
-
);
|
|
439
|
-
if (!hasAccess) {
|
|
440
|
-
await this._handleDenied(result, path, rule.rule);
|
|
441
|
-
continue;
|
|
442
|
-
}
|
|
443
|
-
if (rule.validator && rule.access !== "optional") {
|
|
444
|
-
const validation = await this._validateField(
|
|
445
|
-
rule.validator,
|
|
446
|
-
value,
|
|
447
|
-
normalizedContext,
|
|
448
|
-
path
|
|
449
|
-
);
|
|
450
|
-
if (!validation.isValid) {
|
|
451
|
-
result.errors.push({ path, errors: validation.errors });
|
|
452
|
-
if (rule.rule === "error") {
|
|
453
|
-
throw new Error(`Validation failed: ${path}`);
|
|
454
|
-
}
|
|
455
|
-
await this._handleDenied(result, path, rule.rule);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
result.transformed = JSON.parse(JSON.stringify(result.allowed));
|
|
460
|
-
for (const { path, value, rule } of [...forced, ...regular]) {
|
|
461
|
-
if (!rule.transform) continue;
|
|
462
|
-
const isDenied = result.denied.some((d) => d.path === path);
|
|
463
|
-
if (isDenied) continue;
|
|
464
|
-
const transformed = await this._transformField(
|
|
465
|
-
rule.transform,
|
|
466
|
-
value,
|
|
467
|
-
normalizedContext,
|
|
468
|
-
path,
|
|
469
|
-
result.transformed
|
|
470
|
-
);
|
|
471
|
-
this._setValue(result.transformed, path, transformed);
|
|
472
|
-
}
|
|
473
|
-
return result;
|
|
474
|
-
}
|
|
475
|
-
/**
|
|
476
|
-
* Проверка доступа к полю
|
|
477
|
-
* @private
|
|
478
|
-
*/
|
|
479
|
-
async _checkFieldAccess(access, context, fieldPath, fieldValue) {
|
|
480
|
-
if (access === "allow") return true;
|
|
481
|
-
if (access === "deny") return false;
|
|
482
|
-
if (access === "optional") return true;
|
|
483
|
-
if (typeof access === "function") {
|
|
484
|
-
try {
|
|
485
|
-
return !!await access(context, fieldPath, fieldValue);
|
|
486
|
-
} catch (e) {
|
|
487
|
-
console.error("Field access check error:", e);
|
|
488
|
-
return false;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
return true;
|
|
492
|
-
}
|
|
493
|
-
/**
|
|
494
|
-
* Валидация поля
|
|
495
|
-
* @private
|
|
496
|
-
*/
|
|
497
|
-
async _validateField(validator, value, context, fieldPath) {
|
|
498
|
-
if (typeof validator === "function") {
|
|
499
|
-
try {
|
|
500
|
-
const result = await validator(value, context, fieldPath);
|
|
501
|
-
if (typeof result === "boolean") {
|
|
502
|
-
return { isValid: result, errors: result ? [] : ["Validation failed"] };
|
|
503
|
-
}
|
|
504
|
-
return result;
|
|
505
|
-
} catch (e) {
|
|
506
|
-
return { isValid: false, errors: [e.message] };
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
if (validator && validator.validate) {
|
|
510
|
-
return validator.validate(value);
|
|
511
|
-
}
|
|
512
|
-
return { isValid: true, errors: [] };
|
|
513
|
-
}
|
|
514
|
-
/**
|
|
515
|
-
* Трансформация поля
|
|
516
|
-
* @private
|
|
517
|
-
*/
|
|
518
|
-
async _transformField(transform, value, context, fieldPath, currentData) {
|
|
519
|
-
try {
|
|
520
|
-
return await transform(value, context, fieldPath, currentData);
|
|
521
|
-
} catch (error) {
|
|
522
|
-
console.error("Field transform error:", error);
|
|
523
|
-
return value;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
/**
|
|
527
|
-
* Сбор применимых правил
|
|
528
|
-
* @private
|
|
529
|
-
*/
|
|
530
|
-
_collectRules(data, config, action) {
|
|
531
|
-
const rules = [];
|
|
532
|
-
const patterns = Object.keys(config);
|
|
533
|
-
const dataPaths = this._extractPaths(data);
|
|
534
|
-
for (const path of dataPaths) {
|
|
535
|
-
for (const pattern of patterns) {
|
|
536
|
-
if (this._matchesPattern(path, pattern)) {
|
|
537
|
-
const fieldConfig = config[pattern];
|
|
538
|
-
let rule;
|
|
539
|
-
if (fieldConfig.actions && fieldConfig.actions[action]) {
|
|
540
|
-
rule = fieldConfig.actions[action];
|
|
541
|
-
} else {
|
|
542
|
-
rule = fieldConfig.base;
|
|
543
|
-
}
|
|
544
|
-
if (this._matchesAction(rule.actions, action)) {
|
|
545
|
-
rules.push({
|
|
546
|
-
path,
|
|
547
|
-
value: this._getValue(data, path),
|
|
548
|
-
rule
|
|
549
|
-
});
|
|
550
|
-
break;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
return rules;
|
|
556
|
-
}
|
|
557
|
-
/**
|
|
558
|
-
* Проверка паттерна
|
|
559
|
-
* @private
|
|
560
|
-
*/
|
|
561
|
-
_matchesPattern(path, pattern) {
|
|
562
|
-
if (pattern === path) return true;
|
|
563
|
-
if (pattern === "*") return true;
|
|
564
|
-
const pathParts = path.split(".");
|
|
565
|
-
const patternParts = pattern.split(".");
|
|
566
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
567
|
-
const pp = patternParts[i];
|
|
568
|
-
const pathPart = pathParts[i];
|
|
569
|
-
if (pp === "**") return true;
|
|
570
|
-
if (pp === "*" && pathPart !== void 0) continue;
|
|
571
|
-
if (pp === "[*]" && /^\[\d+\]$/.test(pathPart)) continue;
|
|
572
|
-
if (pp !== pathPart) return false;
|
|
573
|
-
}
|
|
574
|
-
return pathParts.length === patternParts.length;
|
|
575
|
-
}
|
|
576
|
-
/**
|
|
577
|
-
* Извлечение путей
|
|
578
|
-
* @private
|
|
579
|
-
*/
|
|
580
|
-
_extractPaths(obj, prefix = "") {
|
|
581
|
-
const paths = [];
|
|
582
|
-
if (!obj || typeof obj !== "object") return paths;
|
|
583
|
-
if (Array.isArray(obj)) {
|
|
584
|
-
obj.forEach((item, i) => {
|
|
585
|
-
const path = prefix ? `${prefix}[${i}]` : `[${i}]`;
|
|
586
|
-
paths.push(path);
|
|
587
|
-
paths.push(...this._extractPaths(item, path));
|
|
588
|
-
});
|
|
589
|
-
} else {
|
|
590
|
-
for (const key in obj) {
|
|
591
|
-
if (obj.hasOwnProperty(key)) {
|
|
592
|
-
const path = prefix ? `${prefix}.${key}` : key;
|
|
593
|
-
paths.push(path);
|
|
594
|
-
if (obj[key] && typeof obj[key] === "object") {
|
|
595
|
-
paths.push(...this._extractPaths(obj[key], path));
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
return paths;
|
|
601
|
-
}
|
|
602
|
-
/**
|
|
603
|
-
* Проверка доступа
|
|
604
|
-
* @private
|
|
605
|
-
*/
|
|
606
|
-
async _checkAccess(access, ctx) {
|
|
607
|
-
if (access === "allow") return true;
|
|
608
|
-
if (access === "deny") return false;
|
|
609
|
-
if (access === "optional") return true;
|
|
610
|
-
if (typeof access === "function") {
|
|
611
|
-
try {
|
|
612
|
-
return !!await access(ctx);
|
|
613
|
-
} catch (e) {
|
|
614
|
-
console.error("Access check error:", e);
|
|
615
|
-
return false;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
return true;
|
|
619
|
-
}
|
|
620
|
-
/**
|
|
621
|
-
* Валидация
|
|
622
|
-
* @private
|
|
623
|
-
*/
|
|
624
|
-
async _validate(validator, value, ctx) {
|
|
625
|
-
if (typeof validator === "function") {
|
|
626
|
-
try {
|
|
627
|
-
const result = await validator(value, ctx);
|
|
628
|
-
if (typeof result === "boolean") {
|
|
629
|
-
return { isValid: result, errors: result ? [] : ["Validation failed"] };
|
|
630
|
-
}
|
|
631
|
-
return result;
|
|
632
|
-
} catch (e) {
|
|
633
|
-
return { isValid: false, errors: [e.message] };
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
if (validator && validator.validate) {
|
|
637
|
-
return validator.validate(value);
|
|
638
|
-
}
|
|
639
|
-
return { isValid: true, errors: [] };
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* Обработка отказа
|
|
643
|
-
* @private
|
|
644
|
-
*/
|
|
645
|
-
async _handleDenied(result, path, rule, ctx) {
|
|
646
|
-
result.denied.push({ path, reason: rule });
|
|
647
|
-
if (rule === "remove") {
|
|
648
|
-
this._removePath(result.allowed, path);
|
|
649
|
-
} else if (rule === "error") {
|
|
650
|
-
throw new Error(`Access denied: ${path}`);
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
/**
|
|
654
|
-
* Проверка действия
|
|
655
|
-
* @private
|
|
656
|
-
*/
|
|
657
|
-
_matchesAction(actions, action) {
|
|
658
|
-
if (actions === "*") return true;
|
|
659
|
-
return Array.isArray(actions) ? actions.includes(action) : actions === action;
|
|
660
|
-
}
|
|
661
|
-
/**
|
|
662
|
-
* Получение значения
|
|
663
|
-
* @private
|
|
664
|
-
*/
|
|
665
|
-
_getValue(obj, path) {
|
|
666
|
-
const parts = path.split(/\.|\[|\]/).filter(Boolean);
|
|
667
|
-
let current = obj;
|
|
668
|
-
for (const part of parts) {
|
|
669
|
-
if (!current) return void 0;
|
|
670
|
-
current = /^\d+$/.test(part) ? current[parseInt(part)] : current[part];
|
|
671
|
-
}
|
|
672
|
-
return current;
|
|
673
|
-
}
|
|
674
|
-
/**
|
|
675
|
-
* Установка значения
|
|
676
|
-
* @private
|
|
677
|
-
*/
|
|
678
|
-
_setValue(obj, path, value) {
|
|
679
|
-
const parts = path.split(/\.|\[|\]/).filter(Boolean);
|
|
680
|
-
let current = obj;
|
|
681
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
682
|
-
const part = parts[i];
|
|
683
|
-
const next = parts[i + 1];
|
|
684
|
-
const isArray = /^\d+$/.test(next);
|
|
685
|
-
if (!current[part]) {
|
|
686
|
-
current[part] = isArray ? [] : {};
|
|
687
|
-
}
|
|
688
|
-
current = current[part];
|
|
689
|
-
}
|
|
690
|
-
const last = parts[parts.length - 1];
|
|
691
|
-
if (/^\d+$/.test(last)) {
|
|
692
|
-
current[parseInt(last)] = value;
|
|
693
|
-
} else {
|
|
694
|
-
current[last] = value;
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
/**
|
|
698
|
-
* Удаление пути
|
|
699
|
-
* @private
|
|
700
|
-
*/
|
|
701
|
-
_removePath(obj, path) {
|
|
702
|
-
const parts = path.split(/\.|\[|\]/).filter(Boolean);
|
|
703
|
-
let current = obj;
|
|
704
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
705
|
-
const part = /^\d+$/.test(parts[i]) ? parseInt(parts[i]) : parts[i];
|
|
706
|
-
if (!current[part]) return;
|
|
707
|
-
current = current[part];
|
|
708
|
-
}
|
|
709
|
-
const last = parts[parts.length - 1];
|
|
710
|
-
if (/^\d+$/.test(last)) {
|
|
711
|
-
current.splice(parseInt(last), 1);
|
|
712
|
-
} else {
|
|
713
|
-
delete current[last];
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
getConfig(resourceName) {
|
|
717
|
-
return this.configs.get(resourceName);
|
|
718
|
-
}
|
|
719
|
-
hasConfig(resourceName) {
|
|
720
|
-
return this.configs.has(resourceName);
|
|
721
|
-
}
|
|
722
|
-
removeConfig(resourceName) {
|
|
723
|
-
return this.configs.delete(resourceName);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
class ABACExpressAdapter {
|
|
727
|
-
constructor(abac) {
|
|
728
|
-
this.abac = abac;
|
|
729
|
-
}
|
|
730
|
-
middleware(resource, action, options = {}) {
|
|
731
|
-
return async (req, res, next) => {
|
|
732
|
-
try {
|
|
733
|
-
const context = {
|
|
734
|
-
user: req.userId,
|
|
735
|
-
resource,
|
|
736
|
-
action,
|
|
737
|
-
req,
|
|
738
|
-
data: {
|
|
739
|
-
...req.body,
|
|
740
|
-
...req.query,
|
|
741
|
-
params: req.params
|
|
742
|
-
},
|
|
743
|
-
params: req.params,
|
|
744
|
-
customPolicies: options.policies || {},
|
|
745
|
-
options
|
|
746
|
-
};
|
|
747
|
-
const accessResult = await this.abac.checkAccess(context, context.customPolicies);
|
|
748
|
-
if (!accessResult.allow) {
|
|
749
|
-
return res.status(403).json({
|
|
750
|
-
error: {
|
|
751
|
-
code: "ACCESS_DENIED",
|
|
752
|
-
message: accessResult.reason
|
|
753
|
-
}
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
if (options.checkFields) {
|
|
757
|
-
const fieldsResult = await this.abac.checkFields(context, req.body, action);
|
|
758
|
-
if (fieldsResult.errors.length > 0 && options.strictFieldsMode) {
|
|
759
|
-
return res.status(400).json({
|
|
760
|
-
error: {
|
|
761
|
-
code: "FIELD_VALIDATION_ERROR",
|
|
762
|
-
fields: fieldsResult.errors
|
|
763
|
-
}
|
|
764
|
-
});
|
|
765
|
-
}
|
|
766
|
-
req.body = fieldsResult.allowed;
|
|
767
|
-
req.abacFieldsResult = fieldsResult;
|
|
768
|
-
}
|
|
769
|
-
next();
|
|
770
|
-
} catch (error) {
|
|
771
|
-
console.error("ABAC middleware error:", error);
|
|
772
|
-
res.status(500).json({
|
|
773
|
-
error: {
|
|
774
|
-
code: "INTERNAL_ERROR",
|
|
775
|
-
message: "Access control error"
|
|
776
|
-
}
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
policyMiddleware(policyNames = [], customPolicies = {}, options = {}) {
|
|
782
|
-
return async (req, res, next) => {
|
|
783
|
-
try {
|
|
784
|
-
const context = {
|
|
785
|
-
user: req.userId,
|
|
786
|
-
resource: options.resource || "custom",
|
|
787
|
-
action: options.action || "access",
|
|
788
|
-
req,
|
|
789
|
-
data: {
|
|
790
|
-
...req.body,
|
|
791
|
-
...req.query,
|
|
792
|
-
params: req.params
|
|
793
|
-
},
|
|
794
|
-
params: req.params,
|
|
795
|
-
options
|
|
796
|
-
};
|
|
797
|
-
const result = await this.abac.checkPolicies(context, policyNames, customPolicies);
|
|
798
|
-
if (!result.allow) {
|
|
799
|
-
return res.status(403).json({
|
|
800
|
-
error: {
|
|
801
|
-
code: "POLICY_DENIED",
|
|
802
|
-
message: result.reason
|
|
803
|
-
}
|
|
804
|
-
});
|
|
805
|
-
}
|
|
806
|
-
next();
|
|
807
|
-
} catch (error) {
|
|
808
|
-
console.error("Policy middleware error:", error);
|
|
809
|
-
res.status(500).json({
|
|
810
|
-
error: {
|
|
811
|
-
code: "INTERNAL_ERROR",
|
|
812
|
-
message: "Policy check error"
|
|
813
|
-
}
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
};
|
|
817
|
-
}
|
|
818
|
-
fieldsMiddleware(resource, options = {}) {
|
|
819
|
-
return async (req, res, next) => {
|
|
820
|
-
try {
|
|
821
|
-
const methodActionMap = {
|
|
822
|
-
"GET": "read",
|
|
823
|
-
"POST": "create",
|
|
824
|
-
"PUT": "update",
|
|
825
|
-
"PATCH": "update",
|
|
826
|
-
"DELETE": "delete"
|
|
827
|
-
};
|
|
828
|
-
const action = options.action || methodActionMap[req.method] || "access";
|
|
829
|
-
const context = {
|
|
830
|
-
user: req.userId,
|
|
831
|
-
resource,
|
|
832
|
-
action,
|
|
833
|
-
req,
|
|
834
|
-
data: {
|
|
835
|
-
...req.body,
|
|
836
|
-
...req.query,
|
|
837
|
-
params: req.params
|
|
838
|
-
},
|
|
839
|
-
params: req.params,
|
|
840
|
-
options
|
|
841
|
-
};
|
|
842
|
-
let dataToCheck = req.body;
|
|
843
|
-
if (req.method === "GET" && options.checkQuery) {
|
|
844
|
-
dataToCheck = req.query;
|
|
845
|
-
}
|
|
846
|
-
if (options.checkResponse && res.locals.data) {
|
|
847
|
-
dataToCheck = res.locals.data;
|
|
848
|
-
}
|
|
849
|
-
const fieldsResult = await this.abac.checkFields(context, dataToCheck, action);
|
|
850
|
-
if (fieldsResult.errors.length > 0) {
|
|
851
|
-
if (options.strictMode) {
|
|
852
|
-
return res.status(400).json({
|
|
853
|
-
error: {
|
|
854
|
-
code: "FIELD_VALIDATION_ERROR",
|
|
855
|
-
fields: fieldsResult.errors
|
|
856
|
-
}
|
|
857
|
-
});
|
|
858
|
-
}
|
|
859
|
-
console.warn("Field validation errors:", fieldsResult.errors);
|
|
860
|
-
}
|
|
861
|
-
if (req.method === "GET" && options.checkQuery) {
|
|
862
|
-
req.query = fieldsResult.allowed;
|
|
863
|
-
} else if (options.checkResponse && res.locals.data) {
|
|
864
|
-
res.locals.data = fieldsResult.transformed || fieldsResult.allowed;
|
|
865
|
-
} else {
|
|
866
|
-
req.body = fieldsResult.allowed;
|
|
867
|
-
}
|
|
868
|
-
req.abacFieldsResult = fieldsResult;
|
|
869
|
-
next();
|
|
870
|
-
} catch (error) {
|
|
871
|
-
console.error("Fields middleware error:", error);
|
|
872
|
-
if (options.passErrors) {
|
|
873
|
-
next(error);
|
|
874
|
-
} else {
|
|
875
|
-
res.status(500).json({
|
|
876
|
-
error: {
|
|
877
|
-
code: "INTERNAL_ERROR",
|
|
878
|
-
message: "Field check error"
|
|
879
|
-
}
|
|
880
|
-
});
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
};
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
class ABACWebSocketAdapter {
|
|
887
|
-
constructor(abac) {
|
|
888
|
-
this.abac = abac;
|
|
889
|
-
}
|
|
890
|
-
handler(moduleName, options = {}) {
|
|
891
|
-
return async (ws, message) => {
|
|
892
|
-
try {
|
|
893
|
-
const { action = "access", resource = moduleName } = options;
|
|
894
|
-
const context = {
|
|
895
|
-
user: ws.userId,
|
|
896
|
-
resource,
|
|
897
|
-
action,
|
|
898
|
-
data: message,
|
|
899
|
-
socket: ws,
|
|
900
|
-
customPolicies: options.policies || {}
|
|
901
|
-
};
|
|
902
|
-
const accessResult = await this.abac.checkAccess(context, context.customPolicies);
|
|
903
|
-
if (!accessResult.allow) {
|
|
904
|
-
ws.send(JSON.stringify({
|
|
905
|
-
type: "error",
|
|
906
|
-
error: {
|
|
907
|
-
code: "ACCESS_DENIED",
|
|
908
|
-
message: accessResult.reason
|
|
909
|
-
}
|
|
910
|
-
}));
|
|
911
|
-
return false;
|
|
912
|
-
}
|
|
913
|
-
return true;
|
|
914
|
-
} catch (error) {
|
|
915
|
-
console.error("WebSocket access control error:", error);
|
|
916
|
-
ws.send(JSON.stringify({
|
|
917
|
-
type: "error",
|
|
918
|
-
error: {
|
|
919
|
-
code: "INTERNAL_ERROR",
|
|
920
|
-
message: "Access control error"
|
|
921
|
-
}
|
|
922
|
-
}));
|
|
923
|
-
return false;
|
|
924
|
-
}
|
|
925
|
-
};
|
|
926
|
-
}
|
|
927
|
-
// Метод для RPC вызовов через WebSocket
|
|
928
|
-
rpcHandler(module, method, options = {}) {
|
|
929
|
-
return async (params, context) => {
|
|
930
|
-
const abacContext = {
|
|
931
|
-
user: context.userId,
|
|
932
|
-
resource: module,
|
|
933
|
-
action: method,
|
|
934
|
-
data: params,
|
|
935
|
-
socket: context.ws,
|
|
936
|
-
customPolicies: options.policies || {}
|
|
937
|
-
};
|
|
938
|
-
const accessResult = await this.abac.checkAccess(abacContext, abacContext.customPolicies);
|
|
939
|
-
if (!accessResult.allow) {
|
|
940
|
-
throw new Error(accessResult.reason);
|
|
941
|
-
}
|
|
942
|
-
if (options.checkFields && params) {
|
|
943
|
-
const fieldsResult = await this.abac.checkFields(abacContext, params, method);
|
|
944
|
-
if (fieldsResult.denied.length > 0) {
|
|
945
|
-
if (options.strictFieldsMode) {
|
|
946
|
-
throw new Error(`Fields access denied: ${fieldsResult.denied.map((d) => d.path).join(", ")}`);
|
|
947
|
-
}
|
|
948
|
-
return fieldsResult.allowed;
|
|
949
|
-
}
|
|
950
|
-
return fieldsResult.transformed || params;
|
|
951
|
-
}
|
|
952
|
-
return params;
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
class GlobalABAC {
|
|
957
|
-
constructor(db, options = {}) {
|
|
958
|
-
this.db = db;
|
|
959
|
-
this.options = {
|
|
960
|
-
strictMode: false,
|
|
961
|
-
defaultDeny: false,
|
|
962
|
-
serviceKey: process.env.SERVICE_KEY,
|
|
963
|
-
enableAudit: true,
|
|
964
|
-
cacheEnabled: true,
|
|
965
|
-
cacheTTL: 300,
|
|
966
|
-
// 5 минут
|
|
967
|
-
concurrencyLimit: 10,
|
|
968
|
-
...options
|
|
969
|
-
};
|
|
970
|
-
this.cache = new CacheNamespaced({ ttlSeconds: this.options.cacheTTL });
|
|
971
|
-
this.logger = options.auditLogger || new LoggerNamespaced(db);
|
|
972
|
-
this.core = new ABACCore(this);
|
|
973
|
-
this.policies = new ABACPolicies(this);
|
|
974
|
-
this.fields = new ABACFields(this);
|
|
975
|
-
this.express = new ABACExpressAdapter(this);
|
|
976
|
-
this.websocket = new ABACWebSocketAdapter(this);
|
|
977
|
-
}
|
|
978
|
-
// API методы для регистрации политик
|
|
979
|
-
registerGlobalPolicy(name, policyFn, metadata = {}) {
|
|
980
|
-
return this.policies.registerGlobalPolicy(name, policyFn, metadata);
|
|
981
|
-
}
|
|
982
|
-
registerResourcePolicy(resourceName, policyFn, options = {}) {
|
|
983
|
-
return this.policies.registerResourcePolicy(resourceName, policyFn, options);
|
|
984
|
-
}
|
|
985
|
-
registerFieldsPolicy(resourceName, builderFn) {
|
|
986
|
-
return this.fields.registerFieldsPolicy(resourceName, builderFn);
|
|
987
|
-
}
|
|
988
|
-
registerExtension(moduleName, extensionFn) {
|
|
989
|
-
return this.policies.registerExtension(moduleName, extensionFn);
|
|
990
|
-
}
|
|
991
|
-
// Основные методы проверки
|
|
992
|
-
async checkAccess(context, customPolicies = {}) {
|
|
993
|
-
return this.core.checkAccess(context, customPolicies);
|
|
994
|
-
}
|
|
995
|
-
async checkFields(context, data, action = null, isQuery = false) {
|
|
996
|
-
return this.fields.checkFields(context, data, action, isQuery);
|
|
997
|
-
}
|
|
998
|
-
async checkPolicies(context, policyNames = [], customPolicies = {}) {
|
|
999
|
-
return this.policies.checkPolicies(context, policyNames, customPolicies);
|
|
1000
|
-
}
|
|
1001
|
-
// Адаптеры
|
|
1002
|
-
middleware(resource, action, options = {}) {
|
|
1003
|
-
return this.express.middleware(resource, action, options);
|
|
1004
|
-
}
|
|
1005
|
-
policyMiddleware(policyNames = [], customPolicies = {}, options = {}) {
|
|
1006
|
-
return this.express.policyMiddleware(policyNames, customPolicies, options);
|
|
1007
|
-
}
|
|
1008
|
-
fieldsMiddleware(resource, options = {}) {
|
|
1009
|
-
return this.express.fieldsMiddleware(resource, options);
|
|
1010
|
-
}
|
|
1011
|
-
wsHandler(moduleName, options = {}) {
|
|
1012
|
-
return this.websocket.handler(moduleName, options);
|
|
1013
|
-
}
|
|
1014
|
-
getResourceModel(resourceName) {
|
|
1015
|
-
const resourcePolicy = this.policies.resources.get(resourceName);
|
|
1016
|
-
if (!resourcePolicy) return null;
|
|
1017
|
-
const modelName = resourcePolicy.modelName || resourceName;
|
|
1018
|
-
return this.db[modelName] || null;
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
let instance = null;
|
|
1022
|
-
const getInstance = (db, options) => {
|
|
1023
|
-
if (!instance) {
|
|
1024
|
-
instance = new GlobalABAC(db, options);
|
|
1025
|
-
}
|
|
1026
|
-
return instance;
|
|
1027
|
-
};
|
|
1028
|
-
const ABAC = { getInstance };
|
|
1029
|
-
export {
|
|
1030
|
-
ABAC as A
|
|
1031
|
-
};
|