@ketvietlab/ketsuite 0.1.3 → 0.1.6
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/cli.js +1 -1
- package/dist/deployment.d.ts.map +1 -1
- package/dist/deployment.js +3 -0
- package/dist/deployment.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/account_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/account_staff_channel/channel-routes.js +1 -12
- package/dist/modules/account_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/attendance/channel-routes.d.ts.map +1 -1
- package/dist/modules/attendance/channel-routes.js +1 -12
- package/dist/modules/attendance/channel-routes.js.map +1 -1
- package/dist/modules/backend/design/HANDOFF.md +2 -2
- package/dist/modules/backend/design/auth.css +12 -0
- package/dist/modules/backend/design/client/table-selection.d.mts +3 -3
- package/dist/modules/backend/design/client/table-selection.d.mts.map +1 -1
- package/dist/modules/backend/design/client/table-selection.mjs +1 -1
- package/dist/modules/backend/design/client/table-selection.mjs.map +4 -4
- package/dist/modules/backend/design/content.css +43 -0
- package/dist/modules/backend/design/design-system.css +1 -1
- package/dist/modules/backend/design/forms.css +84 -1
- package/dist/modules/backend/design/lists.css +26 -18
- package/dist/modules/backend/design/record.css +5 -2
- package/dist/modules/backend/design/responsive.css +5 -6
- package/dist/modules/backend/design/user-workflow.css +257 -0
- package/dist/modules/backend/forms.d.ts +38 -1
- package/dist/modules/backend/forms.d.ts.map +1 -1
- package/dist/modules/backend/forms.js +29 -0
- package/dist/modules/backend/forms.js.map +1 -1
- package/dist/modules/backend/index.d.ts +3 -2
- package/dist/modules/backend/index.d.ts.map +1 -1
- package/dist/modules/backend/index.js +3 -2
- package/dist/modules/backend/index.js.map +1 -1
- package/dist/modules/backend/messages.d.ts.map +1 -1
- package/dist/modules/backend/messages.js +42 -2
- package/dist/modules/backend/messages.js.map +1 -1
- package/dist/modules/channel_api/core.d.ts +30 -0
- package/dist/modules/channel_api/core.d.ts.map +1 -1
- package/dist/modules/channel_api/core.js +28 -0
- package/dist/modules/channel_api/core.js.map +1 -1
- package/dist/modules/crm/functions.d.ts +5 -0
- package/dist/modules/crm/functions.d.ts.map +1 -1
- package/dist/modules/crm/functions.js +105 -10
- package/dist/modules/crm/functions.js.map +1 -1
- package/dist/modules/crm/jobs.d.ts.map +1 -1
- package/dist/modules/crm/jobs.js +1 -0
- package/dist/modules/crm/jobs.js.map +1 -1
- package/dist/modules/crm/operations.d.ts.map +1 -1
- package/dist/modules/crm/operations.js +2 -1
- package/dist/modules/crm/operations.js.map +1 -1
- package/dist/modules/crm_backend/index.d.ts.map +1 -1
- package/dist/modules/crm_backend/index.js +5 -2
- package/dist/modules/crm_backend/index.js.map +1 -1
- package/dist/modules/crm_backend/messages.d.ts +116 -0
- package/dist/modules/crm_backend/messages.d.ts.map +1 -1
- package/dist/modules/crm_backend/messages.js +122 -6
- package/dist/modules/crm_backend/messages.js.map +1 -1
- package/dist/modules/crm_backend/routes.d.ts.map +1 -1
- package/dist/modules/crm_backend/routes.js +434 -174
- package/dist/modules/crm_backend/routes.js.map +1 -1
- package/dist/modules/crm_backend/screens/case-create.d.ts +7 -0
- package/dist/modules/crm_backend/screens/case-create.d.ts.map +1 -1
- package/dist/modules/crm_backend/screens/case-create.js +13 -4
- package/dist/modules/crm_backend/screens/case-create.js.map +1 -1
- package/dist/modules/crm_backend/screens/case-detail.d.ts +6 -0
- package/dist/modules/crm_backend/screens/case-detail.d.ts.map +1 -1
- package/dist/modules/crm_backend/screens/case-detail.js +73 -23
- package/dist/modules/crm_backend/screens/case-detail.js.map +1 -1
- package/dist/modules/crm_backend/screens/configuration.d.ts +21 -3
- package/dist/modules/crm_backend/screens/configuration.d.ts.map +1 -1
- package/dist/modules/crm_backend/screens/configuration.js +260 -55
- package/dist/modules/crm_backend/screens/configuration.js.map +1 -1
- package/dist/modules/crm_backend/screens/index.d.ts +3 -3
- package/dist/modules/crm_backend/screens/index.d.ts.map +1 -1
- package/dist/modules/crm_backend/screens/index.js +2 -2
- package/dist/modules/crm_backend/screens/index.js.map +1 -1
- package/dist/modules/crm_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/crm_staff_channel/channel-routes.js +1 -12
- package/dist/modules/crm_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/flow/membership.d.ts.map +1 -1
- package/dist/modules/flow/membership.js +7 -1
- package/dist/modules/flow/membership.js.map +1 -1
- package/dist/modules/flow/operations.d.ts +10 -4
- package/dist/modules/flow/operations.d.ts.map +1 -1
- package/dist/modules/flow/operations.js +10 -4
- package/dist/modules/flow/operations.js.map +1 -1
- package/dist/modules/flow_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/flow_staff_channel/channel-routes.js +1 -20
- package/dist/modules/flow_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/hospitality_core/messages.d.ts.map +1 -1
- package/dist/modules/hospitality_core/messages.js +44 -0
- package/dist/modules/hospitality_core/messages.js.map +1 -1
- package/dist/modules/hospitality_core/night-audit.d.ts.map +1 -1
- package/dist/modules/hospitality_core/night-audit.js +7 -0
- package/dist/modules/hospitality_core/night-audit.js.map +1 -1
- package/dist/modules/hospitality_core/operations.d.ts.map +1 -1
- package/dist/modules/hospitality_core/operations.js +150 -40
- package/dist/modules/hospitality_core/operations.js.map +1 -1
- package/dist/modules/hospitality_core/routes.d.ts.map +1 -1
- package/dist/modules/hospitality_core/routes.js +24 -2
- package/dist/modules/hospitality_core/routes.js.map +1 -1
- package/dist/modules/hospitality_core/screens/check-out-prep.d.ts +46 -0
- package/dist/modules/hospitality_core/screens/check-out-prep.d.ts.map +1 -0
- package/dist/modules/hospitality_core/screens/check-out-prep.js +133 -0
- package/dist/modules/hospitality_core/screens/check-out-prep.js.map +1 -0
- package/dist/modules/hospitality_core/screens/folio-detail.d.ts.map +1 -1
- package/dist/modules/hospitality_core/screens/folio-detail.js +2 -4
- package/dist/modules/hospitality_core/screens/folio-detail.js.map +1 -1
- package/dist/modules/hospitality_core/screens/index.d.ts +2 -0
- package/dist/modules/hospitality_core/screens/index.d.ts.map +1 -1
- package/dist/modules/hospitality_core/screens/index.js +1 -0
- package/dist/modules/hospitality_core/screens/index.js.map +1 -1
- package/dist/modules/hospitality_core/screens/reservation-detail.d.ts.map +1 -1
- package/dist/modules/hospitality_core/screens/reservation-detail.js +8 -1
- package/dist/modules/hospitality_core/screens/reservation-detail.js.map +1 -1
- package/dist/modules/hospitality_core/screens/shared.d.ts +12 -0
- package/dist/modules/hospitality_core/screens/shared.d.ts.map +1 -1
- package/dist/modules/hospitality_core/screens/shared.js +25 -4
- package/dist/modules/hospitality_core/screens/shared.js.map +1 -1
- package/dist/modules/hospitality_core/services.d.ts +48 -0
- package/dist/modules/hospitality_core/services.d.ts.map +1 -1
- package/dist/modules/hospitality_core/services.js +70 -0
- package/dist/modules/hospitality_core/services.js.map +1 -1
- package/dist/modules/hospitality_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/hospitality_staff_channel/channel-routes.js +1 -12
- package/dist/modules/hospitality_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/inventory_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/inventory_staff_channel/channel-routes.js +1 -12
- package/dist/modules/inventory_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/loyalty/admin-functions.d.ts +2 -0
- package/dist/modules/loyalty/admin-functions.d.ts.map +1 -1
- package/dist/modules/loyalty/admin-functions.js +96 -20
- package/dist/modules/loyalty/admin-functions.js.map +1 -1
- package/dist/modules/loyalty/engine.d.ts +4 -1
- package/dist/modules/loyalty/engine.d.ts.map +1 -1
- package/dist/modules/loyalty/engine.js +125 -18
- package/dist/modules/loyalty/engine.js.map +1 -1
- package/dist/modules/loyalty/index.d.ts.map +1 -1
- package/dist/modules/loyalty/index.js +4 -3
- package/dist/modules/loyalty/index.js.map +1 -1
- package/dist/modules/loyalty/jobs.d.ts.map +1 -1
- package/dist/modules/loyalty/jobs.js +0 -1
- package/dist/modules/loyalty/jobs.js.map +1 -1
- package/dist/modules/loyalty/membership-functions.d.ts +5 -0
- package/dist/modules/loyalty/membership-functions.d.ts.map +1 -1
- package/dist/modules/loyalty/membership-functions.js +37 -23
- package/dist/modules/loyalty/membership-functions.js.map +1 -1
- package/dist/modules/loyalty/messages.d.ts +14 -0
- package/dist/modules/loyalty/messages.d.ts.map +1 -1
- package/dist/modules/loyalty/messages.js +14 -0
- package/dist/modules/loyalty/messages.js.map +1 -1
- package/dist/modules/loyalty/models.d.ts.map +1 -1
- package/dist/modules/loyalty/models.js +17 -0
- package/dist/modules/loyalty/models.js.map +1 -1
- package/dist/modules/loyalty/order-functions.d.ts.map +1 -1
- package/dist/modules/loyalty/order-functions.js +70 -17
- package/dist/modules/loyalty/order-functions.js.map +1 -1
- package/dist/modules/loyalty/program-design.d.ts +11 -0
- package/dist/modules/loyalty/program-design.d.ts.map +1 -0
- package/dist/modules/loyalty/program-design.js +356 -0
- package/dist/modules/loyalty/program-design.js.map +1 -0
- package/dist/modules/loyalty/stats.d.ts +0 -13
- package/dist/modules/loyalty/stats.d.ts.map +1 -1
- package/dist/modules/loyalty/stats.js +11 -6
- package/dist/modules/loyalty/stats.js.map +1 -1
- package/dist/modules/loyalty/stored-value.d.ts.map +1 -1
- package/dist/modules/loyalty/stored-value.js +7 -2
- package/dist/modules/loyalty/stored-value.js.map +1 -1
- package/dist/modules/loyalty/types.d.ts +1 -1
- package/dist/modules/loyalty/types.d.ts.map +1 -1
- package/dist/modules/loyalty_backend/design-messages.d.ts +221 -0
- package/dist/modules/loyalty_backend/design-messages.d.ts.map +1 -0
- package/dist/modules/loyalty_backend/design-messages.js +221 -0
- package/dist/modules/loyalty_backend/design-messages.js.map +1 -0
- package/dist/modules/loyalty_backend/index.js +224 -412
- package/dist/modules/loyalty_backend/index.js.map +1 -1
- package/dist/modules/loyalty_backend/messages.d.ts +642 -214
- package/dist/modules/loyalty_backend/messages.d.ts.map +1 -1
- package/dist/modules/loyalty_backend/messages.js +19 -13
- package/dist/modules/loyalty_backend/messages.js.map +1 -1
- package/dist/modules/loyalty_backend/program-workspace.d.ts +4 -0
- package/dist/modules/loyalty_backend/program-workspace.d.ts.map +1 -0
- package/dist/modules/loyalty_backend/program-workspace.js +600 -0
- package/dist/modules/loyalty_backend/program-workspace.js.map +1 -0
- package/dist/modules/loyalty_backend/screens/index.d.ts +12 -36
- package/dist/modules/loyalty_backend/screens/index.d.ts.map +1 -1
- package/dist/modules/loyalty_backend/screens/index.js +148 -292
- package/dist/modules/loyalty_backend/screens/index.js.map +1 -1
- package/dist/modules/loyalty_backend/screens/page-frame.d.ts.map +1 -1
- package/dist/modules/loyalty_backend/screens/page-frame.js +2 -2
- package/dist/modules/loyalty_backend/screens/page-frame.js.map +1 -1
- package/dist/modules/loyalty_pos/functions.d.ts.map +1 -1
- package/dist/modules/loyalty_pos/functions.js +1 -0
- package/dist/modules/loyalty_pos/functions.js.map +1 -1
- package/dist/modules/loyalty_pos/index.js +1 -1
- package/dist/modules/loyalty_pos/index.js.map +1 -1
- package/dist/modules/loyalty_sale/functions.d.ts +1 -0
- package/dist/modules/loyalty_sale/functions.d.ts.map +1 -1
- package/dist/modules/loyalty_sale/functions.js +1 -0
- package/dist/modules/loyalty_sale/functions.js.map +1 -1
- package/dist/modules/partner_backend/client/partner.css +3 -1
- package/dist/modules/partner_backend/index.d.ts.map +1 -1
- package/dist/modules/partner_backend/index.js +4 -0
- package/dist/modules/partner_backend/index.js.map +1 -1
- package/dist/modules/partner_backend/routes.d.ts.map +1 -1
- package/dist/modules/partner_backend/routes.js +6 -1
- package/dist/modules/partner_backend/routes.js.map +1 -1
- package/dist/modules/partner_backend/screens/form.d.ts +1 -0
- package/dist/modules/partner_backend/screens/form.d.ts.map +1 -1
- package/dist/modules/partner_backend/screens/form.js +1 -0
- package/dist/modules/partner_backend/screens/form.js.map +1 -1
- package/dist/modules/purchase_staff_channel/order-routes.d.ts.map +1 -1
- package/dist/modules/purchase_staff_channel/order-routes.js +1 -12
- package/dist/modules/purchase_staff_channel/order-routes.js.map +1 -1
- package/dist/modules/quality_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/quality_staff_channel/channel-routes.js +1 -12
- package/dist/modules/quality_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/sale/functions.d.ts.map +1 -1
- package/dist/modules/sale/functions.js +27 -17
- package/dist/modules/sale/functions.js.map +1 -1
- package/dist/modules/sale_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/sale_staff_channel/channel-routes.js +1 -12
- package/dist/modules/sale_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/sale_staff_channel/order-routes.d.ts.map +1 -1
- package/dist/modules/sale_staff_channel/order-routes.js +1 -12
- package/dist/modules/sale_staff_channel/order-routes.js.map +1 -1
- package/dist/modules/stock_staff_channel/operation-routes.d.ts.map +1 -1
- package/dist/modules/stock_staff_channel/operation-routes.js +1 -12
- package/dist/modules/stock_staff_channel/operation-routes.js.map +1 -1
- package/dist/modules/user/authorization.d.ts +64 -1
- package/dist/modules/user/authorization.d.ts.map +1 -1
- package/dist/modules/user/authorization.js +445 -53
- package/dist/modules/user/authorization.js.map +1 -1
- package/dist/modules/user/functions.d.ts.map +1 -1
- package/dist/modules/user/functions.js +45 -23
- package/dist/modules/user/functions.js.map +1 -1
- package/dist/modules/user/index.d.ts +2 -1
- package/dist/modules/user/index.d.ts.map +1 -1
- package/dist/modules/user/index.js +1 -1
- package/dist/modules/user/index.js.map +1 -1
- package/dist/modules/user/models.d.ts.map +1 -1
- package/dist/modules/user/models.js +5 -0
- package/dist/modules/user/models.js.map +1 -1
- package/dist/modules/user/roles.d.ts.map +1 -1
- package/dist/modules/user/roles.js +2 -1
- package/dist/modules/user/roles.js.map +1 -1
- package/dist/modules/user_backend/access-routes.d.ts +31 -0
- package/dist/modules/user_backend/access-routes.d.ts.map +1 -0
- package/dist/modules/user_backend/access-routes.js +447 -0
- package/dist/modules/user_backend/access-routes.js.map +1 -0
- package/dist/modules/user_backend/index.d.ts +1 -0
- package/dist/modules/user_backend/index.d.ts.map +1 -1
- package/dist/modules/user_backend/index.js +1 -0
- package/dist/modules/user_backend/index.js.map +1 -1
- package/dist/modules/user_backend/messages.d.ts +4 -0
- package/dist/modules/user_backend/messages.d.ts.map +1 -1
- package/dist/modules/user_backend/messages.js +4 -0
- package/dist/modules/user_backend/messages.js.map +1 -1
- package/dist/modules/user_backend/routes.d.ts.map +1 -1
- package/dist/modules/user_backend/routes.js +38 -3
- package/dist/modules/user_backend/routes.js.map +1 -1
- package/dist/modules/user_backend/screens/user-form.d.ts +1 -0
- package/dist/modules/user_backend/screens/user-form.d.ts.map +1 -1
- package/dist/modules/user_backend/screens/user-form.js +28 -0
- package/dist/modules/user_backend/screens/user-form.js.map +1 -1
- package/dist/modules/website/cms.d.ts.map +1 -1
- package/dist/modules/website/cms.js +283 -6
- package/dist/modules/website/cms.js.map +1 -1
- package/dist/modules/website/index.d.ts.map +1 -1
- package/dist/modules/website/index.js +12 -0
- package/dist/modules/website/index.js.map +1 -1
- package/dist/modules/website/media-usage.d.ts +38 -0
- package/dist/modules/website/media-usage.d.ts.map +1 -0
- package/dist/modules/website/media-usage.js +87 -0
- package/dist/modules/website/media-usage.js.map +1 -0
- package/dist/modules/website/paths.js +1 -1
- package/dist/modules/website/paths.js.map +1 -1
- package/dist/modules/website/sections.d.ts.map +1 -1
- package/dist/modules/website/sections.js +12 -1
- package/dist/modules/website/sections.js.map +1 -1
- package/dist/modules/website_backend/index.js +138 -0
- package/dist/modules/website_backend/index.js.map +1 -1
- package/dist/modules/website_backend/menus.d.ts.map +1 -1
- package/dist/modules/website_backend/menus.js +11 -0
- package/dist/modules/website_backend/menus.js.map +1 -1
- package/dist/modules/website_backend/routes.d.ts.map +1 -1
- package/dist/modules/website_backend/routes.js +414 -31
- package/dist/modules/website_backend/routes.js.map +1 -1
- package/dist/modules/website_backend/screens/index.d.ts +98 -6
- package/dist/modules/website_backend/screens/index.d.ts.map +1 -1
- package/dist/modules/website_backend/screens/index.js +600 -154
- package/dist/modules/website_backend/screens/index.js.map +1 -1
- package/dist/modules/website_backend/screens/page-frame.d.ts +3 -1
- package/dist/modules/website_backend/screens/page-frame.d.ts.map +1 -1
- package/dist/modules/website_backend/screens/page-frame.js +1 -1
- package/dist/modules/website_backend/screens/page-frame.js.map +1 -1
- package/dist/modules/website_menu/functions.d.ts.map +1 -1
- package/dist/modules/website_menu/functions.js +51 -0
- package/dist/modules/website_menu/functions.js.map +1 -1
- package/dist/modules/website_menu/index.js +2 -0
- package/dist/modules/website_menu/index.js.map +1 -1
- package/dist/modules/website_search/functions.d.ts.map +1 -1
- package/dist/modules/website_search/functions.js +10 -120
- package/dist/modules/website_search/functions.js.map +1 -1
- package/dist/modules/website_search/index.js +2 -0
- package/dist/modules/website_search/index.js.map +1 -1
- package/dist/modules/website_search/jobs.d.ts +3 -0
- package/dist/modules/website_search/jobs.d.ts.map +1 -0
- package/dist/modules/website_search/jobs.js +101 -0
- package/dist/modules/website_search/jobs.js.map +1 -0
- package/dist/modules/website_search/rebuild.d.ts +33 -0
- package/dist/modules/website_search/rebuild.d.ts.map +1 -0
- package/dist/modules/website_search/rebuild.js +125 -0
- package/dist/modules/website_search/rebuild.js.map +1 -0
- package/dist/permission-catalogue.d.ts.map +1 -1
- package/dist/permission-catalogue.js +21 -1
- package/dist/permission-catalogue.js.map +1 -1
- package/dist/scaffold/index.js +1 -1
- package/dist/ui/chrome.d.ts +8 -0
- package/dist/ui/chrome.d.ts.map +1 -1
- package/dist/ui/chrome.js +9 -1
- package/dist/ui/chrome.js.map +1 -1
- package/dist/ui/client/table-selection-view.d.ts.map +1 -1
- package/dist/ui/client/table-selection-view.js +161 -5
- package/dist/ui/client/table-selection-view.js.map +1 -1
- package/dist/ui/client/user-workflow.d.ts +2 -0
- package/dist/ui/client/user-workflow.d.ts.map +1 -0
- package/dist/ui/client/user-workflow.js +141 -0
- package/dist/ui/client/user-workflow.js.map +1 -0
- package/dist/ui/form.d.ts +9 -0
- package/dist/ui/form.d.ts.map +1 -1
- package/dist/ui/form.js +29 -15
- package/dist/ui/form.js.map +1 -1
- package/dist/ui/icons.d.ts.map +1 -1
- package/dist/ui/icons.js +2 -0
- package/dist/ui/icons.js.map +1 -1
- package/dist/ui/index.d.ts +7 -3
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +6 -3
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/layout.d.ts.map +1 -1
- package/dist/ui/layout.js +14 -4
- package/dist/ui/layout.js.map +1 -1
- package/dist/ui/nav.d.ts.map +1 -1
- package/dist/ui/nav.js +2 -1
- package/dist/ui/nav.js.map +1 -1
- package/dist/ui/state.d.ts +20 -1
- package/dist/ui/state.d.ts.map +1 -1
- package/dist/ui/state.js +17 -0
- package/dist/ui/state.js.map +1 -1
- package/dist/ui/table.js +1 -1
- package/dist/ui/table.js.map +1 -1
- package/dist/ui/user-workflow.d.ts +72 -0
- package/dist/ui/user-workflow.d.ts.map +1 -0
- package/dist/ui/user-workflow.js +144 -0
- package/dist/ui/user-workflow.js.map +1 -0
- package/package.json +8 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { and, desc, ilike, lt, or, isNull, defineFn, deleteFrom, eq, from, inArray, isNotNull, KetError, permissionDigest, } from '@ketvietlab/ketjs';
|
|
3
3
|
const issue = (field, code, params) => ({
|
|
4
4
|
field,
|
|
5
5
|
code,
|
|
@@ -14,10 +14,10 @@ class AuthorizationAbort extends Error {
|
|
|
14
14
|
this.result = result;
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
const
|
|
17
|
+
export const abortAuthorization = (result) => {
|
|
18
18
|
throw new AuthorizationAbort(result);
|
|
19
19
|
};
|
|
20
|
-
const authorizationTransaction = async (ctx, body) => {
|
|
20
|
+
export const authorizationTransaction = async (ctx, body) => {
|
|
21
21
|
try {
|
|
22
22
|
return await ctx.tx(body);
|
|
23
23
|
}
|
|
@@ -130,11 +130,11 @@ const bumpRevision = async (ctx, expected) => {
|
|
|
130
130
|
export const recordAuthorizationAudit = async (ctx, values) => {
|
|
131
131
|
await ctx.db.insert('user.SecurityAudit', {
|
|
132
132
|
id: randomUUID(),
|
|
133
|
-
userId: null,
|
|
133
|
+
userId: values.userId ?? values.after?.userId ?? values.before?.userId ?? null,
|
|
134
134
|
event: values.event,
|
|
135
135
|
occurredAt: nowIso(),
|
|
136
136
|
networkFingerprint: null,
|
|
137
|
-
metadata:
|
|
137
|
+
metadata: values.metadata ?? { roleId: values.source, assignmentId: values.targetId },
|
|
138
138
|
actorKey: ctx.actor,
|
|
139
139
|
targetKind: values.targetKind,
|
|
140
140
|
targetId: values.targetId,
|
|
@@ -212,14 +212,15 @@ export const managedRoleHealthIssues = (manifest, role, grants, sources) => {
|
|
|
212
212
|
? ['stale-managed-provenance']
|
|
213
213
|
: [];
|
|
214
214
|
};
|
|
215
|
-
|
|
216
|
-
|
|
215
|
+
export async function resolveEffectivePermissions(ctx, userId, projection = {}) {
|
|
216
|
+
const selectedCompany = projection.companyId === undefined ? ctx.scope.company : projection.companyId;
|
|
217
|
+
const selectedBranch = projection.branchId === undefined ? ctx.scope.branch : projection.branchId;
|
|
217
218
|
const revision = await authorizationRevisionOf(ctx);
|
|
218
219
|
const empty = (issues = []) => ({
|
|
219
220
|
revision,
|
|
220
221
|
context: {
|
|
221
|
-
companyId:
|
|
222
|
-
branchId:
|
|
222
|
+
companyId: selectedCompany ?? null,
|
|
223
|
+
branchId: selectedBranch ?? null,
|
|
223
224
|
},
|
|
224
225
|
superuser: false,
|
|
225
226
|
functions: [],
|
|
@@ -234,18 +235,18 @@ export async function resolveEffectivePermissions(ctx, userId) {
|
|
|
234
235
|
if (expiresAt == null || expiresAt > Date.now())
|
|
235
236
|
return { ...empty(), superuser: true };
|
|
236
237
|
}
|
|
237
|
-
const companyId = String(
|
|
238
|
+
const companyId = String(selectedCompany ?? '');
|
|
238
239
|
if (!companyId)
|
|
239
240
|
return empty([{ code: 'invalid-company-context' }]);
|
|
240
|
-
const companyIds = await activeCompanyMemberships(ctx, userId);
|
|
241
|
+
const companyIds = projection.companies ?? (await activeCompanyMemberships(ctx, userId));
|
|
241
242
|
if (!companyIds.has(companyId))
|
|
242
243
|
return empty([{ code: 'invalid-company-context' }]);
|
|
243
|
-
const branchId = String(
|
|
244
|
-
const branches = await activeBranchMemberships(ctx, userId, companyIds);
|
|
244
|
+
const branchId = String(selectedBranch ?? '');
|
|
245
|
+
const branches = projection.branches ?? (await activeBranchMemberships(ctx, userId, companyIds));
|
|
245
246
|
if (branchId && branches.get(branchId) !== companyId)
|
|
246
247
|
return empty([{ code: 'invalid-branch-context' }]);
|
|
247
248
|
const A = ctx.table('user.Assignment');
|
|
248
|
-
const assignments = (await ctx.db.all(from(A).where(eq(A.userId, userId)))).filter((assignment) => roleApplies(assignment, companyId, branchId));
|
|
249
|
+
const assignments = (projection.assignments ?? (await ctx.db.all(from(A).where(eq(A.userId, userId))))).filter((assignment) => roleApplies(assignment, companyId, branchId));
|
|
249
250
|
if (!assignments.length)
|
|
250
251
|
return empty();
|
|
251
252
|
const roleIds = [...new Set(assignments.map((assignment) => String(assignment.roleId)))];
|
|
@@ -397,7 +398,7 @@ const roleTemplatePreview = async (ctx, roleId, templateKey) => {
|
|
|
397
398
|
highRiskAdded,
|
|
398
399
|
};
|
|
399
400
|
};
|
|
400
|
-
const AUTHORIZATION_EFFECTS = [
|
|
401
|
+
export const AUTHORIZATION_EFFECTS = [
|
|
401
402
|
'read:user.User',
|
|
402
403
|
'write:user.User',
|
|
403
404
|
'read:user.Role',
|
|
@@ -446,7 +447,7 @@ export const authorizationFunctions = {
|
|
|
446
447
|
}),
|
|
447
448
|
}),
|
|
448
449
|
effectiveAccess: defineFn({
|
|
449
|
-
input: { userId: 'id' },
|
|
450
|
+
input: { userId: 'id', companyId: 'id?', branchId: 'id?' },
|
|
450
451
|
output: {
|
|
451
452
|
revision: 'int',
|
|
452
453
|
context: 'json',
|
|
@@ -455,7 +456,10 @@ export const authorizationFunctions = {
|
|
|
455
456
|
issues: 'json',
|
|
456
457
|
},
|
|
457
458
|
effects: AUTHORIZATION_EFFECTS.filter((effect) => !effect.startsWith('write:')),
|
|
458
|
-
handler: (ctx, args) => resolveEffectivePermissions(ctx, String(args.userId)
|
|
459
|
+
handler: (ctx, args) => resolveEffectivePermissions(ctx, String(args.userId), {
|
|
460
|
+
companyId: args.companyId,
|
|
461
|
+
branchId: args.branchId,
|
|
462
|
+
}),
|
|
459
463
|
}),
|
|
460
464
|
previewRoleTemplate: defineFn({
|
|
461
465
|
input: { roleId: 'id?', templateKey: 'text' },
|
|
@@ -505,7 +509,7 @@ export const authorizationFunctions = {
|
|
|
505
509
|
return authorizationTransaction(ctx, async (tx) => {
|
|
506
510
|
const replay = await operationReplay(tx, operationId, args);
|
|
507
511
|
if ('conflict' in replay)
|
|
508
|
-
|
|
512
|
+
abortAuthorization(invalid([issue('idempotencyKey', 'E_ROLE_TEMPLATE_CONFLICT')]));
|
|
509
513
|
if ('replay' in replay && replay.replay)
|
|
510
514
|
return {
|
|
511
515
|
...replay.result,
|
|
@@ -516,15 +520,15 @@ export const authorizationFunctions = {
|
|
|
516
520
|
const role = await tx.db.one(from(R).where(eq(R.id, roleId)));
|
|
517
521
|
const expectedRoleRevision = Number(args.expectedRoleRevision);
|
|
518
522
|
if (Number(role?.revision ?? 0) !== expectedRoleRevision)
|
|
519
|
-
|
|
523
|
+
abortAuthorization(invalid([issue('expectedRoleRevision', 'E_ROLE_TEMPLATE_CONFLICT')]));
|
|
520
524
|
if (role && String(role.mode ?? 'custom') !== 'managed')
|
|
521
|
-
|
|
525
|
+
abortAuthorization(invalid([issue('roleId', 'E_ROLE_TEMPLATE_CONFLICT')]));
|
|
522
526
|
if (role && Number(role.templateVersion ?? 0) > template.version)
|
|
523
|
-
|
|
527
|
+
abortAuthorization(invalid([issue('templateKey', 'E_ROLE_TEMPLATE_STALE')]));
|
|
524
528
|
if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
|
|
525
|
-
|
|
529
|
+
abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
526
530
|
const preview = await roleTemplatePreview(tx, roleId, templateKey);
|
|
527
|
-
const validPreview = preview.ok ? preview :
|
|
531
|
+
const validPreview = preview.ok ? preview : abortAuthorization(preview);
|
|
528
532
|
if (role &&
|
|
529
533
|
role.templateKey === templateKey &&
|
|
530
534
|
Number(role.templateVersion) === template.version &&
|
|
@@ -533,7 +537,7 @@ export const authorizationFunctions = {
|
|
|
533
537
|
validPreview.managedAdded.length === 0 &&
|
|
534
538
|
validPreview.managedRemoved.length === 0) {
|
|
535
539
|
if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
|
|
536
|
-
|
|
540
|
+
abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
537
541
|
const result = {
|
|
538
542
|
ok: true,
|
|
539
543
|
roleId,
|
|
@@ -557,7 +561,7 @@ export const authorizationFunctions = {
|
|
|
557
561
|
revision: 1,
|
|
558
562
|
});
|
|
559
563
|
if (!('dryRun' in inserted) && !inserted.inserted)
|
|
560
|
-
|
|
564
|
+
abortAuthorization(invalid([issue('roleId', 'E_ROLE_TEMPLATE_CONFLICT')]));
|
|
561
565
|
}
|
|
562
566
|
else {
|
|
563
567
|
const changed = await tx.db.compareAndSet('user.Role', { id: roleId }, { revision: expectedRoleRevision }, {
|
|
@@ -570,7 +574,7 @@ export const authorizationFunctions = {
|
|
|
570
574
|
revision: expectedRoleRevision + 1,
|
|
571
575
|
});
|
|
572
576
|
if (!('dryRun' in changed) && !changed.matched)
|
|
573
|
-
|
|
577
|
+
abortAuthorization(invalid([issue('expectedRoleRevision', 'E_ROLE_TEMPLATE_CONFLICT')]));
|
|
574
578
|
}
|
|
575
579
|
const S = tx.table('user.GrantSource');
|
|
576
580
|
const G = tx.table('user.Grant');
|
|
@@ -606,7 +610,7 @@ export const authorizationFunctions = {
|
|
|
606
610
|
await tx.db.del(deleteFrom(G).where(eq(G.roleId, roleId), eq(G.fnKey, fnKey)));
|
|
607
611
|
}
|
|
608
612
|
const revision = (await bumpRevision(tx, Number(args.expectedAuthorizationRevision))) ??
|
|
609
|
-
|
|
613
|
+
abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
610
614
|
const result = {
|
|
611
615
|
ok: true,
|
|
612
616
|
roleId,
|
|
@@ -665,13 +669,13 @@ export const authorizationFunctions = {
|
|
|
665
669
|
return invalid([issue('reason', 'user.error.required')]);
|
|
666
670
|
return authorizationTransaction(ctx, async (tx) => {
|
|
667
671
|
const liveScope = await normalizeAssignmentScope(tx, String(args.userId), args);
|
|
668
|
-
const scope = liveScope.ok ? liveScope.scope :
|
|
672
|
+
const scope = liveScope.ok ? liveScope.scope : abortAuthorization(liveScope);
|
|
669
673
|
const replay = await operationReplay(tx, operationId, {
|
|
670
674
|
...args,
|
|
671
675
|
scope,
|
|
672
676
|
});
|
|
673
677
|
if ('conflict' in replay)
|
|
674
|
-
|
|
678
|
+
abortAuthorization(invalid([issue('idempotencyKey', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
675
679
|
if ('replay' in replay && replay.replay)
|
|
676
680
|
return {
|
|
677
681
|
...replay.result,
|
|
@@ -679,15 +683,15 @@ export const authorizationFunctions = {
|
|
|
679
683
|
};
|
|
680
684
|
const R = tx.table('user.Role');
|
|
681
685
|
if (!(await tx.db.one(from(R).where(eq(R.id, args.roleId)))))
|
|
682
|
-
|
|
686
|
+
abortAuthorization(invalid([issue('roleId', 'user.error.roleMissing')]));
|
|
683
687
|
if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
|
|
684
|
-
|
|
688
|
+
abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
685
689
|
const A = tx.table('user.Assignment');
|
|
686
690
|
const held = await tx.db.one(from(A).where(eq(A.userId, args.userId), eq(A.roleId, args.roleId), eq(A.scopeKey, scope.scopeKey)));
|
|
687
691
|
const assignmentId = String(held?.id ?? args.id);
|
|
688
692
|
if (held) {
|
|
689
693
|
if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
|
|
690
|
-
|
|
694
|
+
abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
691
695
|
const result = {
|
|
692
696
|
ok: true,
|
|
693
697
|
id: assignmentId,
|
|
@@ -705,7 +709,7 @@ export const authorizationFunctions = {
|
|
|
705
709
|
...scope,
|
|
706
710
|
});
|
|
707
711
|
const revision = (await bumpRevision(tx, Number(args.expectedAuthorizationRevision))) ??
|
|
708
|
-
|
|
712
|
+
abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
709
713
|
const result = {
|
|
710
714
|
ok: true,
|
|
711
715
|
id: assignmentId,
|
|
@@ -732,6 +736,7 @@ export const authorizationFunctions = {
|
|
|
732
736
|
unassignScopedRole: defineFn({
|
|
733
737
|
input: {
|
|
734
738
|
userId: 'id',
|
|
739
|
+
assignmentId: 'id?',
|
|
735
740
|
roleId: 'id',
|
|
736
741
|
scopeKey: 'text',
|
|
737
742
|
expectedAuthorizationRevision: 'int',
|
|
@@ -755,19 +760,24 @@ export const authorizationFunctions = {
|
|
|
755
760
|
return authorizationTransaction(ctx, async (tx) => {
|
|
756
761
|
const replay = await operationReplay(tx, operationId, args);
|
|
757
762
|
if ('conflict' in replay)
|
|
758
|
-
|
|
763
|
+
abortAuthorization(invalid([issue('idempotencyKey', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
759
764
|
if ('replay' in replay && replay.replay)
|
|
760
765
|
return {
|
|
761
766
|
...replay.result,
|
|
762
767
|
replayed: true,
|
|
763
768
|
};
|
|
764
769
|
if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
|
|
765
|
-
|
|
770
|
+
abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
766
771
|
const A = tx.table('user.Assignment');
|
|
767
|
-
|
|
772
|
+
let selected = from(A).where(eq(A.userId, args.userId), eq(A.roleId, args.roleId), args.scopeKey === 'tenant'
|
|
773
|
+
? or(eq(A.scopeKey, 'tenant'), isNull(A.scopeKey))
|
|
774
|
+
: eq(A.scopeKey, args.scopeKey));
|
|
775
|
+
if (args.assignmentId)
|
|
776
|
+
selected = selected.where(eq(A.id, args.assignmentId));
|
|
777
|
+
const before = await tx.db.one(selected);
|
|
768
778
|
if (!before) {
|
|
769
779
|
if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
|
|
770
|
-
|
|
780
|
+
abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
771
781
|
const result = {
|
|
772
782
|
ok: true,
|
|
773
783
|
removed: 0,
|
|
@@ -777,9 +787,9 @@ export const authorizationFunctions = {
|
|
|
777
787
|
await completeOperation(tx, operationId, result);
|
|
778
788
|
return result;
|
|
779
789
|
}
|
|
780
|
-
const removed = (await tx.db.del(deleteFrom(A).where(eq(A.
|
|
790
|
+
const removed = (await tx.db.del(deleteFrom(A).where(eq(A.id, before.id)))).changes;
|
|
781
791
|
const revision = (await bumpRevision(tx, Number(args.expectedAuthorizationRevision))) ??
|
|
782
|
-
|
|
792
|
+
abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
783
793
|
const result = { ok: true, removed, revision, replayed: false };
|
|
784
794
|
await recordAuthorizationAudit(tx, {
|
|
785
795
|
event: 'authorization.assignment.removed',
|
|
@@ -824,7 +834,7 @@ export const authorizationFunctions = {
|
|
|
824
834
|
return authorizationTransaction(ctx, async (tx) => {
|
|
825
835
|
const replay = await operationReplay(tx, operationId, args);
|
|
826
836
|
if ('conflict' in replay)
|
|
827
|
-
|
|
837
|
+
abortAuthorization(invalid([issue('idempotencyKey', 'E_ROLE_TEMPLATE_CONFLICT')]));
|
|
828
838
|
if ('replay' in replay && replay.replay)
|
|
829
839
|
return {
|
|
830
840
|
...replay.result,
|
|
@@ -834,9 +844,9 @@ export const authorizationFunctions = {
|
|
|
834
844
|
const sourceRow = await tx.db.one(from(R).where(eq(R.id, args.sourceRoleId)));
|
|
835
845
|
const source = sourceRow && String(sourceRow.mode ?? 'custom') === 'managed'
|
|
836
846
|
? sourceRow
|
|
837
|
-
:
|
|
847
|
+
: abortAuthorization(invalid([issue('sourceRoleId', 'E_ROLE_TEMPLATE_CONFLICT')]));
|
|
838
848
|
if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
|
|
839
|
-
|
|
849
|
+
abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
840
850
|
const inserted = await tx.db.insertIfAbsent('user.Role', {
|
|
841
851
|
id: args.id,
|
|
842
852
|
name,
|
|
@@ -848,7 +858,7 @@ export const authorizationFunctions = {
|
|
|
848
858
|
revision: 1,
|
|
849
859
|
});
|
|
850
860
|
if (!('dryRun' in inserted) && !inserted.inserted)
|
|
851
|
-
|
|
861
|
+
abortAuthorization(invalid([issue('id', 'E_ROLE_TEMPLATE_CONFLICT')]));
|
|
852
862
|
const G = tx.table('user.Grant');
|
|
853
863
|
for (const grant of await tx.db.all(from(G).where(eq(G.roleId, args.sourceRoleId)))) {
|
|
854
864
|
const fnKey = String(grant.fnKey);
|
|
@@ -867,7 +877,7 @@ export const authorizationFunctions = {
|
|
|
867
877
|
});
|
|
868
878
|
}
|
|
869
879
|
const revision = (await bumpRevision(tx, Number(args.expectedAuthorizationRevision))) ??
|
|
870
|
-
|
|
880
|
+
abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
871
881
|
const result = { ok: true, id: args.id, revision, replayed: false };
|
|
872
882
|
await recordAuthorizationAudit(tx, {
|
|
873
883
|
event: 'authorization.role.cloned',
|
|
@@ -926,19 +936,19 @@ export const authorizationFunctions = {
|
|
|
926
936
|
reason,
|
|
927
937
|
});
|
|
928
938
|
if ('conflict' in replay)
|
|
929
|
-
|
|
939
|
+
abortAuthorization(invalid([issue('idempotencyKey', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
930
940
|
if ('replay' in replay && replay.replay)
|
|
931
941
|
return {
|
|
932
942
|
...replay.result,
|
|
933
943
|
replayed: true,
|
|
934
944
|
};
|
|
935
945
|
if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
|
|
936
|
-
|
|
946
|
+
abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
937
947
|
const U = tx.table('user.User');
|
|
938
|
-
const user = await tx.db.one(from(U).where(eq(U.id, args.userId)
|
|
948
|
+
const user = await tx.db.one(from(U).where(eq(U.id, args.userId)));
|
|
939
949
|
if (!user || String(user.accessKind) !== 'internal')
|
|
940
|
-
|
|
941
|
-
const target = user ??
|
|
950
|
+
abortAuthorization(invalid([issue('userId', 'user.error.userMissing')]));
|
|
951
|
+
const target = user ?? abortAuthorization(invalid([issue('userId', 'user.error.userMissing')]));
|
|
942
952
|
const after = enabled
|
|
943
953
|
? {
|
|
944
954
|
superuser: true,
|
|
@@ -959,7 +969,7 @@ export const authorizationFunctions = {
|
|
|
959
969
|
after.superuserExpiresAt;
|
|
960
970
|
if (unchanged) {
|
|
961
971
|
if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
|
|
962
|
-
|
|
972
|
+
abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
963
973
|
const result = {
|
|
964
974
|
ok: true,
|
|
965
975
|
userId: String(args.userId),
|
|
@@ -973,7 +983,7 @@ export const authorizationFunctions = {
|
|
|
973
983
|
}
|
|
974
984
|
await tx.db.update('user.User', { id: args.userId }, after);
|
|
975
985
|
const revision = (await bumpRevision(tx, Number(args.expectedAuthorizationRevision))) ??
|
|
976
|
-
|
|
986
|
+
abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
|
|
977
987
|
const result = {
|
|
978
988
|
ok: true,
|
|
979
989
|
userId: String(args.userId),
|
|
@@ -1006,11 +1016,21 @@ export const authorizationFunctions = {
|
|
|
1006
1016
|
},
|
|
1007
1017
|
}),
|
|
1008
1018
|
listAuthorizationAudit: defineFn({
|
|
1009
|
-
input: {
|
|
1019
|
+
input: {
|
|
1020
|
+
id: 'id?',
|
|
1021
|
+
targetKind: 'text?',
|
|
1022
|
+
targetId: 'text?',
|
|
1023
|
+
userId: 'id?',
|
|
1024
|
+
limit: 'int?',
|
|
1025
|
+
beforeRevision: 'int?',
|
|
1026
|
+
beforeId: 'id?',
|
|
1027
|
+
},
|
|
1010
1028
|
output: {
|
|
1011
1029
|
id: 'id',
|
|
1012
1030
|
event: 'text',
|
|
1013
1031
|
occurredAt: 'datetime',
|
|
1032
|
+
userId: 'id?',
|
|
1033
|
+
metadata: 'json?',
|
|
1014
1034
|
actorKey: 'text?',
|
|
1015
1035
|
targetKind: 'text?',
|
|
1016
1036
|
targetId: 'text?',
|
|
@@ -1027,7 +1047,15 @@ export const authorizationFunctions = {
|
|
|
1027
1047
|
const A = ctx.table('user.SecurityAudit');
|
|
1028
1048
|
let query = from(A)
|
|
1029
1049
|
.where(isNotNull(A.authorizationRevision))
|
|
1030
|
-
.orderBy(
|
|
1050
|
+
.orderBy(desc(A.authorizationRevision), desc(A.id));
|
|
1051
|
+
if (args.id)
|
|
1052
|
+
query = query.where(eq(A.id, args.id));
|
|
1053
|
+
if (args.beforeRevision != null)
|
|
1054
|
+
query = query.where(args.beforeId
|
|
1055
|
+
? or(lt(A.authorizationRevision, args.beforeRevision), and(eq(A.authorizationRevision, args.beforeRevision), lt(A.id, args.beforeId)))
|
|
1056
|
+
: lt(A.authorizationRevision, args.beforeRevision));
|
|
1057
|
+
if (args.userId)
|
|
1058
|
+
query = query.where(eq(A.userId, args.userId));
|
|
1031
1059
|
if (args.targetKind)
|
|
1032
1060
|
query = query.where(eq(A.targetKind, args.targetKind));
|
|
1033
1061
|
if (args.targetId)
|
|
@@ -1036,4 +1064,368 @@ export const authorizationFunctions = {
|
|
|
1036
1064
|
},
|
|
1037
1065
|
}),
|
|
1038
1066
|
};
|
|
1067
|
+
/** Transaction-aware primitives shared by the backend and identity adapter. */
|
|
1068
|
+
export const USER_ACCESS_EFFECTS = [
|
|
1069
|
+
...AUTHORIZATION_EFFECTS,
|
|
1070
|
+
'write:user.Membership',
|
|
1071
|
+
'write:user.BranchMembership',
|
|
1072
|
+
'read:user.EmailReservation',
|
|
1073
|
+
'write:user.EmailReservation',
|
|
1074
|
+
];
|
|
1075
|
+
const required = (field, code) => abortAuthorization(invalid([issue(field, code)]));
|
|
1076
|
+
export async function assertAssignableRoles(ctx, ids) {
|
|
1077
|
+
if (ids.length > 100 || new Set(ids).size !== ids.length)
|
|
1078
|
+
required('roleIds', 'E_ROLE_SELECTION_INVALID');
|
|
1079
|
+
const result = [];
|
|
1080
|
+
for (const id of ids) {
|
|
1081
|
+
const R = ctx.table('user.Role');
|
|
1082
|
+
const row = await ctx.db.one(from(R).where(eq(R.id, id)));
|
|
1083
|
+
if (row?.mode !== 'managed')
|
|
1084
|
+
required('roleIds', 'E_ROLE_NOT_ASSIGNABLE');
|
|
1085
|
+
const template = ctx.manifest.permissions.roleTemplates[String(row.templateKey)];
|
|
1086
|
+
if (!template ||
|
|
1087
|
+
template.version !== Number(row.templateVersion) ||
|
|
1088
|
+
template.digest !== row.templateDigest)
|
|
1089
|
+
required('roleIds', 'E_ROLE_NOT_ASSIGNABLE');
|
|
1090
|
+
const G = ctx.table('user.Grant'), S = ctx.table('user.GrantSource');
|
|
1091
|
+
const grants = await ctx.db.all(from(G).where(eq(G.roleId, id)));
|
|
1092
|
+
const sources = await ctx.db.all(from(S).where(eq(S.roleId, id)));
|
|
1093
|
+
if (managedRoleHealthIssues(ctx.manifest, row, grants, sources).length)
|
|
1094
|
+
required('roleIds', 'E_ROLE_NOT_ASSIGNABLE');
|
|
1095
|
+
result.push(row);
|
|
1096
|
+
}
|
|
1097
|
+
return result;
|
|
1098
|
+
}
|
|
1099
|
+
async function selectionState(ctx, args, removing = false) {
|
|
1100
|
+
const U = ctx.table('user.User');
|
|
1101
|
+
const person = await ctx.db.one(from(U).where(eq(U.id, args.userId)));
|
|
1102
|
+
if (!person || (!removing && (!person.active || person.accessKind !== 'internal')))
|
|
1103
|
+
required('userId', 'user.error.userMissing');
|
|
1104
|
+
if (!['tenant', 'company', 'branch'].includes(args.scopeKind))
|
|
1105
|
+
required('scopeKind', 'E_ASSIGNMENT_SCOPE_INVALID');
|
|
1106
|
+
const companies = await activeCompanyMemberships(ctx, args.userId);
|
|
1107
|
+
const branches = await activeBranchMemberships(ctx, args.userId, companies);
|
|
1108
|
+
const companyId = args.scopeKind === 'tenant' ? null : args.companyId || null;
|
|
1109
|
+
const branchId = args.scopeKind === 'branch' ? args.branchId || null : null;
|
|
1110
|
+
if (companyId && !removing) {
|
|
1111
|
+
const C = ctx.table('company.Company');
|
|
1112
|
+
if (!(await ctx.db.one(from(C).where(eq(C.id, companyId), eq(C.active, true)))))
|
|
1113
|
+
required('companyId', 'E_ASSIGNMENT_SCOPE_INVALID');
|
|
1114
|
+
}
|
|
1115
|
+
else if (!companyId && args.scopeKind !== 'tenant' && !removing)
|
|
1116
|
+
required('companyId', 'E_ASSIGNMENT_SCOPE_INVALID');
|
|
1117
|
+
if (args.scopeKind === 'branch' && !removing) {
|
|
1118
|
+
const B = ctx.table('company.Branch');
|
|
1119
|
+
if (!branchId ||
|
|
1120
|
+
!(await ctx.db.one(from(B).where(eq(B.id, branchId), eq(B.companyId, companyId), eq(B.active, true)))))
|
|
1121
|
+
required('branchId', 'E_ASSIGNMENT_SCOPE_INVALID');
|
|
1122
|
+
}
|
|
1123
|
+
const addCompany = !!companyId && !companies.has(companyId);
|
|
1124
|
+
const addBranch = !!branchId && !branches.has(branchId);
|
|
1125
|
+
if (!removing && (addCompany || addBranch)) {
|
|
1126
|
+
if (!args.addMembership)
|
|
1127
|
+
required('addMembership', 'E_ASSIGNMENT_MEMBERSHIP_REQUIRED');
|
|
1128
|
+
if (!ctx.actor)
|
|
1129
|
+
required('actor', 'E_ACTOR_REQUIRED');
|
|
1130
|
+
const allowed = await effectiveFunctionKeys(ctx, ctx.actor);
|
|
1131
|
+
if (allowed &&
|
|
1132
|
+
((addCompany && !allowed.includes('user.grantCompany')) ||
|
|
1133
|
+
(addBranch && !allowed.includes('user.grantBranch'))))
|
|
1134
|
+
required('addMembership', 'E_MEMBERSHIP_FORBIDDEN');
|
|
1135
|
+
}
|
|
1136
|
+
if (companyId && !removing)
|
|
1137
|
+
companies.add(companyId);
|
|
1138
|
+
if (branchId && !removing)
|
|
1139
|
+
branches.set(branchId, companyId);
|
|
1140
|
+
if (!companies.size && !removing)
|
|
1141
|
+
required('companyId', 'E_ASSIGNMENT_MEMBERSHIP_REQUIRED');
|
|
1142
|
+
const scope = {
|
|
1143
|
+
scopeKind: args.scopeKind,
|
|
1144
|
+
companyId,
|
|
1145
|
+
branchId,
|
|
1146
|
+
scopeKey: args.scopeKind === 'tenant'
|
|
1147
|
+
? 'tenant'
|
|
1148
|
+
: branchId
|
|
1149
|
+
? `branch:${companyId}:${branchId}`
|
|
1150
|
+
: `company:${companyId}`,
|
|
1151
|
+
};
|
|
1152
|
+
const A = ctx.table('user.Assignment');
|
|
1153
|
+
const assignments = await ctx.db.all(from(A).where(eq(A.userId, args.userId)));
|
|
1154
|
+
return { companies, branches, scope, assignments, addCompany, addBranch };
|
|
1155
|
+
}
|
|
1156
|
+
export async function addSelectedRoles(ctx, args) {
|
|
1157
|
+
await assertAssignableRoles(ctx, args.roleIds);
|
|
1158
|
+
const state = await selectionState(ctx, args);
|
|
1159
|
+
if (state.assignments.some((a) => args.roleIds.includes(String(a.roleId)) && String(a.scopeKey ?? 'tenant') === state.scope.scopeKey))
|
|
1160
|
+
required('roleIds', 'E_ROLE_ALREADY_ASSIGNED');
|
|
1161
|
+
if (state.addCompany)
|
|
1162
|
+
await ctx.db.insert('user.Membership', {
|
|
1163
|
+
id: randomUUID(),
|
|
1164
|
+
userId: args.userId,
|
|
1165
|
+
companyId: state.scope.companyId,
|
|
1166
|
+
});
|
|
1167
|
+
if (state.addBranch)
|
|
1168
|
+
await ctx.db.insert('user.BranchMembership', {
|
|
1169
|
+
id: randomUUID(),
|
|
1170
|
+
userId: args.userId,
|
|
1171
|
+
branchId: state.scope.branchId,
|
|
1172
|
+
});
|
|
1173
|
+
const added = [];
|
|
1174
|
+
for (const roleId of args.roleIds) {
|
|
1175
|
+
const row = { id: randomUUID(), userId: args.userId, roleId, ...state.scope };
|
|
1176
|
+
await ctx.db.insert('user.Assignment', row);
|
|
1177
|
+
added.push(row);
|
|
1178
|
+
}
|
|
1179
|
+
return { assignments: added, scope: state.scope };
|
|
1180
|
+
}
|
|
1181
|
+
const selectionInput = {
|
|
1182
|
+
userId: 'id',
|
|
1183
|
+
roleIds: 'json',
|
|
1184
|
+
scopeKind: 'text',
|
|
1185
|
+
companyId: 'id?',
|
|
1186
|
+
branchId: 'id?',
|
|
1187
|
+
addMembership: 'bool?',
|
|
1188
|
+
};
|
|
1189
|
+
const parseSelection = (args) => ({
|
|
1190
|
+
userId: String(args.userId),
|
|
1191
|
+
roleIds: Array.isArray(args.roleIds)
|
|
1192
|
+
? args.roleIds.map(String)
|
|
1193
|
+
: required('roleIds', 'E_ROLE_SELECTION_INVALID'),
|
|
1194
|
+
scopeKind: String(args.scopeKind),
|
|
1195
|
+
companyId: args.companyId,
|
|
1196
|
+
branchId: args.branchId,
|
|
1197
|
+
addMembership: args.addMembership === true,
|
|
1198
|
+
});
|
|
1199
|
+
export async function checkUserEmail(ctx, email, userId) {
|
|
1200
|
+
const normalized = email.trim().toLowerCase();
|
|
1201
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/u.test(normalized))
|
|
1202
|
+
return { ok: false, available: false, code: 'invalid' };
|
|
1203
|
+
const U = ctx.table('user.User'), E = ctx.table('user.EmailReservation');
|
|
1204
|
+
const users = await ctx.db.all(from(U).where(ilike(U.email, normalized.replace(/[\\%_]/g, '\\$&'), true)));
|
|
1205
|
+
const held = await ctx.db.one(from(E).where(eq(E.id, normalized)));
|
|
1206
|
+
return { ok: true, available: !users.some((p) => p.id !== userId) && (!held || held.userId === userId) };
|
|
1207
|
+
}
|
|
1208
|
+
export async function reserveUserEmail(ctx, email, userId) {
|
|
1209
|
+
const normalized = email?.trim().toLowerCase() ?? '';
|
|
1210
|
+
const E = ctx.table('user.EmailReservation');
|
|
1211
|
+
if (normalized && !(await checkUserEmail(ctx, normalized, userId)).available)
|
|
1212
|
+
required('email', 'E_EMAIL_UNAVAILABLE');
|
|
1213
|
+
const previous = await ctx.db.all(from(E).where(eq(E.userId, userId)));
|
|
1214
|
+
if (previous.some((row) => row.id === normalized))
|
|
1215
|
+
return;
|
|
1216
|
+
await ctx.db.del(deleteFrom(E).where(eq(E.userId, userId)));
|
|
1217
|
+
if (normalized) {
|
|
1218
|
+
const held = await ctx.db.insertIfAbsent('user.EmailReservation', { id: normalized, userId });
|
|
1219
|
+
if (!('dryRun' in held) && !held.inserted)
|
|
1220
|
+
required('email', 'E_EMAIL_UNAVAILABLE');
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
export const accessWorkflowFunctions = {
|
|
1224
|
+
checkUserEmail: defineFn({
|
|
1225
|
+
input: { email: 'text' },
|
|
1226
|
+
output: { ok: 'bool', available: 'bool', code: 'text?' },
|
|
1227
|
+
effects: ['read:user.User', 'read:user.EmailReservation'],
|
|
1228
|
+
handler: (ctx, args) => checkUserEmail(ctx, String(args.email)),
|
|
1229
|
+
}),
|
|
1230
|
+
assignRoles: defineFn({
|
|
1231
|
+
input: {
|
|
1232
|
+
...selectionInput,
|
|
1233
|
+
reason: 'text',
|
|
1234
|
+
expectedAuthorizationRevision: 'int',
|
|
1235
|
+
idempotencyKey: 'text',
|
|
1236
|
+
},
|
|
1237
|
+
output: { ok: 'bool', revision: 'int?', errors: 'json?', replayed: 'bool?' },
|
|
1238
|
+
effects: USER_ACCESS_EFFECTS,
|
|
1239
|
+
idempotent: true,
|
|
1240
|
+
handler: (ctx, args) => authorizationTransaction(ctx, async (tx) => {
|
|
1241
|
+
const op = `role-batch:${String(args.idempotencyKey).trim()}`;
|
|
1242
|
+
if (!String(args.reason).trim() || op.endsWith(':'))
|
|
1243
|
+
required('reason', 'user.error.required');
|
|
1244
|
+
const replay = await operationReplay(tx, op, args);
|
|
1245
|
+
if ('conflict' in replay)
|
|
1246
|
+
required('idempotencyKey', 'E_AUTHORIZATION_REVISION_CONFLICT');
|
|
1247
|
+
if ('replay' in replay && replay.replay)
|
|
1248
|
+
return { ...replay.result, replayed: true };
|
|
1249
|
+
const selection = parseSelection(args);
|
|
1250
|
+
if (!selection.roleIds.length)
|
|
1251
|
+
required('roleIds', 'E_ROLE_SELECTION_INVALID');
|
|
1252
|
+
if ((await authorizationRevisionOf(tx)) !== args.expectedAuthorizationRevision)
|
|
1253
|
+
required('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT');
|
|
1254
|
+
const added = await addSelectedRoles(tx, selection);
|
|
1255
|
+
const revision = (await bumpRevision(tx, Number(args.expectedAuthorizationRevision))) ??
|
|
1256
|
+
required('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT');
|
|
1257
|
+
await recordAuthorizationAudit(tx, {
|
|
1258
|
+
event: 'authorization.assignment.created',
|
|
1259
|
+
targetKind: 'user',
|
|
1260
|
+
targetId: selection.userId,
|
|
1261
|
+
userId: selection.userId,
|
|
1262
|
+
scopeKey: added.scope.scopeKey,
|
|
1263
|
+
source: 'system-roles',
|
|
1264
|
+
reason: String(args.reason).trim(),
|
|
1265
|
+
before: null,
|
|
1266
|
+
after: added.assignments,
|
|
1267
|
+
revision,
|
|
1268
|
+
metadata: { roleIds: selection.roleIds, assignmentIds: added.assignments.map((a) => a.id) },
|
|
1269
|
+
});
|
|
1270
|
+
const result = { ok: true, revision };
|
|
1271
|
+
await completeOperation(tx, op, result);
|
|
1272
|
+
return result;
|
|
1273
|
+
}),
|
|
1274
|
+
}),
|
|
1275
|
+
previewRoleAssignment: defineFn({
|
|
1276
|
+
input: { ...selectionInput, assignmentId: 'id?' },
|
|
1277
|
+
output: { ok: 'bool', revision: 'int?', contexts: 'json?', errors: 'json?' },
|
|
1278
|
+
effects: AUTHORIZATION_EFFECTS.filter((x) => !x.startsWith('write:')),
|
|
1279
|
+
handler: (ctx, args) => authorizationTransaction(ctx, async (tx) => {
|
|
1280
|
+
const selection = parseSelection(args), state = await selectionState(tx, selection, !!args.assignmentId);
|
|
1281
|
+
let next;
|
|
1282
|
+
if (args.assignmentId) {
|
|
1283
|
+
if (!state.assignments.some((a) => a.id === args.assignmentId))
|
|
1284
|
+
required('assignmentId', 'user.error.roleMissing');
|
|
1285
|
+
next = state.assignments.filter((a) => a.id !== args.assignmentId);
|
|
1286
|
+
}
|
|
1287
|
+
else {
|
|
1288
|
+
await assertAssignableRoles(tx, selection.roleIds);
|
|
1289
|
+
if (state.assignments.some((a) => selection.roleIds.includes(String(a.roleId)) &&
|
|
1290
|
+
String(a.scopeKey ?? 'tenant') === state.scope.scopeKey))
|
|
1291
|
+
required('roleIds', 'E_ROLE_ALREADY_ASSIGNED');
|
|
1292
|
+
next = [
|
|
1293
|
+
...state.assignments,
|
|
1294
|
+
...selection.roleIds.map((roleId) => ({
|
|
1295
|
+
id: `preview:${roleId}`,
|
|
1296
|
+
userId: selection.userId,
|
|
1297
|
+
roleId,
|
|
1298
|
+
...state.scope,
|
|
1299
|
+
})),
|
|
1300
|
+
];
|
|
1301
|
+
}
|
|
1302
|
+
const contexts = [...state.companies]
|
|
1303
|
+
.filter((c) => !state.scope.companyId || c === state.scope.companyId)
|
|
1304
|
+
.flatMap((companyId) => [
|
|
1305
|
+
...(!state.scope.branchId ? [{ companyId, branchId: null }] : []),
|
|
1306
|
+
...[...state.branches]
|
|
1307
|
+
.filter(([b, c]) => c === companyId && (!state.scope.branchId || b === state.scope.branchId))
|
|
1308
|
+
.map(([branchId]) => ({ companyId, branchId })),
|
|
1309
|
+
]);
|
|
1310
|
+
const results = [];
|
|
1311
|
+
for (const context of contexts) {
|
|
1312
|
+
const before = await resolveEffectivePermissions(tx, selection.userId, context);
|
|
1313
|
+
const after = await resolveEffectivePermissions(tx, selection.userId, {
|
|
1314
|
+
...context,
|
|
1315
|
+
assignments: next,
|
|
1316
|
+
companies: state.companies,
|
|
1317
|
+
branches: state.branches,
|
|
1318
|
+
});
|
|
1319
|
+
const old = new Set(before.functions.map((f) => f.key)), fresh = new Set(after.functions.map((f) => f.key));
|
|
1320
|
+
const added = [...fresh].filter((k) => !old.has(k)), removed = [...old].filter((k) => !fresh.has(k));
|
|
1321
|
+
const bundles = Object.values(tx.manifest.permissions.bundles)
|
|
1322
|
+
.filter((b) => b.functions.some((k) => added.includes(k) || removed.includes(k)))
|
|
1323
|
+
.map((b) => ({
|
|
1324
|
+
key: b.key,
|
|
1325
|
+
labels: b.labels,
|
|
1326
|
+
before: b.functions.filter((k) => old.has(k)).length,
|
|
1327
|
+
after: b.functions.filter((k) => fresh.has(k)).length,
|
|
1328
|
+
total: b.functions.length,
|
|
1329
|
+
}));
|
|
1330
|
+
results.push({
|
|
1331
|
+
...context,
|
|
1332
|
+
superuser: before.superuser,
|
|
1333
|
+
added,
|
|
1334
|
+
removed,
|
|
1335
|
+
bundles,
|
|
1336
|
+
retainedBundles: Object.values(tx.manifest.permissions.bundles)
|
|
1337
|
+
.filter((b) => b.functions.some((k) => old.has(k) && fresh.has(k)))
|
|
1338
|
+
.map((b) => ({
|
|
1339
|
+
key: b.key,
|
|
1340
|
+
labels: b.labels,
|
|
1341
|
+
complete: b.functions.every((k) => fresh.has(k)),
|
|
1342
|
+
})),
|
|
1343
|
+
sensitiveChange: [...added, ...removed].some((k) => ['sensitive', 'security'].includes(tx.manifest.permissions.functions[k]?.risk ?? '')),
|
|
1344
|
+
retained: [...old].filter((k) => fresh.has(k)),
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
const B = tx.table('company.Branch');
|
|
1348
|
+
const roots = new Set((await tx.db.all(from(B).where(isNotNull(B.rootKey)))).map((b) => String(b.id)));
|
|
1349
|
+
const contextsWithoutDuplicateRoots = results.filter((c) => {
|
|
1350
|
+
if (!c.branchId || !roots.has(c.branchId))
|
|
1351
|
+
return true;
|
|
1352
|
+
const parent = results.find((p) => p.companyId === c.companyId && !p.branchId);
|
|
1353
|
+
return (!parent ||
|
|
1354
|
+
permissionDigest({
|
|
1355
|
+
added: parent.added,
|
|
1356
|
+
removed: parent.removed,
|
|
1357
|
+
retained: parent.retained,
|
|
1358
|
+
superuser: parent.superuser,
|
|
1359
|
+
}) !==
|
|
1360
|
+
permissionDigest({
|
|
1361
|
+
added: c.added,
|
|
1362
|
+
removed: c.removed,
|
|
1363
|
+
retained: c.retained,
|
|
1364
|
+
superuser: c.superuser,
|
|
1365
|
+
}));
|
|
1366
|
+
});
|
|
1367
|
+
return {
|
|
1368
|
+
ok: true,
|
|
1369
|
+
revision: await authorizationRevisionOf(tx),
|
|
1370
|
+
contexts: contextsWithoutDuplicateRoots,
|
|
1371
|
+
};
|
|
1372
|
+
}),
|
|
1373
|
+
}),
|
|
1374
|
+
};
|
|
1375
|
+
/** Caller owns the identity adapter; this primitive owns the atomic local account/access boundary. */
|
|
1376
|
+
export async function createInternalUserWithAccess(ctx, input, afterCreate) {
|
|
1377
|
+
return authorizationTransaction(ctx, async (tx) => {
|
|
1378
|
+
const name = input.name.trim(), login = input.login.normalize('NFKC').trim().toLowerCase(), email = input.email.trim().toLowerCase();
|
|
1379
|
+
if (!name || !login || !input.reason.trim())
|
|
1380
|
+
required('name', 'user.error.required');
|
|
1381
|
+
const U = tx.table('user.User');
|
|
1382
|
+
if (await tx.db.one(from(U).where(eq(U.login, login))))
|
|
1383
|
+
required('login', 'user.error.loginUnique');
|
|
1384
|
+
if (!(await checkUserEmail(tx, email)).available)
|
|
1385
|
+
required('email', 'E_EMAIL_UNAVAILABLE');
|
|
1386
|
+
const reserved = await tx.db.insertIfAbsent('user.EmailReservation', { id: email, userId: input.id });
|
|
1387
|
+
if (!('dryRun' in reserved) && !reserved.inserted)
|
|
1388
|
+
required('email', 'E_EMAIL_UNAVAILABLE');
|
|
1389
|
+
await tx.db.insert('user.User', {
|
|
1390
|
+
id: input.id,
|
|
1391
|
+
name,
|
|
1392
|
+
login,
|
|
1393
|
+
email,
|
|
1394
|
+
passwordHash: null,
|
|
1395
|
+
partnerId: null,
|
|
1396
|
+
lang: null,
|
|
1397
|
+
timezone: null,
|
|
1398
|
+
defaultCompanyId: input.companyId,
|
|
1399
|
+
defaultBranchId: input.branchId || null,
|
|
1400
|
+
accessKind: 'internal',
|
|
1401
|
+
securityVersion: 0,
|
|
1402
|
+
lastLoginAt: null,
|
|
1403
|
+
superuser: false,
|
|
1404
|
+
active: true,
|
|
1405
|
+
});
|
|
1406
|
+
const added = await addSelectedRoles(tx, {
|
|
1407
|
+
userId: input.id,
|
|
1408
|
+
roleIds: input.roleIds,
|
|
1409
|
+
scopeKind: input.branchId ? 'branch' : 'company',
|
|
1410
|
+
companyId: input.companyId,
|
|
1411
|
+
branchId: input.branchId,
|
|
1412
|
+
addMembership: true,
|
|
1413
|
+
});
|
|
1414
|
+
const revision = await advanceAuthorizationRevision(tx);
|
|
1415
|
+
await recordAuthorizationAudit(tx, {
|
|
1416
|
+
event: 'authorization.user.created',
|
|
1417
|
+
targetKind: 'user',
|
|
1418
|
+
targetId: input.id,
|
|
1419
|
+
userId: input.id,
|
|
1420
|
+
scopeKey: added.scope.scopeKey,
|
|
1421
|
+
source: 'system-roles',
|
|
1422
|
+
reason: input.reason.trim(),
|
|
1423
|
+
before: null,
|
|
1424
|
+
after: added.assignments,
|
|
1425
|
+
revision,
|
|
1426
|
+
metadata: { roleIds: input.roleIds, assignmentIds: added.assignments.map((a) => a.id) },
|
|
1427
|
+
});
|
|
1428
|
+
return { ok: true, userId: input.id, ...(await afterCreate(tx)) };
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1039
1431
|
//# sourceMappingURL=authorization.js.map
|