@open-mercato/core 0.4.8-develop-28cee031d6 → 0.4.8-develop-15259be22b
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/customers/components/AddressTiles.js +1 -1
- package/dist/modules/customers/components/AddressTiles.js.map +2 -2
- 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/customers/components/AddressTiles.tsx +1 -1
- 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,8 @@
|
|
|
1
|
+
export const id = 'id'
|
|
2
|
+
export const role = 'role'
|
|
3
|
+
export const tenant_id = 'tenant_id'
|
|
4
|
+
export const features_json = 'features_json'
|
|
5
|
+
export const is_portal_admin = 'is_portal_admin'
|
|
6
|
+
export const created_at = 'created_at'
|
|
7
|
+
export const updated_at = 'updated_at'
|
|
8
|
+
export const deleted_at = 'deleted_at'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const id = 'id'
|
|
2
|
+
export const tenant_id = 'tenant_id'
|
|
3
|
+
export const organization_id = 'organization_id'
|
|
4
|
+
export const email = 'email'
|
|
5
|
+
export const email_hash = 'email_hash'
|
|
6
|
+
export const password_hash = 'password_hash'
|
|
7
|
+
export const display_name = 'display_name'
|
|
8
|
+
export const email_verified_at = 'email_verified_at'
|
|
9
|
+
export const failed_login_attempts = 'failed_login_attempts'
|
|
10
|
+
export const locked_until = 'locked_until'
|
|
11
|
+
export const last_login_at = 'last_login_at'
|
|
12
|
+
export const person_entity_id = 'person_entity_id'
|
|
13
|
+
export const customer_entity_id = 'customer_entity_id'
|
|
14
|
+
export const is_active = 'is_active'
|
|
15
|
+
export const created_at = 'created_at'
|
|
16
|
+
export const updated_at = 'updated_at'
|
|
17
|
+
export const deleted_at = 'deleted_at'
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const id = 'id'
|
|
2
|
+
export const user = 'user'
|
|
3
|
+
export const tenant_id = 'tenant_id'
|
|
4
|
+
export const features_json = 'features_json'
|
|
5
|
+
export const is_portal_admin = 'is_portal_admin'
|
|
6
|
+
export const created_at = 'created_at'
|
|
7
|
+
export const updated_at = 'updated_at'
|
|
8
|
+
export const deleted_at = 'deleted_at'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const id = 'id'
|
|
2
|
+
export const tenant_id = 'tenant_id'
|
|
3
|
+
export const organization_id = 'organization_id'
|
|
4
|
+
export const email = 'email'
|
|
5
|
+
export const email_hash = 'email_hash'
|
|
6
|
+
export const token = 'token'
|
|
7
|
+
export const customer_entity_id = 'customer_entity_id'
|
|
8
|
+
export const role_ids_json = 'role_ids_json'
|
|
9
|
+
export const invited_by_user_id = 'invited_by_user_id'
|
|
10
|
+
export const invited_by_customer_user_id = 'invited_by_customer_user_id'
|
|
11
|
+
export const display_name = 'display_name'
|
|
12
|
+
export const expires_at = 'expires_at'
|
|
13
|
+
export const accepted_at = 'accepted_at'
|
|
14
|
+
export const cancelled_at = 'cancelled_at'
|
|
15
|
+
export const created_at = 'created_at'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const id = 'id'
|
|
2
|
+
export const user = 'user'
|
|
3
|
+
export const token_hash = 'token_hash'
|
|
4
|
+
export const ip_address = 'ip_address'
|
|
5
|
+
export const user_agent = 'user_agent'
|
|
6
|
+
export const expires_at = 'expires_at'
|
|
7
|
+
export const last_used_at = 'last_used_at'
|
|
8
|
+
export const created_at = 'created_at'
|
|
9
|
+
export const deleted_at = 'deleted_at'
|
|
@@ -30,7 +30,9 @@ export const M = {
|
|
|
30
30
|
"translations": "translations",
|
|
31
31
|
"inbox_ops": "inbox_ops",
|
|
32
32
|
"payment_gateways": "payment_gateways",
|
|
33
|
-
"shipping_carriers": "shipping_carriers"
|
|
33
|
+
"shipping_carriers": "shipping_carriers",
|
|
34
|
+
"customer_accounts": "customer_accounts",
|
|
35
|
+
"portal": "portal"
|
|
34
36
|
} as const
|
|
35
37
|
export const E = {
|
|
36
38
|
"dashboards": {
|
|
@@ -237,6 +239,17 @@ export const E = {
|
|
|
237
239
|
},
|
|
238
240
|
"shipping_carriers": {
|
|
239
241
|
"carrier_shipment": "shipping_carriers:carrier_shipment"
|
|
242
|
+
},
|
|
243
|
+
"customer_accounts": {
|
|
244
|
+
"customer_user": "customer_accounts:customer_user",
|
|
245
|
+
"customer_role": "customer_accounts:customer_role",
|
|
246
|
+
"customer_role_acl": "customer_accounts:customer_role_acl",
|
|
247
|
+
"customer_user_role": "customer_accounts:customer_user_role",
|
|
248
|
+
"customer_user_acl": "customer_accounts:customer_user_acl",
|
|
249
|
+
"customer_user_session": "customer_accounts:customer_user_session",
|
|
250
|
+
"customer_user_email_verification": "customer_accounts:customer_user_email_verification",
|
|
251
|
+
"customer_user_password_reset": "customer_accounts:customer_user_password_reset",
|
|
252
|
+
"customer_user_invitation": "customer_accounts:customer_user_invitation"
|
|
240
253
|
}
|
|
241
254
|
} as const
|
|
242
255
|
export type KnownModuleId = keyof typeof M
|
|
@@ -38,10 +38,19 @@ import * as customer_entity from './entities/customer_entity/index'
|
|
|
38
38
|
import * as customer_person_profile from './entities/customer_person_profile/index'
|
|
39
39
|
import * as customer_pipeline from './entities/customer_pipeline/index'
|
|
40
40
|
import * as customer_pipeline_stage from './entities/customer_pipeline_stage/index'
|
|
41
|
+
import * as customer_role from './entities/customer_role/index'
|
|
42
|
+
import * as customer_role_acl from './entities/customer_role_acl/index'
|
|
41
43
|
import * as customer_settings from './entities/customer_settings/index'
|
|
42
44
|
import * as customer_tag from './entities/customer_tag/index'
|
|
43
45
|
import * as customer_tag_assignment from './entities/customer_tag_assignment/index'
|
|
44
46
|
import * as customer_todo_link from './entities/customer_todo_link/index'
|
|
47
|
+
import * as customer_user from './entities/customer_user/index'
|
|
48
|
+
import * as customer_user_acl from './entities/customer_user_acl/index'
|
|
49
|
+
import * as customer_user_email_verification from './entities/customer_user_email_verification/index'
|
|
50
|
+
import * as customer_user_invitation from './entities/customer_user_invitation/index'
|
|
51
|
+
import * as customer_user_password_reset from './entities/customer_user_password_reset/index'
|
|
52
|
+
import * as customer_user_role from './entities/customer_user_role/index'
|
|
53
|
+
import * as customer_user_session from './entities/customer_user_session/index'
|
|
45
54
|
import * as dashboard_layout from './entities/dashboard_layout/index'
|
|
46
55
|
import * as dashboard_role_widgets from './entities/dashboard_role_widgets/index'
|
|
47
56
|
import * as dashboard_user_widgets from './entities/dashboard_user_widgets/index'
|
|
@@ -185,10 +194,19 @@ export const entityFieldsRegistry: Record<string, Record<string, string>> = {
|
|
|
185
194
|
customer_person_profile,
|
|
186
195
|
customer_pipeline,
|
|
187
196
|
customer_pipeline_stage,
|
|
197
|
+
customer_role,
|
|
198
|
+
customer_role_acl,
|
|
188
199
|
customer_settings,
|
|
189
200
|
customer_tag,
|
|
190
201
|
customer_tag_assignment,
|
|
191
202
|
customer_todo_link,
|
|
203
|
+
customer_user,
|
|
204
|
+
customer_user_acl,
|
|
205
|
+
customer_user_email_verification,
|
|
206
|
+
customer_user_invitation,
|
|
207
|
+
customer_user_password_reset,
|
|
208
|
+
customer_user_role,
|
|
209
|
+
customer_user_session,
|
|
192
210
|
dashboard_layout,
|
|
193
211
|
dashboard_role_widgets,
|
|
194
212
|
dashboard_user_widgets,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.4.8-develop-
|
|
3
|
+
"version": "0.4.8-develop-15259be22b",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -217,10 +217,10 @@
|
|
|
217
217
|
"semver": "^7.6.3"
|
|
218
218
|
},
|
|
219
219
|
"peerDependencies": {
|
|
220
|
-
"@open-mercato/shared": "0.4.8-develop-
|
|
220
|
+
"@open-mercato/shared": "0.4.8-develop-15259be22b"
|
|
221
221
|
},
|
|
222
222
|
"devDependencies": {
|
|
223
|
-
"@open-mercato/shared": "0.4.8-develop-
|
|
223
|
+
"@open-mercato/shared": "0.4.8-develop-15259be22b",
|
|
224
224
|
"@testing-library/dom": "^10.4.1",
|
|
225
225
|
"@testing-library/jest-dom": "^6.9.1",
|
|
226
226
|
"@testing-library/react": "^16.3.1",
|
|
@@ -4,6 +4,7 @@ import { getCurrentCacheTenant, runWithCacheTenant } from '@open-mercato/cache'
|
|
|
4
4
|
import { UserAcl, RoleAcl, User, UserRole } from '@open-mercato/core/modules/auth/data/entities'
|
|
5
5
|
import { ApiKey } from '@open-mercato/core/modules/api_keys/data/entities'
|
|
6
6
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
7
|
+
import { matchFeature as sharedMatchFeature, hasAllFeatures as sharedHasAllFeatures } from '@open-mercato/shared/lib/auth/featureMatch'
|
|
7
8
|
|
|
8
9
|
interface AclData {
|
|
9
10
|
isSuperAdmin: boolean
|
|
@@ -61,18 +62,11 @@ export class RbacService {
|
|
|
61
62
|
* matchFeature('users.view', 'users.view') // true - exact match
|
|
62
63
|
*/
|
|
63
64
|
private matchFeature(required: string, granted: string): boolean {
|
|
64
|
-
|
|
65
|
-
if (granted.endsWith('.*')) {
|
|
66
|
-
const prefix = granted.slice(0, -2)
|
|
67
|
-
return required === prefix || required.startsWith(prefix + '.')
|
|
68
|
-
}
|
|
69
|
-
return granted === required
|
|
65
|
+
return sharedMatchFeature(required, granted)
|
|
70
66
|
}
|
|
71
67
|
|
|
72
68
|
public hasAllFeatures(required: string[], granted: string[]): boolean {
|
|
73
|
-
|
|
74
|
-
if (!granted.length) return false
|
|
75
|
-
return required.every((req) => granted.some((g) => this.matchFeature(req, g)))
|
|
69
|
+
return sharedHasAllFeatures(required, granted)
|
|
76
70
|
}
|
|
77
71
|
|
|
78
72
|
private getCacheKey(userId: string, scope: { tenantId: string | null; organizationId: string | null }): string {
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# Customer Accounts Module — Agent Guidelines
|
|
2
|
+
|
|
3
|
+
Customer-facing identity and portal authentication with a two-tier RBAC model. This module manages customer user accounts, sessions, roles, invitations, and the authentication flow for the customer portal. It is separate from the internal `auth` module, which handles staff authentication.
|
|
4
|
+
|
|
5
|
+
## MUST Rules
|
|
6
|
+
|
|
7
|
+
1. **MUST hash passwords with `bcryptjs` (cost >= 10)** — never store plaintext passwords
|
|
8
|
+
2. **MUST return minimal error messages on auth endpoints** — never reveal whether an email exists (use generic "Invalid email or password")
|
|
9
|
+
3. **MUST rate-limit all public auth endpoints** (login, signup, password reset, magic link) — both per-email and per-IP
|
|
10
|
+
4. **MUST validate all inputs with zod** — schemas live in `data/validators.ts`
|
|
11
|
+
5. **MUST export `openApi`** from every API route file
|
|
12
|
+
6. **MUST scope all queries by `tenantId`** and filter `deletedAt: null` for soft-deleted records
|
|
13
|
+
7. **MUST NOT expose cross-tenant data** — session validation checks tenant match
|
|
14
|
+
8. **MUST use `hashForLookup` for email-based lookups** — emails are stored with a deterministic hash for indexed queries
|
|
15
|
+
9. **MUST use `hashToken` for storing session/verification/reset tokens** — raw tokens are never persisted
|
|
16
|
+
10. **MUST emit events via `emitCustomerAccountsEvent`** for all state changes (login, signup, lock, password reset)
|
|
17
|
+
11. **MUST NOT import staff auth services** — customer auth is a fully separate identity system
|
|
18
|
+
|
|
19
|
+
## Data Model
|
|
20
|
+
|
|
21
|
+
### Entities
|
|
22
|
+
|
|
23
|
+
| Entity | Table | Purpose |
|
|
24
|
+
|--------|-------|---------|
|
|
25
|
+
| `CustomerUser` | `customer_users` | Customer user accounts with credentials and CRM links |
|
|
26
|
+
| `CustomerRole` | `customer_roles` | Named role definitions (portal_admin, buyer, viewer) |
|
|
27
|
+
| `CustomerRoleAcl` | `customer_role_acls` | Feature permissions assigned to roles |
|
|
28
|
+
| `CustomerUserAcl` | `customer_user_acls` | Per-user feature overrides |
|
|
29
|
+
| `CustomerUserRole` | `customer_user_roles` | User-to-role junction (M2M) |
|
|
30
|
+
| `CustomerUserSession` | `customer_user_sessions` | Active sessions with hashed tokens |
|
|
31
|
+
| `CustomerUserEmailVerification` | `customer_user_email_verifications` | Email verification and magic link tokens |
|
|
32
|
+
| `CustomerUserPasswordReset` | `customer_user_password_resets` | Password reset tokens |
|
|
33
|
+
| `CustomerUserInvitation` | `customer_user_invitations` | Pending user invitations with role pre-assignment |
|
|
34
|
+
|
|
35
|
+
### Key Relationships
|
|
36
|
+
|
|
37
|
+
- `CustomerUser.personEntityId` -> CRM person (optional FK to customers module)
|
|
38
|
+
- `CustomerUser.customerEntityId` -> CRM company (optional FK to customers module)
|
|
39
|
+
- `CustomerUserRole` links users to roles (M2M junction)
|
|
40
|
+
- `CustomerRoleAcl` is 1:1 with `CustomerRole` per tenant
|
|
41
|
+
- `CustomerUserAcl` is 1:1 with `CustomerUser` per tenant (overrides role-based features)
|
|
42
|
+
|
|
43
|
+
### Custom Entities (ce.ts)
|
|
44
|
+
|
|
45
|
+
Two custom entity definitions are registered:
|
|
46
|
+
- `customer_accounts:customer_user` — labeled by `displayName`
|
|
47
|
+
- `customer_accounts:customer_role` — labeled by `name`
|
|
48
|
+
|
|
49
|
+
Both have `showInSidebar: false` and `defaultEditor: false`.
|
|
50
|
+
|
|
51
|
+
## Authentication Flow
|
|
52
|
+
|
|
53
|
+
### Login (`POST /api/login`)
|
|
54
|
+
|
|
55
|
+
1. Rate-limit check (per-email + per-IP)
|
|
56
|
+
2. Validate input with `loginSchema`
|
|
57
|
+
3. Look up user by email hash + tenantId
|
|
58
|
+
4. Check account active and not locked
|
|
59
|
+
5. Verify password with bcrypt
|
|
60
|
+
6. On failure: increment failed attempts, lock after 5 failures (15 min lockout)
|
|
61
|
+
7. On success: reset failed attempts, update `lastLoginAt`
|
|
62
|
+
8. Resolve RBAC features via `CustomerRbacService.loadAcl`
|
|
63
|
+
9. Create session (raw token + hashed token persisted)
|
|
64
|
+
10. Sign JWT with customer claims (`type: 'customer'`, features, CRM links)
|
|
65
|
+
11. Set `customer_auth_token` (JWT, httpOnly) and `customer_session_token` (raw, httpOnly) cookies
|
|
66
|
+
|
|
67
|
+
### Signup (`POST /api/signup`)
|
|
68
|
+
|
|
69
|
+
1. Rate-limit check (per-IP)
|
|
70
|
+
2. Validate input with `signupSchema`
|
|
71
|
+
3. Check for existing user (generic error on duplicate)
|
|
72
|
+
4. Create user with hashed password
|
|
73
|
+
5. Assign default role (`isDefault: true`)
|
|
74
|
+
6. Create email verification token
|
|
75
|
+
7. Emit `customer_accounts.user.created` event (triggers CRM auto-link + staff notification)
|
|
76
|
+
|
|
77
|
+
### Magic Link (`POST /api/magic-link/request` + `POST /api/magic-link/verify`)
|
|
78
|
+
|
|
79
|
+
1. Request: rate-limit, find user, create magic link token (15 min TTL)
|
|
80
|
+
2. Verify: validate token, mark used, create session
|
|
81
|
+
|
|
82
|
+
### Password Reset (`POST /api/password/reset-request` + `POST /api/password/reset-confirm`)
|
|
83
|
+
|
|
84
|
+
1. Request: rate-limit, find user, create reset token (60 min TTL)
|
|
85
|
+
2. Confirm: validate token, mark used, update password hash
|
|
86
|
+
|
|
87
|
+
### Email Verification (`POST /api/email/verify`)
|
|
88
|
+
|
|
89
|
+
Validates token, sets `emailVerifiedAt` on the user.
|
|
90
|
+
|
|
91
|
+
### Invitation Flow (`POST /api/admin/users-invite` + `POST /api/invitations/accept`)
|
|
92
|
+
|
|
93
|
+
1. Admin or portal admin creates invitation with email, role IDs, optional company link
|
|
94
|
+
2. Invitation token generated (72 hour TTL)
|
|
95
|
+
3. Acceptance: creates user, assigns roles, marks email verified, marks invitation accepted
|
|
96
|
+
|
|
97
|
+
### Session Refresh (`POST /api/portal/sessions-refresh`)
|
|
98
|
+
|
|
99
|
+
Re-signs the JWT with fresh RBAC features using the long-lived session token.
|
|
100
|
+
|
|
101
|
+
### Two-Cookie Strategy
|
|
102
|
+
|
|
103
|
+
| Cookie | Content | TTL | Purpose |
|
|
104
|
+
|--------|---------|-----|---------|
|
|
105
|
+
| `customer_auth_token` | Signed JWT | 8 hours | Short-lived auth with embedded claims |
|
|
106
|
+
| `customer_session_token` | Raw session token | 30 days | Long-lived session for JWT refresh |
|
|
107
|
+
|
|
108
|
+
## Customer RBAC
|
|
109
|
+
|
|
110
|
+
### Two-Layer Model (mirrors staff RBAC)
|
|
111
|
+
|
|
112
|
+
1. **Role ACLs** (`CustomerRoleAcl`) — features assigned to roles
|
|
113
|
+
2. **User ACLs** (`CustomerUserAcl`) — per-user overrides (takes precedence if present)
|
|
114
|
+
|
|
115
|
+
Effective permissions = User ACL (if exists) OR aggregated Role ACLs.
|
|
116
|
+
|
|
117
|
+
### Portal Admin Flag
|
|
118
|
+
|
|
119
|
+
`isPortalAdmin: true` on a role/user ACL bypasses all feature checks (equivalent to staff `isSuperAdmin`).
|
|
120
|
+
|
|
121
|
+
### Default Roles (seeded on tenant creation)
|
|
122
|
+
|
|
123
|
+
| Role | Slug | Features | Portal Admin |
|
|
124
|
+
|------|------|----------|-------------|
|
|
125
|
+
| Portal Admin | `portal_admin` | `portal.*` | Yes |
|
|
126
|
+
| Buyer | `buyer` | `portal.account.manage`, `portal.orders.*`, `portal.quotes.*`, `portal.invoices.view`, `portal.catalog.view` | No |
|
|
127
|
+
| Viewer | `viewer` | `portal.account.manage`, `portal.orders.view`, `portal.invoices.view`, `portal.catalog.view` | No |
|
|
128
|
+
|
|
129
|
+
### Feature Convention
|
|
130
|
+
|
|
131
|
+
Customer portal features use the `portal.<area>.<action>` naming convention (e.g., `portal.orders.view`, `portal.catalog.view`).
|
|
132
|
+
|
|
133
|
+
### Cross-Module Feature Merging
|
|
134
|
+
|
|
135
|
+
Other modules can declare `defaultCustomerRoleFeatures` in their `setup.ts`. During `seedDefaults`, the customer_accounts module collects these from all enabled modules and merges them into the corresponding `CustomerRoleAcl` records.
|
|
136
|
+
|
|
137
|
+
### Server-Side Auth Helpers
|
|
138
|
+
|
|
139
|
+
| Helper | Import | Use |
|
|
140
|
+
|--------|--------|-----|
|
|
141
|
+
| `getCustomerAuthFromRequest` | `lib/customerAuth` | API routes — reads JWT from `Authorization` header or cookie |
|
|
142
|
+
| `requireCustomerAuth` | `lib/customerAuth` | API routes — throws 401 if not authenticated |
|
|
143
|
+
| `requireCustomerFeature` | `lib/customerAuth` | API routes — throws 403 if features missing |
|
|
144
|
+
| `getCustomerAuthFromCookies` | `lib/customerAuthServer` | Server components — reads JWT from Next.js `cookies()` |
|
|
145
|
+
|
|
146
|
+
### RBAC Check (Service)
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
const customerRbacService = container.resolve('customerRbacService')
|
|
150
|
+
const hasAccess = await customerRbacService.userHasAllFeatures(userId, ['portal.orders.view'], { tenantId, organizationId })
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Services and DI
|
|
154
|
+
|
|
155
|
+
| DI Name | Class | Purpose |
|
|
156
|
+
|---------|-------|---------|
|
|
157
|
+
| `customerUserService` | `CustomerUserService` | User CRUD, password verification, lockout management |
|
|
158
|
+
| `customerSessionService` | `CustomerSessionService` | Session creation, JWT signing, token lookup, revocation |
|
|
159
|
+
| `customerTokenService` | `CustomerTokenService` | Email verification, magic link, and password reset tokens |
|
|
160
|
+
| `customerRbacService` | `CustomerRbacService` | ACL resolution with cache, feature checks |
|
|
161
|
+
| `customerInvitationService` | `CustomerInvitationService` | Invitation creation and acceptance |
|
|
162
|
+
|
|
163
|
+
All services are registered as **scoped** (per-request) via `di.ts`.
|
|
164
|
+
|
|
165
|
+
`CustomerRbacService` uses tag-based cache invalidation (`customer_rbac:user:<id>`, `customer_rbac:tenant:<id>`, `customer_rbac:all`) with a 5-minute TTL.
|
|
166
|
+
|
|
167
|
+
## API Directory Structure
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
api/
|
|
171
|
+
├── post/
|
|
172
|
+
│ ├── login.ts # Customer login
|
|
173
|
+
│ ├── signup.ts # Self-registration
|
|
174
|
+
│ ├── email/verify.ts # Email verification
|
|
175
|
+
│ ├── password/reset-request.ts # Request password reset
|
|
176
|
+
│ ├── password/reset-confirm.ts # Confirm password reset
|
|
177
|
+
│ ├── magic-link/request.ts # Request magic link
|
|
178
|
+
│ ├── magic-link/verify.ts # Verify magic link
|
|
179
|
+
│ ├── invitations/accept.ts # Accept invitation
|
|
180
|
+
│ ├── admin/users.ts # Admin: create user
|
|
181
|
+
│ ├── admin/users-invite.ts # Admin: invite user
|
|
182
|
+
│ ├── admin/users/[id]/reset-password.ts # Admin: reset user password
|
|
183
|
+
│ ├── admin/users/[id]/verify-email.ts # Admin: force verify email
|
|
184
|
+
│ ├── admin/roles.ts # Admin: create role
|
|
185
|
+
│ ├── portal/logout.ts # Portal: logout
|
|
186
|
+
│ ├── portal/sessions-refresh.ts # Portal: refresh JWT
|
|
187
|
+
│ ├── portal/password-change.ts # Portal: change password
|
|
188
|
+
│ ├── portal/users-invite.ts # Portal admin: invite user
|
|
189
|
+
│ └── portal/feature-check.ts # Portal: check feature access
|
|
190
|
+
├── get/
|
|
191
|
+
│ ├── admin/users.ts # Admin: list users
|
|
192
|
+
│ ├── admin/users/[id].ts # Admin: get user detail
|
|
193
|
+
│ ├── admin/roles.ts # Admin: list roles
|
|
194
|
+
│ ├── admin/roles/[id].ts # Admin: get role detail
|
|
195
|
+
│ ├── portal/profile.ts # Portal: get own profile
|
|
196
|
+
│ ├── portal/sessions.ts # Portal: list own sessions
|
|
197
|
+
│ ├── portal/users.ts # Portal admin: list company users
|
|
198
|
+
│ ├── portal/events/stream.ts # Portal: SSE event stream
|
|
199
|
+
│ ├── portal/notifications.ts # Portal: list notifications
|
|
200
|
+
│ └── portal/notifications/unread-count.ts # Portal: unread count
|
|
201
|
+
├── put/
|
|
202
|
+
│ ├── admin/users/[id].ts # Admin: update user
|
|
203
|
+
│ ├── admin/roles/[id].ts # Admin: update role
|
|
204
|
+
│ ├── admin/roles/[id]/acl.ts # Admin: update role ACL
|
|
205
|
+
│ ├── portal/profile.ts # Portal: update own profile
|
|
206
|
+
│ ├── portal/users/[id]/roles.ts # Portal admin: assign roles
|
|
207
|
+
│ ├── portal/notifications/[id]/read.ts # Portal: mark notification read
|
|
208
|
+
│ ├── portal/notifications/[id]/dismiss.ts # Portal: dismiss notification
|
|
209
|
+
│ └── portal/notifications/mark-all-read.ts # Portal: mark all read
|
|
210
|
+
└── delete/
|
|
211
|
+
├── admin/users/[id].ts # Admin: soft-delete user
|
|
212
|
+
├── admin/roles/[id].ts # Admin: delete role
|
|
213
|
+
├── portal/users/[id].ts # Portal admin: remove user
|
|
214
|
+
└── portal/sessions/[id].ts # Portal: revoke session
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### API Scopes
|
|
218
|
+
|
|
219
|
+
- **Public** (`/api/login`, `/api/signup`, `/api/password/*`, `/api/magic-link/*`, `/api/email/*`, `/api/invitations/*`) — unauthenticated, rate-limited
|
|
220
|
+
- **Admin** (`/api/admin/*`) — requires staff auth + `customer_accounts.view` / `customer_accounts.manage` features
|
|
221
|
+
- **Portal** (`/api/portal/*`) — requires customer auth (JWT), some endpoints require `isPortalAdmin`
|
|
222
|
+
|
|
223
|
+
## Events
|
|
224
|
+
|
|
225
|
+
Declared in `events.ts` via `createModuleEvents`. Emit with `emitCustomerAccountsEvent`.
|
|
226
|
+
|
|
227
|
+
| Event ID | Category | Client Broadcast |
|
|
228
|
+
|----------|----------|-----------------|
|
|
229
|
+
| `customer_accounts.user.created` | crud | Yes |
|
|
230
|
+
| `customer_accounts.user.updated` | crud | No |
|
|
231
|
+
| `customer_accounts.user.deleted` | crud | No |
|
|
232
|
+
| `customer_accounts.user.locked` | lifecycle | No |
|
|
233
|
+
| `customer_accounts.user.unlocked` | lifecycle | No |
|
|
234
|
+
| `customer_accounts.login.success` | lifecycle | No |
|
|
235
|
+
| `customer_accounts.login.failed` | lifecycle | No |
|
|
236
|
+
| `customer_accounts.email.verified` | lifecycle | No |
|
|
237
|
+
| `customer_accounts.password.reset` | lifecycle | No |
|
|
238
|
+
| `customer_accounts.role.created` | crud | No |
|
|
239
|
+
| `customer_accounts.role.updated` | crud | No |
|
|
240
|
+
| `customer_accounts.role.deleted` | crud | No |
|
|
241
|
+
| `customer_accounts.invitation.accepted` | lifecycle | Yes |
|
|
242
|
+
|
|
243
|
+
## Subscribers
|
|
244
|
+
|
|
245
|
+
| Subscriber | Listens To | Purpose |
|
|
246
|
+
|------------|-----------|---------|
|
|
247
|
+
| `autoLinkCrm` | `customer_accounts.user.created` | Links new customer user to existing CRM person/company by email match |
|
|
248
|
+
| `autoLinkCrmReverse` | `customers.person.created` | Links new CRM person to existing customer user by email match |
|
|
249
|
+
| `notifyStaffOnSignup` | `customer_accounts.user.created` | Emits in-app notification to staff about new signups |
|
|
250
|
+
|
|
251
|
+
All three are **persistent** subscribers (retried on failure).
|
|
252
|
+
|
|
253
|
+
### CRM Auto-Linking Logic
|
|
254
|
+
|
|
255
|
+
- **Forward** (`autoLinkCrm`): When a customer user signs up, searches CRM `CustomerEntity` (kind=person) for matching email. If found, sets `personEntityId` on the user. Also looks up the person's company via `customer_people.company_entity_id` and sets `customerEntityId`.
|
|
256
|
+
- **Reverse** (`autoLinkCrmReverse`): When a CRM person is created, looks for an unlinked customer user with matching email hash and links them.
|
|
257
|
+
|
|
258
|
+
## Workers
|
|
259
|
+
|
|
260
|
+
| Worker | Queue | Purpose |
|
|
261
|
+
|--------|-------|---------|
|
|
262
|
+
| `cleanupExpiredSessions` | `customer-accounts-cleanup-sessions` | Deletes expired and soft-deleted sessions |
|
|
263
|
+
| `cleanupExpiredTokens` | `customer-accounts-cleanup-tokens` | Deletes expired/used email verifications, password resets, and accepted/cancelled invitations |
|
|
264
|
+
|
|
265
|
+
Both run with `concurrency: 1`.
|
|
266
|
+
|
|
267
|
+
## Notification Types
|
|
268
|
+
|
|
269
|
+
Declared in `notifications.ts` and `notifications.client.ts`.
|
|
270
|
+
|
|
271
|
+
| Type | Severity | Trigger |
|
|
272
|
+
|------|----------|---------|
|
|
273
|
+
| `customer_accounts.user.signup` | info | New customer registration |
|
|
274
|
+
| `customer_accounts.user.locked` | warning | Account locked after failed attempts |
|
|
275
|
+
|
|
276
|
+
Both link to `/backend/customer_accounts/{sourceEntityId}` for staff review.
|
|
277
|
+
|
|
278
|
+
## Widget Injection
|
|
279
|
+
|
|
280
|
+
### Injection Table
|
|
281
|
+
|
|
282
|
+
| Spot ID | Widget | Purpose |
|
|
283
|
+
|---------|--------|---------|
|
|
284
|
+
| `crud-form:customers:customer_person_profile:fields` | `account-status` | Shows portal account status on CRM person detail page |
|
|
285
|
+
| `crud-form:customers:customer_company_profile:fields` | `company-users` | Shows portal users linked to a CRM company |
|
|
286
|
+
|
|
287
|
+
Both inject as column 2 groups with priority 200, gated by `customer_accounts.view` feature.
|
|
288
|
+
|
|
289
|
+
## Backend Pages
|
|
290
|
+
|
|
291
|
+
| Path | Purpose |
|
|
292
|
+
|------|---------|
|
|
293
|
+
| `backend/page.tsx` | Customer accounts list (`/backend/customer_accounts`) |
|
|
294
|
+
| `backend/customer_accounts/[id]/page.tsx` | User detail/edit |
|
|
295
|
+
| `backend/customer_accounts/roles/page.tsx` | Role list |
|
|
296
|
+
| `backend/customer_accounts/roles/create/page.tsx` | Create role |
|
|
297
|
+
| `backend/customer_accounts/roles/[id]/page.tsx` | Role detail/edit ACL |
|
|
298
|
+
|
|
299
|
+
## Security
|
|
300
|
+
|
|
301
|
+
### Password Handling
|
|
302
|
+
|
|
303
|
+
- Hash with `bcryptjs` cost 10 (`BCRYPT_COST = 10`)
|
|
304
|
+
- Minimum 8 characters, maximum 128 characters
|
|
305
|
+
- Never log password values
|
|
306
|
+
|
|
307
|
+
### Account Lockout
|
|
308
|
+
|
|
309
|
+
- 5 failed login attempts triggers 15-minute lockout
|
|
310
|
+
- Failed attempts tracked per user in `failedLoginAttempts`
|
|
311
|
+
- Lockout stored as `lockedUntil` timestamp
|
|
312
|
+
- Successful login resets counter and clears lock
|
|
313
|
+
|
|
314
|
+
### Rate Limiting
|
|
315
|
+
|
|
316
|
+
All public endpoints have dual rate limits (per-identifier + per-IP). Defaults:
|
|
317
|
+
|
|
318
|
+
| Endpoint | Per-Email | Per-IP | Block Duration |
|
|
319
|
+
|----------|-----------|--------|----------------|
|
|
320
|
+
| Login | 5/60s | 20/60s | 60s |
|
|
321
|
+
| Signup | 3/60s | 10/60s | 120s |
|
|
322
|
+
| Password Reset | 3/60s | 10/60s | 120s |
|
|
323
|
+
| Magic Link | 3/60s | 10/60s | 120s |
|
|
324
|
+
|
|
325
|
+
Configurable via environment variables (`CUSTOMER_LOGIN_POINTS`, `CUSTOMER_LOGIN_DURATION`, etc.) using `readEndpointRateLimitConfig`.
|
|
326
|
+
|
|
327
|
+
### Token Security
|
|
328
|
+
|
|
329
|
+
- Tokens generated with `crypto.randomBytes(32)` (base64url)
|
|
330
|
+
- Tokens stored as SHA-256 hashes — raw tokens never persisted
|
|
331
|
+
- TTLs: email verification 24h, magic link 15min, password reset 60min, invitation 72h
|
|
332
|
+
|
|
333
|
+
### Email Privacy
|
|
334
|
+
|
|
335
|
+
- Emails stored in plaintext but lookups use `hashForLookup` (deterministic hash)
|
|
336
|
+
- Unique constraint on `(tenantId, emailHash)` prevents duplicates
|
|
337
|
+
- Error messages never confirm whether an email is registered
|
|
338
|
+
|
|
339
|
+
## Lib Utilities
|
|
340
|
+
|
|
341
|
+
| File | Exports | Purpose |
|
|
342
|
+
|------|---------|---------|
|
|
343
|
+
| `lib/customerAuth.ts` | `getCustomerAuthFromRequest`, `requireCustomerAuth`, `requireCustomerFeature`, `CustomerAuthContext` | Request-level auth helpers for API routes |
|
|
344
|
+
| `lib/customerAuthServer.ts` | `getCustomerAuthFromCookies` | Server component auth via Next.js `cookies()` |
|
|
345
|
+
| `lib/rateLimiter.ts` | Rate limit configs, `checkAuthRateLimit`, `resetAuthRateLimit` | Dual rate limiting for all public endpoints |
|
|
346
|
+
| `lib/tokenGenerator.ts` | `generateSecureToken`, `hashToken` | Cryptographic token generation and hashing |
|
|
347
|
+
|
|
348
|
+
## ACL Features (Staff-Side)
|
|
349
|
+
|
|
350
|
+
Declared in `acl.ts` — these control staff access to customer account management in the admin backend:
|
|
351
|
+
|
|
352
|
+
| Feature | Purpose |
|
|
353
|
+
|---------|---------|
|
|
354
|
+
| `customer_accounts.view` | View customer accounts and roles |
|
|
355
|
+
| `customer_accounts.manage` | Create, update, delete customer users |
|
|
356
|
+
| `customer_accounts.roles.manage` | Create, update, delete customer roles and ACLs |
|
|
357
|
+
| `customer_accounts.invite` | Invite customer users |
|
|
358
|
+
|
|
359
|
+
Default role assignments (from `setup.ts`):
|
|
360
|
+
- `superadmin`: `customer_accounts.*`
|
|
361
|
+
- `admin`: `customer_accounts.*`
|
|
362
|
+
|
|
363
|
+
## Key Directories
|
|
364
|
+
|
|
365
|
+
| Directory | When to modify |
|
|
366
|
+
|-----------|---------------|
|
|
367
|
+
| `api/post/` | When adding new public or admin write endpoints |
|
|
368
|
+
| `api/get/` | When adding read endpoints (admin or portal) |
|
|
369
|
+
| `api/put/` | When adding update endpoints |
|
|
370
|
+
| `api/delete/` | When adding deletion endpoints |
|
|
371
|
+
| `backend/` | When changing admin UI pages for customer management |
|
|
372
|
+
| `data/` | When changing ORM entities or zod validators |
|
|
373
|
+
| `lib/` | When modifying auth helpers, rate limiting, or token generation |
|
|
374
|
+
| `services/` | When modifying user, session, token, RBAC, or invitation logic |
|
|
375
|
+
| `subscribers/` | When adding event-driven side effects (CRM linking, notifications) |
|
|
376
|
+
| `workers/` | When modifying cleanup jobs for sessions/tokens |
|
|
377
|
+
| `widgets/injection/` | When adding/modifying widgets injected into CRM forms |
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const features = [
|
|
2
|
+
{ id: 'customer_accounts.view', title: 'View customer accounts', module: 'customer_accounts' },
|
|
3
|
+
{ id: 'customer_accounts.manage', title: 'Manage customer accounts', module: 'customer_accounts' },
|
|
4
|
+
{ id: 'customer_accounts.roles.manage', title: 'Manage customer roles', module: 'customer_accounts' },
|
|
5
|
+
{ id: 'customer_accounts.invite', title: 'Invite customer users', module: 'customer_accounts' },
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
export default features
|