@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.
Files changed (333) hide show
  1. package/agentic/standalone-guide.md +235 -0
  2. package/dist/generated/entities/customer_role/index.js +27 -0
  3. package/dist/generated/entities/customer_role/index.js.map +7 -0
  4. package/dist/generated/entities/customer_role_acl/index.js +19 -0
  5. package/dist/generated/entities/customer_role_acl/index.js.map +7 -0
  6. package/dist/generated/entities/customer_user/index.js +37 -0
  7. package/dist/generated/entities/customer_user/index.js.map +7 -0
  8. package/dist/generated/entities/customer_user_acl/index.js +19 -0
  9. package/dist/generated/entities/customer_user_acl/index.js.map +7 -0
  10. package/dist/generated/entities/customer_user_email_verification/index.js +17 -0
  11. package/dist/generated/entities/customer_user_email_verification/index.js.map +7 -0
  12. package/dist/generated/entities/customer_user_invitation/index.js +33 -0
  13. package/dist/generated/entities/customer_user_invitation/index.js.map +7 -0
  14. package/dist/generated/entities/customer_user_password_reset/index.js +15 -0
  15. package/dist/generated/entities/customer_user_password_reset/index.js.map +7 -0
  16. package/dist/generated/entities/customer_user_role/index.js +13 -0
  17. package/dist/generated/entities/customer_user_role/index.js.map +7 -0
  18. package/dist/generated/entities/customer_user_session/index.js +21 -0
  19. package/dist/generated/entities/customer_user_session/index.js.map +7 -0
  20. package/dist/generated/entities/organization/index.js +2 -0
  21. package/dist/generated/entities/organization/index.js.map +2 -2
  22. package/dist/generated/entities.ids.generated.js +14 -1
  23. package/dist/generated/entities.ids.generated.js.map +2 -2
  24. package/dist/generated/entity-fields-registry.js +18 -0
  25. package/dist/generated/entity-fields-registry.js.map +2 -2
  26. package/dist/modules/auth/services/rbacService.js +3 -9
  27. package/dist/modules/auth/services/rbacService.js.map +2 -2
  28. package/dist/modules/customer_accounts/acl.js +12 -0
  29. package/dist/modules/customer_accounts/acl.js.map +7 -0
  30. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +87 -0
  31. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +7 -0
  32. package/dist/modules/customer_accounts/api/admin/roles/[id].js +216 -0
  33. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +7 -0
  34. package/dist/modules/customer_accounts/api/admin/roles.js +189 -0
  35. package/dist/modules/customer_accounts/api/admin/roles.js.map +7 -0
  36. package/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js +69 -0
  37. package/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js.map +7 -0
  38. package/dist/modules/customer_accounts/api/admin/users/[id]/verify-email.js +64 -0
  39. package/dist/modules/customer_accounts/api/admin/users/[id]/verify-email.js.map +7 -0
  40. package/dist/modules/customer_accounts/api/admin/users/[id].js +253 -0
  41. package/dist/modules/customer_accounts/api/admin/users/[id].js.map +7 -0
  42. package/dist/modules/customer_accounts/api/admin/users-invite.js +78 -0
  43. package/dist/modules/customer_accounts/api/admin/users-invite.js.map +7 -0
  44. package/dist/modules/customer_accounts/api/admin/users.js +251 -0
  45. package/dist/modules/customer_accounts/api/admin/users.js.map +7 -0
  46. package/dist/modules/customer_accounts/api/email/verify.js +59 -0
  47. package/dist/modules/customer_accounts/api/email/verify.js.map +7 -0
  48. package/dist/modules/customer_accounts/api/interceptors.js +5 -0
  49. package/dist/modules/customer_accounts/api/interceptors.js.map +7 -0
  50. package/dist/modules/customer_accounts/api/invitations/accept.js +114 -0
  51. package/dist/modules/customer_accounts/api/invitations/accept.js.map +7 -0
  52. package/dist/modules/customer_accounts/api/login.js +143 -0
  53. package/dist/modules/customer_accounts/api/login.js.map +7 -0
  54. package/dist/modules/customer_accounts/api/magic-link/request.js +78 -0
  55. package/dist/modules/customer_accounts/api/magic-link/request.js.map +7 -0
  56. package/dist/modules/customer_accounts/api/magic-link/verify.js +114 -0
  57. package/dist/modules/customer_accounts/api/magic-link/verify.js.map +7 -0
  58. package/dist/modules/customer_accounts/api/password/reset-confirm.js +59 -0
  59. package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +7 -0
  60. package/dist/modules/customer_accounts/api/password/reset-request.js +77 -0
  61. package/dist/modules/customer_accounts/api/password/reset-request.js.map +7 -0
  62. package/dist/modules/customer_accounts/api/portal/events/stream.js +163 -0
  63. package/dist/modules/customer_accounts/api/portal/events/stream.js.map +7 -0
  64. package/dist/modules/customer_accounts/api/portal/feature-check.js +57 -0
  65. package/dist/modules/customer_accounts/api/portal/feature-check.js.map +7 -0
  66. package/dist/modules/customer_accounts/api/portal/logout.js +64 -0
  67. package/dist/modules/customer_accounts/api/portal/logout.js.map +7 -0
  68. package/dist/modules/customer_accounts/api/portal/notifications/[id]/dismiss.js +49 -0
  69. package/dist/modules/customer_accounts/api/portal/notifications/[id]/dismiss.js.map +7 -0
  70. package/dist/modules/customer_accounts/api/portal/notifications/[id]/read.js +49 -0
  71. package/dist/modules/customer_accounts/api/portal/notifications/[id]/read.js.map +7 -0
  72. package/dist/modules/customer_accounts/api/portal/notifications/mark-all-read.js +46 -0
  73. package/dist/modules/customer_accounts/api/portal/notifications/mark-all-read.js.map +7 -0
  74. package/dist/modules/customer_accounts/api/portal/notifications/unread-count.js +42 -0
  75. package/dist/modules/customer_accounts/api/portal/notifications/unread-count.js.map +7 -0
  76. package/dist/modules/customer_accounts/api/portal/notifications.js +105 -0
  77. package/dist/modules/customer_accounts/api/portal/notifications.js.map +7 -0
  78. package/dist/modules/customer_accounts/api/portal/password-change.js +57 -0
  79. package/dist/modules/customer_accounts/api/portal/password-change.js.map +7 -0
  80. package/dist/modules/customer_accounts/api/portal/profile.js +135 -0
  81. package/dist/modules/customer_accounts/api/portal/profile.js.map +7 -0
  82. package/dist/modules/customer_accounts/api/portal/sessions/[id].js +62 -0
  83. package/dist/modules/customer_accounts/api/portal/sessions/[id].js.map +7 -0
  84. package/dist/modules/customer_accounts/api/portal/sessions-refresh.js +75 -0
  85. package/dist/modules/customer_accounts/api/portal/sessions-refresh.js.map +7 -0
  86. package/dist/modules/customer_accounts/api/portal/sessions.js +77 -0
  87. package/dist/modules/customer_accounts/api/portal/sessions.js.map +7 -0
  88. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +90 -0
  89. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +7 -0
  90. package/dist/modules/customer_accounts/api/portal/users/[id].js +71 -0
  91. package/dist/modules/customer_accounts/api/portal/users/[id].js.map +7 -0
  92. package/dist/modules/customer_accounts/api/portal/users-invite.js +92 -0
  93. package/dist/modules/customer_accounts/api/portal/users-invite.js.map +7 -0
  94. package/dist/modules/customer_accounts/api/portal/users.js +79 -0
  95. package/dist/modules/customer_accounts/api/portal/users.js.map +7 -0
  96. package/dist/modules/customer_accounts/api/signup.js +121 -0
  97. package/dist/modules/customer_accounts/api/signup.js.map +7 -0
  98. package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.js +491 -0
  99. package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.js.map +7 -0
  100. package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.meta.js +15 -0
  101. package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.meta.js.map +7 -0
  102. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +343 -0
  103. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +7 -0
  104. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.meta.js +16 -0
  105. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.meta.js.map +7 -0
  106. package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.js +180 -0
  107. package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.js.map +7 -0
  108. package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.meta.js +16 -0
  109. package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.meta.js.map +7 -0
  110. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +176 -0
  111. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +7 -0
  112. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.meta.js +33 -0
  113. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.meta.js.map +7 -0
  114. package/dist/modules/customer_accounts/backend/page.js +466 -0
  115. package/dist/modules/customer_accounts/backend/page.js.map +7 -0
  116. package/dist/modules/customer_accounts/backend/page.meta.js +35 -0
  117. package/dist/modules/customer_accounts/backend/page.meta.js.map +7 -0
  118. package/dist/modules/customer_accounts/ce.js +26 -0
  119. package/dist/modules/customer_accounts/ce.js.map +7 -0
  120. package/dist/modules/customer_accounts/data/enrichers.js +85 -0
  121. package/dist/modules/customer_accounts/data/enrichers.js.map +7 -0
  122. package/dist/modules/customer_accounts/data/entities.js +377 -0
  123. package/dist/modules/customer_accounts/data/entities.js.map +7 -0
  124. package/dist/modules/customer_accounts/data/extensions.js +8 -0
  125. package/dist/modules/customer_accounts/data/extensions.js.map +7 -0
  126. package/dist/modules/customer_accounts/data/validators.js +111 -0
  127. package/dist/modules/customer_accounts/data/validators.js.map +7 -0
  128. package/dist/modules/customer_accounts/di.js +17 -0
  129. package/dist/modules/customer_accounts/di.js.map +7 -0
  130. package/dist/modules/customer_accounts/events.js +28 -0
  131. package/dist/modules/customer_accounts/events.js.map +7 -0
  132. package/dist/modules/customer_accounts/index.js +15 -0
  133. package/dist/modules/customer_accounts/index.js.map +7 -0
  134. package/dist/modules/customer_accounts/lib/customerAuth.js +71 -0
  135. package/dist/modules/customer_accounts/lib/customerAuth.js.map +7 -0
  136. package/dist/modules/customer_accounts/lib/customerAuthServer.js +29 -0
  137. package/dist/modules/customer_accounts/lib/customerAuthServer.js.map +7 -0
  138. package/dist/modules/customer_accounts/lib/rateLimiter.js +63 -0
  139. package/dist/modules/customer_accounts/lib/rateLimiter.js.map +7 -0
  140. package/dist/modules/customer_accounts/lib/tokenGenerator.js +12 -0
  141. package/dist/modules/customer_accounts/lib/tokenGenerator.js.map +7 -0
  142. package/dist/modules/customer_accounts/migrations/Migration20260313222043.js +49 -0
  143. package/dist/modules/customer_accounts/migrations/Migration20260313222043.js.map +7 -0
  144. package/dist/modules/customer_accounts/notifications.client.js +47 -0
  145. package/dist/modules/customer_accounts/notifications.client.js.map +7 -0
  146. package/dist/modules/customer_accounts/notifications.js +46 -0
  147. package/dist/modules/customer_accounts/notifications.js.map +7 -0
  148. package/dist/modules/customer_accounts/search.js +120 -0
  149. package/dist/modules/customer_accounts/search.js.map +7 -0
  150. package/dist/modules/customer_accounts/services/customerInvitationService.js +87 -0
  151. package/dist/modules/customer_accounts/services/customerInvitationService.js.map +7 -0
  152. package/dist/modules/customer_accounts/services/customerRbacService.js +109 -0
  153. package/dist/modules/customer_accounts/services/customerRbacService.js.map +7 -0
  154. package/dist/modules/customer_accounts/services/customerSessionService.js +75 -0
  155. package/dist/modules/customer_accounts/services/customerSessionService.js.map +7 -0
  156. package/dist/modules/customer_accounts/services/customerTokenService.js +91 -0
  157. package/dist/modules/customer_accounts/services/customerTokenService.js.map +7 -0
  158. package/dist/modules/customer_accounts/services/customerUserService.js +92 -0
  159. package/dist/modules/customer_accounts/services/customerUserService.js.map +7 -0
  160. package/dist/modules/customer_accounts/setup.js +179 -0
  161. package/dist/modules/customer_accounts/setup.js.map +7 -0
  162. package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js +54 -0
  163. package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js.map +7 -0
  164. package/dist/modules/customer_accounts/subscribers/autoLinkCrmReverse.js +68 -0
  165. package/dist/modules/customer_accounts/subscribers/autoLinkCrmReverse.js.map +7 -0
  166. package/dist/modules/customer_accounts/subscribers/notifyStaffOnSignup.js +29 -0
  167. package/dist/modules/customer_accounts/subscribers/notifyStaffOnSignup.js.map +7 -0
  168. package/dist/modules/customer_accounts/translations.js +9 -0
  169. package/dist/modules/customer_accounts/translations.js.map +7 -0
  170. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +63 -0
  171. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +7 -0
  172. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.js +17 -0
  173. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.js.map +7 -0
  174. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js +55 -0
  175. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js.map +7 -0
  176. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.js +17 -0
  177. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.js.map +7 -0
  178. package/dist/modules/customer_accounts/widgets/injection-table.js +26 -0
  179. package/dist/modules/customer_accounts/widgets/injection-table.js.map +7 -0
  180. package/dist/modules/customer_accounts/workers/cleanupExpiredSessions.js +23 -0
  181. package/dist/modules/customer_accounts/workers/cleanupExpiredSessions.js.map +7 -0
  182. package/dist/modules/customer_accounts/workers/cleanupExpiredTokens.js +38 -0
  183. package/dist/modules/customer_accounts/workers/cleanupExpiredTokens.js.map +7 -0
  184. package/dist/modules/customers/components/AddressTiles.js +1 -1
  185. package/dist/modules/customers/components/AddressTiles.js.map +2 -2
  186. package/dist/modules/directory/api/get/organizations/lookup.js +83 -0
  187. package/dist/modules/directory/api/get/organizations/lookup.js.map +7 -0
  188. package/dist/modules/directory/commands/organizations.js +32 -1
  189. package/dist/modules/directory/commands/organizations.js.map +2 -2
  190. package/dist/modules/directory/data/entities.js +6 -2
  191. package/dist/modules/directory/data/entities.js.map +2 -2
  192. package/dist/modules/directory/data/validators.js +3 -0
  193. package/dist/modules/directory/data/validators.js.map +2 -2
  194. package/dist/modules/directory/migrations/Migration20260314143323.js +15 -0
  195. package/dist/modules/directory/migrations/Migration20260314143323.js.map +7 -0
  196. package/dist/modules/directory/setup.js +36 -0
  197. package/dist/modules/directory/setup.js.map +2 -2
  198. package/dist/modules/payment_gateways/migrations/Migration20260313222043.js +15 -0
  199. package/dist/modules/payment_gateways/migrations/Migration20260313222043.js.map +7 -0
  200. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +131 -0
  201. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +7 -0
  202. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +96 -0
  203. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +7 -0
  204. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js +94 -0
  205. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js.map +7 -0
  206. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js +89 -0
  207. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js.map +7 -0
  208. package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js +104 -0
  209. package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js.map +7 -0
  210. package/dist/modules/portal/index.js +11 -0
  211. package/dist/modules/portal/index.js.map +7 -0
  212. package/dist/modules/portal/setup.js +23 -0
  213. package/dist/modules/portal/setup.js.map +7 -0
  214. package/generated/entities/customer_role/index.ts +12 -0
  215. package/generated/entities/customer_role_acl/index.ts +8 -0
  216. package/generated/entities/customer_user/index.ts +17 -0
  217. package/generated/entities/customer_user_acl/index.ts +8 -0
  218. package/generated/entities/customer_user_email_verification/index.ts +7 -0
  219. package/generated/entities/customer_user_invitation/index.ts +15 -0
  220. package/generated/entities/customer_user_password_reset/index.ts +6 -0
  221. package/generated/entities/customer_user_role/index.ts +5 -0
  222. package/generated/entities/customer_user_session/index.ts +9 -0
  223. package/generated/entities/organization/index.ts +1 -0
  224. package/generated/entities.ids.generated.ts +14 -1
  225. package/generated/entity-fields-registry.ts +18 -0
  226. package/package.json +3 -3
  227. package/src/modules/auth/services/rbacService.ts +3 -9
  228. package/src/modules/customer_accounts/AGENTS.md +377 -0
  229. package/src/modules/customer_accounts/acl.ts +8 -0
  230. package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +98 -0
  231. package/src/modules/customer_accounts/api/admin/roles/[id].ts +246 -0
  232. package/src/modules/customer_accounts/api/admin/roles.ts +212 -0
  233. package/src/modules/customer_accounts/api/admin/users/[id]/reset-password.ts +78 -0
  234. package/src/modules/customer_accounts/api/admin/users/[id]/verify-email.ts +72 -0
  235. package/src/modules/customer_accounts/api/admin/users/[id].ts +289 -0
  236. package/src/modules/customer_accounts/api/admin/users-invite.ts +86 -0
  237. package/src/modules/customer_accounts/api/admin/users.ts +280 -0
  238. package/src/modules/customer_accounts/api/email/verify.ts +66 -0
  239. package/src/modules/customer_accounts/api/interceptors.ts +3 -0
  240. package/src/modules/customer_accounts/api/invitations/accept.ts +128 -0
  241. package/src/modules/customer_accounts/api/login.ts +163 -0
  242. package/src/modules/customer_accounts/api/magic-link/request.ts +87 -0
  243. package/src/modules/customer_accounts/api/magic-link/verify.ts +132 -0
  244. package/src/modules/customer_accounts/api/password/reset-confirm.ts +69 -0
  245. package/src/modules/customer_accounts/api/password/reset-request.ts +87 -0
  246. package/src/modules/customer_accounts/api/portal/events/stream.ts +209 -0
  247. package/src/modules/customer_accounts/api/portal/feature-check.ts +60 -0
  248. package/src/modules/customer_accounts/api/portal/logout.ts +71 -0
  249. package/src/modules/customer_accounts/api/portal/notifications/[id]/dismiss.ts +54 -0
  250. package/src/modules/customer_accounts/api/portal/notifications/[id]/read.ts +54 -0
  251. package/src/modules/customer_accounts/api/portal/notifications/mark-all-read.ts +49 -0
  252. package/src/modules/customer_accounts/api/portal/notifications/unread-count.ts +45 -0
  253. package/src/modules/customer_accounts/api/portal/notifications.ts +115 -0
  254. package/src/modules/customer_accounts/api/portal/password-change.ts +65 -0
  255. package/src/modules/customer_accounts/api/portal/profile.ts +151 -0
  256. package/src/modules/customer_accounts/api/portal/sessions/[id].ts +70 -0
  257. package/src/modules/customer_accounts/api/portal/sessions-refresh.ts +87 -0
  258. package/src/modules/customer_accounts/api/portal/sessions.ts +84 -0
  259. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +106 -0
  260. package/src/modules/customer_accounts/api/portal/users/[id].ts +81 -0
  261. package/src/modules/customer_accounts/api/portal/users-invite.ts +103 -0
  262. package/src/modules/customer_accounts/api/portal/users.ts +86 -0
  263. package/src/modules/customer_accounts/api/signup.ts +136 -0
  264. package/src/modules/customer_accounts/backend/customer_accounts/[id]/page.meta.ts +11 -0
  265. package/src/modules/customer_accounts/backend/customer_accounts/[id]/page.tsx +607 -0
  266. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.meta.ts +12 -0
  267. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +385 -0
  268. package/src/modules/customer_accounts/backend/customer_accounts/roles/create/page.meta.ts +12 -0
  269. package/src/modules/customer_accounts/backend/customer_accounts/roles/create/page.tsx +203 -0
  270. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.meta.ts +31 -0
  271. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +217 -0
  272. package/src/modules/customer_accounts/backend/page.meta.ts +33 -0
  273. package/src/modules/customer_accounts/backend/page.tsx +535 -0
  274. package/src/modules/customer_accounts/ce.ts +22 -0
  275. package/src/modules/customer_accounts/data/enrichers.ts +117 -0
  276. package/src/modules/customer_accounts/data/entities.ts +302 -0
  277. package/src/modules/customer_accounts/data/extensions.ts +4 -0
  278. package/src/modules/customer_accounts/data/validators.ts +128 -0
  279. package/src/modules/customer_accounts/di.ts +15 -0
  280. package/src/modules/customer_accounts/events.ts +28 -0
  281. package/src/modules/customer_accounts/i18n/de.json +176 -0
  282. package/src/modules/customer_accounts/i18n/en.json +176 -0
  283. package/src/modules/customer_accounts/i18n/es.json +176 -0
  284. package/src/modules/customer_accounts/i18n/pl.json +176 -0
  285. package/src/modules/customer_accounts/index.ts +13 -0
  286. package/src/modules/customer_accounts/lib/customerAuth.ts +85 -0
  287. package/src/modules/customer_accounts/lib/customerAuthServer.ts +54 -0
  288. package/src/modules/customer_accounts/lib/rateLimiter.ts +36 -0
  289. package/src/modules/customer_accounts/lib/tokenGenerator.ts +9 -0
  290. package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +1255 -0
  291. package/src/modules/customer_accounts/migrations/Migration20260313222043.ts +62 -0
  292. package/src/modules/customer_accounts/notifications.client.ts +46 -0
  293. package/src/modules/customer_accounts/notifications.ts +44 -0
  294. package/src/modules/customer_accounts/search.ts +134 -0
  295. package/src/modules/customer_accounts/services/customerInvitationService.ts +109 -0
  296. package/src/modules/customer_accounts/services/customerRbacService.ts +144 -0
  297. package/src/modules/customer_accounts/services/customerSessionService.ts +90 -0
  298. package/src/modules/customer_accounts/services/customerTokenService.ts +98 -0
  299. package/src/modules/customer_accounts/services/customerUserService.ts +105 -0
  300. package/src/modules/customer_accounts/setup.ts +212 -0
  301. package/src/modules/customer_accounts/subscribers/autoLinkCrm.ts +65 -0
  302. package/src/modules/customer_accounts/subscribers/autoLinkCrmReverse.ts +78 -0
  303. package/src/modules/customer_accounts/subscribers/notifyStaffOnSignup.ts +32 -0
  304. package/src/modules/customer_accounts/translations.ts +5 -0
  305. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +89 -0
  306. package/src/modules/customer_accounts/widgets/injection/account-status/widget.ts +16 -0
  307. package/src/modules/customer_accounts/widgets/injection/company-users/widget.client.tsx +78 -0
  308. package/src/modules/customer_accounts/widgets/injection/company-users/widget.ts +16 -0
  309. package/src/modules/customer_accounts/widgets/injection-table.ts +24 -0
  310. package/src/modules/customer_accounts/workers/cleanupExpiredSessions.ts +33 -0
  311. package/src/modules/customer_accounts/workers/cleanupExpiredTokens.ts +51 -0
  312. package/src/modules/customers/components/AddressTiles.tsx +1 -1
  313. package/src/modules/directory/api/get/organizations/lookup.ts +92 -0
  314. package/src/modules/directory/commands/organizations.ts +34 -1
  315. package/src/modules/directory/data/entities.ts +5 -1
  316. package/src/modules/directory/data/validators.ts +4 -0
  317. package/src/modules/directory/migrations/.snapshot-open-mercato.json +20 -1
  318. package/src/modules/directory/migrations/Migration20260314143323.ts +15 -0
  319. package/src/modules/directory/setup.ts +41 -0
  320. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +4 -1
  321. package/src/modules/payment_gateways/migrations/Migration20260313222043.ts +17 -0
  322. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +158 -0
  323. package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +120 -0
  324. package/src/modules/portal/frontend/[orgSlug]/portal/page.tsx +118 -0
  325. package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.tsx +112 -0
  326. package/src/modules/portal/frontend/[orgSlug]/portal/signup/page.tsx +138 -0
  327. package/src/modules/portal/i18n/de.json +93 -0
  328. package/src/modules/portal/i18n/en.json +93 -0
  329. package/src/modules/portal/i18n/es.json +93 -0
  330. package/src/modules/portal/i18n/pl.json +93 -0
  331. package/src/modules/portal/index.ts +9 -0
  332. package/src/modules/portal/setup.ts +23 -0
  333. 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,7 @@
