@open-mercato/core 0.7.0 → 0.7.1-develop.7103.1.41ff100d93
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/.turbo/turbo-build.log +1 -1
- package/dist/helpers/integration/api.js +13 -1
- package/dist/helpers/integration/api.js.map +2 -2
- package/dist/helpers/integration/communicationChannelsFixtures.js +19 -0
- package/dist/helpers/integration/communicationChannelsFixtures.js.map +2 -2
- package/dist/modules/api_keys/api/keys/route.js +1 -1
- package/dist/modules/api_keys/api/keys/route.js.map +2 -2
- package/dist/modules/api_keys/backend/api-keys/page.js +3 -1
- package/dist/modules/api_keys/backend/api-keys/page.js.map +2 -2
- package/dist/modules/api_keys/di.js +9 -0
- package/dist/modules/api_keys/di.js.map +7 -0
- package/dist/modules/api_keys/services/principalService.js +31 -0
- package/dist/modules/api_keys/services/principalService.js.map +7 -0
- package/dist/modules/attachments/api/route.js +2 -2
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentLibrary.js +2 -0
- package/dist/modules/attachments/components/AttachmentLibrary.js.map +2 -2
- package/dist/modules/attachments/di.js +11 -1
- package/dist/modules/attachments/di.js.map +2 -2
- package/dist/modules/attachments/index.js.map +1 -1
- package/dist/modules/attachments/lib/attachment-service.js +313 -0
- package/dist/modules/attachments/lib/attachment-service.js.map +7 -0
- package/dist/modules/attachments/lib/scoped-upload-service.js +15 -2
- package/dist/modules/attachments/lib/scoped-upload-service.js.map +2 -2
- package/dist/modules/attachments/lib/upload-limits.js +6 -2
- package/dist/modules/attachments/lib/upload-limits.js.map +2 -2
- package/dist/modules/audit_logs/backend/audit-logs/page.js +6 -0
- package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
- package/dist/modules/audit_logs/lib/display-helpers.js +19 -11
- package/dist/modules/audit_logs/lib/display-helpers.js.map +2 -2
- package/dist/modules/audit_logs/services/actionLogService.js +57 -12
- package/dist/modules/audit_logs/services/actionLogService.js.map +3 -3
- package/dist/modules/auth/acl.js +30 -5
- package/dist/modules/auth/acl.js.map +2 -2
- package/dist/modules/auth/api/feature-check.js +8 -2
- package/dist/modules/auth/api/feature-check.js.map +2 -2
- package/dist/modules/auth/api/login.js +4 -1
- package/dist/modules/auth/api/login.js.map +2 -2
- package/dist/modules/auth/api/reset/validate.js +55 -0
- package/dist/modules/auth/api/reset/validate.js.map +7 -0
- package/dist/modules/auth/api/roles/acl/route.js +28 -33
- package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
- package/dist/modules/auth/api/session/refresh.js +25 -1
- package/dist/modules/auth/api/session/refresh.js.map +2 -2
- package/dist/modules/auth/api/users/acl/route.js +96 -51
- package/dist/modules/auth/api/users/acl/route.js.map +2 -2
- package/dist/modules/auth/api/users/route.js +13 -5
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/backend/roles/[id]/edit/page.meta.js +5 -1
- package/dist/modules/auth/backend/roles/[id]/edit/page.meta.js.map +2 -2
- package/dist/modules/auth/backend/roles/page.js +3 -1
- package/dist/modules/auth/backend/roles/page.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.meta.js +5 -1
- package/dist/modules/auth/backend/users/[id]/edit/page.meta.js.map +2 -2
- package/dist/modules/auth/backend/users/page.js +2 -1
- package/dist/modules/auth/backend/users/page.js.map +2 -2
- package/dist/modules/auth/commands/acl.js +254 -0
- package/dist/modules/auth/commands/acl.js.map +7 -0
- package/dist/modules/auth/components/AclEditor.js +39 -30
- package/dist/modules/auth/components/AclEditor.js.map +2 -2
- package/dist/modules/auth/data/entities.js.map +2 -2
- package/dist/modules/auth/data/validators.js +5 -1
- package/dist/modules/auth/data/validators.js.map +2 -2
- package/dist/modules/auth/di.js +35 -9
- package/dist/modules/auth/di.js.map +2 -2
- package/dist/modules/auth/frontend/reset/[token]/page.js +46 -0
- package/dist/modules/auth/frontend/reset/[token]/page.js.map +2 -2
- package/dist/modules/auth/lib/grantChecks.js.map +2 -2
- package/dist/modules/auth/lib/userIdFilter.js +16 -0
- package/dist/modules/auth/lib/userIdFilter.js.map +7 -0
- package/dist/modules/auth/services/authService.js +16 -2
- package/dist/modules/auth/services/authService.js.map +2 -2
- package/dist/modules/auth/services/principalService.js +200 -0
- package/dist/modules/auth/services/principalService.js.map +7 -0
- package/dist/modules/auth/services/rbacService.js +67 -24
- package/dist/modules/auth/services/rbacService.js.map +3 -3
- package/dist/modules/auth/services/roleOrganizationScope.js +41 -0
- package/dist/modules/auth/services/roleOrganizationScope.js.map +7 -0
- package/dist/modules/business_rules/backend/logs/page.js +3 -1
- package/dist/modules/business_rules/backend/logs/page.js.map +2 -2
- package/dist/modules/business_rules/backend/rules/page.js +3 -1
- package/dist/modules/business_rules/backend/rules/page.js.map +2 -2
- package/dist/modules/business_rules/backend/sets/page.js +3 -1
- package/dist/modules/business_rules/backend/sets/page.js.map +2 -2
- package/dist/modules/business_rules/lib/expression-evaluator.js +9 -9
- package/dist/modules/business_rules/lib/expression-evaluator.js.map +2 -2
- package/dist/modules/business_rules/lib/rule-evaluator.js +10 -9
- package/dist/modules/business_rules/lib/rule-evaluator.js.map +2 -2
- package/dist/modules/catalog/ai-agents.js +1 -1
- package/dist/modules/catalog/ai-agents.js.map +1 -1
- package/dist/modules/catalog/ai-tools/merchandising-pack.js +2 -2
- package/dist/modules/catalog/ai-tools/merchandising-pack.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +5 -2
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +2 -2
- package/dist/modules/catalog/components/categories/CategoriesDataTable.js +2 -0
- package/dist/modules/catalog/components/categories/CategoriesDataTable.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductsDataTable.js +7 -1
- package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js +2 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +9 -1
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/test-seed/route.js +95 -10
- package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +3 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +2 -2
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js +14 -1
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +2 -2
- package/dist/modules/communication_channels/di.js +7 -1
- package/dist/modules/communication_channels/di.js.map +2 -2
- package/dist/modules/communication_channels/lib/email-capabilities.js +4 -1
- package/dist/modules/communication_channels/lib/email-capabilities.js.map +2 -2
- package/dist/modules/communication_channels/lib/outbound-recipient.js +33 -0
- package/dist/modules/communication_channels/lib/outbound-recipient.js.map +7 -0
- package/dist/modules/communication_channels/lib/resolve-channel-type.js +51 -0
- package/dist/modules/communication_channels/lib/resolve-channel-type.js.map +7 -0
- package/dist/modules/communication_channels/lib/test-seed.js +41 -2
- package/dist/modules/communication_channels/lib/test-seed.js.map +2 -2
- package/dist/modules/configs/lib/system-status.js +9 -0
- package/dist/modules/configs/lib/system-status.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/page.js +3 -1
- package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js +3 -1
- package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
- package/dist/modules/currencies/services/rateFetchingService.js +6 -1
- package/dist/modules/currencies/services/rateFetchingService.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/domain-mappings.js +9 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +3 -1
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js +3 -1
- package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js.map +2 -2
- package/dist/modules/customer_accounts/services/domainMappingService.js +10 -0
- package/dist/modules/customer_accounts/services/domainMappingService.js.map +2 -2
- package/dist/modules/customers/ai-tools/companies-pack.js +4 -1
- package/dist/modules/customers/ai-tools/companies-pack.js.map +2 -2
- package/dist/modules/customers/ai-tools/deal-analyzer-pack.js +13 -2
- package/dist/modules/customers/ai-tools/deal-analyzer-pack.js.map +2 -2
- package/dist/modules/customers/ai-tools/deals-pack.js +52 -6
- package/dist/modules/customers/ai-tools/deals-pack.js.map +2 -2
- package/dist/modules/customers/ai-tools/people-pack.js +4 -1
- package/dist/modules/customers/ai-tools/people-pack.js.map +2 -2
- package/dist/modules/customers/api/activities/route.js +48 -6
- package/dist/modules/customers/api/activities/route.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/people/route.js +20 -64
- package/dist/modules/customers/api/companies/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/route.js +17 -94
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/dashboard/widgets/customer-todos/route.js +8 -5
- package/dist/modules/customers/api/dashboard/widgets/customer-todos/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/companies/route.js +1 -1
- package/dist/modules/customers/api/deals/[id]/companies/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/people/route.js +1 -1
- package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/route.js +8 -25
- package/dist/modules/customers/api/deals/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/stats/route.js +3 -22
- package/dist/modules/customers/api/deals/[id]/stats/route.js.map +2 -2
- package/dist/modules/customers/api/deals/aggregate/route.js +14 -9
- package/dist/modules/customers/api/deals/aggregate/route.js.map +2 -2
- package/dist/modules/customers/api/deals/map/route.js +5 -5
- package/dist/modules/customers/api/deals/map/route.js.map +2 -2
- package/dist/modules/customers/api/deals/route.js +38 -1
- package/dist/modules/customers/api/deals/route.js.map +2 -2
- package/dist/modules/customers/api/entity-roles-factory.js +4 -4
- package/dist/modules/customers/api/entity-roles-factory.js.map +2 -2
- package/dist/modules/customers/api/interactions/route.js +3 -1
- package/dist/modules/customers/api/interactions/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/tasks/route.js +27 -4
- package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js +4 -3
- package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/context.js +1 -1
- package/dist/modules/customers/api/people/[id]/companies/context.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/enriched/route.js +1 -1
- package/dist/modules/customers/api/people/[id]/companies/enriched/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/route.js +11 -10
- package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/people/check-phone/route.js +51 -9
- package/dist/modules/customers/api/people/check-phone/route.js.map +2 -2
- package/dist/modules/customers/api/todos/route.js +30 -5
- package/dist/modules/customers/api/todos/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +4 -2
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +6 -1
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +5 -2
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/Lane.js +4 -13
- package/dist/modules/customers/backend/customers/deals/pipeline/components/Lane.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.js +85 -14
- package/dist/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/toneClasses.js +18 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/toneClasses.js.map +7 -0
- package/dist/modules/customers/backend/customers/people/page.js +4 -2
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/backend/hooks/useEmailDuplicateCheck.js +9 -3
- package/dist/modules/customers/backend/hooks/useEmailDuplicateCheck.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +21 -34
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js.map +1 -1
- package/dist/modules/customers/commands/personCompanyLinks.js +131 -2
- package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
- package/dist/modules/customers/components/CustomerTodosTable.js +1 -0
- package/dist/modules/customers/components/CustomerTodosTable.js.map +2 -2
- package/dist/modules/customers/components/DealsKpiStrip.js +7 -6
- package/dist/modules/customers/components/DealsKpiStrip.js.map +2 -2
- package/dist/modules/customers/components/calendar/AgendaList.js +2 -2
- package/dist/modules/customers/components/calendar/AgendaList.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js +1 -1
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarScreen.js +1 -1
- package/dist/modules/customers/components/calendar/CalendarScreen.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +1 -1
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
- package/dist/modules/customers/components/calendar/EventBlock.js +1 -1
- package/dist/modules/customers/components/calendar/EventBlock.js.map +2 -2
- package/dist/modules/customers/components/calendar/EventPeekPopover.js +3 -2
- package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +40 -5
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js +2 -1
- package/dist/modules/customers/components/calendar/editor/hooks.js.map +2 -2
- package/dist/modules/customers/components/calendar/types.js +1 -1
- package/dist/modules/customers/components/calendar/types.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyKpiBar.js +5 -4
- package/dist/modules/customers/components/detail/CompanyKpiBar.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js +3 -1
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +19 -7
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/DealsSection.js +6 -5
- package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
- package/dist/modules/customers/components/detail/EntityTagsDialog.js +35 -11
- package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonCompaniesSection.js +3 -1
- package/dist/modules/customers/components/detail/PersonCompaniesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/hooks/usePersonTasks.js +19 -13
- package/dist/modules/customers/components/detail/hooks/usePersonTasks.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +4 -4
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +3 -3
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
- package/dist/modules/customers/components/detail/utils.js +5 -5
- package/dist/modules/customers/components/detail/utils.js.map +2 -2
- package/dist/modules/customers/data/entities.js.map +1 -1
- package/dist/modules/customers/data/validators.js +32 -3
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/events.js +5 -0
- package/dist/modules/customers/events.js.map +2 -2
- package/dist/modules/customers/lib/calendar/conflicts.js +8 -2
- package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
- package/dist/modules/customers/lib/calendar/editorPayload.js +3 -2
- package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
- package/dist/modules/customers/lib/calendar/mapItem.js +8 -3
- package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
- package/dist/modules/customers/lib/calendar/participantIdentity.js +9 -0
- package/dist/modules/customers/lib/calendar/participantIdentity.js.map +7 -0
- package/dist/modules/customers/lib/closureStage.js +45 -0
- package/dist/modules/customers/lib/closureStage.js.map +7 -0
- package/dist/modules/customers/lib/dealStatus.js +30 -0
- package/dist/modules/customers/lib/dealStatus.js.map +2 -2
- package/dist/modules/customers/lib/detailReadAccess.js +12 -0
- package/dist/modules/customers/lib/detailReadAccess.js.map +7 -0
- package/dist/modules/customers/lib/findPeopleByAddresses.js +1 -0
- package/dist/modules/customers/lib/findPeopleByAddresses.js.map +2 -2
- package/dist/modules/customers/lib/personCompanies.js +100 -2
- package/dist/modules/customers/lib/personCompanies.js.map +2 -2
- package/dist/modules/customers/lib/todoCompatibility.js +12 -0
- package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
- package/dist/modules/dashboards/lib/widgetScope.js +6 -1
- package/dist/modules/dashboards/lib/widgetScope.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +3 -1
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/lib/abandoned-run.js +19 -0
- package/dist/modules/data_sync/lib/abandoned-run.js.map +7 -0
- package/dist/modules/data_sync/lib/sync-engine.js +72 -6
- package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
- package/dist/modules/data_sync/workers/sync-export.js +5 -1
- package/dist/modules/data_sync/workers/sync-export.js.map +2 -2
- package/dist/modules/data_sync/workers/sync-import.js +5 -1
- package/dist/modules/data_sync/workers/sync-import.js.map +2 -2
- package/dist/modules/devices/acl.js +9 -1
- package/dist/modules/devices/acl.js.map +2 -2
- package/dist/modules/devices/api/admin/devices/[id]/route.js +5 -32
- package/dist/modules/devices/api/admin/devices/[id]/route.js.map +2 -2
- package/dist/modules/devices/api/admin/devices/route.js +14 -1
- package/dist/modules/devices/api/admin/devices/route.js.map +2 -2
- package/dist/modules/devices/api/deviceList.js +99 -12
- package/dist/modules/devices/api/deviceList.js.map +2 -2
- package/dist/modules/devices/api/deviceSerialization.js +46 -0
- package/dist/modules/devices/api/deviceSerialization.js.map +7 -0
- package/dist/modules/devices/api/openapi.js +3 -1
- package/dist/modules/devices/api/openapi.js.map +2 -2
- package/dist/modules/devices/api/route.js +9 -1
- package/dist/modules/devices/api/route.js.map +2 -2
- package/dist/modules/devices/backend/devices/[id]/page.js +10 -26
- package/dist/modules/devices/backend/devices/[id]/page.js.map +2 -2
- package/dist/modules/devices/backend/devices/create/page.js +17 -1
- package/dist/modules/devices/backend/devices/create/page.js.map +2 -2
- package/dist/modules/devices/backend/devices/page.js +28 -34
- package/dist/modules/devices/backend/devices/page.js.map +2 -2
- package/dist/modules/devices/backend/devices/useDeviceUserLabels.js +34 -0
- package/dist/modules/devices/backend/devices/useDeviceUserLabels.js.map +7 -0
- package/dist/modules/devices/backend/devices/userOptions.js +52 -0
- package/dist/modules/devices/backend/devices/userOptions.js.map +7 -0
- package/dist/modules/devices/setup.js +6 -2
- package/dist/modules/devices/setup.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/page.js +2 -1
- package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/page.js +2 -1
- package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
- package/dist/modules/directory/di.js +9 -0
- package/dist/modules/directory/di.js.map +2 -2
- package/dist/modules/directory/services/organizationHierarchyService.js +29 -0
- package/dist/modules/directory/services/organizationHierarchyService.js.map +7 -0
- package/dist/modules/directory/services/organizationScopeService.js +56 -0
- package/dist/modules/directory/services/organizationScopeService.js.map +7 -0
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/dist/modules/entities/api/records.js +4 -2
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +3 -1
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/eudr/api/plots/route.js +5 -1
- package/dist/modules/eudr/api/plots/route.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/evidence-submissions/page.js +3 -0
- package/dist/modules/eudr/backend/eudr/evidence-submissions/page.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/plots/page.js +3 -0
- package/dist/modules/eudr/backend/eudr/plots/page.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/product-mappings/page.js +3 -0
- package/dist/modules/eudr/backend/eudr/product-mappings/page.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/risk-assessments/page.js +3 -0
- package/dist/modules/eudr/backend/eudr/risk-assessments/page.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/statements/page.js +3 -0
- package/dist/modules/eudr/backend/eudr/statements/page.js.map +2 -2
- package/dist/modules/eudr/commands/statements.js +0 -3
- package/dist/modules/eudr/commands/statements.js.map +2 -2
- package/dist/modules/eudr/data/validators.js +4 -1
- package/dist/modules/eudr/data/validators.js.map +2 -2
- package/dist/modules/feature_toggles/components/FeatureTogglesTable.js +1 -0
- package/dist/modules/feature_toggles/components/FeatureTogglesTable.js.map +2 -2
- package/dist/modules/feature_toggles/components/OverridesTable.js +1 -0
- package/dist/modules/feature_toggles/components/OverridesTable.js.map +2 -2
- package/dist/modules/feature_toggles/defaults.json +0 -8
- package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js +3 -0
- package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/page.js +3 -0
- package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +2 -2
- package/dist/modules/inbox_ops/lib/htmlToPlainText.js +1 -12
- package/dist/modules/inbox_ops/lib/htmlToPlainText.js.map +2 -2
- package/dist/modules/integrations/api/[id]/credentials/route.js +6 -1
- package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +38 -29
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +36 -9
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/credential-secret-fields.js +55 -0
- package/dist/modules/integrations/backend/integrations/credential-secret-fields.js.map +7 -0
- package/dist/modules/integrations/data/validators.js +11 -3
- package/dist/modules/integrations/data/validators.js.map +2 -2
- package/dist/modules/integrations/lib/credentials-masking.js +6 -2
- package/dist/modules/integrations/lib/credentials-masking.js.map +2 -2
- package/dist/modules/messages/api/[id]/route.js +29 -1
- package/dist/modules/messages/api/[id]/route.js.map +2 -2
- package/dist/modules/messages/api/openapi.js +17 -2
- package/dist/modules/messages/api/openapi.js.map +2 -2
- package/dist/modules/messages/api/route.js +17 -2
- package/dist/modules/messages/api/route.js.map +2 -2
- package/dist/modules/messages/components/MessagesInboxPageClient.js +7 -14
- package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
- package/dist/modules/messages/components/inboxFilters.js +15 -0
- package/dist/modules/messages/components/inboxFilters.js.map +2 -2
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js +11 -1
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js.map +2 -2
- package/dist/modules/messages/components/message-detail/panels/MessageHeader.js +3 -1
- package/dist/modules/messages/components/message-detail/panels/MessageHeader.js.map +2 -2
- package/dist/modules/messages/components/message-detail/panels/thread-panel.js +2 -1
- package/dist/modules/messages/components/message-detail/panels/thread-panel.js.map +2 -2
- package/dist/modules/messages/components/messageListLabels.js +14 -2
- package/dist/modules/messages/components/messageListLabels.js.map +2 -2
- package/dist/modules/messages/data/validators.js +18 -4
- package/dist/modules/messages/data/validators.js.map +2 -2
- package/dist/modules/messages/lib/channel-sender-identity.js +26 -0
- package/dist/modules/messages/lib/channel-sender-identity.js.map +7 -0
- package/dist/modules/messages/lib/composeSourceChannelType.js +41 -0
- package/dist/modules/messages/lib/composeSourceChannelType.js.map +7 -0
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +4 -6
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js +3 -1
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +2 -2
- package/dist/modules/payment_gateways/lib/webhook-processor.js +7 -0
- package/dist/modules/payment_gateways/lib/webhook-processor.js.map +2 -2
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js +3 -0
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
- package/dist/modules/push_notifications/backend/push_notifications/page.js +3 -1
- package/dist/modules/push_notifications/backend/push_notifications/page.js.map +2 -2
- package/dist/modules/push_notifications/backend/push_notifications/send/page.js +1 -1
- package/dist/modules/push_notifications/backend/push_notifications/send/page.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +150 -51
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/lib/search-entity-policy.js +14 -0
- package/dist/modules/query_index/lib/search-entity-policy.js.map +7 -0
- package/dist/modules/query_index/lib/search-tokens.js +79 -3
- package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
- package/dist/modules/resources/backend/resources/resource-types/page.js +3 -1
- package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/page.js +3 -1
- package/dist/modules/resources/backend/resources/resources/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/offers/page.js +3 -0
- package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.js +3 -0
- package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +10 -8
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +79 -1
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/components/PriceWithCurrency.js +7 -5
- package/dist/modules/sales/components/PriceWithCurrency.js.map +2 -2
- package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js +3 -0
- package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
- package/dist/modules/sales/components/documents/AdjustmentsSection.js +8 -6
- package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +51 -22
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +83 -18
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/PaymentsSection.js +23 -9
- package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ReturnsSection.js +8 -6
- package/dist/modules/sales/components/documents/ReturnsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js +68 -14
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesOrderDraftLines.js +6 -5
- package/dist/modules/sales/components/documents/SalesOrderDraftLines.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +5 -4
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentsSection.js +8 -6
- package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/lineItemShipmentLock.js +54 -0
- package/dist/modules/sales/components/documents/lineItemShipmentLock.js.map +7 -0
- package/dist/modules/sales/components/documents/lineItemUtils.js +2 -2
- package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
- package/dist/modules/sales/data/entities.js +8 -2
- package/dist/modules/sales/data/entities.js.map +2 -2
- package/dist/modules/sales/extension-points.js +1 -0
- package/dist/modules/sales/extension-points.js.map +2 -2
- package/dist/modules/sales/lib/salesChannelsToggleSeed.js +33 -0
- package/dist/modules/sales/lib/salesChannelsToggleSeed.js.map +7 -0
- package/dist/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.js +32 -0
- package/dist/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.js.map +7 -0
- package/dist/modules/sales/setup.js +2 -0
- package/dist/modules/sales/setup.js.map +2 -2
- package/dist/modules/sales/widgets/injection/payment-gateway-status-column/widget.js +10 -2
- package/dist/modules/sales/widgets/injection/payment-gateway-status-column/widget.js.map +2 -2
- package/dist/modules/sales/widgets/injection-table.js +4 -7
- package/dist/modules/sales/widgets/injection-table.js.map +2 -2
- package/dist/modules/staff/backend/staff/leave-requests/page.js +3 -0
- package/dist/modules/staff/backend/staff/leave-requests/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/my-leave-requests/page.js +3 -0
- package/dist/modules/staff/backend/staff/my-leave-requests/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/page.js +3 -0
- package/dist/modules/staff/backend/staff/team-members/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-roles/page.js +3 -0
- package/dist/modules/staff/backend/staff/team-roles/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +3 -0
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/page.js +3 -0
- package/dist/modules/staff/backend/staff/teams/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/page.js +29 -18
- package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js +3 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
- package/dist/modules/sync_excel/lib/adapters/customers.js +1 -0
- package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
- package/dist/modules/warranty_claims/api/assignees/route.js +3 -3
- package/dist/modules/warranty_claims/api/assignees/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/attachments/route.js +2 -2
- package/dist/modules/warranty_claims/api/portal/attachments/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/route.js +12 -9
- package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
- package/dist/modules/warranty_claims/backend/page.js +3 -0
- package/dist/modules/warranty_claims/backend/page.js.map +2 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/page.js +3 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/page.js.map +2 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.js +3 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.js.map +2 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.js +3 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.js.map +2 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js +3 -0
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js.map +2 -2
- package/dist/modules/wms/api/warehouseSearch.js +21 -0
- package/dist/modules/wms/api/warehouseSearch.js.map +7 -0
- package/dist/modules/wms/api/warehouses/route.js +13 -9
- package/dist/modules/wms/api/warehouses/route.js.map +2 -2
- package/dist/modules/wms/api/zones/route.js +15 -2
- package/dist/modules/wms/api/zones/route.js.map +2 -2
- package/dist/modules/wms/commands/configuration.js +121 -9
- package/dist/modules/wms/commands/configuration.js.map +2 -2
- package/dist/modules/wms/commands/inventory-actions.js +3 -0
- package/dist/modules/wms/commands/inventory-actions.js.map +2 -2
- package/dist/modules/wms/commands/shared.js +9 -1
- package/dist/modules/wms/commands/shared.js.map +2 -2
- package/dist/modules/wms/components/backend/WarehouseEditDialog.js +158 -0
- package/dist/modules/wms/components/backend/WarehouseEditDialog.js.map +7 -0
- package/dist/modules/wms/components/backend/WmsConfigurationPage.js +76 -111
- package/dist/modules/wms/components/backend/WmsConfigurationPage.js.map +3 -3
- package/dist/modules/wms/components/backend/WmsInventoryConsolePage.js +1 -0
- package/dist/modules/wms/components/backend/WmsInventoryConsolePage.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsLotsListPage.js +1 -0
- package/dist/modules/wms/components/backend/WmsLotsListPage.js.map +2 -2
- package/dist/modules/wms/components/backend/inventoryMutationLoaders.js +27 -20
- package/dist/modules/wms/components/backend/inventoryMutationLoaders.js.map +2 -2
- package/dist/modules/wms/components/backend/warehouseFormOptions.js +66 -0
- package/dist/modules/wms/components/backend/warehouseFormOptions.js.map +7 -0
- package/dist/modules/workflows/backend/definitions/page.js +3 -1
- package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
- package/dist/modules/workflows/backend/events/page.js +3 -1
- package/dist/modules/workflows/backend/events/page.js.map +2 -2
- package/dist/modules/workflows/backend/instances/page.js +3 -1
- package/dist/modules/workflows/backend/instances/page.js.map +2 -2
- package/dist/modules/workflows/backend/tasks/page.js +3 -1
- package/dist/modules/workflows/backend/tasks/page.js.map +2 -2
- package/dist/modules/workflows/lib/activity-executor.js +4 -0
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/package.json +9 -10
- package/src/helpers/integration/api.ts +33 -1
- package/src/helpers/integration/communicationChannelsFixtures.ts +69 -1
- package/src/modules/api_keys/api/keys/route.ts +3 -1
- package/src/modules/api_keys/backend/api-keys/page.tsx +4 -1
- package/src/modules/api_keys/di.ts +7 -0
- package/src/modules/api_keys/services/principalService.ts +33 -0
- package/src/modules/attachments/AGENTS.md +15 -0
- package/src/modules/attachments/api/route.ts +2 -2
- package/src/modules/attachments/components/AttachmentLibrary.tsx +3 -0
- package/src/modules/attachments/di.ts +16 -0
- package/src/modules/attachments/i18n/de.json +5 -0
- package/src/modules/attachments/i18n/en.json +5 -0
- package/src/modules/attachments/i18n/es.json +5 -0
- package/src/modules/attachments/i18n/ko.json +5 -0
- package/src/modules/attachments/i18n/pl.json +5 -0
- package/src/modules/attachments/index.ts +10 -0
- package/src/modules/attachments/lib/attachment-service.ts +468 -0
- package/src/modules/attachments/lib/scoped-upload-service.ts +46 -0
- package/src/modules/attachments/lib/upload-limits.ts +6 -2
- package/src/modules/audit_logs/backend/audit-logs/page.tsx +8 -0
- package/src/modules/audit_logs/lib/display-helpers.tsx +42 -32
- package/src/modules/audit_logs/services/actionLogService.ts +74 -11
- package/src/modules/auth/acl.ts +30 -6
- package/src/modules/auth/api/feature-check.ts +10 -3
- package/src/modules/auth/api/login.ts +9 -1
- package/src/modules/auth/api/reset/validate.ts +57 -0
- package/src/modules/auth/api/roles/acl/route.ts +42 -39
- package/src/modules/auth/api/session/refresh.ts +32 -1
- package/src/modules/auth/api/users/acl/route.ts +177 -53
- package/src/modules/auth/api/users/route.ts +18 -5
- package/src/modules/auth/backend/roles/[id]/edit/page.meta.ts +5 -2
- package/src/modules/auth/backend/roles/page.tsx +4 -2
- package/src/modules/auth/backend/users/[id]/edit/page.meta.ts +5 -2
- package/src/modules/auth/backend/users/page.tsx +3 -2
- package/src/modules/auth/commands/acl.ts +495 -0
- package/src/modules/auth/components/AclEditor.tsx +63 -33
- package/src/modules/auth/data/entities.ts +4 -1
- package/src/modules/auth/data/validators.ts +5 -0
- package/src/modules/auth/di.ts +46 -16
- package/src/modules/auth/frontend/reset/[token]/page.tsx +74 -0
- package/src/modules/auth/i18n/de.json +389 -0
- package/src/modules/auth/i18n/en.json +389 -0
- package/src/modules/auth/i18n/es.json +389 -0
- package/src/modules/auth/i18n/ko.json +389 -0
- package/src/modules/auth/i18n/pl.json +391 -2
- package/src/modules/auth/lib/grantChecks.ts +6 -0
- package/src/modules/auth/lib/userIdFilter.ts +31 -0
- package/src/modules/auth/services/authService.ts +18 -2
- package/src/modules/auth/services/principalService.ts +266 -0
- package/src/modules/auth/services/rbacService.ts +110 -24
- package/src/modules/auth/services/roleOrganizationScope.ts +70 -0
- package/src/modules/business_rules/backend/logs/page.tsx +4 -1
- package/src/modules/business_rules/backend/rules/page.tsx +4 -1
- package/src/modules/business_rules/backend/sets/page.tsx +4 -1
- package/src/modules/business_rules/lib/expression-evaluator.ts +9 -9
- package/src/modules/business_rules/lib/rule-evaluator.ts +10 -9
- package/src/modules/catalog/ai-agents.ts +1 -1
- package/src/modules/catalog/ai-tools/merchandising-pack.ts +2 -2
- package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +12 -5
- package/src/modules/catalog/components/categories/CategoriesDataTable.tsx +3 -0
- package/src/modules/catalog/components/products/ProductsDataTable.tsx +7 -0
- package/src/modules/catalog/i18n/de.json +1 -0
- package/src/modules/catalog/i18n/en.json +1 -0
- package/src/modules/catalog/i18n/es.json +1 -0
- package/src/modules/catalog/i18n/ko.json +1 -0
- package/src/modules/catalog/i18n/pl.json +1 -0
- package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.tsx +4 -0
- package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +17 -1
- package/src/modules/communication_channels/api/post/test-seed/route.ts +155 -16
- package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +4 -0
- package/src/modules/communication_channels/commands/ingest-inbound-message.ts +38 -1
- package/src/modules/communication_channels/di.ts +7 -0
- package/src/modules/communication_channels/lib/adapter.ts +14 -0
- package/src/modules/communication_channels/lib/email-capabilities.ts +4 -0
- package/src/modules/communication_channels/lib/outbound-recipient.ts +71 -0
- package/src/modules/communication_channels/lib/resolve-channel-type.ts +99 -0
- package/src/modules/communication_channels/lib/test-seed.ts +64 -4
- package/src/modules/configs/i18n/de.json +2 -0
- package/src/modules/configs/i18n/en.json +2 -0
- package/src/modules/configs/i18n/es.json +2 -0
- package/src/modules/configs/i18n/ko.json +2 -0
- package/src/modules/configs/i18n/pl.json +2 -0
- package/src/modules/configs/lib/system-status.ts +9 -0
- package/src/modules/currencies/AGENTS.md +3 -0
- package/src/modules/currencies/backend/currencies/page.tsx +4 -1
- package/src/modules/currencies/backend/exchange-rates/page.tsx +4 -1
- package/src/modules/currencies/services/README.md +45 -0
- package/src/modules/currencies/services/rateFetchingService.ts +6 -1
- package/src/modules/customer_accounts/api/admin/domain-mappings.ts +7 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +4 -1
- package/src/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.tsx +4 -1
- package/src/modules/customer_accounts/services/domainMappingService.ts +10 -0
- package/src/modules/customers/ai-tools/companies-pack.ts +4 -1
- package/src/modules/customers/ai-tools/deal-analyzer-pack.ts +17 -2
- package/src/modules/customers/ai-tools/deals-pack.ts +66 -1
- package/src/modules/customers/ai-tools/people-pack.ts +4 -1
- package/src/modules/customers/api/activities/route.ts +45 -0
- package/src/modules/customers/api/companies/[id]/people/route.ts +24 -73
- package/src/modules/customers/api/companies/[id]/route.ts +24 -107
- package/src/modules/customers/api/dashboard/widgets/customer-todos/route.ts +16 -8
- package/src/modules/customers/api/deals/[id]/companies/route.ts +4 -1
- package/src/modules/customers/api/deals/[id]/people/route.ts +4 -1
- package/src/modules/customers/api/deals/[id]/route.ts +17 -28
- package/src/modules/customers/api/deals/[id]/stats/route.ts +10 -25
- package/src/modules/customers/api/deals/aggregate/route.ts +17 -9
- package/src/modules/customers/api/deals/map/route.ts +4 -4
- package/src/modules/customers/api/deals/route.ts +50 -1
- package/src/modules/customers/api/entity-roles-factory.ts +8 -4
- package/src/modules/customers/api/interactions/route.ts +8 -2
- package/src/modules/customers/api/interactions/tasks/route.ts +24 -1
- package/src/modules/customers/api/people/[id]/companies/[linkId]/route.ts +12 -4
- package/src/modules/customers/api/people/[id]/companies/context.ts +4 -1
- package/src/modules/customers/api/people/[id]/companies/enriched/route.ts +4 -1
- package/src/modules/customers/api/people/[id]/route.ts +16 -11
- package/src/modules/customers/api/people/check-phone/route.ts +75 -10
- package/src/modules/customers/api/todos/route.ts +27 -2
- package/src/modules/customers/backend/customers/companies/page.tsx +5 -2
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +7 -2
- package/src/modules/customers/backend/customers/deals/page.tsx +8 -2
- package/src/modules/customers/backend/customers/deals/pipeline/components/Lane.tsx +5 -14
- package/src/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.tsx +109 -27
- package/src/modules/customers/backend/customers/deals/pipeline/components/toneClasses.ts +21 -0
- package/src/modules/customers/backend/customers/people/page.tsx +5 -2
- package/src/modules/customers/backend/hooks/useEmailDuplicateCheck.ts +12 -4
- package/src/modules/customers/commands/deals.ts +34 -59
- package/src/modules/customers/commands/interactions.ts +1 -1
- package/src/modules/customers/commands/personCompanyLinks.ts +224 -8
- package/src/modules/customers/components/CustomerTodosTable.tsx +2 -0
- package/src/modules/customers/components/DealsKpiStrip.tsx +7 -7
- package/src/modules/customers/components/calendar/AgendaList.tsx +3 -3
- package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +1 -1
- package/src/modules/customers/components/calendar/CalendarScreen.tsx +1 -1
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +1 -1
- package/src/modules/customers/components/calendar/EventBlock.tsx +2 -2
- package/src/modules/customers/components/calendar/EventPeekPopover.tsx +4 -2
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +61 -5
- package/src/modules/customers/components/calendar/editor/hooks.ts +7 -1
- package/src/modules/customers/components/calendar/types.ts +2 -2
- package/src/modules/customers/components/detail/CompanyKpiBar.tsx +5 -4
- package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +8 -1
- package/src/modules/customers/components/detail/DealForm.tsx +22 -2
- package/src/modules/customers/components/detail/DealsSection.tsx +11 -5
- package/src/modules/customers/components/detail/EntityTagsDialog.tsx +58 -12
- package/src/modules/customers/components/detail/PersonCompaniesSection.tsx +8 -1
- package/src/modules/customers/components/detail/hooks/usePersonTasks.ts +23 -14
- package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +5 -5
- package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +5 -5
- package/src/modules/customers/components/detail/types.ts +1 -1
- package/src/modules/customers/components/detail/utils.ts +15 -5
- package/src/modules/customers/data/entities.ts +1 -1
- package/src/modules/customers/data/validators.ts +50 -9
- package/src/modules/customers/events.ts +5 -0
- package/src/modules/customers/i18n/de.json +9 -1
- package/src/modules/customers/i18n/en.json +9 -1
- package/src/modules/customers/i18n/es.json +9 -1
- package/src/modules/customers/i18n/ko.json +9 -1
- package/src/modules/customers/i18n/pl.json +9 -1
- package/src/modules/customers/lib/calendar/conflicts.ts +10 -2
- package/src/modules/customers/lib/calendar/editorPayload.ts +4 -3
- package/src/modules/customers/lib/calendar/mapItem.ts +8 -3
- package/src/modules/customers/lib/calendar/participantIdentity.ts +17 -0
- package/src/modules/customers/lib/closureStage.ts +76 -0
- package/src/modules/customers/lib/dealStatus.ts +42 -0
- package/src/modules/customers/lib/detailReadAccess.ts +35 -0
- package/src/modules/customers/lib/findPeopleByAddresses.ts +8 -6
- package/src/modules/customers/lib/personCompanies.ts +136 -1
- package/src/modules/customers/lib/todoCompatibility.ts +19 -1
- package/src/modules/dashboards/i18n/es.json +4 -4
- package/src/modules/dashboards/i18n/pl.json +7 -7
- package/src/modules/dashboards/lib/widgetScope.ts +6 -0
- package/src/modules/data_sync/AGENTS.md +1 -1
- package/src/modules/data_sync/backend/data-sync/page.tsx +4 -1
- package/src/modules/data_sync/lib/abandoned-run.ts +56 -0
- package/src/modules/data_sync/lib/adapter.ts +36 -0
- package/src/modules/data_sync/lib/sync-engine.ts +113 -5
- package/src/modules/data_sync/workers/sync-export.ts +4 -0
- package/src/modules/data_sync/workers/sync-import.ts +4 -0
- package/src/modules/devices/AGENTS.md +20 -1
- package/src/modules/devices/acl.ts +9 -1
- package/src/modules/devices/api/admin/devices/[id]/route.ts +5 -34
- package/src/modules/devices/api/admin/devices/route.ts +14 -1
- package/src/modules/devices/api/deviceList.ts +135 -11
- package/src/modules/devices/api/deviceSerialization.ts +50 -0
- package/src/modules/devices/api/openapi.ts +9 -1
- package/src/modules/devices/api/route.ts +9 -1
- package/src/modules/devices/backend/devices/[id]/page.tsx +18 -32
- package/src/modules/devices/backend/devices/create/page.tsx +17 -1
- package/src/modules/devices/backend/devices/page.tsx +44 -45
- package/src/modules/devices/backend/devices/useDeviceUserLabels.ts +45 -0
- package/src/modules/devices/backend/devices/userOptions.ts +99 -0
- package/src/modules/devices/i18n/de.json +3 -2
- package/src/modules/devices/i18n/en.json +3 -2
- package/src/modules/devices/i18n/es.json +3 -2
- package/src/modules/devices/i18n/ko.json +3 -2
- package/src/modules/devices/i18n/pl.json +3 -2
- package/src/modules/devices/setup.ts +6 -2
- package/src/modules/directory/backend/directory/organizations/page.tsx +3 -1
- package/src/modules/directory/backend/directory/tenants/page.tsx +3 -1
- package/src/modules/directory/di.ts +14 -2
- package/src/modules/directory/services/organizationHierarchyService.ts +34 -0
- package/src/modules/directory/services/organizationScopeService.ts +85 -0
- package/src/modules/directory/utils/organizationScope.ts +4 -15
- package/src/modules/entities/api/records.ts +2 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +4 -1
- package/src/modules/eudr/api/plots/route.ts +4 -0
- package/src/modules/eudr/backend/eudr/evidence-submissions/page.tsx +4 -0
- package/src/modules/eudr/backend/eudr/plots/page.tsx +4 -0
- package/src/modules/eudr/backend/eudr/product-mappings/page.tsx +4 -0
- package/src/modules/eudr/backend/eudr/risk-assessments/page.tsx +4 -0
- package/src/modules/eudr/backend/eudr/statements/page.tsx +4 -0
- package/src/modules/eudr/commands/statements.ts +0 -3
- package/src/modules/eudr/data/validators.ts +16 -1
- package/src/modules/feature_toggles/components/FeatureTogglesTable.tsx +2 -1
- package/src/modules/feature_toggles/components/OverridesTable.tsx +2 -1
- package/src/modules/feature_toggles/defaults.json +0 -8
- package/src/modules/inbox_ops/backend/inbox-ops/log/page.tsx +4 -0
- package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +4 -0
- package/src/modules/inbox_ops/lib/htmlToPlainText.ts +1 -17
- package/src/modules/integrations/api/[id]/credentials/route.ts +6 -4
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +61 -31
- package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +44 -9
- package/src/modules/integrations/backend/integrations/credential-secret-fields.ts +84 -0
- package/src/modules/integrations/data/validators.ts +12 -2
- package/src/modules/integrations/i18n/de.json +1 -0
- package/src/modules/integrations/i18n/en.json +1 -0
- package/src/modules/integrations/i18n/es.json +1 -0
- package/src/modules/integrations/i18n/ko.json +1 -0
- package/src/modules/integrations/i18n/pl.json +1 -0
- package/src/modules/integrations/lib/credentials-masking.ts +10 -6
- package/src/modules/messages/api/[id]/route.ts +55 -1
- package/src/modules/messages/api/openapi.ts +20 -0
- package/src/modules/messages/api/route.ts +31 -2
- package/src/modules/messages/components/MessagesInboxPageClient.tsx +9 -16
- package/src/modules/messages/components/inboxFilters.ts +36 -0
- package/src/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.ts +14 -1
- package/src/modules/messages/components/message-detail/panels/MessageHeader.tsx +3 -1
- package/src/modules/messages/components/message-detail/panels/thread-panel.tsx +2 -1
- package/src/modules/messages/components/message-detail/types.ts +3 -0
- package/src/modules/messages/components/messageListLabels.ts +52 -3
- package/src/modules/messages/data/validators.ts +43 -4
- package/src/modules/messages/lib/channel-sender-identity.ts +55 -0
- package/src/modules/messages/lib/composeSourceChannelType.ts +95 -0
- package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +4 -6
- package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +4 -1
- package/src/modules/payment_gateways/lib/webhook-processor.ts +11 -0
- package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +4 -0
- package/src/modules/push_notifications/backend/push_notifications/page.tsx +4 -1
- package/src/modules/push_notifications/backend/push_notifications/send/page.tsx +4 -4
- package/src/modules/query_index/lib/engine.ts +249 -61
- package/src/modules/query_index/lib/search-entity-policy.ts +46 -0
- package/src/modules/query_index/lib/search-tokens.ts +135 -3
- package/src/modules/resources/backend/resources/resource-types/page.tsx +4 -1
- package/src/modules/resources/backend/resources/resources/page.tsx +4 -1
- package/src/modules/sales/backend/sales/channels/offers/page.tsx +4 -0
- package/src/modules/sales/backend/sales/channels/page.tsx +4 -0
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +14 -8
- package/src/modules/sales/commands/documents.ts +93 -0
- package/src/modules/sales/components/PriceWithCurrency.tsx +17 -5
- package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +4 -0
- package/src/modules/sales/components/documents/AdjustmentsSection.tsx +6 -5
- package/src/modules/sales/components/documents/ItemsSection.tsx +56 -18
- package/src/modules/sales/components/documents/LineItemDialog.tsx +137 -20
- package/src/modules/sales/components/documents/PaymentsSection.tsx +20 -9
- package/src/modules/sales/components/documents/ReturnsSection.tsx +6 -5
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +106 -14
- package/src/modules/sales/components/documents/SalesOrderDraftLines.tsx +6 -5
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +5 -4
- package/src/modules/sales/components/documents/ShipmentsSection.tsx +6 -5
- package/src/modules/sales/components/documents/lineItemShipmentLock.ts +74 -0
- package/src/modules/sales/components/documents/lineItemUtils.ts +7 -2
- package/src/modules/sales/data/entities.ts +6 -0
- package/src/modules/sales/extension-points.ts +1 -0
- package/src/modules/sales/i18n/de.json +8 -0
- package/src/modules/sales/i18n/en.json +8 -0
- package/src/modules/sales/i18n/es.json +8 -0
- package/src/modules/sales/i18n/ko.json +8 -0
- package/src/modules/sales/i18n/pl.json +8 -0
- package/src/modules/sales/lib/salesChannelsToggleSeed.ts +42 -0
- package/src/modules/sales/migrations/.snapshot-open-mercato.json +60 -0
- package/src/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.ts +87 -0
- package/src/modules/sales/setup.ts +2 -0
- package/src/modules/sales/widgets/injection/payment-gateway-status-column/widget.ts +9 -1
- package/src/modules/sales/widgets/injection-table.ts +4 -7
- package/src/modules/staff/backend/staff/leave-requests/page.tsx +4 -0
- package/src/modules/staff/backend/staff/my-leave-requests/page.tsx +4 -0
- package/src/modules/staff/backend/staff/team-members/page.tsx +6 -0
- package/src/modules/staff/backend/staff/team-roles/page.tsx +4 -0
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +4 -0
- package/src/modules/staff/backend/staff/teams/page.tsx +4 -0
- package/src/modules/staff/backend/staff/timesheets/page.tsx +12 -1
- package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +4 -0
- package/src/modules/sync_excel/lib/adapters/customers.ts +5 -0
- package/src/modules/warranty_claims/api/assignees/route.ts +3 -3
- package/src/modules/warranty_claims/api/portal/attachments/route.ts +2 -2
- package/src/modules/warranty_claims/api/portal/claims/route.ts +12 -9
- package/src/modules/warranty_claims/backend/page.tsx +4 -0
- package/src/modules/warranty_claims/backend/warranty_claims/registrations/page.tsx +4 -0
- package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.tsx +4 -0
- package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.tsx +4 -0
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.tsx +4 -0
- package/src/modules/wms/api/warehouseSearch.ts +23 -0
- package/src/modules/wms/api/warehouses/route.ts +13 -9
- package/src/modules/wms/api/zones/route.ts +16 -0
- package/src/modules/wms/commands/configuration.ts +159 -10
- package/src/modules/wms/commands/inventory-actions.ts +3 -0
- package/src/modules/wms/commands/shared.ts +17 -0
- package/src/modules/wms/components/backend/WarehouseEditDialog.tsx +216 -0
- package/src/modules/wms/components/backend/WmsConfigurationPage.tsx +95 -141
- package/src/modules/wms/components/backend/WmsInventoryConsolePage.tsx +2 -0
- package/src/modules/wms/components/backend/WmsLotsListPage.tsx +2 -0
- package/src/modules/wms/components/backend/inventoryMutationLoaders.ts +36 -20
- package/src/modules/wms/components/backend/warehouseFormOptions.ts +72 -0
- package/src/modules/wms/i18n/de.json +2 -0
- package/src/modules/wms/i18n/en.json +2 -0
- package/src/modules/wms/i18n/es.json +2 -0
- package/src/modules/wms/i18n/ko.json +2 -0
- package/src/modules/wms/i18n/pl.json +2 -0
- package/src/modules/workflows/backend/definitions/page.tsx +4 -1
- package/src/modules/workflows/backend/events/page.tsx +4 -1
- package/src/modules/workflows/backend/instances/page.tsx +4 -1
- package/src/modules/workflows/backend/tasks/page.tsx +4 -1
- package/src/modules/workflows/lib/activity-executor.ts +9 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/auth/frontend/reset/%5Btoken%5D/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { useEffect, useState } from 'react'\nimport { useRouter } from 'next/navigation'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { formatPasswordRequirements, getPasswordPolicy, validatePassword } from '@open-mercato/shared/lib/auth/passwordPolicy'\n\nexport default function ResetWithTokenPage({ params }: { params: { token: string } }) {\n const router = useRouter()\n const t = useT()\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const [clientReady, setClientReady] = useState(false)\n const passwordPolicy = getPasswordPolicy()\n const passwordRequirements = formatPasswordRequirements(passwordPolicy, t)\n const passwordDescription = passwordRequirements\n ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })\n : ''\n\n useEffect(() => {\n setClientReady(true)\n }, [])\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n setError(null)\n const form = new FormData(e.currentTarget)\n const password = String(form.get('password') ?? '')\n const confirmPassword = String(form.get('confirmPassword') ?? '')\n\n if (!password) {\n setError(t('auth.profile.form.errors.newPasswordRequired', 'New password is required.'))\n return\n }\n if (!confirmPassword) {\n setError(t('auth.profile.form.errors.confirmPasswordRequired', 'Please confirm the new password.'))\n return\n }\n if (password !== confirmPassword) {\n setError(t('auth.profile.form.errors.passwordMismatch', 'Passwords do not match.'))\n return\n }\n if (!validatePassword(password, passwordPolicy).ok) {\n setError(t('auth.profile.form.errors.passwordRequirements', 'Password must meet the requirements.'))\n return\n }\n\n setSubmitting(true)\n try {\n form.set('token', params.token)\n const { ok, result } = await apiCall<{ ok?: boolean; error?: string; redirect?: string }>(\n '/api/auth/reset/confirm',\n { method: 'POST', body: form },\n )\n if (!ok || result?.ok === false) {\n setError(result?.error || t('auth.reset.errors.failed', 'Unable to reset password'))\n return\n }\n router.replace(result?.redirect || '/login')\n } finally {\n setSubmitting(false)\n }\n }\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader>\n <CardTitle>{t('auth.reset.title', 'Set a new password')}</CardTitle>\n <CardDescription>{t('auth.reset.subtitle', 'Choose a strong password for your account.')}</CardDescription>\n </CardHeader>\n <CardContent>\n <form className=\"grid gap-3\" onSubmit={onSubmit} data-auth-ready={clientReady ? '1' : '0'}>\n {error && <div className=\"text-sm text-status-error-text\">{error}</div>}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"password\">{t('auth.reset.form.password', 'New password')}</Label>\n <PasswordInput id=\"password\" name=\"password\" required minLength={passwordPolicy.minLength} autoComplete=\"new-password\" />\n {passwordDescription ? (\n <p className=\"text-xs text-muted-foreground\">{passwordDescription}</p>\n ) : null}\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"confirmPassword\">{t('auth.profile.form.confirmPassword', 'Confirm new password')}</Label>\n <PasswordInput\n id=\"confirmPassword\"\n name=\"confirmPassword\"\n required\n minLength={passwordPolicy.minLength}\n autoComplete=\"new-password\"\n />\n </div>\n <Button type=\"submit\" className=\"mt-2 w-full\" disabled={submitting}>\n {submitting ? t('auth.reset.form.loading', '...') : t('auth.reset.form.submit', 'Update password')}\n </Button>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { useEffect, useState } from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { formatPasswordRequirements, getPasswordPolicy, validatePassword } from '@open-mercato/shared/lib/auth/passwordPolicy'\n\ntype TokenState = 'checking' | 'usable' | 'expired'\n\nexport default function ResetWithTokenPage({ params }: { params: { token: string } }) {\n const router = useRouter()\n const t = useT()\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const [clientReady, setClientReady] = useState(false)\n const [tokenState, setTokenState] = useState<TokenState>('checking')\n const passwordPolicy = getPasswordPolicy()\n const passwordRequirements = formatPasswordRequirements(passwordPolicy, t)\n const passwordDescription = passwordRequirements\n ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })\n : ''\n\n useEffect(() => {\n setClientReady(true)\n }, [])\n\n useEffect(() => {\n let cancelled = false\n async function checkToken() {\n const body = new FormData()\n body.set('token', params.token)\n // Fail open whenever the check cannot answer \u2014 an unreachable or failing\n // endpoint must not strand a user holding a perfectly good link, and the\n // confirm endpoint still rejects a dead token on submit.\n try {\n const { ok, result } = await apiCall<{ ok?: boolean; valid?: boolean }>(\n '/api/auth/reset/validate',\n { method: 'POST', body },\n )\n if (cancelled) return\n setTokenState(ok && result?.valid === false ? 'expired' : 'usable')\n } catch {\n if (cancelled) return\n setTokenState('usable')\n }\n }\n void checkToken()\n return () => { cancelled = true }\n }, [params.token])\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n setError(null)\n const form = new FormData(e.currentTarget)\n const password = String(form.get('password') ?? '')\n const confirmPassword = String(form.get('confirmPassword') ?? '')\n\n if (!password) {\n setError(t('auth.profile.form.errors.newPasswordRequired', 'New password is required.'))\n return\n }\n if (!confirmPassword) {\n setError(t('auth.profile.form.errors.confirmPasswordRequired', 'Please confirm the new password.'))\n return\n }\n if (password !== confirmPassword) {\n setError(t('auth.profile.form.errors.passwordMismatch', 'Passwords do not match.'))\n return\n }\n if (!validatePassword(password, passwordPolicy).ok) {\n setError(t('auth.profile.form.errors.passwordRequirements', 'Password must meet the requirements.'))\n return\n }\n\n setSubmitting(true)\n try {\n form.set('token', params.token)\n const { ok, result } = await apiCall<{ ok?: boolean; error?: string; redirect?: string }>(\n '/api/auth/reset/confirm',\n { method: 'POST', body: form },\n )\n if (!ok || result?.ok === false) {\n setError(result?.error || t('auth.reset.errors.failed', 'Unable to reset password'))\n return\n }\n router.replace(result?.redirect || '/login')\n } finally {\n setSubmitting(false)\n }\n }\n\n if (tokenState === 'checking') {\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader>\n <CardTitle>{t('auth.reset.title', 'Set a new password')}</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\" data-auth-token-state=\"checking\">\n <Spinner size=\"sm\" />\n <span>{t('auth.reset.token.checking', 'Checking your reset link...')}</span>\n </div>\n </CardContent>\n </Card>\n </div>\n )\n }\n\n if (tokenState === 'expired') {\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader>\n <CardTitle>{t('auth.reset.token.expiredTitle', 'This reset link is no longer valid')}</CardTitle>\n <CardDescription>\n {t('auth.reset.token.expiredSubtitle', 'It has already been used or has expired. Request a new link to set your password.')}\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"grid gap-3\" data-auth-token-state=\"expired\">\n <Button asChild className=\"w-full\">\n <Link href=\"/reset\">{t('auth.reset.token.requestNew', 'Request a new link')}</Link>\n </Button>\n <div className=\"text-center\">\n <Link href=\"/login\" className=\"text-xs text-muted-foreground underline\">\n {t('auth.reset.backToLogin', 'Back to login')}\n </Link>\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n )\n }\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader>\n <CardTitle>{t('auth.reset.title', 'Set a new password')}</CardTitle>\n <CardDescription>{t('auth.reset.subtitle', 'Choose a strong password for your account.')}</CardDescription>\n </CardHeader>\n <CardContent>\n <form className=\"grid gap-3\" onSubmit={onSubmit} data-auth-ready={clientReady ? '1' : '0'}>\n {error && <div className=\"text-sm text-status-error-text\">{error}</div>}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"password\">{t('auth.reset.form.password', 'New password')}</Label>\n <PasswordInput id=\"password\" name=\"password\" required minLength={passwordPolicy.minLength} autoComplete=\"new-password\" />\n {passwordDescription ? (\n <p className=\"text-xs text-muted-foreground\">{passwordDescription}</p>\n ) : null}\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"confirmPassword\">{t('auth.profile.form.confirmPassword', 'Confirm new password')}</Label>\n <PasswordInput\n id=\"confirmPassword\"\n name=\"confirmPassword\"\n required\n minLength={passwordPolicy.minLength}\n autoComplete=\"new-password\"\n />\n </div>\n <Button type=\"submit\" className=\"mt-2 w-full\" disabled={submitting}>\n {submitting ? t('auth.reset.form.loading', '...') : t('auth.reset.form.submit', 'Update password')}\n </Button>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/auth/lib/grantChecks.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { badRequest, CrudHttpError, forbidden } from '@open-mercato/shared/lib/crud/errors'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { Role, RoleAcl, User, UserAcl, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { OrganizationScope } from '@open-mercato/core/modules/directory/utils/organizationScope'\n\ntype ActorAcl = {\n isSuperAdmin: boolean\n features: string[]\n organizations: string[] | null\n}\n\ntype GrantCheckContext = {\n em: EntityManager\n rbacService: RbacService\n actorUserId: string | null | undefined\n tenantId: string | null | undefined\n organizationId?: string | null | undefined\n}\n\ntype RoleGrantCheckInput = GrantCheckContext & {\n roles: Role[]\n}\n\ntype RoleTokenGrantCheckInput = GrantCheckContext & {\n roleTokens: unknown\n}\n\ntype UserDestinationRolesInput = {\n em: EntityManager\n targetUserId: string\n destinationTenantId: string | null | undefined\n roleTokens: unknown\n}\n\ntype UserDestinationScopeCheckInput = GrantCheckContext & {\n actorIsSuperAdmin?: boolean\n allowedOrganizationIds?: string[] | null\n destinationTenantId: string | null | undefined\n destinationOrganizationId: string | null | undefined\n roles: Role[]\n}\n\ntype FeatureGrantCheckInput = GrantCheckContext & {\n features: unknown\n isSuperAdmin?: boolean\n organizations?: string[] | null\n}\n\ntype SuperAdminUserTargetInput = GrantCheckContext & {\n targetUserId: string\n actorIsSuperAdmin?: boolean\n}\n\ntype UserTargetAccessInput = SuperAdminUserTargetInput & {\n organizationScope: Pick<OrganizationScope, 'allowedIds'>\n}\n\ntype SuperAdminRoleTargetInput = GrantCheckContext & {\n targetRoleId: string\n actorIsSuperAdmin?: boolean\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nexport async function assertActorCanGrantRoleTokens(input: RoleTokenGrantCheckInput): Promise<Role[]> {\n const tokens = normalizeStringList(input.roleTokens)\n if (!tokens.length) return []\n\n const tenantId = normalizeNullableString(input.tenantId)\n const roles = await resolveRolesForGrant(input.em, tokens, tenantId)\n await assertActorCanGrantRoles({ ...input, tenantId, roles })\n return roles\n}\n\nexport async function resolveUserDestinationRoles(input: UserDestinationRolesInput): Promise<Role[]> {\n const destinationTenantId = normalizeNullableString(input.destinationTenantId)\n if (Array.isArray(input.roleTokens)) {\n return resolveRolesForGrant(input.em, normalizeStringList(input.roleTokens), destinationTenantId)\n }\n\n const links = await findWithDecryption(\n input.em,\n UserRole,\n { user: input.targetUserId as unknown as User } as FilterQuery<UserRole>,\n { populate: ['role'] },\n { tenantId: null, organizationId: null },\n )\n const roles: Role[] = []\n for (const link of links) {\n const linkedRole = (link as { role?: Role | string | null }).role\n if (linkedRole && typeof linkedRole === 'object') {\n roles.push(linkedRole)\n continue\n }\n if (typeof linkedRole === 'string') {\n const resolvedRole = await resolveRoleForGrant(input.em, linkedRole, null)\n if (resolvedRole) {\n roles.push(resolvedRole)\n continue\n }\n }\n throw badRequest(await translateAuthError(\n 'auth.users.errors.invalidRoleAssignment',\n 'User has an invalid role assignment',\n ))\n }\n return roles\n}\n\nexport async function assertActorCanAssignUserDestination(\n input: UserDestinationScopeCheckInput,\n): Promise<void> {\n const destinationTenantId = normalizeNullableString(input.destinationTenantId)\n const destinationOrganizationId = normalizeNullableString(input.destinationOrganizationId)\n if (!destinationTenantId || !destinationOrganizationId) {\n return throwUserDestinationOrganizationNotFound(400)\n }\n\n for (const role of input.roles) {\n if (normalizeNullableString(role.tenantId) !== destinationTenantId) {\n throw forbidden(await translateAuthError(\n 'auth.users.errors.roleOutsideDestinationTenant',\n 'Cannot retain or assign a role outside the destination tenant.',\n ))\n }\n }\n\n if (await resolveActorIsSuperAdmin(input)) return\n\n const actorTenantId = normalizeNullableString(input.tenantId)\n if (!actorTenantId || actorTenantId !== destinationTenantId) {\n return throwUserDestinationOrganizationNotFound(404)\n }\n\n const actorAcl = await loadActorAcl(input)\n const allowedOrganizationIds = input.allowedOrganizationIds === undefined\n ? actorAcl.organizations\n : input.allowedOrganizationIds\n if (\n allowedOrganizationIds !== null\n && !allowedOrganizationIds.includes('__all__')\n && !allowedOrganizationIds.includes(destinationOrganizationId)\n ) {\n throw forbidden(await translateAuthError(\n 'auth.users.errors.destinationOrganizationOutsideScope',\n 'Cannot assign user to a destination organization outside actor scope.',\n ))\n }\n\n await assertActorCanGrantRoles({\n ...input,\n tenantId: destinationTenantId,\n roles: input.roles,\n })\n}\n\nexport async function throwUserDestinationOrganizationNotFound(status: 400 | 404): Promise<never> {\n throw new CrudHttpError(status, {\n error: await translateAuthError(\n 'auth.users.errors.organizationNotFound',\n 'Organization not found',\n ),\n })\n}\n\nexport async function assertActorCanGrantRoles(input: RoleGrantCheckInput): Promise<void> {\n if (!input.roles.length) return\n\n const tenantId = normalizeNullableString(input.tenantId)\n const actorAcl = await loadActorAcl({ ...input, tenantId })\n if (actorAcl.isSuperAdmin) return\n\n if (!tenantId) {\n throw forbidden('Tenant context is required to grant roles.')\n }\n\n for (const role of input.roles) {\n const roleTenantId = normalizeNullableString(role.tenantId)\n if (roleTenantId !== tenantId) {\n throw forbidden('Cannot grant a role outside the target tenant.')\n }\n\n const acl = await findOneWithDecryption(\n input.em,\n RoleAcl,\n { role, tenantId } as FilterQuery<RoleAcl>,\n {},\n { tenantId, organizationId: null },\n )\n if (!acl) continue\n\n assertActorCanGrantAclSnapshot(actorAcl, {\n isSuperAdmin: !!acl.isSuperAdmin,\n features: normalizeStringList(acl.featuresJson),\n organizations: normalizeOrganizationList(acl.organizationsJson),\n })\n }\n}\n\nexport async function assertActorCanGrantAcl(input: FeatureGrantCheckInput): Promise<void> {\n const actorAcl = await loadActorAcl(input)\n if (actorAcl.isSuperAdmin) return\n\n const tenantId = normalizeNullableString(input.tenantId)\n if (!tenantId) {\n throw forbidden('Tenant context is required to grant ACL features.')\n }\n\n assertActorCanGrantAclSnapshot(actorAcl, {\n isSuperAdmin: !!input.isSuperAdmin,\n features: normalizeStringList(input.features),\n organizations: input.organizations === undefined ? undefined : normalizeOrganizationList(input.organizations),\n })\n}\n\nexport function normalizeGrantFeatureList(features: unknown): string[] {\n return normalizeStringList(features)\n}\n\nexport async function assertActorCanModifySuperAdminUserTarget(input: SuperAdminUserTargetInput): Promise<void> {\n const actorIsSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (actorIsSuperAdmin) return\n const targetIsSuperAdmin = await isUserEffectivelySuperAdmin(input.em, input.targetUserId)\n if (targetIsSuperAdmin) {\n throw forbidden('Only super administrators can modify super administrator accounts.')\n }\n}\n\nexport async function assertActorCanModifySuperAdminRoleTarget(input: SuperAdminRoleTargetInput): Promise<void> {\n const actorIsSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (actorIsSuperAdmin) return\n const targetIsSuperAdmin = await isRoleEffectivelySuperAdmin(input.em, input.targetRoleId)\n if (targetIsSuperAdmin) {\n throw forbidden('Only super administrators can modify super administrator roles.')\n }\n}\n\nexport async function assertActorCanAccessUserTarget(input: UserTargetAccessInput): Promise<void> {\n const isSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (isSuperAdmin) return\n\n const target = await findOneWithDecryption(\n input.em,\n User,\n { id: input.targetUserId } as FilterQuery<User>,\n {},\n { tenantId: null, organizationId: null },\n )\n // Not found (incl. soft-deleted, which MikroORM's soft-delete filter hides):\n // delegate to the caller. Every wired call site is itself tenant-scoped \u2014 the\n // ACL/consents reads filter by auth.tenantId and the user commands re-load by\n // id within tenant \u2014 so a missing target yields a safe empty/404 there. The\n // guard's job is to block a foreign *existing* target, below.\n if (!target) return\n\n const actorTenantId = normalizeNullableString(input.tenantId)\n const targetTenantId = normalizeNullableString((target as { tenantId?: string | null }).tenantId)\n if (!targetTenantId || targetTenantId !== actorTenantId) {\n throw new CrudHttpError(404, { error: 'User not found' })\n }\n\n if (input.organizationScope.allowedIds !== null) {\n const targetOrganizationId = normalizeNullableString((target as { organizationId?: string | null }).organizationId)\n if (!targetOrganizationId || !input.organizationScope.allowedIds.includes(targetOrganizationId)) {\n throw forbidden('Not authorized to access this user.')\n }\n }\n}\n\nexport async function assertActorCanAccessRoleTarget(input: SuperAdminRoleTargetInput): Promise<void> {\n const isSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (isSuperAdmin) return\n\n const target = await findOneWithDecryption(\n input.em,\n Role,\n { id: input.targetRoleId } as FilterQuery<Role>,\n {},\n { tenantId: null, organizationId: null },\n )\n // Not found (incl. soft-deleted): delegate (see assertActorCanAccessUserTarget).\n if (!target) return\n\n const actorTenantId = normalizeNullableString(input.tenantId)\n const targetTenantId = normalizeNullableString((target as { tenantId?: string | null }).tenantId)\n if (!targetTenantId || targetTenantId !== actorTenantId) {\n throw new CrudHttpError(404, { error: 'Role not found' })\n }\n}\n\nasync function resolveActorIsSuperAdmin(input: GrantCheckContext & { actorIsSuperAdmin?: boolean }): Promise<boolean> {\n if (typeof input.actorIsSuperAdmin === 'boolean') return input.actorIsSuperAdmin\n const acl = await loadActorAcl(input)\n return acl.isSuperAdmin\n}\n\nexport async function isUserEffectivelySuperAdmin(em: EntityManager, userId: string): Promise<boolean> {\n const directGrant = await em.findOne(\n UserAcl,\n { user: userId as unknown, isSuperAdmin: true } as FilterQuery<UserAcl>,\n )\n if (directGrant && (directGrant as { isSuperAdmin?: boolean }).isSuperAdmin === true) return true\n const links = await findWithDecryption(\n em,\n UserRole,\n { user: userId as unknown } as FilterQuery<UserRole>,\n { populate: ['role'] },\n { tenantId: null, organizationId: null },\n )\n const roleIds = (Array.isArray(links) ? links : [])\n .map((link) => {\n const role = (link as { role?: { id?: unknown } | string | null }).role\n if (!role) return null\n if (typeof role === 'string') return role\n return role.id ? String(role.id) : null\n })\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n if (!roleIds.length) return false\n const roleGrant = await em.findOne(\n RoleAcl,\n { role: { $in: roleIds } as unknown, isSuperAdmin: true } as FilterQuery<RoleAcl>,\n )\n return !!roleGrant && (roleGrant as { isSuperAdmin?: boolean }).isSuperAdmin === true\n}\n\nexport async function isRoleEffectivelySuperAdmin(em: EntityManager, roleId: string): Promise<boolean> {\n const grant = await em.findOne(\n RoleAcl,\n { role: roleId as unknown, isSuperAdmin: true } as FilterQuery<RoleAcl>,\n )\n return !!grant && (grant as { isSuperAdmin?: boolean }).isSuperAdmin === true\n}\n\nexport async function listSuperAdminUserIds(em: EntityManager, tenantId: string | null): Promise<Set<string>> {\n const ids = new Set<string>()\n const userAclFilter: Record<string, unknown> = { isSuperAdmin: true }\n if (tenantId) userAclFilter.tenantId = tenantId\n const userAcls = await em.find(UserAcl, userAclFilter as FilterQuery<UserAcl>)\n for (const acl of userAcls) {\n const userRef = (acl as { user?: { id?: unknown } | string | null }).user\n const userId = userRef && typeof userRef === 'object'\n ? userRef.id\n : userRef\n if (userId) ids.add(String(userId))\n }\n const roleAcls = await em.find(\n RoleAcl,\n { isSuperAdmin: true } as FilterQuery<RoleAcl>,\n )\n const roleIds = roleAcls\n .map((acl) => {\n const roleRef = (acl as { role?: { id?: unknown } | string | null }).role\n if (!roleRef) return null\n if (typeof roleRef === 'string') return roleRef\n return roleRef.id ? String(roleRef.id) : null\n })\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n if (roleIds.length) {\n const links = await findWithDecryption(\n em,\n UserRole,\n { role: { $in: roleIds } as unknown } as FilterQuery<UserRole>,\n {},\n { tenantId: null, organizationId: null },\n )\n for (const link of Array.isArray(links) ? links : []) {\n const userRef = (link as { user?: { id?: unknown } | string | null }).user\n const userId = userRef && typeof userRef === 'object'\n ? userRef.id\n : userRef\n if (userId) ids.add(String(userId))\n }\n }\n return ids\n}\n\nasync function loadActorAcl(input: GrantCheckContext): Promise<ActorAcl> {\n const actorUserId = normalizeNullableString(input.actorUserId)\n if (!actorUserId) throw forbidden('Not authorized to grant ACL privileges.')\n\n const acl = await input.rbacService.loadAcl(actorUserId, {\n tenantId: normalizeNullableString(input.tenantId),\n organizationId: normalizeNullableString(input.organizationId),\n })\n\n return {\n isSuperAdmin: !!acl?.isSuperAdmin,\n features: normalizeStringList(acl?.features),\n organizations: normalizeOrganizationList(acl?.organizations),\n }\n}\n\nasync function resolveRolesForGrant(\n em: EntityManager,\n roleTokens: string[],\n tenantId: string | null,\n): Promise<Role[]> {\n const roles: Role[] = []\n const missingRoles: string[] = []\n\n for (const token of roleTokens) {\n const role = await resolveRoleForGrant(em, token, tenantId)\n if (!role) {\n missingRoles.push(token)\n } else {\n roles.push(role)\n }\n }\n\n if (missingRoles.length) {\n const labels = missingRoles.map((role) => `\"${role}\"`).join(', ')\n throw new CrudHttpError(400, { error: `Role(s) not found: ${labels}` })\n }\n\n return roles\n}\n\nasync function resolveRoleForGrant(\n em: EntityManager,\n token: string,\n tenantId: string | null,\n): Promise<Role | null> {\n const where: Record<string, unknown> = UUID_RE.test(token)\n ? { id: token, deletedAt: null }\n : { name: token, deletedAt: null }\n if (tenantId) where.tenantId = tenantId\n return findOneWithDecryption(\n em,\n Role,\n where as FilterQuery<Role>,\n {},\n { tenantId, organizationId: null },\n )\n}\n\nfunction assertActorCanGrantAclSnapshot(\n actorAcl: ActorAcl,\n requested: {\n isSuperAdmin: boolean\n features: string[]\n organizations?: string[] | null\n },\n): void {\n if (requested.isSuperAdmin) {\n throw forbidden('Only super administrators can grant super admin access.')\n }\n\n const actorGrantableFeatures = actorAcl.features.filter((grant) => grant !== '*')\n for (const feature of requested.features) {\n if (feature === '*') {\n throw forbidden('Only super administrators can grant global wildcard access.')\n }\n if (isWildcardFeature(feature)) {\n if (!hasFeature(actorGrantableFeatures, feature)) {\n throw forbidden(`Cannot grant feature wildcard ${feature}.`)\n }\n continue\n }\n if (!hasFeature(actorGrantableFeatures, feature)) {\n throw forbidden(`Cannot grant feature ${feature}.`)\n }\n }\n\n if (requested.organizations !== undefined) {\n assertActorCanGrantOrganizations(actorAcl.organizations, requested.organizations)\n }\n}\n\nfunction assertActorCanGrantOrganizations(\n actorOrganizations: string[] | null,\n requestedOrganizations: string[] | null,\n): void {\n if (actorOrganizations === null || actorOrganizations.includes('__all__')) return\n\n if (requestedOrganizations === null || requestedOrganizations.includes('__all__')) {\n throw forbidden('Cannot grant unrestricted organization access.')\n }\n\n for (const organizationId of requestedOrganizations) {\n if (!actorOrganizations.includes(organizationId)) {\n throw forbidden('Cannot grant organization access outside actor scope.')\n }\n }\n}\n\nfunction normalizeStringList(values: unknown): string[] {\n if (!Array.isArray(values)) return []\n const dedup = new Set<string>()\n for (const value of values) {\n if (typeof value !== 'string') continue\n const trimmed = value.trim()\n if (!trimmed) continue\n dedup.add(trimmed)\n }\n return Array.from(dedup)\n}\n\nfunction normalizeOrganizationList(values: unknown): string[] | null {\n if (values === null || values === undefined) return null\n return normalizeStringList(values)\n}\n\nfunction normalizeNullableString(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n}\n\nfunction isWildcardFeature(feature: string): boolean {\n return feature.endsWith('.*')\n}\n\nasync function translateAuthError(key: string, fallback: string): Promise<string> {\n const { translate } = await resolveTranslations()\n return translate(key, fallback)\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { badRequest, CrudHttpError, forbidden } from '@open-mercato/shared/lib/crud/errors'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { Role, RoleAcl, User, UserAcl, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { OrganizationScope } from '@open-mercato/core/modules/directory/utils/organizationScope'\n\ntype ActorAcl = {\n isSuperAdmin: boolean\n features: string[]\n organizations: string[] | null\n}\n\ntype GrantCheckContext = {\n em: EntityManager\n rbacService: RbacService\n actorUserId: string | null | undefined\n tenantId: string | null | undefined\n organizationId?: string | null | undefined\n}\n\ntype RoleGrantCheckInput = GrantCheckContext & {\n roles: Role[]\n}\n\ntype RoleTokenGrantCheckInput = GrantCheckContext & {\n roleTokens: unknown\n}\n\ntype UserDestinationRolesInput = {\n em: EntityManager\n targetUserId: string\n destinationTenantId: string | null | undefined\n roleTokens: unknown\n}\n\ntype UserDestinationScopeCheckInput = GrantCheckContext & {\n actorIsSuperAdmin?: boolean\n allowedOrganizationIds?: string[] | null\n destinationTenantId: string | null | undefined\n destinationOrganizationId: string | null | undefined\n roles: Role[]\n}\n\ntype FeatureGrantCheckInput = GrantCheckContext & {\n features: unknown\n isSuperAdmin?: boolean\n organizations?: string[] | null\n}\n\ntype SuperAdminUserTargetInput = GrantCheckContext & {\n targetUserId: string\n actorIsSuperAdmin?: boolean\n}\n\ntype UserTargetAccessInput = SuperAdminUserTargetInput & {\n organizationScope: Pick<OrganizationScope, 'allowedIds'>\n}\n\ntype SuperAdminRoleTargetInput = GrantCheckContext & {\n targetRoleId: string\n actorIsSuperAdmin?: boolean\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nexport async function assertActorCanGrantRoleTokens(input: RoleTokenGrantCheckInput): Promise<Role[]> {\n const tokens = normalizeStringList(input.roleTokens)\n if (!tokens.length) return []\n\n const tenantId = normalizeNullableString(input.tenantId)\n const roles = await resolveRolesForGrant(input.em, tokens, tenantId)\n await assertActorCanGrantRoles({ ...input, tenantId, roles })\n return roles\n}\n\nexport async function resolveUserDestinationRoles(input: UserDestinationRolesInput): Promise<Role[]> {\n const destinationTenantId = normalizeNullableString(input.destinationTenantId)\n if (Array.isArray(input.roleTokens)) {\n return resolveRolesForGrant(input.em, normalizeStringList(input.roleTokens), destinationTenantId)\n }\n\n const links = await findWithDecryption(\n input.em,\n UserRole,\n { user: input.targetUserId as unknown as User } as FilterQuery<UserRole>,\n { populate: ['role'] },\n { tenantId: null, organizationId: null },\n )\n const roles: Role[] = []\n for (const link of links) {\n const linkedRole = (link as { role?: Role | string | null }).role\n if (linkedRole && typeof linkedRole === 'object') {\n roles.push(linkedRole)\n continue\n }\n if (typeof linkedRole === 'string') {\n const resolvedRole = await resolveRoleForGrant(input.em, linkedRole, null)\n if (resolvedRole) {\n roles.push(resolvedRole)\n continue\n }\n }\n throw badRequest(await translateAuthError(\n 'auth.users.errors.invalidRoleAssignment',\n 'User has an invalid role assignment',\n ))\n }\n return roles\n}\n\nexport async function assertActorCanAssignUserDestination(\n input: UserDestinationScopeCheckInput,\n): Promise<void> {\n const destinationTenantId = normalizeNullableString(input.destinationTenantId)\n const destinationOrganizationId = normalizeNullableString(input.destinationOrganizationId)\n if (!destinationTenantId || !destinationOrganizationId) {\n return throwUserDestinationOrganizationNotFound(400)\n }\n\n for (const role of input.roles) {\n if (normalizeNullableString(role.tenantId) !== destinationTenantId) {\n throw forbidden(await translateAuthError(\n 'auth.users.errors.roleOutsideDestinationTenant',\n 'Cannot retain or assign a role outside the destination tenant.',\n ))\n }\n }\n\n if (await resolveActorIsSuperAdmin(input)) return\n\n const actorTenantId = normalizeNullableString(input.tenantId)\n if (!actorTenantId || actorTenantId !== destinationTenantId) {\n return throwUserDestinationOrganizationNotFound(404)\n }\n\n const actorAcl = await loadActorAcl(input)\n const allowedOrganizationIds = input.allowedOrganizationIds === undefined\n ? actorAcl.organizations\n : input.allowedOrganizationIds\n if (\n allowedOrganizationIds !== null\n && !allowedOrganizationIds.includes('__all__')\n && !allowedOrganizationIds.includes(destinationOrganizationId)\n ) {\n throw forbidden(await translateAuthError(\n 'auth.users.errors.destinationOrganizationOutsideScope',\n 'Cannot assign user to a destination organization outside actor scope.',\n ))\n }\n\n await assertActorCanGrantRoles({\n ...input,\n tenantId: destinationTenantId,\n roles: input.roles,\n })\n}\n\nexport async function throwUserDestinationOrganizationNotFound(status: 400 | 404): Promise<never> {\n throw new CrudHttpError(status, {\n error: await translateAuthError(\n 'auth.users.errors.organizationNotFound',\n 'Organization not found',\n ),\n })\n}\n\nexport async function assertActorCanGrantRoles(input: RoleGrantCheckInput): Promise<void> {\n if (!input.roles.length) return\n\n const tenantId = normalizeNullableString(input.tenantId)\n const actorAcl = await loadActorAcl({ ...input, tenantId })\n if (actorAcl.isSuperAdmin) return\n\n if (!tenantId) {\n throw forbidden('Tenant context is required to grant roles.')\n }\n\n for (const role of input.roles) {\n const roleTenantId = normalizeNullableString(role.tenantId)\n if (roleTenantId !== tenantId) {\n throw forbidden('Cannot grant a role outside the target tenant.')\n }\n\n const acl = await findOneWithDecryption(\n input.em,\n RoleAcl,\n { role, tenantId } as FilterQuery<RoleAcl>,\n {},\n { tenantId, organizationId: null },\n )\n if (!acl) continue\n\n assertActorCanGrantAclSnapshot(actorAcl, {\n isSuperAdmin: !!acl.isSuperAdmin,\n features: normalizeStringList(acl.featuresJson),\n organizations: normalizeOrganizationList(acl.organizationsJson),\n })\n }\n}\n\nexport async function assertActorCanGrantAcl(input: FeatureGrantCheckInput): Promise<void> {\n const actorAcl = await loadActorAcl(input)\n if (actorAcl.isSuperAdmin) return\n\n const tenantId = normalizeNullableString(input.tenantId)\n if (!tenantId) {\n throw forbidden('Tenant context is required to grant ACL features.')\n }\n\n assertActorCanGrantAclSnapshot(actorAcl, {\n isSuperAdmin: !!input.isSuperAdmin,\n features: normalizeStringList(input.features),\n organizations: input.organizations === undefined ? undefined : normalizeOrganizationList(input.organizations),\n })\n}\n\nexport function normalizeGrantFeatureList(features: unknown): string[] {\n return normalizeStringList(features)\n}\n\nexport async function assertActorCanModifySuperAdminUserTarget(input: SuperAdminUserTargetInput): Promise<void> {\n const actorIsSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (actorIsSuperAdmin) return\n const targetIsSuperAdmin = await isUserEffectivelySuperAdmin(input.em, input.targetUserId)\n if (targetIsSuperAdmin) {\n throw forbidden('Only super administrators can modify super administrator accounts.')\n }\n}\n\nexport async function assertActorCanModifySuperAdminRoleTarget(input: SuperAdminRoleTargetInput): Promise<void> {\n const actorIsSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (actorIsSuperAdmin) return\n const targetIsSuperAdmin = await isRoleEffectivelySuperAdmin(input.em, input.targetRoleId)\n if (targetIsSuperAdmin) {\n throw forbidden('Only super administrators can modify super administrator roles.')\n }\n}\n\n/**\n * `input.tenantId` is the ACTOR's scope, not the target's: the check below is a\n * comparison between the two, so passing a tenant derived from the target turns\n * it into a tautology. Callers that resolve a record scope separately MUST still\n * hand this guard `auth.tenantId`.\n */\nexport async function assertActorCanAccessUserTarget(input: UserTargetAccessInput): Promise<void> {\n const isSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (isSuperAdmin) return\n\n const target = await findOneWithDecryption(\n input.em,\n User,\n { id: input.targetUserId } as FilterQuery<User>,\n {},\n { tenantId: null, organizationId: null },\n )\n // Not found (incl. soft-deleted, which MikroORM's soft-delete filter hides):\n // delegate to the caller. Every wired call site is itself tenant-scoped \u2014 the\n // ACL/consents reads filter by auth.tenantId and the user commands re-load by\n // id within tenant \u2014 so a missing target yields a safe empty/404 there. The\n // guard's job is to block a foreign *existing* target, below.\n if (!target) return\n\n const actorTenantId = normalizeNullableString(input.tenantId)\n const targetTenantId = normalizeNullableString((target as { tenantId?: string | null }).tenantId)\n if (!targetTenantId || targetTenantId !== actorTenantId) {\n throw new CrudHttpError(404, { error: 'User not found' })\n }\n\n if (input.organizationScope.allowedIds !== null) {\n const targetOrganizationId = normalizeNullableString((target as { organizationId?: string | null }).organizationId)\n if (!targetOrganizationId || !input.organizationScope.allowedIds.includes(targetOrganizationId)) {\n throw forbidden('Not authorized to access this user.')\n }\n }\n}\n\nexport async function assertActorCanAccessRoleTarget(input: SuperAdminRoleTargetInput): Promise<void> {\n const isSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (isSuperAdmin) return\n\n const target = await findOneWithDecryption(\n input.em,\n Role,\n { id: input.targetRoleId } as FilterQuery<Role>,\n {},\n { tenantId: null, organizationId: null },\n )\n // Not found (incl. soft-deleted): delegate (see assertActorCanAccessUserTarget).\n if (!target) return\n\n const actorTenantId = normalizeNullableString(input.tenantId)\n const targetTenantId = normalizeNullableString((target as { tenantId?: string | null }).tenantId)\n if (!targetTenantId || targetTenantId !== actorTenantId) {\n throw new CrudHttpError(404, { error: 'Role not found' })\n }\n}\n\nasync function resolveActorIsSuperAdmin(input: GrantCheckContext & { actorIsSuperAdmin?: boolean }): Promise<boolean> {\n if (typeof input.actorIsSuperAdmin === 'boolean') return input.actorIsSuperAdmin\n const acl = await loadActorAcl(input)\n return acl.isSuperAdmin\n}\n\nexport async function isUserEffectivelySuperAdmin(em: EntityManager, userId: string): Promise<boolean> {\n const directGrant = await em.findOne(\n UserAcl,\n { user: userId as unknown, isSuperAdmin: true } as FilterQuery<UserAcl>,\n )\n if (directGrant && (directGrant as { isSuperAdmin?: boolean }).isSuperAdmin === true) return true\n const links = await findWithDecryption(\n em,\n UserRole,\n { user: userId as unknown } as FilterQuery<UserRole>,\n { populate: ['role'] },\n { tenantId: null, organizationId: null },\n )\n const roleIds = (Array.isArray(links) ? links : [])\n .map((link) => {\n const role = (link as { role?: { id?: unknown } | string | null }).role\n if (!role) return null\n if (typeof role === 'string') return role\n return role.id ? String(role.id) : null\n })\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n if (!roleIds.length) return false\n const roleGrant = await em.findOne(\n RoleAcl,\n { role: { $in: roleIds } as unknown, isSuperAdmin: true } as FilterQuery<RoleAcl>,\n )\n return !!roleGrant && (roleGrant as { isSuperAdmin?: boolean }).isSuperAdmin === true\n}\n\nexport async function isRoleEffectivelySuperAdmin(em: EntityManager, roleId: string): Promise<boolean> {\n const grant = await em.findOne(\n RoleAcl,\n { role: roleId as unknown, isSuperAdmin: true } as FilterQuery<RoleAcl>,\n )\n return !!grant && (grant as { isSuperAdmin?: boolean }).isSuperAdmin === true\n}\n\nexport async function listSuperAdminUserIds(em: EntityManager, tenantId: string | null): Promise<Set<string>> {\n const ids = new Set<string>()\n const userAclFilter: Record<string, unknown> = { isSuperAdmin: true }\n if (tenantId) userAclFilter.tenantId = tenantId\n const userAcls = await em.find(UserAcl, userAclFilter as FilterQuery<UserAcl>)\n for (const acl of userAcls) {\n const userRef = (acl as { user?: { id?: unknown } | string | null }).user\n const userId = userRef && typeof userRef === 'object'\n ? userRef.id\n : userRef\n if (userId) ids.add(String(userId))\n }\n const roleAcls = await em.find(\n RoleAcl,\n { isSuperAdmin: true } as FilterQuery<RoleAcl>,\n )\n const roleIds = roleAcls\n .map((acl) => {\n const roleRef = (acl as { role?: { id?: unknown } | string | null }).role\n if (!roleRef) return null\n if (typeof roleRef === 'string') return roleRef\n return roleRef.id ? String(roleRef.id) : null\n })\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n if (roleIds.length) {\n const links = await findWithDecryption(\n em,\n UserRole,\n { role: { $in: roleIds } as unknown } as FilterQuery<UserRole>,\n {},\n { tenantId: null, organizationId: null },\n )\n for (const link of Array.isArray(links) ? links : []) {\n const userRef = (link as { user?: { id?: unknown } | string | null }).user\n const userId = userRef && typeof userRef === 'object'\n ? userRef.id\n : userRef\n if (userId) ids.add(String(userId))\n }\n }\n return ids\n}\n\nasync function loadActorAcl(input: GrantCheckContext): Promise<ActorAcl> {\n const actorUserId = normalizeNullableString(input.actorUserId)\n if (!actorUserId) throw forbidden('Not authorized to grant ACL privileges.')\n\n const acl = await input.rbacService.loadAcl(actorUserId, {\n tenantId: normalizeNullableString(input.tenantId),\n organizationId: normalizeNullableString(input.organizationId),\n })\n\n return {\n isSuperAdmin: !!acl?.isSuperAdmin,\n features: normalizeStringList(acl?.features),\n organizations: normalizeOrganizationList(acl?.organizations),\n }\n}\n\nasync function resolveRolesForGrant(\n em: EntityManager,\n roleTokens: string[],\n tenantId: string | null,\n): Promise<Role[]> {\n const roles: Role[] = []\n const missingRoles: string[] = []\n\n for (const token of roleTokens) {\n const role = await resolveRoleForGrant(em, token, tenantId)\n if (!role) {\n missingRoles.push(token)\n } else {\n roles.push(role)\n }\n }\n\n if (missingRoles.length) {\n const labels = missingRoles.map((role) => `\"${role}\"`).join(', ')\n throw new CrudHttpError(400, { error: `Role(s) not found: ${labels}` })\n }\n\n return roles\n}\n\nasync function resolveRoleForGrant(\n em: EntityManager,\n token: string,\n tenantId: string | null,\n): Promise<Role | null> {\n const where: Record<string, unknown> = UUID_RE.test(token)\n ? { id: token, deletedAt: null }\n : { name: token, deletedAt: null }\n if (tenantId) where.tenantId = tenantId\n return findOneWithDecryption(\n em,\n Role,\n where as FilterQuery<Role>,\n {},\n { tenantId, organizationId: null },\n )\n}\n\nfunction assertActorCanGrantAclSnapshot(\n actorAcl: ActorAcl,\n requested: {\n isSuperAdmin: boolean\n features: string[]\n organizations?: string[] | null\n },\n): void {\n if (requested.isSuperAdmin) {\n throw forbidden('Only super administrators can grant super admin access.')\n }\n\n const actorGrantableFeatures = actorAcl.features.filter((grant) => grant !== '*')\n for (const feature of requested.features) {\n if (feature === '*') {\n throw forbidden('Only super administrators can grant global wildcard access.')\n }\n if (isWildcardFeature(feature)) {\n if (!hasFeature(actorGrantableFeatures, feature)) {\n throw forbidden(`Cannot grant feature wildcard ${feature}.`)\n }\n continue\n }\n if (!hasFeature(actorGrantableFeatures, feature)) {\n throw forbidden(`Cannot grant feature ${feature}.`)\n }\n }\n\n if (requested.organizations !== undefined) {\n assertActorCanGrantOrganizations(actorAcl.organizations, requested.organizations)\n }\n}\n\nfunction assertActorCanGrantOrganizations(\n actorOrganizations: string[] | null,\n requestedOrganizations: string[] | null,\n): void {\n if (actorOrganizations === null || actorOrganizations.includes('__all__')) return\n\n if (requestedOrganizations === null || requestedOrganizations.includes('__all__')) {\n throw forbidden('Cannot grant unrestricted organization access.')\n }\n\n for (const organizationId of requestedOrganizations) {\n if (!actorOrganizations.includes(organizationId)) {\n throw forbidden('Cannot grant organization access outside actor scope.')\n }\n }\n}\n\nfunction normalizeStringList(values: unknown): string[] {\n if (!Array.isArray(values)) return []\n const dedup = new Set<string>()\n for (const value of values) {\n if (typeof value !== 'string') continue\n const trimmed = value.trim()\n if (!trimmed) continue\n dedup.add(trimmed)\n }\n return Array.from(dedup)\n}\n\nfunction normalizeOrganizationList(values: unknown): string[] | null {\n if (values === null || values === undefined) return null\n return normalizeStringList(values)\n}\n\nfunction normalizeNullableString(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n}\n\nfunction isWildcardFeature(feature: string): boolean {\n return feature.endsWith('.*')\n}\n\nasync function translateAuthError(key: string, fallback: string): Promise<string> {\n const { translate } = await resolveTranslations()\n return translate(key, fallback)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { isIdsParamProvided, parseIdsParam } from "@open-mercato/shared/lib/crud/ids";
|
|
2
|
+
const MAX_USER_LOOKUP_IDS = 100;
|
|
3
|
+
function resolveUserIdFilter(rawIds, id, maxIds = MAX_USER_LOOKUP_IDS) {
|
|
4
|
+
const single = typeof id === "string" && id.trim() ? id.trim() : null;
|
|
5
|
+
if (!isIdsParamProvided(rawIds)) {
|
|
6
|
+
return single ? { kind: "ids", ids: [single] } : { kind: "unfiltered" };
|
|
7
|
+
}
|
|
8
|
+
const parsed = parseIdsParam(rawIds, maxIds);
|
|
9
|
+
const ids = single ? parsed.filter((value) => value === single) : parsed;
|
|
10
|
+
return ids.length ? { kind: "ids", ids } : { kind: "none" };
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
MAX_USER_LOOKUP_IDS,
|
|
14
|
+
resolveUserIdFilter
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=userIdFilter.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/auth/lib/userIdFilter.ts"],
|
|
4
|
+
"sourcesContent": ["import { isIdsParamProvided, parseIdsParam } from '@open-mercato/shared/lib/crud/ids'\n\n// Batch id lookup shares the list's page cap, so `?ids=` can never pull more rows than `?pageSize=`.\nexport const MAX_USER_LOOKUP_IDS = 100\n\nexport type UserIdFilter =\n /** Neither `?id=` nor `?ids=` was supplied \u2014 the list is filtered by the other params only. */\n | { kind: 'unfiltered' }\n /** Restrict the list to these ids. */\n | { kind: 'ids'; ids: string[] }\n /**\n * `?ids=` was supplied but nothing usable survived \u2014 either no value was a UUID, or the\n * intersection with `?id=` is empty. Match nothing rather than dropping the filter and returning\n * the full first page, which would turn a malformed request into a record-count side channel\n * (the same rule `mergeIdFilter` enforces for CRUD list routes, #4143 Finding 3).\n */\n | { kind: 'none' }\n\nexport function resolveUserIdFilter(\n rawIds: unknown,\n id?: string | null,\n maxIds: number = MAX_USER_LOOKUP_IDS,\n): UserIdFilter {\n const single = typeof id === 'string' && id.trim() ? id.trim() : null\n if (!isIdsParamProvided(rawIds)) {\n return single ? { kind: 'ids', ids: [single] } : { kind: 'unfiltered' }\n }\n const parsed = parseIdsParam(rawIds, maxIds)\n const ids = single ? parsed.filter((value) => value === single) : parsed\n return ids.length ? { kind: 'ids', ids } : { kind: 'none' }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB,qBAAqB;AAG3C,MAAM,sBAAsB;AAe5B,SAAS,oBACd,QACA,IACA,SAAiB,qBACH;AACd,QAAM,SAAS,OAAO,OAAO,YAAY,GAAG,KAAK,IAAI,GAAG,KAAK,IAAI;AACjE,MAAI,CAAC,mBAAmB,MAAM,GAAG;AAC/B,WAAO,SAAS,EAAE,MAAM,OAAO,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,aAAa;AAAA,EACxE;AACA,QAAM,SAAS,cAAc,QAAQ,MAAM;AAC3C,QAAM,MAAM,SAAS,OAAO,OAAO,CAAC,UAAU,UAAU,MAAM,IAAI;AAClE,SAAO,IAAI,SAAS,EAAE,MAAM,OAAO,IAAI,IAAI,EAAE,MAAM,OAAO;AAC5D;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -110,11 +110,25 @@ class AuthService {
|
|
|
110
110
|
await this.em.persist(row).flush();
|
|
111
111
|
return { user, token: rawToken };
|
|
112
112
|
}
|
|
113
|
-
async
|
|
113
|
+
async findUnconsumedPasswordReset(token) {
|
|
114
114
|
const now = /* @__PURE__ */ new Date();
|
|
115
115
|
const hashedToken = hashAuthToken(token);
|
|
116
116
|
const row = await this.em.findOne(PasswordReset, { token: hashedToken });
|
|
117
|
-
if (!row
|
|
117
|
+
if (!row) return null;
|
|
118
|
+
if (row.usedAt && row.usedAt <= now) return null;
|
|
119
|
+
if (row.expiresAt <= now) return null;
|
|
120
|
+
return row;
|
|
121
|
+
}
|
|
122
|
+
async isPasswordResetTokenValid(token) {
|
|
123
|
+
const row = await this.findUnconsumedPasswordReset(token);
|
|
124
|
+
if (!row) return false;
|
|
125
|
+
const user = await findOneWithDecryption(this.em, User, { id: row.user.id, deletedAt: null });
|
|
126
|
+
return user != null;
|
|
127
|
+
}
|
|
128
|
+
async confirmPasswordReset(token, newPassword) {
|
|
129
|
+
const now = /* @__PURE__ */ new Date();
|
|
130
|
+
const row = await this.findUnconsumedPasswordReset(token);
|
|
131
|
+
if (!row) return null;
|
|
118
132
|
const affected = await this.em.nativeUpdate(
|
|
119
133
|
PasswordReset,
|
|
120
134
|
{ id: row.id, usedAt: null },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/auth/services/authService.ts"],
|
|
4
|
-
"sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { compare, hash } from 'bcryptjs'\nimport { User, Role, UserRole, Session, PasswordReset } from '@open-mercato/core/modules/auth/data/entities'\nimport { emailHashLookupValues } from '@open-mercato/core/modules/auth/lib/emailHash'\nimport { generateAuthToken, hashAuthToken } from '@open-mercato/core/modules/auth/lib/tokenHash'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\n// A fixed, valid bcrypt hash (cost 10) of a throwaway value no real password\n// can match. verifyPassword compares against it whenever the user is missing or\n// has no password hash, so a failed login spends the same bcrypt CPU time\n// regardless of whether the account exists \u2014 closing the timing side channel\n// for account enumeration (issue #2242).\nconst TIMING_EQUALIZER_PASSWORD_HASH = '$2b$10$OcZrhmZpIzJOjkfwUrk7d.Nl0eHNzOvalBcBlt5Ran.4lj8R3HZg6'\n\nexport class AuthService {\n constructor(private em: EntityManager) {}\n\n async findUserByEmail(email: string) {\n const emailHashes = emailHashLookupValues(email)\n return findOneWithDecryption(this.em, User, {\n deletedAt: null,\n $or: [\n { email },\n { emailHash: { $in: emailHashes } },\n ],\n } as any)\n }\n\n async findUsersByEmail(email: string) {\n const emailHashes = emailHashLookupValues(email)\n return findWithDecryption(this.em, User, {\n deletedAt: null,\n $or: [\n { email },\n { emailHash: { $in: emailHashes } },\n ],\n } as any)\n }\n\n async findUserByEmailAndTenant(email: string, tenantId: string) {\n const emailHashes = emailHashLookupValues(email)\n return findOneWithDecryption(\n this.em,\n User,\n {\n tenantId,\n deletedAt: null,\n $or: [\n { email },\n { emailHash: { $in: emailHashes } },\n ],\n } as any,\n undefined,\n { tenantId },\n )\n }\n\n async verifyPassword(user: User | null, password: string) {\n const storedHash = user?.passwordHash ?? null\n // Always run a bcrypt comparison \u2014 against a fixed dummy hash when the user\n // is absent or has no password \u2014 so login latency does not reveal whether\n // the account exists (timing-based enumeration, issue #2242).\n const matched = await compare(password, storedHash ?? TIMING_EQUALIZER_PASSWORD_HASH)\n return storedHash !== null && matched\n }\n\n async updateLastLoginAt(user: User) {\n const now = new Date()\n // Use native update to avoid flushing unrelated entities that might be pending in this EM\n await this.em.nativeUpdate(User, { id: user.id }, { lastLoginAt: now })\n user.lastLoginAt = now\n }\n\n async getUserRoles(user: User, tenantId?: string | null): Promise<string[]> {\n const resolvedTenantId = tenantId ?? user.tenantId ?? null\n if (!resolvedTenantId) return []\n const links = await findWithDecryption(\n this.em,\n UserRole,\n { user, deletedAt: null, role: { tenantId: resolvedTenantId, deletedAt: null } as any },\n { populate: ['role'] },\n { tenantId: resolvedTenantId, organizationId: user.organizationId ?? null },\n )\n // A populated `role` can still be null when the link points at a soft-deleted\n // role (the Role soft-delete filter suppresses hydration), e.g. an admin link\n // orphaned by a re-seed during interrupted-provisioning recovery. Dropping such\n // links keeps role resolution from throwing on the login / session-refresh hot\n // path, mirroring resolveCanonicalStaffAuthContext in lib/sessionIntegrity.ts.\n return links\n .map((l) => l.role)\n .filter((role): role is Role => !!role)\n .map((role) => role.name)\n .filter((name): name is string => typeof name === 'string' && name.trim().length > 0)\n }\n\n\n async createSession(user: User, expiresAt: Date): Promise<{ session: Session; token: string }> {\n const rawToken = generateAuthToken()\n const tokenHash = hashAuthToken(rawToken)\n const sess = this.em.create(Session as any, { user, token: tokenHash, expiresAt, createdAt: new Date() } as any)\n await this.em.persist(sess).flush()\n return { session: sess as Session, token: rawToken }\n }\n\n async deleteSessionByToken(token: string) {\n const hashedToken = hashAuthToken(token)\n await this.em.nativeDelete(Session, { token: hashedToken })\n }\n\n async deleteSessionById(sessionId: string) {\n await this.em.nativeDelete(Session, { id: sessionId })\n }\n\n async findActiveSessionById(sessionId: string): Promise<Session | null> {\n const session = await this.em.findOne(Session, { id: sessionId, deletedAt: null })\n if (!session) return null\n if (session.expiresAt.getTime() < Date.now()) return null\n return session\n }\n\n async deleteAllUserSessions(userId: string) {\n await this.em.nativeDelete(Session, { user: userId })\n }\n\n async refreshFromSessionToken(token: string) {\n const now = new Date()\n const hashedToken = hashAuthToken(token)\n const sess = await this.em.findOne(Session, { token: hashedToken })\n if (!sess || sess.expiresAt <= now) return null\n const user = await findOneWithDecryption(this.em, User, { id: sess.user.id, deletedAt: null })\n if (!user) return null\n const roles = await this.getUserRoles(user, user.tenantId ?? null)\n return { user, roles, session: sess }\n }\n\n async requestPasswordReset(email: string) {\n const user = await this.findUserByEmail(email)\n if (!user) return null\n const rawToken = generateAuthToken()\n const tokenHash = hashAuthToken(rawToken)\n const expiresAt = new Date(Date.now() + 60 * 60 * 1000)\n const row = this.em.create(PasswordReset as any, { user, token: tokenHash, expiresAt, createdAt: new Date() } as any)\n await this.em.persist(row).flush()\n return { user, token: rawToken }\n }\n\n async
|
|
5
|
-
"mappings": "AACA,SAAS,SAAS,YAAY;AAC9B,SAAS,MAAY,UAAU,SAAS,qBAAqB;AAC7D,SAAS,6BAA6B;AACtC,SAAS,mBAAmB,qBAAqB;AACjD,SAAS,oBAAoB,6BAA6B;AAO1D,MAAM,iCAAiC;AAEhC,MAAM,YAAY;AAAA,EACvB,YAAoB,IAAmB;AAAnB;AAAA,EAAoB;AAAA,EAExC,MAAM,gBAAgB,OAAe;AACnC,UAAM,cAAc,sBAAsB,KAAK;AAC/C,WAAO,sBAAsB,KAAK,IAAI,MAAM;AAAA,MAC1C,WAAW;AAAA,MACX,KAAK;AAAA,QACH,EAAE,MAAM;AAAA,QACR,EAAE,WAAW,EAAE,KAAK,YAAY,EAAE;AAAA,MACpC;AAAA,IACF,CAAQ;AAAA,EACV;AAAA,EAEA,MAAM,iBAAiB,OAAe;AACpC,UAAM,cAAc,sBAAsB,KAAK;AAC/C,WAAO,mBAAmB,KAAK,IAAI,MAAM;AAAA,MACvC,WAAW;AAAA,MACX,KAAK;AAAA,QACH,EAAE,MAAM;AAAA,QACR,EAAE,WAAW,EAAE,KAAK,YAAY,EAAE;AAAA,MACpC;AAAA,IACF,CAAQ;AAAA,EACV;AAAA,EAEA,MAAM,yBAAyB,OAAe,UAAkB;AAC9D,UAAM,cAAc,sBAAsB,KAAK;AAC/C,WAAO;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA;AAAA,QACE;AAAA,QACA,WAAW;AAAA,QACX,KAAK;AAAA,UACH,EAAE,MAAM;AAAA,UACR,EAAE,WAAW,EAAE,KAAK,YAAY,EAAE;AAAA,QACpC;AAAA,MACF;AAAA,MACA;AAAA,MACA,EAAE,SAAS;AAAA,IACb;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,MAAmB,UAAkB;AACxD,UAAM,aAAa,MAAM,gBAAgB;AAIzC,UAAM,UAAU,MAAM,QAAQ,UAAU,cAAc,8BAA8B;AACpF,WAAO,eAAe,QAAQ;AAAA,EAChC;AAAA,EAEA,MAAM,kBAAkB,MAAY;AAClC,UAAM,MAAM,oBAAI,KAAK;AAErB,UAAM,KAAK,GAAG,aAAa,MAAM,EAAE,IAAI,KAAK,GAAG,GAAG,EAAE,aAAa,IAAI,CAAC;AACtE,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,MAAM,aAAa,MAAY,UAA6C;AAC1E,UAAM,mBAAmB,YAAY,KAAK,YAAY;AACtD,QAAI,CAAC,iBAAkB,QAAO,CAAC;AAC/B,UAAM,QAAQ,MAAM;AAAA,MAClB,KAAK;AAAA,MACL;AAAA,MACA,EAAE,MAAM,WAAW,MAAM,MAAM,EAAE,UAAU,kBAAkB,WAAW,KAAK,EAAS;AAAA,MACtF,EAAE,UAAU,CAAC,MAAM,EAAE;AAAA,MACrB,EAAE,UAAU,kBAAkB,gBAAgB,KAAK,kBAAkB,KAAK;AAAA,IAC5E;AAMA,WAAO,MACJ,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,OAAO,CAAC,SAAuB,CAAC,CAAC,IAAI,EACrC,IAAI,CAAC,SAAS,KAAK,IAAI,EACvB,OAAO,CAAC,SAAyB,OAAO,SAAS,YAAY,KAAK,KAAK,EAAE,SAAS,CAAC;AAAA,EACxF;AAAA,EAGA,MAAM,cAAc,MAAY,WAA+D;AAC7F,UAAM,WAAW,kBAAkB;AACnC,UAAM,YAAY,cAAc,QAAQ;AACxC,UAAM,OAAO,KAAK,GAAG,OAAO,SAAgB,EAAE,MAAM,OAAO,WAAW,WAAW,WAAW,oBAAI,KAAK,EAAE,CAAQ;AAC/G,UAAM,KAAK,GAAG,QAAQ,IAAI,EAAE,MAAM;AAClC,WAAO,EAAE,SAAS,MAAiB,OAAO,SAAS;AAAA,EACrD;AAAA,EAEA,MAAM,qBAAqB,OAAe;AACxC,UAAM,cAAc,cAAc,KAAK;AACvC,UAAM,KAAK,GAAG,aAAa,SAAS,EAAE,OAAO,YAAY,CAAC;AAAA,EAC5D;AAAA,EAEA,MAAM,kBAAkB,WAAmB;AACzC,UAAM,KAAK,GAAG,aAAa,SAAS,EAAE,IAAI,UAAU,CAAC;AAAA,EACvD;AAAA,EAEA,MAAM,sBAAsB,WAA4C;AACtE,UAAM,UAAU,MAAM,KAAK,GAAG,QAAQ,SAAS,EAAE,IAAI,WAAW,WAAW,KAAK,CAAC;AACjF,QAAI,CAAC,QAAS,QAAO;AACrB,QAAI,QAAQ,UAAU,QAAQ,IAAI,KAAK,IAAI,EAAG,QAAO;AACrD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,sBAAsB,QAAgB;AAC1C,UAAM,KAAK,GAAG,aAAa,SAAS,EAAE,MAAM,OAAO,CAAC;AAAA,EACtD;AAAA,EAEA,MAAM,wBAAwB,OAAe;AAC3C,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,cAAc,cAAc,KAAK;AACvC,UAAM,OAAO,MAAM,KAAK,GAAG,QAAQ,SAAS,EAAE,OAAO,YAAY,CAAC;AAClE,QAAI,CAAC,QAAQ,KAAK,aAAa,IAAK,QAAO;AAC3C,UAAM,OAAO,MAAM,sBAAsB,KAAK,IAAI,MAAM,EAAE,IAAI,KAAK,KAAK,IAAI,WAAW,KAAK,CAAC;AAC7F,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,QAAQ,MAAM,KAAK,aAAa,MAAM,KAAK,YAAY,IAAI;AACjE,WAAO,EAAE,MAAM,OAAO,SAAS,KAAK;AAAA,EACtC;AAAA,EAEA,MAAM,qBAAqB,OAAe;AACxC,UAAM,OAAO,MAAM,KAAK,gBAAgB,KAAK;AAC7C,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,WAAW,kBAAkB;AACnC,UAAM,YAAY,cAAc,QAAQ;AACxC,UAAM,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,GAAI;AACtD,UAAM,MAAM,KAAK,GAAG,OAAO,eAAsB,EAAE,MAAM,OAAO,WAAW,WAAW,WAAW,oBAAI,KAAK,EAAE,CAAQ;AACpH,UAAM,KAAK,GAAG,QAAQ,GAAG,EAAE,MAAM;AACjC,WAAO,EAAE,MAAM,OAAO,SAAS;AAAA,EACjC;AAAA,EAEA,
|
|
4
|
+
"sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { compare, hash } from 'bcryptjs'\nimport { User, Role, UserRole, Session, PasswordReset } from '@open-mercato/core/modules/auth/data/entities'\nimport { emailHashLookupValues } from '@open-mercato/core/modules/auth/lib/emailHash'\nimport { generateAuthToken, hashAuthToken } from '@open-mercato/core/modules/auth/lib/tokenHash'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\n// A fixed, valid bcrypt hash (cost 10) of a throwaway value no real password\n// can match. verifyPassword compares against it whenever the user is missing or\n// has no password hash, so a failed login spends the same bcrypt CPU time\n// regardless of whether the account exists \u2014 closing the timing side channel\n// for account enumeration (issue #2242).\nconst TIMING_EQUALIZER_PASSWORD_HASH = '$2b$10$OcZrhmZpIzJOjkfwUrk7d.Nl0eHNzOvalBcBlt5Ran.4lj8R3HZg6'\n\nexport class AuthService {\n constructor(private em: EntityManager) {}\n\n async findUserByEmail(email: string) {\n const emailHashes = emailHashLookupValues(email)\n return findOneWithDecryption(this.em, User, {\n deletedAt: null,\n $or: [\n { email },\n { emailHash: { $in: emailHashes } },\n ],\n } as any)\n }\n\n async findUsersByEmail(email: string) {\n const emailHashes = emailHashLookupValues(email)\n return findWithDecryption(this.em, User, {\n deletedAt: null,\n $or: [\n { email },\n { emailHash: { $in: emailHashes } },\n ],\n } as any)\n }\n\n async findUserByEmailAndTenant(email: string, tenantId: string) {\n const emailHashes = emailHashLookupValues(email)\n return findOneWithDecryption(\n this.em,\n User,\n {\n tenantId,\n deletedAt: null,\n $or: [\n { email },\n { emailHash: { $in: emailHashes } },\n ],\n } as any,\n undefined,\n { tenantId },\n )\n }\n\n async verifyPassword(user: User | null, password: string) {\n const storedHash = user?.passwordHash ?? null\n // Always run a bcrypt comparison \u2014 against a fixed dummy hash when the user\n // is absent or has no password \u2014 so login latency does not reveal whether\n // the account exists (timing-based enumeration, issue #2242).\n const matched = await compare(password, storedHash ?? TIMING_EQUALIZER_PASSWORD_HASH)\n return storedHash !== null && matched\n }\n\n async updateLastLoginAt(user: User) {\n const now = new Date()\n // Use native update to avoid flushing unrelated entities that might be pending in this EM\n await this.em.nativeUpdate(User, { id: user.id }, { lastLoginAt: now })\n user.lastLoginAt = now\n }\n\n async getUserRoles(user: User, tenantId?: string | null): Promise<string[]> {\n const resolvedTenantId = tenantId ?? user.tenantId ?? null\n if (!resolvedTenantId) return []\n const links = await findWithDecryption(\n this.em,\n UserRole,\n { user, deletedAt: null, role: { tenantId: resolvedTenantId, deletedAt: null } as any },\n { populate: ['role'] },\n { tenantId: resolvedTenantId, organizationId: user.organizationId ?? null },\n )\n // A populated `role` can still be null when the link points at a soft-deleted\n // role (the Role soft-delete filter suppresses hydration), e.g. an admin link\n // orphaned by a re-seed during interrupted-provisioning recovery. Dropping such\n // links keeps role resolution from throwing on the login / session-refresh hot\n // path, mirroring resolveCanonicalStaffAuthContext in lib/sessionIntegrity.ts.\n return links\n .map((l) => l.role)\n .filter((role): role is Role => !!role)\n .map((role) => role.name)\n .filter((name): name is string => typeof name === 'string' && name.trim().length > 0)\n }\n\n\n async createSession(user: User, expiresAt: Date): Promise<{ session: Session; token: string }> {\n const rawToken = generateAuthToken()\n const tokenHash = hashAuthToken(rawToken)\n const sess = this.em.create(Session as any, { user, token: tokenHash, expiresAt, createdAt: new Date() } as any)\n await this.em.persist(sess).flush()\n return { session: sess as Session, token: rawToken }\n }\n\n async deleteSessionByToken(token: string) {\n const hashedToken = hashAuthToken(token)\n await this.em.nativeDelete(Session, { token: hashedToken })\n }\n\n async deleteSessionById(sessionId: string) {\n await this.em.nativeDelete(Session, { id: sessionId })\n }\n\n async findActiveSessionById(sessionId: string): Promise<Session | null> {\n const session = await this.em.findOne(Session, { id: sessionId, deletedAt: null })\n if (!session) return null\n if (session.expiresAt.getTime() < Date.now()) return null\n return session\n }\n\n async deleteAllUserSessions(userId: string) {\n await this.em.nativeDelete(Session, { user: userId })\n }\n\n async refreshFromSessionToken(token: string) {\n const now = new Date()\n const hashedToken = hashAuthToken(token)\n const sess = await this.em.findOne(Session, { token: hashedToken })\n if (!sess || sess.expiresAt <= now) return null\n const user = await findOneWithDecryption(this.em, User, { id: sess.user.id, deletedAt: null })\n if (!user) return null\n const roles = await this.getUserRoles(user, user.tenantId ?? null)\n return { user, roles, session: sess }\n }\n\n async requestPasswordReset(email: string) {\n const user = await this.findUserByEmail(email)\n if (!user) return null\n const rawToken = generateAuthToken()\n const tokenHash = hashAuthToken(rawToken)\n const expiresAt = new Date(Date.now() + 60 * 60 * 1000)\n const row = this.em.create(PasswordReset as any, { user, token: tokenHash, expiresAt, createdAt: new Date() } as any)\n await this.em.persist(row).flush()\n return { user, token: rawToken }\n }\n\n private async findUnconsumedPasswordReset(token: string): Promise<PasswordReset | null> {\n const now = new Date()\n const hashedToken = hashAuthToken(token)\n const row = await this.em.findOne(PasswordReset, { token: hashedToken })\n if (!row) return null\n if (row.usedAt && row.usedAt <= now) return null\n if (row.expiresAt <= now) return null\n return row\n }\n\n async isPasswordResetTokenValid(token: string): Promise<boolean> {\n const row = await this.findUnconsumedPasswordReset(token)\n if (!row) return false\n const user = await findOneWithDecryption(this.em, User, { id: row.user.id, deletedAt: null })\n return user != null\n }\n\n async confirmPasswordReset(token: string, newPassword: string): Promise<User | null> {\n const now = new Date()\n const row = await this.findUnconsumedPasswordReset(token)\n if (!row) return null\n\n // Atomic compare-and-set: only mark used if still unused \u2014 prevents token replay under concurrency\n const affected = await this.em.nativeUpdate(\n PasswordReset,\n { id: row.id, usedAt: null },\n { usedAt: now },\n )\n if (affected === 0) return null\n\n const user = await findOneWithDecryption(this.em, User, { id: row.user.id, deletedAt: null })\n if (!user) return null\n user.passwordHash = await hash(newPassword, 10)\n await this.em.flush()\n await this.deleteAllUserSessions(String(user.id))\n return user\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
2
|
+
import { escapeLikePattern } from "@open-mercato/shared/lib/db/escapeLikePattern";
|
|
3
|
+
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
4
|
+
import { Role, RoleAcl, User, UserRole } from "../data/entities.js";
|
|
5
|
+
import { listSuperAdminUserIds } from "../lib/grantChecks.js";
|
|
6
|
+
import { resolveRoleOrganizationScope, roleAclGrantsPrincipalOrganization } from "./roleOrganizationScope.js";
|
|
7
|
+
const MAX_ROLE_PAGE_SIZE = 100;
|
|
8
|
+
const MAX_ROLE_RESULTS = 1e3;
|
|
9
|
+
function normalizeIds(ids) {
|
|
10
|
+
return Array.from(new Set(ids.map((id) => id.trim()).filter(Boolean)));
|
|
11
|
+
}
|
|
12
|
+
function relationId(value) {
|
|
13
|
+
if (typeof value === "string") return value.trim() || null;
|
|
14
|
+
if (!value || typeof value !== "object") return null;
|
|
15
|
+
const id = value.id;
|
|
16
|
+
return typeof id === "string" && id.trim().length > 0 ? id.trim() : null;
|
|
17
|
+
}
|
|
18
|
+
class DefaultAuthPrincipalService {
|
|
19
|
+
constructor(em, organizationHierarchyService) {
|
|
20
|
+
this.em = em;
|
|
21
|
+
this.organizationHierarchyService = organizationHierarchyService;
|
|
22
|
+
}
|
|
23
|
+
async principalExists(input) {
|
|
24
|
+
if (input.type === "role") {
|
|
25
|
+
return (await this.filterActiveRoleIds([input.id], input.scope)).includes(input.id);
|
|
26
|
+
}
|
|
27
|
+
return Boolean(await findOneWithDecryption(
|
|
28
|
+
this.em,
|
|
29
|
+
User,
|
|
30
|
+
{
|
|
31
|
+
id: input.id,
|
|
32
|
+
tenantId: input.scope.tenantId,
|
|
33
|
+
deletedAt: null,
|
|
34
|
+
$or: [{ organizationId: null }, { organizationId: input.scope.organizationId }]
|
|
35
|
+
},
|
|
36
|
+
{ fields: ["id"] },
|
|
37
|
+
input.scope
|
|
38
|
+
));
|
|
39
|
+
}
|
|
40
|
+
async resolveActiveUserRoleIds(userId, scope) {
|
|
41
|
+
const links = await findWithDecryption(
|
|
42
|
+
this.em,
|
|
43
|
+
UserRole,
|
|
44
|
+
{
|
|
45
|
+
user: userId,
|
|
46
|
+
deletedAt: null,
|
|
47
|
+
role: { tenantId: scope.tenantId, deletedAt: null }
|
|
48
|
+
},
|
|
49
|
+
{ fields: ["role"] },
|
|
50
|
+
scope
|
|
51
|
+
);
|
|
52
|
+
return this.filterActiveRoleIds(
|
|
53
|
+
normalizeIds(links.flatMap((link) => {
|
|
54
|
+
const id = relationId(link.role);
|
|
55
|
+
return id ? [id] : [];
|
|
56
|
+
})),
|
|
57
|
+
scope
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
async filterActiveRoleIds(roleIds, scope) {
|
|
61
|
+
const ids = normalizeIds(roleIds);
|
|
62
|
+
if (ids.length === 0) return [];
|
|
63
|
+
const roles = await findWithDecryption(
|
|
64
|
+
this.em,
|
|
65
|
+
Role,
|
|
66
|
+
{ id: { $in: ids }, tenantId: scope.tenantId, deletedAt: null },
|
|
67
|
+
{ fields: ["id"] },
|
|
68
|
+
scope
|
|
69
|
+
);
|
|
70
|
+
const activeIds = normalizeIds(roles.map((role) => role.id));
|
|
71
|
+
if (activeIds.length === 0) return [];
|
|
72
|
+
const roleOrganizationScope = await resolveRoleOrganizationScope(
|
|
73
|
+
this.organizationHierarchyService,
|
|
74
|
+
scope.tenantId,
|
|
75
|
+
scope.organizationId
|
|
76
|
+
);
|
|
77
|
+
const acls = await findWithDecryption(
|
|
78
|
+
this.em,
|
|
79
|
+
RoleAcl,
|
|
80
|
+
{
|
|
81
|
+
tenantId: scope.tenantId,
|
|
82
|
+
role: { $in: activeIds },
|
|
83
|
+
deletedAt: null
|
|
84
|
+
},
|
|
85
|
+
{ fields: ["role", "organizationsJson"] },
|
|
86
|
+
scope
|
|
87
|
+
);
|
|
88
|
+
const eligibilityById = /* @__PURE__ */ new Map();
|
|
89
|
+
for (const acl of acls) {
|
|
90
|
+
const id = relationId(acl.role);
|
|
91
|
+
if (!id) continue;
|
|
92
|
+
eligibilityById.set(
|
|
93
|
+
id,
|
|
94
|
+
eligibilityById.get(id) === true || roleAclGrantsPrincipalOrganization(acl, roleOrganizationScope)
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return activeIds.filter((id) => eligibilityById.get(id) !== false);
|
|
98
|
+
}
|
|
99
|
+
async resolveLabels(input) {
|
|
100
|
+
const ids = normalizeIds(input.ids);
|
|
101
|
+
if (ids.length === 0) return [];
|
|
102
|
+
if (input.type === "role") {
|
|
103
|
+
const eligibleIds = await this.filterActiveRoleIds(ids, input.scope);
|
|
104
|
+
if (eligibleIds.length === 0) return [];
|
|
105
|
+
const roles = await findWithDecryption(
|
|
106
|
+
this.em,
|
|
107
|
+
Role,
|
|
108
|
+
{ id: { $in: eligibleIds }, tenantId: input.scope.tenantId, deletedAt: null },
|
|
109
|
+
{ fields: ["id", "name"] },
|
|
110
|
+
input.scope
|
|
111
|
+
);
|
|
112
|
+
return roles.map((role) => ({ id: role.id, label: role.name, secondary: null }));
|
|
113
|
+
}
|
|
114
|
+
const users = await findWithDecryption(
|
|
115
|
+
this.em,
|
|
116
|
+
User,
|
|
117
|
+
{
|
|
118
|
+
id: { $in: ids },
|
|
119
|
+
tenantId: input.scope.tenantId,
|
|
120
|
+
deletedAt: null,
|
|
121
|
+
$or: [{ organizationId: null }, { organizationId: input.scope.organizationId }]
|
|
122
|
+
},
|
|
123
|
+
{ fields: ["id", "name", "email"] },
|
|
124
|
+
input.scope
|
|
125
|
+
);
|
|
126
|
+
return users.map((user) => ({
|
|
127
|
+
id: user.id,
|
|
128
|
+
label: user.name?.trim() || user.email,
|
|
129
|
+
secondary: user.name?.trim() && user.email !== user.name.trim() ? user.email : null
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
async queryActiveRolePage(input) {
|
|
133
|
+
const page = Math.max(1, Math.floor(input.page));
|
|
134
|
+
const pageSize = Math.min(MAX_ROLE_PAGE_SIZE, Math.max(1, Math.floor(input.pageSize)));
|
|
135
|
+
const offset = (page - 1) * pageSize;
|
|
136
|
+
const excludedIds = normalizeIds(input.excludedIds ?? []);
|
|
137
|
+
const roleOrganizationScope = await resolveRoleOrganizationScope(
|
|
138
|
+
this.organizationHierarchyService,
|
|
139
|
+
input.scope.tenantId,
|
|
140
|
+
input.scope.organizationId
|
|
141
|
+
);
|
|
142
|
+
const db = this.em.getKysely();
|
|
143
|
+
let eligibleRoles = db.selectFrom("roles as r").where("r.tenant_id", "=", input.scope.tenantId).where("r.deleted_at", "is", null);
|
|
144
|
+
if (roleOrganizationScope) {
|
|
145
|
+
const allowedOrganizations = Array.from(roleOrganizationScope);
|
|
146
|
+
const organizationPredicates = [
|
|
147
|
+
sql`ra.organizations_json is null`,
|
|
148
|
+
sql`ra.organizations_json::jsonb @> ${JSON.stringify(["__all__"])}::jsonb`,
|
|
149
|
+
...allowedOrganizations.map((organizationId) => sql`ra.organizations_json::jsonb @> ${JSON.stringify([organizationId])}::jsonb`)
|
|
150
|
+
];
|
|
151
|
+
const organizationAllowed = sql`(${sql.join(organizationPredicates, sql` or `)})`;
|
|
152
|
+
eligibleRoles = eligibleRoles.where(sql`(
|
|
153
|
+
not exists (
|
|
154
|
+
select 1
|
|
155
|
+
from role_acls as ra_any
|
|
156
|
+
where ra_any.role_id = r.id
|
|
157
|
+
and ra_any.tenant_id = ${input.scope.tenantId}
|
|
158
|
+
and ra_any.deleted_at is null
|
|
159
|
+
)
|
|
160
|
+
or exists (
|
|
161
|
+
select 1
|
|
162
|
+
from role_acls as ra
|
|
163
|
+
where ra.role_id = r.id
|
|
164
|
+
and ra.tenant_id = ${input.scope.tenantId}
|
|
165
|
+
and ra.deleted_at is null
|
|
166
|
+
and ${organizationAllowed}
|
|
167
|
+
)
|
|
168
|
+
)`);
|
|
169
|
+
}
|
|
170
|
+
if (input.search?.trim()) {
|
|
171
|
+
const searchPattern = `%${escapeLikePattern(input.search.trim())}%`;
|
|
172
|
+
eligibleRoles = eligibleRoles.where(sql`r.name ilike ${searchPattern}`);
|
|
173
|
+
}
|
|
174
|
+
if (excludedIds.length > 0) {
|
|
175
|
+
eligibleRoles = eligibleRoles.where("r.id", "not in", excludedIds);
|
|
176
|
+
}
|
|
177
|
+
const boundedRoleIds = eligibleRoles.select("r.id").limit(MAX_ROLE_RESULTS).as("bounded_roles");
|
|
178
|
+
const countQuery = db.selectFrom(boundedRoleIds).select(sql`count(*)`.as("count"));
|
|
179
|
+
const pageQuery = eligibleRoles.select(["r.id", "r.name"]).orderBy("r.id", "asc").limit(offset < MAX_ROLE_RESULTS ? Math.min(pageSize, MAX_ROLE_RESULTS - offset) : 0).offset(Math.min(offset, MAX_ROLE_RESULTS));
|
|
180
|
+
const [rows, countRow] = await Promise.all([
|
|
181
|
+
pageQuery.execute(),
|
|
182
|
+
countQuery.executeTakeFirst()
|
|
183
|
+
]);
|
|
184
|
+
const parsedTotal = Number(countRow?.count ?? 0);
|
|
185
|
+
const total = Number.isFinite(parsedTotal) ? Math.min(MAX_ROLE_RESULTS, Math.max(0, Math.floor(parsedTotal))) : 0;
|
|
186
|
+
return {
|
|
187
|
+
items: rows.map((role) => ({ id: role.id, label: role.name, secondary: null })),
|
|
188
|
+
page,
|
|
189
|
+
pageSize,
|
|
190
|
+
total
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
async listSuperAdminUserIds(tenantId) {
|
|
194
|
+
return Array.from(await listSuperAdminUserIds(this.em, tenantId));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export {
|
|
198
|
+
DefaultAuthPrincipalService
|
|
199
|
+
};
|
|
200
|
+
//# sourceMappingURL=principalService.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/auth/services/principalService.ts"],
|
|
4
|
+
"sourcesContent": ["import type { FilterQuery } from '@mikro-orm/core'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { sql } from 'kysely'\nimport type {\n AuthPrincipalLabel,\n AuthPrincipalRolePage,\n AuthPrincipalService,\n OrganizationHierarchyService,\n PrincipalScope,\n} from '@open-mercato/shared/lib/auth/principal-service'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { Role, RoleAcl, User, UserRole } from '../data/entities'\nimport { listSuperAdminUserIds } from '../lib/grantChecks'\nimport { resolveRoleOrganizationScope, roleAclGrantsPrincipalOrganization } from './roleOrganizationScope'\n\nconst MAX_ROLE_PAGE_SIZE = 100\nconst MAX_ROLE_RESULTS = 1_000\n\ntype AuthPrincipalDatabase = {\n roles: {\n id: string\n tenant_id: string\n name: string\n deleted_at: Date | null\n }\n role_acls: {\n role_id: string\n tenant_id: string\n organizations_json: string[] | null\n deleted_at: Date | null\n }\n}\n\nfunction normalizeIds(ids: string[]): string[] {\n return Array.from(new Set(ids.map((id) => id.trim()).filter(Boolean)))\n}\n\nfunction relationId(value: unknown): string | null {\n if (typeof value === 'string') return value.trim() || null\n if (!value || typeof value !== 'object') return null\n const id = (value as { id?: unknown }).id\n return typeof id === 'string' && id.trim().length > 0 ? id.trim() : null\n}\n\nexport class DefaultAuthPrincipalService implements AuthPrincipalService {\n constructor(\n private readonly em: EntityManager,\n private readonly organizationHierarchyService?: OrganizationHierarchyService,\n ) {}\n\n async principalExists(input: {\n type: 'user' | 'role'\n id: string\n scope: PrincipalScope\n }): Promise<boolean> {\n if (input.type === 'role') {\n return (await this.filterActiveRoleIds([input.id], input.scope)).includes(input.id)\n }\n return Boolean(await findOneWithDecryption(\n this.em,\n User,\n {\n id: input.id,\n tenantId: input.scope.tenantId,\n deletedAt: null,\n $or: [{ organizationId: null }, { organizationId: input.scope.organizationId }],\n } as FilterQuery<User>,\n { fields: ['id'] as const },\n input.scope,\n ))\n }\n\n async resolveActiveUserRoleIds(userId: string, scope: PrincipalScope): Promise<string[]> {\n const links = await findWithDecryption(\n this.em,\n UserRole,\n {\n user: userId,\n deletedAt: null,\n role: { tenantId: scope.tenantId, deletedAt: null },\n } as FilterQuery<UserRole>,\n { fields: ['role'] as const },\n scope,\n )\n return this.filterActiveRoleIds(\n normalizeIds(links.flatMap((link) => {\n const id = relationId(link.role)\n return id ? [id] : []\n })),\n scope,\n )\n }\n\n async filterActiveRoleIds(roleIds: string[], scope: PrincipalScope): Promise<string[]> {\n const ids = normalizeIds(roleIds)\n if (ids.length === 0) return []\n const roles = await findWithDecryption(\n this.em,\n Role,\n { id: { $in: ids }, tenantId: scope.tenantId, deletedAt: null } as FilterQuery<Role>,\n { fields: ['id'] as const },\n scope,\n )\n const activeIds = normalizeIds(roles.map((role) => role.id))\n if (activeIds.length === 0) return []\n const roleOrganizationScope = await resolveRoleOrganizationScope(\n this.organizationHierarchyService,\n scope.tenantId,\n scope.organizationId,\n )\n const acls = await findWithDecryption(\n this.em,\n RoleAcl,\n {\n tenantId: scope.tenantId,\n role: { $in: activeIds },\n deletedAt: null,\n } as FilterQuery<RoleAcl>,\n { fields: ['role', 'organizationsJson'] as const },\n scope,\n )\n const eligibilityById = new Map<string, boolean>()\n for (const acl of acls) {\n const id = relationId(acl.role)\n if (!id) continue\n eligibilityById.set(\n id,\n eligibilityById.get(id) === true || roleAclGrantsPrincipalOrganization(acl, roleOrganizationScope),\n )\n }\n // A tenant role without an ACL remains a valid explicit-share principal.\n // Only an existing ACL can restrict that role away from this organization.\n return activeIds.filter((id) => eligibilityById.get(id) !== false)\n }\n\n async resolveLabels(input: {\n type: 'user' | 'role'\n ids: string[]\n scope: PrincipalScope\n }): Promise<AuthPrincipalLabel[]> {\n const ids = normalizeIds(input.ids)\n if (ids.length === 0) return []\n if (input.type === 'role') {\n const eligibleIds = await this.filterActiveRoleIds(ids, input.scope)\n if (eligibleIds.length === 0) return []\n const roles = await findWithDecryption(\n this.em,\n Role,\n { id: { $in: eligibleIds }, tenantId: input.scope.tenantId, deletedAt: null } as FilterQuery<Role>,\n { fields: ['id', 'name'] as const },\n input.scope,\n )\n return roles.map((role) => ({ id: role.id, label: role.name, secondary: null }))\n }\n const users = await findWithDecryption(\n this.em,\n User,\n {\n id: { $in: ids },\n tenantId: input.scope.tenantId,\n deletedAt: null,\n $or: [{ organizationId: null }, { organizationId: input.scope.organizationId }],\n } as FilterQuery<User>,\n { fields: ['id', 'name', 'email'] as const },\n input.scope,\n )\n return users.map((user) => ({\n id: user.id,\n label: user.name?.trim() || user.email,\n secondary: user.name?.trim() && user.email !== user.name.trim() ? user.email : null,\n }))\n }\n\n async queryActiveRolePage(input: {\n scope: PrincipalScope\n search?: string\n excludedIds?: string[]\n page: number\n pageSize: number\n }): Promise<AuthPrincipalRolePage> {\n const page = Math.max(1, Math.floor(input.page))\n const pageSize = Math.min(MAX_ROLE_PAGE_SIZE, Math.max(1, Math.floor(input.pageSize)))\n const offset = (page - 1) * pageSize\n const excludedIds = normalizeIds(input.excludedIds ?? [])\n const roleOrganizationScope = await resolveRoleOrganizationScope(\n this.organizationHierarchyService,\n input.scope.tenantId,\n input.scope.organizationId,\n )\n const db = this.em.getKysely<AuthPrincipalDatabase>()\n let eligibleRoles = db\n .selectFrom('roles as r')\n .where('r.tenant_id', '=', input.scope.tenantId)\n .where('r.deleted_at', 'is', null)\n\n if (roleOrganizationScope) {\n const allowedOrganizations = Array.from(roleOrganizationScope)\n const organizationPredicates = [\n sql<boolean>`ra.organizations_json is null`,\n sql<boolean>`ra.organizations_json::jsonb @> ${JSON.stringify(['__all__'])}::jsonb`,\n ...allowedOrganizations.map((organizationId) => (\n sql<boolean>`ra.organizations_json::jsonb @> ${JSON.stringify([organizationId])}::jsonb`\n )),\n ]\n const organizationAllowed = sql<boolean>`(${sql.join(organizationPredicates, sql` or `)})`\n eligibleRoles = eligibleRoles.where(sql<boolean>`(\n not exists (\n select 1\n from role_acls as ra_any\n where ra_any.role_id = r.id\n and ra_any.tenant_id = ${input.scope.tenantId}\n and ra_any.deleted_at is null\n )\n or exists (\n select 1\n from role_acls as ra\n where ra.role_id = r.id\n and ra.tenant_id = ${input.scope.tenantId}\n and ra.deleted_at is null\n and ${organizationAllowed}\n )\n )`)\n }\n if (input.search?.trim()) {\n const searchPattern = `%${escapeLikePattern(input.search.trim())}%`\n eligibleRoles = eligibleRoles.where(sql<boolean>`r.name ilike ${searchPattern}`)\n }\n if (excludedIds.length > 0) {\n eligibleRoles = eligibleRoles.where('r.id', 'not in', excludedIds)\n }\n\n const boundedRoleIds = eligibleRoles\n .select('r.id')\n .limit(MAX_ROLE_RESULTS)\n .as('bounded_roles')\n const countQuery = db\n .selectFrom(boundedRoleIds)\n .select(sql<string>`count(*)`.as('count'))\n const pageQuery = eligibleRoles\n .select(['r.id', 'r.name'])\n .orderBy('r.id', 'asc')\n .limit(offset < MAX_ROLE_RESULTS ? Math.min(pageSize, MAX_ROLE_RESULTS - offset) : 0)\n .offset(Math.min(offset, MAX_ROLE_RESULTS))\n\n const [rows, countRow] = await Promise.all([\n pageQuery.execute() as Promise<Array<{ id: string; name: string }>>,\n countQuery.executeTakeFirst() as Promise<{ count?: string | number } | undefined>,\n ])\n const parsedTotal = Number(countRow?.count ?? 0)\n const total = Number.isFinite(parsedTotal)\n ? Math.min(MAX_ROLE_RESULTS, Math.max(0, Math.floor(parsedTotal)))\n : 0\n\n return {\n items: rows.map((role) => ({ id: role.id, label: role.name, secondary: null })),\n page,\n pageSize,\n total,\n }\n }\n\n async listSuperAdminUserIds(tenantId: string): Promise<string[]> {\n return Array.from(await listSuperAdminUserIds(this.em, tenantId))\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|