@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
|
@@ -4,191 +4,273 @@ export default class ABACExpressAdapter {
|
|
|
4
4
|
this.abac = abac;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Основной middleware - разделен на составные части
|
|
9
|
+
*/
|
|
7
10
|
middleware(resource, action, options = {}) {
|
|
11
|
+
const middlewares = [];
|
|
12
|
+
|
|
13
|
+
// 1. Access control
|
|
14
|
+
middlewares.push(this._accessMiddleware(resource, action, options));
|
|
15
|
+
|
|
16
|
+
// 2. Field validation (если включена)
|
|
17
|
+
if (options.checkFields) {
|
|
18
|
+
middlewares.push(this._fieldsValidationMiddleware(resource, action, options));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return middlewares;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Middleware проверки доступа
|
|
26
|
+
* @private
|
|
27
|
+
*/
|
|
28
|
+
_accessMiddleware(resource, action, options = {}) {
|
|
8
29
|
return async (req, res, next) => {
|
|
9
30
|
try {
|
|
10
|
-
const context =
|
|
11
|
-
user: req.userId,
|
|
12
|
-
resource,
|
|
13
|
-
action,
|
|
14
|
-
req,
|
|
15
|
-
data: {
|
|
16
|
-
...req.body,
|
|
17
|
-
...req.query,
|
|
18
|
-
params: req.params,
|
|
19
|
-
},
|
|
20
|
-
params: req.params,
|
|
21
|
-
customPolicies: options.policies || {},
|
|
22
|
-
options
|
|
23
|
-
};
|
|
24
|
-
|
|
31
|
+
const context = this._buildContext(req, resource, action, options);
|
|
25
32
|
const accessResult = await this.abac.checkAccess(context, context.customPolicies);
|
|
26
33
|
|
|
27
34
|
if (!accessResult.allow) {
|
|
28
|
-
return res.
|
|
29
|
-
error: {
|
|
30
|
-
code: 'ACCESS_DENIED',
|
|
31
|
-
message: accessResult.reason
|
|
32
|
-
}
|
|
33
|
-
});
|
|
35
|
+
return this._sendAccessDenied(res, accessResult.reason);
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
req.abacFieldsResult = fieldsResult;
|
|
52
|
-
}
|
|
38
|
+
// Сохраняем результат для последующих middleware
|
|
39
|
+
req.abacContext = context;
|
|
40
|
+
req.abacAccessResult = accessResult;
|
|
41
|
+
next();
|
|
42
|
+
} catch (error) {
|
|
43
|
+
this.abac.logger?.error('ABAC middleware error', {
|
|
44
|
+
resource,
|
|
45
|
+
action,
|
|
46
|
+
error: error.message,
|
|
47
|
+
stack: error.stack
|
|
48
|
+
});
|
|
49
|
+
return this._sendError(res);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Middleware проверки полей
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
58
|
+
_fieldsValidationMiddleware(resource, action, options = {}) {
|
|
59
|
+
return async (req, res, next) => {
|
|
60
|
+
try {
|
|
61
|
+
const context = req.abacContext || this._buildContext(req, resource, action, options);
|
|
62
|
+
const fieldsResult = await this.abac.checkFields(context, req.body, action);
|
|
63
|
+
|
|
64
|
+
if (fieldsResult.errors.length > 0 && options.strictFieldsMode) {
|
|
65
|
+
return this._sendFieldError(res, fieldsResult.errors);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Заменяем body отфильтрованными данными
|
|
69
|
+
req.body = fieldsResult.allowed;
|
|
70
|
+
req.abacFieldsResult = fieldsResult;
|
|
71
|
+
|
|
54
72
|
next();
|
|
55
73
|
} catch (error) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
message: 'Access control error'
|
|
61
|
-
}
|
|
74
|
+
this.abac.logger?.error('Fields middleware error', {
|
|
75
|
+
resource,
|
|
76
|
+
action,
|
|
77
|
+
error: error.message
|
|
62
78
|
});
|
|
79
|
+
|
|
80
|
+
if (options.passErrors) {
|
|
81
|
+
next(error);
|
|
82
|
+
} else {
|
|
83
|
+
return this._sendError(res);
|
|
84
|
+
}
|
|
63
85
|
}
|
|
64
86
|
};
|
|
65
87
|
}
|
|
66
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Построение контекста
|
|
91
|
+
* @private
|
|
92
|
+
*/
|
|
93
|
+
_buildContext(req, resource, action, options = {}) {
|
|
94
|
+
return {
|
|
95
|
+
user: req.userId,
|
|
96
|
+
resource,
|
|
97
|
+
action,
|
|
98
|
+
req,
|
|
99
|
+
// НЕ смешиваем body и query - передаем структурированно
|
|
100
|
+
data: {
|
|
101
|
+
body: req.body || {},
|
|
102
|
+
query: req.query || {},
|
|
103
|
+
params: req.params || {}
|
|
104
|
+
},
|
|
105
|
+
params: req.params,
|
|
106
|
+
customPolicies: options.policies || {},
|
|
107
|
+
options
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Policy middleware
|
|
113
|
+
*/
|
|
67
114
|
policyMiddleware(policyNames = [], customPolicies = {}, options = {}) {
|
|
68
115
|
return async (req, res, next) => {
|
|
69
116
|
try {
|
|
70
|
-
const context =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
req,
|
|
75
|
-
data: {
|
|
76
|
-
...req.body,
|
|
77
|
-
...req.query,
|
|
78
|
-
params: req.params,
|
|
79
|
-
},
|
|
80
|
-
params: req.params,
|
|
117
|
+
const context = this._buildContext(
|
|
118
|
+
req,
|
|
119
|
+
options.resource || 'custom',
|
|
120
|
+
options.action || 'access',
|
|
81
121
|
options
|
|
82
|
-
|
|
122
|
+
);
|
|
83
123
|
|
|
84
124
|
const result = await this.abac.checkPolicies(context, policyNames, customPolicies);
|
|
85
125
|
|
|
86
126
|
if (!result.allow) {
|
|
87
|
-
return res.
|
|
88
|
-
error: {
|
|
89
|
-
code: 'POLICY_DENIED',
|
|
90
|
-
message: result.reason
|
|
91
|
-
}
|
|
92
|
-
});
|
|
127
|
+
return this._sendPolicyDenied(res, result.reason);
|
|
93
128
|
}
|
|
94
129
|
|
|
130
|
+
req.abacPolicyResult = result;
|
|
95
131
|
next();
|
|
96
132
|
} catch (error) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
error:
|
|
100
|
-
code: 'INTERNAL_ERROR',
|
|
101
|
-
message: 'Policy check error'
|
|
102
|
-
}
|
|
133
|
+
this.abac.logger?.error('Policy middleware error', {
|
|
134
|
+
policies: policyNames,
|
|
135
|
+
error: error.message
|
|
103
136
|
});
|
|
137
|
+
return this._sendError(res);
|
|
104
138
|
}
|
|
105
139
|
};
|
|
106
140
|
}
|
|
107
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Fields middleware для проверки/трансформации полей
|
|
144
|
+
*/
|
|
108
145
|
fieldsMiddleware(resource, options = {}) {
|
|
109
146
|
return async (req, res, next) => {
|
|
110
147
|
try {
|
|
111
148
|
// Определяем action из метода запроса
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
'POST': 'create',
|
|
115
|
-
'PUT': 'update',
|
|
116
|
-
'PATCH': 'update',
|
|
117
|
-
'DELETE': 'delete'
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const action = options.action || methodActionMap[req.method] || 'access';
|
|
121
|
-
|
|
122
|
-
const context = {
|
|
123
|
-
user: req.userId,
|
|
124
|
-
resource,
|
|
125
|
-
action,
|
|
126
|
-
req,
|
|
127
|
-
data: {
|
|
128
|
-
...req.body,
|
|
129
|
-
...req.query,
|
|
130
|
-
params: req.params,
|
|
131
|
-
},
|
|
132
|
-
params: req.params,
|
|
133
|
-
options
|
|
134
|
-
};
|
|
149
|
+
const action = options.action || this._getActionFromMethod(req.method);
|
|
150
|
+
const context = this._buildContext(req, resource, action, options);
|
|
135
151
|
|
|
136
152
|
// Определяем какие данные проверять
|
|
137
|
-
let dataToCheck = req
|
|
138
|
-
|
|
139
|
-
// Для GET запросов проверяем query параметры
|
|
140
|
-
if (req.method === 'GET' && options.checkQuery) {
|
|
141
|
-
dataToCheck = req.query;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Для ответов проверяем res.locals.data
|
|
145
|
-
if (options.checkResponse && res.locals.data) {
|
|
146
|
-
dataToCheck = res.locals.data;
|
|
147
|
-
}
|
|
153
|
+
let dataToCheck = this._getDataToCheck(req, res, options);
|
|
148
154
|
|
|
149
155
|
const fieldsResult = await this.abac.checkFields(context, dataToCheck, action);
|
|
150
156
|
|
|
151
157
|
// Обработка ошибок
|
|
152
158
|
if (fieldsResult.errors.length > 0) {
|
|
153
159
|
if (options.strictMode) {
|
|
154
|
-
return res.
|
|
155
|
-
error: {
|
|
156
|
-
code: 'FIELD_VALIDATION_ERROR',
|
|
157
|
-
fields: fieldsResult.errors
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
+
return this._sendFieldError(res, fieldsResult.errors);
|
|
160
161
|
}
|
|
161
162
|
// В нестрогом режиме логируем ошибки
|
|
162
|
-
|
|
163
|
+
this.abac.logger?.warn('Field validation errors', {
|
|
164
|
+
resource,
|
|
165
|
+
action,
|
|
166
|
+
errors: fieldsResult.errors
|
|
167
|
+
});
|
|
163
168
|
}
|
|
164
169
|
|
|
165
170
|
// Применяем результаты
|
|
166
|
-
|
|
167
|
-
req.query = fieldsResult.allowed;
|
|
168
|
-
} else if (options.checkResponse && res.locals.data) {
|
|
169
|
-
res.locals.data = fieldsResult.transformed || fieldsResult.allowed;
|
|
170
|
-
} else {
|
|
171
|
-
req.body = fieldsResult.allowed;
|
|
172
|
-
}
|
|
171
|
+
this._applyFieldsResult(req, res, options, fieldsResult);
|
|
173
172
|
|
|
174
|
-
// Сохраняем результат
|
|
173
|
+
// Сохраняем результат
|
|
175
174
|
req.abacFieldsResult = fieldsResult;
|
|
176
175
|
|
|
177
176
|
next();
|
|
178
177
|
} catch (error) {
|
|
179
|
-
|
|
178
|
+
this.abac.logger?.error('Fields middleware error', {
|
|
179
|
+
resource,
|
|
180
|
+
error: error.message
|
|
181
|
+
});
|
|
180
182
|
|
|
181
183
|
if (options.passErrors) {
|
|
182
184
|
next(error);
|
|
183
185
|
} else {
|
|
184
|
-
|
|
185
|
-
error: {
|
|
186
|
-
code: 'INTERNAL_ERROR',
|
|
187
|
-
message: 'Field check error'
|
|
188
|
-
}
|
|
189
|
-
});
|
|
186
|
+
return this._sendError(res);
|
|
190
187
|
}
|
|
191
188
|
}
|
|
192
189
|
};
|
|
193
190
|
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Получение action из HTTP метода
|
|
194
|
+
* @private
|
|
195
|
+
*/
|
|
196
|
+
_getActionFromMethod(method) {
|
|
197
|
+
const methodActionMap = {
|
|
198
|
+
'GET': 'read',
|
|
199
|
+
'POST': 'create',
|
|
200
|
+
'PUT': 'update',
|
|
201
|
+
'PATCH': 'update',
|
|
202
|
+
'DELETE': 'delete'
|
|
203
|
+
};
|
|
204
|
+
return methodActionMap[method] || 'access';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Определение данных для проверки
|
|
209
|
+
* @private
|
|
210
|
+
*/
|
|
211
|
+
_getDataToCheck(req, res, options) {
|
|
212
|
+
if (req.method === 'GET' && options.checkQuery) {
|
|
213
|
+
return req.query;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (options.checkResponse && res.locals.data) {
|
|
217
|
+
return res.locals.data;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return req.body;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Применение результатов проверки полей
|
|
225
|
+
* @private
|
|
226
|
+
*/
|
|
227
|
+
_applyFieldsResult(req, res, options, fieldsResult) {
|
|
228
|
+
if (req.method === 'GET' && options.checkQuery) {
|
|
229
|
+
req.query = fieldsResult.allowed;
|
|
230
|
+
} else if (options.checkResponse && res.locals.data) {
|
|
231
|
+
res.locals.data = fieldsResult.transformed || fieldsResult.allowed;
|
|
232
|
+
} else {
|
|
233
|
+
req.body = fieldsResult.allowed;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Отправка ошибок - унифицированные методы
|
|
239
|
+
* @private
|
|
240
|
+
*/
|
|
241
|
+
_sendAccessDenied(res, reason) {
|
|
242
|
+
return res.status(403).json({
|
|
243
|
+
error: {
|
|
244
|
+
code: 'ACCESS_DENIED',
|
|
245
|
+
message: reason
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
_sendPolicyDenied(res, reason) {
|
|
251
|
+
return res.status(403).json({
|
|
252
|
+
error: {
|
|
253
|
+
code: 'POLICY_DENIED',
|
|
254
|
+
message: reason
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
_sendFieldError(res, errors) {
|
|
260
|
+
return res.status(400).json({
|
|
261
|
+
error: {
|
|
262
|
+
code: 'FIELD_VALIDATION_ERROR',
|
|
263
|
+
fields: errors
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
_sendError(res) {
|
|
269
|
+
return res.status(500).json({
|
|
270
|
+
error: {
|
|
271
|
+
code: 'INTERNAL_ERROR',
|
|
272
|
+
message: 'Access control error'
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
194
276
|
}
|
|
@@ -4,81 +4,234 @@ export default class ABACWebSocketAdapter {
|
|
|
4
4
|
this.abac = abac;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* WebSocket handler для проверки доступа
|
|
9
|
+
* @param {string} moduleName - Имя модуля
|
|
10
|
+
* @param {Object} [options] - Опции
|
|
11
|
+
* @returns {Function} Handler функция
|
|
12
|
+
*/
|
|
7
13
|
handler(moduleName, options = {}) {
|
|
8
14
|
return async (ws, message) => {
|
|
9
15
|
try {
|
|
10
16
|
const { action = 'access', resource = moduleName } = options;
|
|
11
17
|
|
|
12
|
-
const context =
|
|
13
|
-
user: ws.userId,
|
|
14
|
-
resource,
|
|
15
|
-
action,
|
|
16
|
-
data: message,
|
|
17
|
-
socket: ws,
|
|
18
|
-
customPolicies: options.policies || {}
|
|
19
|
-
};
|
|
20
|
-
|
|
18
|
+
const context = this._buildContext(ws, resource, action, message, options);
|
|
21
19
|
const accessResult = await this.abac.checkAccess(context, context.customPolicies);
|
|
22
20
|
|
|
23
21
|
if (!accessResult.allow) {
|
|
24
|
-
|
|
25
|
-
type: 'error',
|
|
26
|
-
error: {
|
|
27
|
-
code: 'ACCESS_DENIED',
|
|
28
|
-
message: accessResult.reason
|
|
29
|
-
}
|
|
30
|
-
}));
|
|
22
|
+
await this._sendError(ws, 'ACCESS_DENIED', accessResult.reason);
|
|
31
23
|
return false;
|
|
32
24
|
}
|
|
33
25
|
|
|
26
|
+
// Проверка полей если включена
|
|
27
|
+
if (options.checkFields && message) {
|
|
28
|
+
const fieldsResult = await this.abac.checkFields(context, message, action);
|
|
29
|
+
|
|
30
|
+
if (fieldsResult.errors.length > 0 && options.strictFieldsMode) {
|
|
31
|
+
await this._sendError(ws, 'FIELD_VALIDATION_ERROR', 'Field validation failed', {
|
|
32
|
+
fields: fieldsResult.errors
|
|
33
|
+
});
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Возвращаем отфильтрованные данные
|
|
38
|
+
return {
|
|
39
|
+
allowed: true,
|
|
40
|
+
data: fieldsResult.transformed || fieldsResult.allowed,
|
|
41
|
+
fieldsResult
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
return true;
|
|
35
46
|
} catch (error) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}));
|
|
47
|
+
this.abac.logger?.error('WebSocket access control error', {
|
|
48
|
+
module: moduleName,
|
|
49
|
+
error: error.message,
|
|
50
|
+
stack: error.stack
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await this._sendError(ws, 'INTERNAL_ERROR', 'Access control error');
|
|
44
54
|
return false;
|
|
45
55
|
}
|
|
46
56
|
};
|
|
47
57
|
}
|
|
48
58
|
|
|
49
|
-
|
|
59
|
+
/**
|
|
60
|
+
* RPC handler для вызовов через WebSocket
|
|
61
|
+
* @param {string} module - Модуль
|
|
62
|
+
* @param {string} method - Метод
|
|
63
|
+
* @param {Object} [options] - Опции
|
|
64
|
+
* @returns {Function} RPC handler
|
|
65
|
+
*/
|
|
50
66
|
rpcHandler(module, method, options = {}) {
|
|
51
|
-
return async (params,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
};
|
|
67
|
+
return async (params, rpcContext) => {
|
|
68
|
+
try {
|
|
69
|
+
const context = this._buildRPCContext(rpcContext, module, method, params, options);
|
|
70
|
+
const accessResult = await this.abac.checkAccess(context, context.customPolicies);
|
|
71
|
+
|
|
72
|
+
if (!accessResult.allow) {
|
|
73
|
+
throw new RPCError('ACCESS_DENIED', accessResult.reason);
|
|
74
|
+
}
|
|
60
75
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
// Проверка полей если нужно
|
|
77
|
+
if (options.checkFields && params) {
|
|
78
|
+
const fieldsResult = await this.abac.checkFields(context, params, method);
|
|
79
|
+
|
|
80
|
+
if (fieldsResult.errors.length > 0) {
|
|
81
|
+
if (options.strictFieldsMode) {
|
|
82
|
+
throw new RPCError(
|
|
83
|
+
'FIELD_VALIDATION_ERROR',
|
|
84
|
+
`Fields validation failed: ${fieldsResult.errors.map(e => e.path).join(', ')}`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Логируем ошибки в нестрогом режиме
|
|
89
|
+
this.abac.logger?.warn('RPC field validation errors', {
|
|
90
|
+
module,
|
|
91
|
+
method,
|
|
92
|
+
errors: fieldsResult.errors
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Возвращаем трансформированные данные
|
|
97
|
+
return fieldsResult.transformed || fieldsResult.allowed;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return params;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
// Пробрасываем RPC ошибки как есть
|
|
103
|
+
if (error instanceof RPCError) {
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this.abac.logger?.error('RPC access control error', {
|
|
108
|
+
module,
|
|
109
|
+
method,
|
|
110
|
+
error: error.message,
|
|
111
|
+
stack: error.stack
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
throw new RPCError('INTERNAL_ERROR', 'Access control error');
|
|
65
115
|
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
66
118
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Middleware для обработки WebSocket сообщений
|
|
121
|
+
* @param {Object} [options] - Опции
|
|
122
|
+
* @returns {Function} Middleware функция
|
|
123
|
+
*/
|
|
124
|
+
messageMiddleware(options = {}) {
|
|
125
|
+
return async (ws, message, next) => {
|
|
126
|
+
try {
|
|
127
|
+
const { resource = 'message', action = 'send' } = options;
|
|
128
|
+
const context = this._buildContext(ws, resource, action, message, options);
|
|
129
|
+
|
|
130
|
+
const accessResult = await this.abac.checkAccess(context, options.policies || {});
|
|
131
|
+
|
|
132
|
+
if (!accessResult.allow) {
|
|
133
|
+
await this._sendError(ws, 'ACCESS_DENIED', accessResult.reason);
|
|
134
|
+
return;
|
|
76
135
|
}
|
|
77
|
-
|
|
78
|
-
|
|
136
|
+
|
|
137
|
+
// Сохраняем результат в контексте WS
|
|
138
|
+
ws.abacContext = context;
|
|
139
|
+
ws.abacAccessResult = accessResult;
|
|
140
|
+
|
|
141
|
+
next();
|
|
142
|
+
} catch (error) {
|
|
143
|
+
this.abac.logger?.error('WebSocket middleware error', {
|
|
144
|
+
error: error.message
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await this._sendError(ws, 'INTERNAL_ERROR', 'Access control error');
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Построение контекста для WebSocket
|
|
154
|
+
* @private
|
|
155
|
+
*/
|
|
156
|
+
_buildContext(ws, resource, action, data, options) {
|
|
157
|
+
return {
|
|
158
|
+
user: ws.userId || ws.user?.id,
|
|
159
|
+
resource,
|
|
160
|
+
action,
|
|
161
|
+
data: data || {},
|
|
162
|
+
socket: ws,
|
|
163
|
+
customPolicies: options.policies || {},
|
|
164
|
+
options,
|
|
165
|
+
// Дополнительная информация о соединении
|
|
166
|
+
connectionInfo: {
|
|
167
|
+
id: ws.id,
|
|
168
|
+
remoteAddress: ws._socket?.remoteAddress,
|
|
169
|
+
protocol: ws.protocol,
|
|
170
|
+
readyState: ws.readyState
|
|
79
171
|
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
80
174
|
|
|
81
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Построение контекста для RPC
|
|
177
|
+
* @private
|
|
178
|
+
*/
|
|
179
|
+
_buildRPCContext(rpcContext, module, method, params, options) {
|
|
180
|
+
return {
|
|
181
|
+
user: rpcContext.userId || rpcContext.user?.id,
|
|
182
|
+
resource: module,
|
|
183
|
+
action: method,
|
|
184
|
+
data: params || {},
|
|
185
|
+
socket: rpcContext.ws,
|
|
186
|
+
customPolicies: options.policies || {},
|
|
187
|
+
options,
|
|
188
|
+
rpcInfo: {
|
|
189
|
+
id: rpcContext.id,
|
|
190
|
+
module,
|
|
191
|
+
method,
|
|
192
|
+
timestamp: new Date().toISOString()
|
|
193
|
+
}
|
|
82
194
|
};
|
|
83
195
|
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Отправка ошибки через WebSocket
|
|
199
|
+
* @private
|
|
200
|
+
*/
|
|
201
|
+
async _sendError(ws, code, message, details = {}) {
|
|
202
|
+
if (ws.readyState !== 1) { // WebSocket.OPEN
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const errorMessage = JSON.stringify({
|
|
208
|
+
type: 'error',
|
|
209
|
+
error: {
|
|
210
|
+
code,
|
|
211
|
+
message,
|
|
212
|
+
...details
|
|
213
|
+
},
|
|
214
|
+
timestamp: new Date().toISOString()
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
ws.send(errorMessage);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
this.abac.logger?.error('Failed to send WebSocket error', {
|
|
220
|
+
originalError: { code, message },
|
|
221
|
+
sendError: error.message
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Класс для RPC ошибок
|
|
229
|
+
*/
|
|
230
|
+
class RPCError extends Error {
|
|
231
|
+
constructor(code, message, details = {}) {
|
|
232
|
+
super(message);
|
|
233
|
+
this.name = 'RPCError';
|
|
234
|
+
this.code = code;
|
|
235
|
+
this.details = details;
|
|
236
|
+
}
|
|
84
237
|
}
|