1
+ export const id = 'id'
2
+ export const user = 'user'
3
+ export const token = 'token'
4
+ export const purpose = 'purpose'
5
+ export const expires_at = 'expires_at'
6
+ export const used_at = 'used_at'
7
+ export const created_at = 'created_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,6 @@
1
+ export const id = 'id'
2
+ export const user = 'user'
3
+ export const token = 'token'
4
+ export const expires_at = 'expires_at'
5
+ export const used_at = 'used_at'
6
+ export const created_at = 'created_at'
@@ -0,0 +1,5 @@
1
+ export const id = 'id'
2
+ export const user = 'user'
3
+ export const role = 'role'
4
+ export const created_at = 'created_at'
5
+ export const deleted_at = 'deleted_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'
@@ -1,6 +1,7 @@
1
1
  export const id = 'id'
2
2
  export const tenant = 'tenant'
3
3
  export const name = 'name'
4
+ export const slug = 'slug'
4
5
  export const is_active = 'is_active'
5
6
  export const parent_id = 'parent_id'
6
7
  export const root_id = 'root_id'
@@ -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-28cee031d6",
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-28cee031d6"
220
+ "@open-mercato/shared": "0.4.8-develop-15259be22b"
221
221
  },
222
222
  "devDependencies": {
223
- "@open-mercato/shared": "0.4.8-develop-28cee031d6",
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
- if (granted === '*') return true
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
- if (!required.length) return true
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