@open-mercato/core 0.4.8-develop-28cee031d6 → 0.4.8-develop-84f3678a58
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/agentic/standalone-guide.md +235 -0
- package/dist/generated/entities/customer_role/index.js +27 -0
- package/dist/generated/entities/customer_role/index.js.map +7 -0
- package/dist/generated/entities/customer_role_acl/index.js +19 -0
- package/dist/generated/entities/customer_role_acl/index.js.map +7 -0
- package/dist/generated/entities/customer_user/index.js +37 -0
- package/dist/generated/entities/customer_user/index.js.map +7 -0
- package/dist/generated/entities/customer_user_acl/index.js +19 -0
- package/dist/generated/entities/customer_user_acl/index.js.map +7 -0
- package/dist/generated/entities/customer_user_email_verification/index.js +17 -0
- package/dist/generated/entities/customer_user_email_verification/index.js.map +7 -0
- package/dist/generated/entities/customer_user_invitation/index.js +33 -0
- package/dist/generated/entities/customer_user_invitation/index.js.map +7 -0
- package/dist/generated/entities/customer_user_password_reset/index.js +15 -0
- package/dist/generated/entities/customer_user_password_reset/index.js.map +7 -0
- package/dist/generated/entities/customer_user_role/index.js +13 -0
- package/dist/generated/entities/customer_user_role/index.js.map +7 -0
- package/dist/generated/entities/customer_user_session/index.js +21 -0
- package/dist/generated/entities/customer_user_session/index.js.map +7 -0
- package/dist/generated/entities/organization/index.js +2 -0
- package/dist/generated/entities/organization/index.js.map +2 -2
- package/dist/generated/entities.ids.generated.js +14 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +18 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/auth/services/rbacService.js +3 -9
- package/dist/modules/auth/services/rbacService.js.map +2 -2
- package/dist/modules/customer_accounts/acl.js +12 -0
- package/dist/modules/customer_accounts/acl.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +87 -0
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/roles/[id].js +216 -0
- package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/roles.js +189 -0
- package/dist/modules/customer_accounts/api/admin/roles.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js +69 -0
- package/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/users/[id]/verify-email.js +64 -0
- package/dist/modules/customer_accounts/api/admin/users/[id]/verify-email.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/users/[id].js +253 -0
- package/dist/modules/customer_accounts/api/admin/users/[id].js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/users-invite.js +78 -0
- package/dist/modules/customer_accounts/api/admin/users-invite.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/users.js +251 -0
- package/dist/modules/customer_accounts/api/admin/users.js.map +7 -0
- package/dist/modules/customer_accounts/api/email/verify.js +59 -0
- package/dist/modules/customer_accounts/api/email/verify.js.map +7 -0
- package/dist/modules/customer_accounts/api/interceptors.js +5 -0
- package/dist/modules/customer_accounts/api/interceptors.js.map +7 -0
- package/dist/modules/customer_accounts/api/invitations/accept.js +114 -0
- package/dist/modules/customer_accounts/api/invitations/accept.js.map +7 -0
- package/dist/modules/customer_accounts/api/login.js +143 -0
- package/dist/modules/customer_accounts/api/login.js.map +7 -0
- package/dist/modules/customer_accounts/api/magic-link/request.js +78 -0
- package/dist/modules/customer_accounts/api/magic-link/request.js.map +7 -0
- package/dist/modules/customer_accounts/api/magic-link/verify.js +114 -0
- package/dist/modules/customer_accounts/api/magic-link/verify.js.map +7 -0
- package/dist/modules/customer_accounts/api/password/reset-confirm.js +59 -0
- package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +7 -0
- package/dist/modules/customer_accounts/api/password/reset-request.js +77 -0
- package/dist/modules/customer_accounts/api/password/reset-request.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/events/stream.js +163 -0
- package/dist/modules/customer_accounts/api/portal/events/stream.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/feature-check.js +57 -0
- package/dist/modules/customer_accounts/api/portal/feature-check.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/logout.js +64 -0
- package/dist/modules/customer_accounts/api/portal/logout.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/notifications/[id]/dismiss.js +49 -0
- package/dist/modules/customer_accounts/api/portal/notifications/[id]/dismiss.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/notifications/[id]/read.js +49 -0
- package/dist/modules/customer_accounts/api/portal/notifications/[id]/read.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/notifications/mark-all-read.js +46 -0
- package/dist/modules/customer_accounts/api/portal/notifications/mark-all-read.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/notifications/unread-count.js +42 -0
- package/dist/modules/customer_accounts/api/portal/notifications/unread-count.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/notifications.js +105 -0
- package/dist/modules/customer_accounts/api/portal/notifications.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/password-change.js +57 -0
- package/dist/modules/customer_accounts/api/portal/password-change.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/profile.js +135 -0
- package/dist/modules/customer_accounts/api/portal/profile.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/sessions/[id].js +62 -0
- package/dist/modules/customer_accounts/api/portal/sessions/[id].js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/sessions-refresh.js +75 -0
- package/dist/modules/customer_accounts/api/portal/sessions-refresh.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/sessions.js +77 -0
- package/dist/modules/customer_accounts/api/portal/sessions.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +90 -0
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/users/[id].js +71 -0
- package/dist/modules/customer_accounts/api/portal/users/[id].js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/users-invite.js +92 -0
- package/dist/modules/customer_accounts/api/portal/users-invite.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/users.js +79 -0
- package/dist/modules/customer_accounts/api/portal/users.js.map +7 -0
- package/dist/modules/customer_accounts/api/signup.js +121 -0
- package/dist/modules/customer_accounts/api/signup.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.js +491 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.meta.js +15 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.meta.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +343 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.meta.js +16 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.meta.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.js +180 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.meta.js +16 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.meta.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +176 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.meta.js +33 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.meta.js.map +7 -0
- package/dist/modules/customer_accounts/backend/page.js +466 -0
- package/dist/modules/customer_accounts/backend/page.js.map +7 -0
- package/dist/modules/customer_accounts/backend/page.meta.js +35 -0
- package/dist/modules/customer_accounts/backend/page.meta.js.map +7 -0
- package/dist/modules/customer_accounts/ce.js +26 -0
- package/dist/modules/customer_accounts/ce.js.map +7 -0
- package/dist/modules/customer_accounts/data/enrichers.js +85 -0
- package/dist/modules/customer_accounts/data/enrichers.js.map +7 -0
- package/dist/modules/customer_accounts/data/entities.js +377 -0
- package/dist/modules/customer_accounts/data/entities.js.map +7 -0
- package/dist/modules/customer_accounts/data/extensions.js +8 -0
- package/dist/modules/customer_accounts/data/extensions.js.map +7 -0
- package/dist/modules/customer_accounts/data/validators.js +111 -0
- package/dist/modules/customer_accounts/data/validators.js.map +7 -0
- package/dist/modules/customer_accounts/di.js +17 -0
- package/dist/modules/customer_accounts/di.js.map +7 -0
- package/dist/modules/customer_accounts/events.js +28 -0
- package/dist/modules/customer_accounts/events.js.map +7 -0
- package/dist/modules/customer_accounts/index.js +15 -0
- package/dist/modules/customer_accounts/index.js.map +7 -0
- package/dist/modules/customer_accounts/lib/customerAuth.js +71 -0
- package/dist/modules/customer_accounts/lib/customerAuth.js.map +7 -0
- package/dist/modules/customer_accounts/lib/customerAuthServer.js +29 -0
- package/dist/modules/customer_accounts/lib/customerAuthServer.js.map +7 -0
- package/dist/modules/customer_accounts/lib/rateLimiter.js +63 -0
- package/dist/modules/customer_accounts/lib/rateLimiter.js.map +7 -0
- package/dist/modules/customer_accounts/lib/tokenGenerator.js +12 -0
- package/dist/modules/customer_accounts/lib/tokenGenerator.js.map +7 -0
- package/dist/modules/customer_accounts/migrations/Migration20260313222043.js +49 -0
- package/dist/modules/customer_accounts/migrations/Migration20260313222043.js.map +7 -0
- package/dist/modules/customer_accounts/notifications.client.js +47 -0
- package/dist/modules/customer_accounts/notifications.client.js.map +7 -0
- package/dist/modules/customer_accounts/notifications.js +46 -0
- package/dist/modules/customer_accounts/notifications.js.map +7 -0
- package/dist/modules/customer_accounts/search.js +120 -0
- package/dist/modules/customer_accounts/search.js.map +7 -0
- package/dist/modules/customer_accounts/services/customerInvitationService.js +87 -0
- package/dist/modules/customer_accounts/services/customerInvitationService.js.map +7 -0
- package/dist/modules/customer_accounts/services/customerRbacService.js +109 -0
- package/dist/modules/customer_accounts/services/customerRbacService.js.map +7 -0
- package/dist/modules/customer_accounts/services/customerSessionService.js +75 -0
- package/dist/modules/customer_accounts/services/customerSessionService.js.map +7 -0
- package/dist/modules/customer_accounts/services/customerTokenService.js +91 -0
- package/dist/modules/customer_accounts/services/customerTokenService.js.map +7 -0
- package/dist/modules/customer_accounts/services/customerUserService.js +92 -0
- package/dist/modules/customer_accounts/services/customerUserService.js.map +7 -0
- package/dist/modules/customer_accounts/setup.js +179 -0
- package/dist/modules/customer_accounts/setup.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js +54 -0
- package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/autoLinkCrmReverse.js +68 -0
- package/dist/modules/customer_accounts/subscribers/autoLinkCrmReverse.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyStaffOnSignup.js +29 -0
- package/dist/modules/customer_accounts/subscribers/notifyStaffOnSignup.js.map +7 -0
- package/dist/modules/customer_accounts/translations.js +9 -0
- package/dist/modules/customer_accounts/translations.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +63 -0
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.js +17 -0
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js +55 -0
- package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection/company-users/widget.js +17 -0
- package/dist/modules/customer_accounts/widgets/injection/company-users/widget.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection-table.js +26 -0
- package/dist/modules/customer_accounts/widgets/injection-table.js.map +7 -0
- package/dist/modules/customer_accounts/workers/cleanupExpiredSessions.js +23 -0
- package/dist/modules/customer_accounts/workers/cleanupExpiredSessions.js.map +7 -0
- package/dist/modules/customer_accounts/workers/cleanupExpiredTokens.js +38 -0
- package/dist/modules/customer_accounts/workers/cleanupExpiredTokens.js.map +7 -0
- package/dist/modules/directory/api/get/organizations/lookup.js +83 -0
- package/dist/modules/directory/api/get/organizations/lookup.js.map +7 -0
- package/dist/modules/directory/commands/organizations.js +32 -1
- package/dist/modules/directory/commands/organizations.js.map +2 -2
- package/dist/modules/directory/data/entities.js +6 -2
- package/dist/modules/directory/data/entities.js.map +2 -2
- package/dist/modules/directory/data/validators.js +3 -0
- package/dist/modules/directory/data/validators.js.map +2 -2
- package/dist/modules/directory/migrations/Migration20260314143323.js +15 -0
- package/dist/modules/directory/migrations/Migration20260314143323.js.map +7 -0
- package/dist/modules/directory/setup.js +36 -0
- package/dist/modules/directory/setup.js.map +2 -2
- package/dist/modules/payment_gateways/migrations/Migration20260313222043.js +15 -0
- package/dist/modules/payment_gateways/migrations/Migration20260313222043.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +131 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +96 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/page.js +94 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/page.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js +89 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js +104 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js.map +7 -0
- package/dist/modules/portal/index.js +11 -0
- package/dist/modules/portal/index.js.map +7 -0
- package/dist/modules/portal/setup.js +23 -0
- package/dist/modules/portal/setup.js.map +7 -0
- package/generated/entities/customer_role/index.ts +12 -0
- package/generated/entities/customer_role_acl/index.ts +8 -0
- package/generated/entities/customer_user/index.ts +17 -0
- package/generated/entities/customer_user_acl/index.ts +8 -0
- package/generated/entities/customer_user_email_verification/index.ts +7 -0
- package/generated/entities/customer_user_invitation/index.ts +15 -0
- package/generated/entities/customer_user_password_reset/index.ts +6 -0
- package/generated/entities/customer_user_role/index.ts +5 -0
- package/generated/entities/customer_user_session/index.ts +9 -0
- package/generated/entities/organization/index.ts +1 -0
- package/generated/entities.ids.generated.ts +14 -1
- package/generated/entity-fields-registry.ts +18 -0
- package/package.json +3 -3
- package/src/modules/auth/services/rbacService.ts +3 -9
- package/src/modules/customer_accounts/AGENTS.md +377 -0
- package/src/modules/customer_accounts/acl.ts +8 -0
- package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +98 -0
- package/src/modules/customer_accounts/api/admin/roles/[id].ts +246 -0
- package/src/modules/customer_accounts/api/admin/roles.ts +212 -0
- package/src/modules/customer_accounts/api/admin/users/[id]/reset-password.ts +78 -0
- package/src/modules/customer_accounts/api/admin/users/[id]/verify-email.ts +72 -0
- package/src/modules/customer_accounts/api/admin/users/[id].ts +289 -0
- package/src/modules/customer_accounts/api/admin/users-invite.ts +86 -0
- package/src/modules/customer_accounts/api/admin/users.ts +280 -0
- package/src/modules/customer_accounts/api/email/verify.ts +66 -0
- package/src/modules/customer_accounts/api/interceptors.ts +3 -0
- package/src/modules/customer_accounts/api/invitations/accept.ts +128 -0
- package/src/modules/customer_accounts/api/login.ts +163 -0
- package/src/modules/customer_accounts/api/magic-link/request.ts +87 -0
- package/src/modules/customer_accounts/api/magic-link/verify.ts +132 -0
- package/src/modules/customer_accounts/api/password/reset-confirm.ts +69 -0
- package/src/modules/customer_accounts/api/password/reset-request.ts +87 -0
- package/src/modules/customer_accounts/api/portal/events/stream.ts +209 -0
- package/src/modules/customer_accounts/api/portal/feature-check.ts +60 -0
- package/src/modules/customer_accounts/api/portal/logout.ts +71 -0
- package/src/modules/customer_accounts/api/portal/notifications/[id]/dismiss.ts +54 -0
- package/src/modules/customer_accounts/api/portal/notifications/[id]/read.ts +54 -0
- package/src/modules/customer_accounts/api/portal/notifications/mark-all-read.ts +49 -0
- package/src/modules/customer_accounts/api/portal/notifications/unread-count.ts +45 -0
- package/src/modules/customer_accounts/api/portal/notifications.ts +115 -0
- package/src/modules/customer_accounts/api/portal/password-change.ts +65 -0
- package/src/modules/customer_accounts/api/portal/profile.ts +151 -0
- package/src/modules/customer_accounts/api/portal/sessions/[id].ts +70 -0
- package/src/modules/customer_accounts/api/portal/sessions-refresh.ts +87 -0
- package/src/modules/customer_accounts/api/portal/sessions.ts +84 -0
- package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +106 -0
- package/src/modules/customer_accounts/api/portal/users/[id].ts +81 -0
- package/src/modules/customer_accounts/api/portal/users-invite.ts +103 -0
- package/src/modules/customer_accounts/api/portal/users.ts +86 -0
- package/src/modules/customer_accounts/api/signup.ts +136 -0
- package/src/modules/customer_accounts/backend/customer_accounts/[id]/page.meta.ts +11 -0
- package/src/modules/customer_accounts/backend/customer_accounts/[id]/page.tsx +607 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.meta.ts +12 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +385 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/create/page.meta.ts +12 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/create/page.tsx +203 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/page.meta.ts +31 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +217 -0
- package/src/modules/customer_accounts/backend/page.meta.ts +33 -0
- package/src/modules/customer_accounts/backend/page.tsx +535 -0
- package/src/modules/customer_accounts/ce.ts +22 -0
- package/src/modules/customer_accounts/data/enrichers.ts +117 -0
- package/src/modules/customer_accounts/data/entities.ts +302 -0
- package/src/modules/customer_accounts/data/extensions.ts +4 -0
- package/src/modules/customer_accounts/data/validators.ts +128 -0
- package/src/modules/customer_accounts/di.ts +15 -0
- package/src/modules/customer_accounts/events.ts +28 -0
- package/src/modules/customer_accounts/i18n/de.json +176 -0
- package/src/modules/customer_accounts/i18n/en.json +176 -0
- package/src/modules/customer_accounts/i18n/es.json +176 -0
- package/src/modules/customer_accounts/i18n/pl.json +176 -0
- package/src/modules/customer_accounts/index.ts +13 -0
- package/src/modules/customer_accounts/lib/customerAuth.ts +85 -0
- package/src/modules/customer_accounts/lib/customerAuthServer.ts +54 -0
- package/src/modules/customer_accounts/lib/rateLimiter.ts +36 -0
- package/src/modules/customer_accounts/lib/tokenGenerator.ts +9 -0
- package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +1255 -0
- package/src/modules/customer_accounts/migrations/Migration20260313222043.ts +62 -0
- package/src/modules/customer_accounts/notifications.client.ts +46 -0
- package/src/modules/customer_accounts/notifications.ts +44 -0
- package/src/modules/customer_accounts/search.ts +134 -0
- package/src/modules/customer_accounts/services/customerInvitationService.ts +109 -0
- package/src/modules/customer_accounts/services/customerRbacService.ts +144 -0
- package/src/modules/customer_accounts/services/customerSessionService.ts +90 -0
- package/src/modules/customer_accounts/services/customerTokenService.ts +98 -0
- package/src/modules/customer_accounts/services/customerUserService.ts +105 -0
- package/src/modules/customer_accounts/setup.ts +212 -0
- package/src/modules/customer_accounts/subscribers/autoLinkCrm.ts +65 -0
- package/src/modules/customer_accounts/subscribers/autoLinkCrmReverse.ts +78 -0
- package/src/modules/customer_accounts/subscribers/notifyStaffOnSignup.ts +32 -0
- package/src/modules/customer_accounts/translations.ts +5 -0
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +89 -0
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.ts +16 -0
- package/src/modules/customer_accounts/widgets/injection/company-users/widget.client.tsx +78 -0
- package/src/modules/customer_accounts/widgets/injection/company-users/widget.ts +16 -0
- package/src/modules/customer_accounts/widgets/injection-table.ts +24 -0
- package/src/modules/customer_accounts/workers/cleanupExpiredSessions.ts +33 -0
- package/src/modules/customer_accounts/workers/cleanupExpiredTokens.ts +51 -0
- package/src/modules/directory/api/get/organizations/lookup.ts +92 -0
- package/src/modules/directory/commands/organizations.ts +34 -1
- package/src/modules/directory/data/entities.ts +5 -1
- package/src/modules/directory/data/validators.ts +4 -0
- package/src/modules/directory/migrations/.snapshot-open-mercato.json +20 -1
- package/src/modules/directory/migrations/Migration20260314143323.ts +15 -0
- package/src/modules/directory/setup.ts +41 -0
- package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +4 -1
- package/src/modules/payment_gateways/migrations/Migration20260313222043.ts +17 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +158 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +120 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/page.tsx +118 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.tsx +112 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/signup/page.tsx +138 -0
- package/src/modules/portal/i18n/de.json +93 -0
- package/src/modules/portal/i18n/en.json +93 -0
- package/src/modules/portal/i18n/es.json +93 -0
- package/src/modules/portal/i18n/pl.json +93 -0
- package/src/modules/portal/index.ts +9 -0
- package/src/modules/portal/setup.ts +23 -0
- package/src/modules/shipping_carriers/migrations/.snapshot-open-mercato.json +226 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260313222043 extends Migration {
|
|
4
|
+
|
|
5
|
+
override async up(): Promise<void> {
|
|
6
|
+
this.addSql(`create table "customer_roles" ("id" uuid not null default gen_random_uuid(), "tenant_id" uuid not null, "organization_id" uuid not null, "name" text not null, "slug" text not null, "description" text null, "is_default" boolean not null default false, "is_system" boolean not null default false, "customer_assignable" boolean not null default false, "created_at" timestamptz not null, "updated_at" timestamptz null, "deleted_at" timestamptz null, constraint "customer_roles_pkey" primary key ("id"));`);
|
|
7
|
+
this.addSql(`alter table "customer_roles" add constraint "customer_roles_tenant_slug_uniq" unique ("tenant_id", "slug");`);
|
|
8
|
+
|
|
9
|
+
this.addSql(`create table "customer_role_acls" ("id" uuid not null default gen_random_uuid(), "role_id" uuid not null, "tenant_id" uuid not null, "features_json" jsonb null, "is_portal_admin" boolean not null default false, "created_at" timestamptz not null, "updated_at" timestamptz null, "deleted_at" timestamptz null, constraint "customer_role_acls_pkey" primary key ("id"));`);
|
|
10
|
+
this.addSql(`alter table "customer_role_acls" add constraint "customer_role_acls_role_tenant_uniq" unique ("role_id", "tenant_id");`);
|
|
11
|
+
|
|
12
|
+
this.addSql(`create table "customer_users" ("id" uuid not null default gen_random_uuid(), "tenant_id" uuid not null, "organization_id" uuid not null, "email" text not null, "email_hash" text not null, "password_hash" text null, "display_name" text not null, "email_verified_at" timestamptz null, "failed_login_attempts" int not null default 0, "locked_until" timestamptz null, "last_login_at" timestamptz null, "person_entity_id" uuid null, "customer_entity_id" uuid null, "is_active" boolean not null default true, "created_at" timestamptz not null, "updated_at" timestamptz null, "deleted_at" timestamptz null, constraint "customer_users_pkey" primary key ("id"));`);
|
|
13
|
+
this.addSql(`create index "customer_users_email_hash_idx" on "customer_users" ("email_hash");`);
|
|
14
|
+
this.addSql(`create index "customer_users_person_entity_idx" on "customer_users" ("person_entity_id");`);
|
|
15
|
+
this.addSql(`create index "customer_users_customer_entity_idx" on "customer_users" ("customer_entity_id");`);
|
|
16
|
+
this.addSql(`alter table "customer_users" add constraint "customer_users_tenant_email_hash_uniq" unique ("tenant_id", "email_hash");`);
|
|
17
|
+
|
|
18
|
+
this.addSql(`create table "customer_user_acls" ("id" uuid not null default gen_random_uuid(), "user_id" uuid not null, "tenant_id" uuid not null, "features_json" jsonb null, "is_portal_admin" boolean not null default false, "created_at" timestamptz not null, "updated_at" timestamptz null, "deleted_at" timestamptz null, constraint "customer_user_acls_pkey" primary key ("id"));`);
|
|
19
|
+
this.addSql(`alter table "customer_user_acls" add constraint "customer_user_acls_user_tenant_uniq" unique ("user_id", "tenant_id");`);
|
|
20
|
+
|
|
21
|
+
this.addSql(`create table "customer_user_email_verifications" ("id" uuid not null default gen_random_uuid(), "user_id" uuid not null, "token" text not null, "purpose" text not null default 'email_verification', "expires_at" timestamptz not null, "used_at" timestamptz null, "created_at" timestamptz not null, constraint "customer_user_email_verifications_pkey" primary key ("id"));`);
|
|
22
|
+
this.addSql(`create index "customer_user_email_verifications_token_idx" on "customer_user_email_verifications" ("token");`);
|
|
23
|
+
|
|
24
|
+
this.addSql(`create table "customer_user_invitations" ("id" uuid not null default gen_random_uuid(), "tenant_id" uuid not null, "organization_id" uuid not null, "email" text not null, "email_hash" text not null, "token" text not null, "customer_entity_id" uuid null, "role_ids_json" jsonb null, "invited_by_user_id" uuid null, "invited_by_customer_user_id" uuid null, "display_name" text null, "expires_at" timestamptz not null, "accepted_at" timestamptz null, "cancelled_at" timestamptz null, "created_at" timestamptz not null, constraint "customer_user_invitations_pkey" primary key ("id"));`);
|
|
25
|
+
this.addSql(`create index "customer_user_invitations_tenant_email_hash_idx" on "customer_user_invitations" ("tenant_id", "email_hash");`);
|
|
26
|
+
this.addSql(`create index "customer_user_invitations_token_idx" on "customer_user_invitations" ("token");`);
|
|
27
|
+
|
|
28
|
+
this.addSql(`create table "customer_user_password_resets" ("id" uuid not null default gen_random_uuid(), "user_id" uuid not null, "token" text not null, "expires_at" timestamptz not null, "used_at" timestamptz null, "created_at" timestamptz not null, constraint "customer_user_password_resets_pkey" primary key ("id"));`);
|
|
29
|
+
this.addSql(`create index "customer_user_password_resets_token_idx" on "customer_user_password_resets" ("token");`);
|
|
30
|
+
|
|
31
|
+
this.addSql(`create table "customer_user_roles" ("id" uuid not null default gen_random_uuid(), "user_id" uuid not null, "role_id" uuid not null, "created_at" timestamptz not null, "deleted_at" timestamptz null, constraint "customer_user_roles_pkey" primary key ("id"));`);
|
|
32
|
+
this.addSql(`alter table "customer_user_roles" add constraint "customer_user_roles_user_role_uniq" unique ("user_id", "role_id");`);
|
|
33
|
+
|
|
34
|
+
this.addSql(`create table "customer_user_sessions" ("id" uuid not null default gen_random_uuid(), "user_id" uuid not null, "token_hash" text not null, "ip_address" text null, "user_agent" text null, "expires_at" timestamptz not null, "last_used_at" timestamptz null, "created_at" timestamptz not null, "deleted_at" timestamptz null, constraint "customer_user_sessions_pkey" primary key ("id"));`);
|
|
35
|
+
this.addSql(`create index "customer_user_sessions_token_hash_idx" on "customer_user_sessions" ("token_hash");`);
|
|
36
|
+
|
|
37
|
+
this.addSql(`alter table "customer_role_acls" add constraint "customer_role_acls_role_id_foreign" foreign key ("role_id") references "customer_roles" ("id") on update cascade;`);
|
|
38
|
+
|
|
39
|
+
this.addSql(`alter table "customer_user_acls" add constraint "customer_user_acls_user_id_foreign" foreign key ("user_id") references "customer_users" ("id") on update cascade;`);
|
|
40
|
+
|
|
41
|
+
this.addSql(`alter table "customer_user_email_verifications" add constraint "customer_user_email_verifications_user_id_foreign" foreign key ("user_id") references "customer_users" ("id") on update cascade;`);
|
|
42
|
+
|
|
43
|
+
this.addSql(`alter table "customer_user_password_resets" add constraint "customer_user_password_resets_user_id_foreign" foreign key ("user_id") references "customer_users" ("id") on update cascade;`);
|
|
44
|
+
|
|
45
|
+
this.addSql(`alter table "customer_user_roles" add constraint "customer_user_roles_user_id_foreign" foreign key ("user_id") references "customer_users" ("id") on update cascade;`);
|
|
46
|
+
this.addSql(`alter table "customer_user_roles" add constraint "customer_user_roles_role_id_foreign" foreign key ("role_id") references "customer_roles" ("id") on update cascade;`);
|
|
47
|
+
|
|
48
|
+
this.addSql(`alter table "customer_user_sessions" add constraint "customer_user_sessions_user_id_foreign" foreign key ("user_id") references "customer_users" ("id") on update cascade;`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override async down(): Promise<void> {
|
|
52
|
+
this.addSql(`drop table if exists "customer_user_sessions" cascade;`);
|
|
53
|
+
this.addSql(`drop table if exists "customer_user_roles" cascade;`);
|
|
54
|
+
this.addSql(`drop table if exists "customer_user_password_resets" cascade;`);
|
|
55
|
+
this.addSql(`drop table if exists "customer_user_invitations" cascade;`);
|
|
56
|
+
this.addSql(`drop table if exists "customer_user_email_verifications" cascade;`);
|
|
57
|
+
this.addSql(`drop table if exists "customer_user_acls" cascade;`);
|
|
58
|
+
this.addSql(`drop table if exists "customer_role_acls" cascade;`);
|
|
59
|
+
this.addSql(`drop table if exists "customer_users" cascade;`);
|
|
60
|
+
this.addSql(`drop table if exists "customer_roles" cascade;`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'
|
|
4
|
+
|
|
5
|
+
export const customerAccountsNotificationTypes: NotificationTypeDefinition[] = [
|
|
6
|
+
{
|
|
7
|
+
type: 'customer_accounts.user.signup',
|
|
8
|
+
module: 'customer_accounts',
|
|
9
|
+
titleKey: 'customer_accounts.notifications.user.signup.title',
|
|
10
|
+
bodyKey: 'customer_accounts.notifications.user.signup.body',
|
|
11
|
+
icon: 'user-plus',
|
|
12
|
+
severity: 'info',
|
|
13
|
+
actions: [
|
|
14
|
+
{
|
|
15
|
+
id: 'view',
|
|
16
|
+
labelKey: 'common.view',
|
|
17
|
+
variant: 'outline',
|
|
18
|
+
href: '/backend/customer_accounts/{sourceEntityId}',
|
|
19
|
+
icon: 'external-link',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
linkHref: '/backend/customer_accounts/{sourceEntityId}',
|
|
23
|
+
expiresAfterHours: 168,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'customer_accounts.user.locked',
|
|
27
|
+
module: 'customer_accounts',
|
|
28
|
+
titleKey: 'customer_accounts.notifications.user.locked.title',
|
|
29
|
+
bodyKey: 'customer_accounts.notifications.user.locked.body',
|
|
30
|
+
icon: 'lock',
|
|
31
|
+
severity: 'warning',
|
|
32
|
+
actions: [
|
|
33
|
+
{
|
|
34
|
+
id: 'view',
|
|
35
|
+
labelKey: 'common.view',
|
|
36
|
+
variant: 'outline',
|
|
37
|
+
href: '/backend/customer_accounts/{sourceEntityId}',
|
|
38
|
+
icon: 'external-link',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
linkHref: '/backend/customer_accounts/{sourceEntityId}',
|
|
42
|
+
expiresAfterHours: 168,
|
|
43
|
+
},
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
export default customerAccountsNotificationTypes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'
|
|
2
|
+
|
|
3
|
+
export const notificationTypes: NotificationTypeDefinition[] = [
|
|
4
|
+
{
|
|
5
|
+
type: 'customer_accounts.user.signup',
|
|
6
|
+
module: 'customer_accounts',
|
|
7
|
+
titleKey: 'customer_accounts.notifications.user.signup.title',
|
|
8
|
+
bodyKey: 'customer_accounts.notifications.user.signup.body',
|
|
9
|
+
icon: 'user-plus',
|
|
10
|
+
severity: 'info',
|
|
11
|
+
actions: [
|
|
12
|
+
{
|
|
13
|
+
id: 'view',
|
|
14
|
+
labelKey: 'common.view',
|
|
15
|
+
variant: 'outline',
|
|
16
|
+
href: '/backend/customer_accounts/{sourceEntityId}',
|
|
17
|
+
icon: 'external-link',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
linkHref: '/backend/customer_accounts/{sourceEntityId}',
|
|
21
|
+
expiresAfterHours: 168,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: 'customer_accounts.user.locked',
|
|
25
|
+
module: 'customer_accounts',
|
|
26
|
+
titleKey: 'customer_accounts.notifications.user.locked.title',
|
|
27
|
+
bodyKey: 'customer_accounts.notifications.user.locked.body',
|
|
28
|
+
icon: 'lock',
|
|
29
|
+
severity: 'warning',
|
|
30
|
+
actions: [
|
|
31
|
+
{
|
|
32
|
+
id: 'view',
|
|
33
|
+
labelKey: 'common.view',
|
|
34
|
+
variant: 'outline',
|
|
35
|
+
href: '/backend/customer_accounts/{sourceEntityId}',
|
|
36
|
+
icon: 'external-link',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
linkHref: '/backend/customer_accounts/{sourceEntityId}',
|
|
40
|
+
expiresAfterHours: 168,
|
|
41
|
+
},
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
export default notificationTypes
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SearchModuleConfig,
|
|
3
|
+
SearchBuildContext,
|
|
4
|
+
SearchResultPresenter,
|
|
5
|
+
SearchIndexSource,
|
|
6
|
+
} from '@open-mercato/shared/modules/search'
|
|
7
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
8
|
+
|
|
9
|
+
function pickString(...candidates: Array<unknown>): string | null {
|
|
10
|
+
for (const candidate of candidates) {
|
|
11
|
+
if (typeof candidate !== 'string') continue
|
|
12
|
+
const trimmed = candidate.trim()
|
|
13
|
+
if (trimmed.length > 0) return trimmed
|
|
14
|
+
}
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function snippet(value: unknown, max = 140): string | undefined {
|
|
19
|
+
if (typeof value !== 'string') return undefined
|
|
20
|
+
const trimmed = value.trim()
|
|
21
|
+
if (!trimmed.length) return undefined
|
|
22
|
+
if (trimmed.length <= max) return trimmed
|
|
23
|
+
return `${trimmed.slice(0, max - 3)}...`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function appendLine(lines: string[], label: string, value: unknown) {
|
|
27
|
+
if (value === null || value === undefined) return
|
|
28
|
+
const text = typeof value === 'object' ? JSON.stringify(value) : String(value)
|
|
29
|
+
if (!text.trim()) return
|
|
30
|
+
lines.push(`${label}: ${text}`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function formatSubtitle(...parts: Array<unknown>): string | undefined {
|
|
34
|
+
const text = parts
|
|
35
|
+
.map((part) => (part === null || part === undefined ? '' : String(part)))
|
|
36
|
+
.map((part) => part.trim())
|
|
37
|
+
.filter(Boolean)
|
|
38
|
+
if (text.length === 0) return undefined
|
|
39
|
+
return text.join(' · ')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildIndexSource(
|
|
43
|
+
ctx: SearchBuildContext,
|
|
44
|
+
presenter: SearchResultPresenter,
|
|
45
|
+
lines: string[],
|
|
46
|
+
): SearchIndexSource | null {
|
|
47
|
+
if (!lines.length) return null
|
|
48
|
+
return {
|
|
49
|
+
text: lines,
|
|
50
|
+
presenter,
|
|
51
|
+
checksumSource: { record: ctx.record, customFields: ctx.customFields },
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const searchConfig: SearchModuleConfig = {
|
|
56
|
+
entities: [
|
|
57
|
+
{
|
|
58
|
+
entityId: 'customer_accounts:customer_user',
|
|
59
|
+
enabled: true,
|
|
60
|
+
priority: 6,
|
|
61
|
+
buildSource: async (ctx) => {
|
|
62
|
+
const { t } = await resolveTranslations()
|
|
63
|
+
const record = ctx.record
|
|
64
|
+
const lines: string[] = []
|
|
65
|
+
appendLine(lines, 'Name', record.display_name ?? record.displayName)
|
|
66
|
+
appendLine(lines, 'Email', record.email)
|
|
67
|
+
return buildIndexSource(
|
|
68
|
+
ctx,
|
|
69
|
+
{
|
|
70
|
+
title: pickString(record.display_name, record.displayName) ?? String(record.id),
|
|
71
|
+
subtitle: formatSubtitle(record.email),
|
|
72
|
+
icon: 'user',
|
|
73
|
+
badge: t('customer_accounts.search.badge.customerUser', 'Customer User'),
|
|
74
|
+
},
|
|
75
|
+
lines,
|
|
76
|
+
)
|
|
77
|
+
},
|
|
78
|
+
formatResult: async (ctx) => {
|
|
79
|
+
const { t } = await resolveTranslations()
|
|
80
|
+
const record = ctx.record
|
|
81
|
+
return {
|
|
82
|
+
title: pickString(record.display_name, record.displayName) ?? String(record.id),
|
|
83
|
+
subtitle: formatSubtitle(record.email),
|
|
84
|
+
icon: 'user',
|
|
85
|
+
badge: t('customer_accounts.search.badge.customerUser', 'Customer User'),
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
resolveUrl: async (ctx) => `/backend/customer-accounts/users/${encodeURIComponent(String(ctx.record.id))}`,
|
|
89
|
+
fieldPolicy: {
|
|
90
|
+
searchable: ['display_name', 'email'],
|
|
91
|
+
excluded: ['password_hash', 'email_hash'],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
entityId: 'customer_accounts:customer_role',
|
|
96
|
+
enabled: true,
|
|
97
|
+
priority: 6,
|
|
98
|
+
buildSource: async (ctx) => {
|
|
99
|
+
const { t } = await resolveTranslations()
|
|
100
|
+
const record = ctx.record
|
|
101
|
+
const lines: string[] = []
|
|
102
|
+
appendLine(lines, 'Name', record.name)
|
|
103
|
+
appendLine(lines, 'Description', record.description)
|
|
104
|
+
return buildIndexSource(
|
|
105
|
+
ctx,
|
|
106
|
+
{
|
|
107
|
+
title: pickString(record.name) ?? String(record.id),
|
|
108
|
+
subtitle: formatSubtitle(snippet(record.description)),
|
|
109
|
+
icon: 'shield',
|
|
110
|
+
badge: t('customer_accounts.search.badge.customerRole', 'Customer Role'),
|
|
111
|
+
},
|
|
112
|
+
lines,
|
|
113
|
+
)
|
|
114
|
+
},
|
|
115
|
+
formatResult: async (ctx) => {
|
|
116
|
+
const { t } = await resolveTranslations()
|
|
117
|
+
const record = ctx.record
|
|
118
|
+
return {
|
|
119
|
+
title: pickString(record.name) ?? String(record.id),
|
|
120
|
+
subtitle: formatSubtitle(snippet(record.description)),
|
|
121
|
+
icon: 'shield',
|
|
122
|
+
badge: t('customer_accounts.search.badge.customerRole', 'Customer Role'),
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
resolveUrl: async (ctx) => `/backend/customer-accounts/roles/${encodeURIComponent(String(ctx.record.id))}/edit`,
|
|
126
|
+
fieldPolicy: {
|
|
127
|
+
searchable: ['name', 'description'],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export default searchConfig
|
|
134
|
+
export const config = searchConfig
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { hash } from 'bcryptjs'
|
|
3
|
+
import {
|
|
4
|
+
CustomerUser,
|
|
5
|
+
CustomerUserInvitation,
|
|
6
|
+
CustomerUserRole,
|
|
7
|
+
CustomerRole,
|
|
8
|
+
} from '@open-mercato/core/modules/customer_accounts/data/entities'
|
|
9
|
+
import { generateSecureToken, hashToken } from '@open-mercato/core/modules/customer_accounts/lib/tokenGenerator'
|
|
10
|
+
import { hashForLookup } from '@open-mercato/shared/lib/encryption/aes'
|
|
11
|
+
|
|
12
|
+
const BCRYPT_COST = 10
|
|
13
|
+
const INVITATION_TTL_MS = 72 * 60 * 60 * 1000 // 72 hours
|
|
14
|
+
|
|
15
|
+
export class CustomerInvitationService {
|
|
16
|
+
constructor(private em: EntityManager) {}
|
|
17
|
+
|
|
18
|
+
async createInvitation(
|
|
19
|
+
email: string,
|
|
20
|
+
scope: { tenantId: string; organizationId: string },
|
|
21
|
+
options: {
|
|
22
|
+
customerEntityId?: string | null
|
|
23
|
+
roleIds: string[]
|
|
24
|
+
invitedByUserId?: string | null
|
|
25
|
+
invitedByCustomerUserId?: string | null
|
|
26
|
+
displayName?: string | null
|
|
27
|
+
},
|
|
28
|
+
): Promise<{ invitation: CustomerUserInvitation; rawToken: string }> {
|
|
29
|
+
const token = generateSecureToken()
|
|
30
|
+
const emailHash = hashForLookup(email)
|
|
31
|
+
const expiresAt = new Date(Date.now() + INVITATION_TTL_MS)
|
|
32
|
+
|
|
33
|
+
const tokenHashed = hashToken(token)
|
|
34
|
+
const invitation = this.em.create(CustomerUserInvitation, {
|
|
35
|
+
tenantId: scope.tenantId,
|
|
36
|
+
organizationId: scope.organizationId,
|
|
37
|
+
email: email.toLowerCase().trim(),
|
|
38
|
+
emailHash,
|
|
39
|
+
token: tokenHashed,
|
|
40
|
+
customerEntityId: options.customerEntityId || null,
|
|
41
|
+
roleIdsJson: options.roleIds,
|
|
42
|
+
invitedByUserId: options.invitedByUserId || null,
|
|
43
|
+
invitedByCustomerUserId: options.invitedByCustomerUserId || null,
|
|
44
|
+
displayName: options.displayName || null,
|
|
45
|
+
expiresAt,
|
|
46
|
+
createdAt: new Date(),
|
|
47
|
+
} as any) as CustomerUserInvitation
|
|
48
|
+
await this.em.persistAndFlush(invitation)
|
|
49
|
+
return { invitation, rawToken: token }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async findByToken(token: string): Promise<CustomerUserInvitation | null> {
|
|
53
|
+
const tokenHashed = hashToken(token)
|
|
54
|
+
const invitation = await this.em.findOne(CustomerUserInvitation, { token: tokenHashed })
|
|
55
|
+
if (!invitation) return null
|
|
56
|
+
if (invitation.acceptedAt) return null
|
|
57
|
+
if (invitation.cancelledAt) return null
|
|
58
|
+
if (invitation.expiresAt.getTime() < Date.now()) return null
|
|
59
|
+
return invitation
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async acceptInvitation(
|
|
63
|
+
token: string,
|
|
64
|
+
password: string,
|
|
65
|
+
displayName: string,
|
|
66
|
+
): Promise<{ user: CustomerUser; invitation: CustomerUserInvitation } | null> {
|
|
67
|
+
const invitation = await this.findByToken(token)
|
|
68
|
+
if (!invitation) return null
|
|
69
|
+
|
|
70
|
+
const passwordHash = await hash(password, BCRYPT_COST)
|
|
71
|
+
const emailHash = hashForLookup(invitation.email)
|
|
72
|
+
|
|
73
|
+
// Create user
|
|
74
|
+
const user = this.em.create(CustomerUser, {
|
|
75
|
+
email: invitation.email,
|
|
76
|
+
emailHash,
|
|
77
|
+
passwordHash,
|
|
78
|
+
displayName: displayName || invitation.displayName || invitation.email,
|
|
79
|
+
tenantId: invitation.tenantId,
|
|
80
|
+
organizationId: invitation.organizationId,
|
|
81
|
+
customerEntityId: invitation.customerEntityId || null,
|
|
82
|
+
isActive: true,
|
|
83
|
+
emailVerifiedAt: new Date(), // Invitation implicitly verifies email
|
|
84
|
+
failedLoginAttempts: 0,
|
|
85
|
+
createdAt: new Date(),
|
|
86
|
+
} as any) as CustomerUser
|
|
87
|
+
this.em.persist(user)
|
|
88
|
+
|
|
89
|
+
// Assign roles
|
|
90
|
+
const roleIds = Array.isArray(invitation.roleIdsJson) ? invitation.roleIdsJson : []
|
|
91
|
+
for (const roleId of roleIds) {
|
|
92
|
+
const role = await this.em.findOne(CustomerRole, { id: roleId, tenantId: invitation.tenantId, deletedAt: null })
|
|
93
|
+
if (role) {
|
|
94
|
+
const userRole = this.em.create(CustomerUserRole, {
|
|
95
|
+
user,
|
|
96
|
+
role,
|
|
97
|
+
createdAt: new Date(),
|
|
98
|
+
} as any)
|
|
99
|
+
this.em.persist(userRole)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Mark invitation as accepted
|
|
104
|
+
invitation.acceptedAt = new Date()
|
|
105
|
+
|
|
106
|
+
await this.em.flush()
|
|
107
|
+
return { user, invitation }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import type { CacheStrategy } from '@open-mercato/cache'
|
|
3
|
+
import {
|
|
4
|
+
CustomerUserAcl,
|
|
5
|
+
CustomerRoleAcl,
|
|
6
|
+
CustomerUserRole,
|
|
7
|
+
} from '@open-mercato/core/modules/customer_accounts/data/entities'
|
|
8
|
+
import { hasAllFeatures } from '@open-mercato/shared/lib/auth/featureMatch'
|
|
9
|
+
|
|
10
|
+
interface CustomerAclData {
|
|
11
|
+
isPortalAdmin: boolean
|
|
12
|
+
features: string[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isCustomerAclData(value: unknown): value is CustomerAclData {
|
|
16
|
+
if (typeof value !== 'object' || value === null) return false
|
|
17
|
+
const record = value as Partial<CustomerAclData>
|
|
18
|
+
if (typeof record.isPortalAdmin !== 'boolean') return false
|
|
19
|
+
if (!Array.isArray(record.features) || record.features.some((f) => typeof f !== 'string')) return false
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class CustomerRbacService {
|
|
24
|
+
private cacheTtlMs: number = 5 * 60 * 1000
|
|
25
|
+
private cache: CacheStrategy | null = null
|
|
26
|
+
|
|
27
|
+
constructor(private em: EntityManager, cache?: CacheStrategy) {
|
|
28
|
+
this.cache = cache || null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private getCacheKey(userId: string, scope: { tenantId: string; organizationId: string }): string {
|
|
32
|
+
return `customer_rbac:${userId}:${scope.tenantId}:${scope.organizationId}`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private getUserTag(userId: string): string {
|
|
36
|
+
return `customer_rbac:user:${userId}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private getTenantTag(tenantId: string): string {
|
|
40
|
+
return `customer_rbac:tenant:${tenantId}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private async getFromCache(cacheKey: string): Promise<CustomerAclData | null> {
|
|
44
|
+
if (!this.cache) return null
|
|
45
|
+
const cached = await this.cache.get(cacheKey)
|
|
46
|
+
if (!cached) return null
|
|
47
|
+
return isCustomerAclData(cached) ? cached : null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private async setCache(
|
|
51
|
+
cacheKey: string,
|
|
52
|
+
data: CustomerAclData,
|
|
53
|
+
userId: string,
|
|
54
|
+
scope: { tenantId: string; organizationId: string },
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
if (!this.cache) return
|
|
57
|
+
const tags = [
|
|
58
|
+
this.getUserTag(userId),
|
|
59
|
+
this.getTenantTag(scope.tenantId),
|
|
60
|
+
'customer_rbac:all',
|
|
61
|
+
]
|
|
62
|
+
await this.cache.set(cacheKey, data, { ttl: this.cacheTtlMs, tags })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async loadAcl(
|
|
66
|
+
userId: string,
|
|
67
|
+
scope: { tenantId: string; organizationId: string },
|
|
68
|
+
): Promise<CustomerAclData> {
|
|
69
|
+
const cacheKey = this.getCacheKey(userId, scope)
|
|
70
|
+
const cached = await this.getFromCache(cacheKey)
|
|
71
|
+
if (cached) return cached
|
|
72
|
+
|
|
73
|
+
const em = this.em.fork()
|
|
74
|
+
|
|
75
|
+
// Per-user ACL first
|
|
76
|
+
const userAcl = await em.findOne(CustomerUserAcl, {
|
|
77
|
+
user: userId as any,
|
|
78
|
+
tenantId: scope.tenantId,
|
|
79
|
+
})
|
|
80
|
+
if (userAcl) {
|
|
81
|
+
const result: CustomerAclData = {
|
|
82
|
+
isPortalAdmin: !!userAcl.isPortalAdmin,
|
|
83
|
+
features: Array.isArray(userAcl.featuresJson) ? userAcl.featuresJson : [],
|
|
84
|
+
}
|
|
85
|
+
await this.setCache(cacheKey, result, userId, scope)
|
|
86
|
+
return result
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Aggregate role ACLs
|
|
90
|
+
const links = await em.find(CustomerUserRole, {
|
|
91
|
+
user: userId as any,
|
|
92
|
+
deletedAt: null,
|
|
93
|
+
}, { populate: ['role'] })
|
|
94
|
+
const roleIds = links.map((l) => (l.role as any)?.id).filter(Boolean)
|
|
95
|
+
|
|
96
|
+
let isPortalAdmin = false
|
|
97
|
+
const features: string[] = []
|
|
98
|
+
if (roleIds.length) {
|
|
99
|
+
const roleAcls = await em.find(CustomerRoleAcl, {
|
|
100
|
+
tenantId: scope.tenantId,
|
|
101
|
+
role: { $in: roleIds as any },
|
|
102
|
+
} as any)
|
|
103
|
+
for (const acl of roleAcls) {
|
|
104
|
+
isPortalAdmin = isPortalAdmin || !!acl.isPortalAdmin
|
|
105
|
+
if (Array.isArray(acl.featuresJson)) {
|
|
106
|
+
for (const f of acl.featuresJson) {
|
|
107
|
+
if (!features.includes(f)) features.push(f)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const result: CustomerAclData = { isPortalAdmin, features }
|
|
114
|
+
await this.setCache(cacheKey, result, userId, scope)
|
|
115
|
+
return result
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async userHasAllFeatures(
|
|
119
|
+
userId: string,
|
|
120
|
+
required: string[],
|
|
121
|
+
scope: { tenantId: string; organizationId: string },
|
|
122
|
+
): Promise<boolean> {
|
|
123
|
+
if (!required.length) return true
|
|
124
|
+
const acl = await this.loadAcl(userId, scope)
|
|
125
|
+
if (acl.isPortalAdmin) return true
|
|
126
|
+
return hasAllFeatures(required, acl.features)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async invalidateUserCache(userId: string): Promise<void> {
|
|
130
|
+
if (!this.cache) return
|
|
131
|
+
await this.cache.deleteByTags([this.getUserTag(userId)])
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async invalidateRoleCache(roleId: string): Promise<void> {
|
|
135
|
+
if (!this.cache) return
|
|
136
|
+
// When a role changes, invalidate all customer RBAC caches since we don't track role→user mappings
|
|
137
|
+
await this.cache.deleteByTags(['customer_rbac:all'])
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async invalidateTenantCache(tenantId: string): Promise<void> {
|
|
141
|
+
if (!this.cache) return
|
|
142
|
+
await this.cache.deleteByTags([this.getTenantTag(tenantId)])
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { CustomerUser, CustomerUserSession } from '@open-mercato/core/modules/customer_accounts/data/entities'
|
|
3
|
+
import { generateSecureToken, hashToken } from '@open-mercato/core/modules/customer_accounts/lib/tokenGenerator'
|
|
4
|
+
import { signJwt } from '@open-mercato/shared/lib/auth/jwt'
|
|
5
|
+
|
|
6
|
+
const DEFAULT_SESSION_TTL_DAYS = 30
|
|
7
|
+
|
|
8
|
+
export class CustomerSessionService {
|
|
9
|
+
constructor(private em: EntityManager) {}
|
|
10
|
+
|
|
11
|
+
async createSession(
|
|
12
|
+
user: CustomerUser,
|
|
13
|
+
resolvedFeatures: string[],
|
|
14
|
+
ip?: string | null,
|
|
15
|
+
userAgent?: string | null,
|
|
16
|
+
): Promise<{ rawToken: string; jwt: string; session: CustomerUserSession }> {
|
|
17
|
+
const rawToken = generateSecureToken()
|
|
18
|
+
const tokenHash = hashToken(rawToken)
|
|
19
|
+
const days = Number(process.env.CUSTOMER_SESSION_TTL_DAYS || DEFAULT_SESSION_TTL_DAYS)
|
|
20
|
+
const expiresAt = new Date(Date.now() + days * 24 * 60 * 60 * 1000)
|
|
21
|
+
|
|
22
|
+
const session = this.em.create(CustomerUserSession, {
|
|
23
|
+
user,
|
|
24
|
+
tokenHash,
|
|
25
|
+
ipAddress: ip || null,
|
|
26
|
+
userAgent: userAgent || null,
|
|
27
|
+
expiresAt,
|
|
28
|
+
lastUsedAt: new Date(),
|
|
29
|
+
createdAt: new Date(),
|
|
30
|
+
} as any) as CustomerUserSession
|
|
31
|
+
await this.em.persistAndFlush(session)
|
|
32
|
+
|
|
33
|
+
const jwt = this.signCustomerJwt(user, resolvedFeatures)
|
|
34
|
+
|
|
35
|
+
return { rawToken, jwt, session }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
signCustomerJwt(user: CustomerUser, resolvedFeatures: string[]): string {
|
|
39
|
+
return signJwt({
|
|
40
|
+
sub: user.id,
|
|
41
|
+
type: 'customer',
|
|
42
|
+
tenantId: user.tenantId,
|
|
43
|
+
orgId: user.organizationId,
|
|
44
|
+
email: user.email,
|
|
45
|
+
displayName: user.displayName || '',
|
|
46
|
+
customerEntityId: user.customerEntityId || null,
|
|
47
|
+
personEntityId: user.personEntityId || null,
|
|
48
|
+
resolvedFeatures,
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async findByToken(rawToken: string, tenantId?: string): Promise<CustomerUserSession | null> {
|
|
53
|
+
const tokenHash = hashToken(rawToken)
|
|
54
|
+
const session = await this.em.findOne(CustomerUserSession, {
|
|
55
|
+
tokenHash,
|
|
56
|
+
deletedAt: null,
|
|
57
|
+
}, { populate: ['user'] })
|
|
58
|
+
if (!session) return null
|
|
59
|
+
if (session.expiresAt.getTime() < Date.now()) return null
|
|
60
|
+
const user = session.user as CustomerUser
|
|
61
|
+
if (tenantId && user?.tenantId !== tenantId) return null
|
|
62
|
+
return session
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async refreshSession(
|
|
66
|
+
rawToken: string,
|
|
67
|
+
resolvedFeatures: string[],
|
|
68
|
+
): Promise<{ jwt: string; user: CustomerUser } | null> {
|
|
69
|
+
const session = await this.findByToken(rawToken)
|
|
70
|
+
if (!session) return null
|
|
71
|
+
const user = session.user as CustomerUser
|
|
72
|
+
if (!user || user.deletedAt || !user.isActive) return null
|
|
73
|
+
|
|
74
|
+
await this.em.nativeUpdate(CustomerUserSession, { id: session.id }, { lastUsedAt: new Date() })
|
|
75
|
+
const jwt = this.signCustomerJwt(user, resolvedFeatures)
|
|
76
|
+
return { jwt, user }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async revokeSession(sessionId: string): Promise<void> {
|
|
80
|
+
await this.em.nativeUpdate(CustomerUserSession, { id: sessionId }, { deletedAt: new Date() })
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async revokeAllUserSessions(userId: string): Promise<void> {
|
|
84
|
+
await this.em.nativeUpdate(
|
|
85
|
+
CustomerUserSession,
|
|
86
|
+
{ user: userId as any, deletedAt: null },
|
|
87
|
+
{ deletedAt: new Date() },
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
}
|