@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,114 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { magicLinkVerifySchema } from "@open-mercato/core/modules/customer_accounts/data/validators";
4
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
5
+ import { CustomerUser } from "@open-mercato/core/modules/customer_accounts/data/entities";
6
+ import { emitCustomerAccountsEvent } from "@open-mercato/core/modules/customer_accounts/events";
7
+ import { getClientIp } from "@open-mercato/shared/lib/ratelimit/helpers";
8
+ const metadata = {};
9
+ async function POST(req) {
10
+ let body;
11
+ try {
12
+ body = await req.json();
13
+ } catch {
14
+ return NextResponse.json({ ok: false, error: "Invalid request body" }, { status: 400 });
15
+ }
16
+ const parsed = magicLinkVerifySchema.safeParse(body);
17
+ if (!parsed.success) {
18
+ return NextResponse.json({ ok: false, error: "Invalid token" }, { status: 400 });
19
+ }
20
+ const container = await createRequestContainer();
21
+ const customerTokenService = container.resolve("customerTokenService");
22
+ const customerUserService = container.resolve("customerUserService");
23
+ const customerSessionService = container.resolve("customerSessionService");
24
+ const customerRbacService = container.resolve("customerRbacService");
25
+ const em = container.resolve("em");
26
+ const result = await customerTokenService.verifyEmailToken(parsed.data.token, "magic_link");
27
+ if (!result) {
28
+ return NextResponse.json({ ok: false, error: "Invalid or expired token" }, { status: 400 });
29
+ }
30
+ const user = await customerUserService.findById(result.userId, result.tenantId);
31
+ if (!user || !user.isActive) {
32
+ return NextResponse.json({ ok: false, error: "Account not found or deactivated" }, { status: 401 });
33
+ }
34
+ if (!user.emailVerifiedAt) {
35
+ await em.nativeUpdate(CustomerUser, { id: user.id }, { emailVerifiedAt: /* @__PURE__ */ new Date() });
36
+ user.emailVerifiedAt = /* @__PURE__ */ new Date();
37
+ }
38
+ await customerUserService.resetFailedAttempts(user);
39
+ await customerUserService.updateLastLoginAt(user);
40
+ const acl = await customerRbacService.loadAcl(user.id, { tenantId: user.tenantId, organizationId: user.organizationId });
41
+ const resolvedFeatures = acl.features;
42
+ const ip = getClientIp(req, 0);
43
+ const userAgent = req.headers.get("user-agent") || null;
44
+ const { rawToken, jwt } = await customerSessionService.createSession(user, resolvedFeatures, ip, userAgent);
45
+ void emitCustomerAccountsEvent("customer_accounts.login.success", {
46
+ id: user.id,
47
+ email: user.email,
48
+ tenantId: user.tenantId,
49
+ organizationId: user.organizationId
50
+ }).catch(() => void 0);
51
+ const res = NextResponse.json({
52
+ ok: true,
53
+ user: {
54
+ id: user.id,
55
+ email: user.email,
56
+ displayName: user.displayName,
57
+ emailVerified: true
58
+ },
59
+ resolvedFeatures
60
+ });
61
+ res.cookies.set("customer_auth_token", jwt, {
62
+ httpOnly: true,
63
+ path: "/",
64
+ sameSite: "lax",
65
+ secure: process.env.NODE_ENV === "production",
66
+ maxAge: 60 * 60 * 8
67
+ });
68
+ res.cookies.set("customer_session_token", rawToken, {
69
+ httpOnly: true,
70
+ path: "/",
71
+ sameSite: "lax",
72
+ secure: process.env.NODE_ENV === "production",
73
+ maxAge: 60 * 60 * 24 * 30
74
+ });
75
+ return res;
76
+ }
77
+ const loginSuccessSchema = z.object({
78
+ ok: z.literal(true),
79
+ user: z.object({
80
+ id: z.string().uuid(),
81
+ email: z.string().email(),
82
+ displayName: z.string(),
83
+ emailVerified: z.boolean()
84
+ }),
85
+ resolvedFeatures: z.array(z.string())
86
+ });
87
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() });
88
+ const methodDoc = {
89
+ summary: "Verify magic link token",
90
+ description: "Validates the magic link token, auto-verifies email, and creates a session.",
91
+ tags: ["Customer Authentication"],
92
+ requestBody: {
93
+ schema: magicLinkVerifySchema,
94
+ description: "Magic link verification token."
95
+ },
96
+ responses: [
97
+ { status: 200, description: "Login successful", schema: loginSuccessSchema }
98
+ ],
99
+ errors: [
100
+ { status: 400, description: "Invalid or expired token", schema: errorSchema },
101
+ { status: 401, description: "Account not found", schema: errorSchema }
102
+ ]
103
+ };
104
+ const openApi = {
105
+ summary: "Verify customer magic link",
106
+ description: "Handles magic link verification and auto-login.",
107
+ methods: { POST: methodDoc }
108
+ };
109
+ export {
110
+ POST,
111
+ metadata,
112
+ openApi
113
+ };
114
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/customer_accounts/api/magic-link/verify.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { magicLinkVerifySchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\nimport { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'\nimport { CustomerUser } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\nimport { getClientIp } from '@open-mercato/shared/lib/ratelimit/helpers'\n\nexport const metadata: { path?: string } = {}\n\nexport async function POST(req: Request) {\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = magicLinkVerifySchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Invalid token' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const customerTokenService = container.resolve('customerTokenService') as CustomerTokenService\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService\n const customerRbacService = container.resolve('customerRbacService') as CustomerRbacService\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const result = await customerTokenService.verifyEmailToken(parsed.data.token, 'magic_link')\n if (!result) {\n return NextResponse.json({ ok: false, error: 'Invalid or expired token' }, { status: 400 })\n }\n\n const user = await customerUserService.findById(result.userId, result.tenantId)\n if (!user || !user.isActive) {\n return NextResponse.json({ ok: false, error: 'Account not found or deactivated' }, { status: 401 })\n }\n\n // Auto-verify email on magic link login\n if (!user.emailVerifiedAt) {\n await em.nativeUpdate(CustomerUser, { id: user.id }, { emailVerifiedAt: new Date() })\n user.emailVerifiedAt = new Date()\n }\n\n await customerUserService.resetFailedAttempts(user)\n await customerUserService.updateLastLoginAt(user)\n\n const acl = await customerRbacService.loadAcl(user.id, { tenantId: user.tenantId, organizationId: user.organizationId })\n const resolvedFeatures = acl.features\n\n const ip = getClientIp(req, 0)\n const userAgent = req.headers.get('user-agent') || null\n const { rawToken, jwt } = await customerSessionService.createSession(user, resolvedFeatures, ip, userAgent)\n\n void emitCustomerAccountsEvent('customer_accounts.login.success', {\n id: user.id,\n email: user.email,\n tenantId: user.tenantId,\n organizationId: user.organizationId,\n }).catch(() => undefined)\n\n const res = NextResponse.json({\n ok: true,\n user: {\n id: user.id,\n email: user.email,\n displayName: user.displayName,\n emailVerified: true,\n },\n resolvedFeatures,\n })\n\n res.cookies.set('customer_auth_token', jwt, {\n httpOnly: true,\n path: '/',\n sameSite: 'lax',\n secure: process.env.NODE_ENV === 'production',\n maxAge: 60 * 60 * 8,\n })\n res.cookies.set('customer_session_token', rawToken, {\n httpOnly: true,\n path: '/',\n sameSite: 'lax',\n secure: process.env.NODE_ENV === 'production',\n maxAge: 60 * 60 * 24 * 30,\n })\n\n return res\n}\n\nconst loginSuccessSchema = z.object({\n ok: z.literal(true),\n user: z.object({\n id: z.string().uuid(),\n email: z.string().email(),\n displayName: z.string(),\n emailVerified: z.boolean(),\n }),\n resolvedFeatures: z.array(z.string()),\n})\n\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Verify magic link token',\n description: 'Validates the magic link token, auto-verifies email, and creates a session.',\n tags: ['Customer Authentication'],\n requestBody: {\n schema: magicLinkVerifySchema,\n description: 'Magic link verification token.',\n },\n responses: [\n { status: 200, description: 'Login successful', schema: loginSuccessSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid or expired token', schema: errorSchema },\n { status: 401, description: 'Account not found', schema: errorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Verify customer magic link',\n description: 'Handles magic link verification and auto-login.',\n methods: { POST: methodDoc },\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,59 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { passwordResetConfirmSchema } from "@open-mercato/core/modules/customer_accounts/data/validators";
4
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
5
+ const metadata = {};
6
+ async function POST(req) {
7
+ let body;
8
+ try {
9
+ body = await req.json();
10
+ } catch {
11
+ return NextResponse.json({ ok: false, error: "Invalid request body" }, { status: 400 });
12
+ }
13
+ const parsed = passwordResetConfirmSchema.safeParse(body);
14
+ if (!parsed.success) {
15
+ return NextResponse.json({ ok: false, error: "Validation failed" }, { status: 400 });
16
+ }
17
+ const container = await createRequestContainer();
18
+ const customerTokenService = container.resolve("customerTokenService");
19
+ const customerUserService = container.resolve("customerUserService");
20
+ const customerSessionService = container.resolve("customerSessionService");
21
+ const result = await customerTokenService.verifyPasswordResetToken(parsed.data.token);
22
+ if (!result) {
23
+ return NextResponse.json({ ok: false, error: "Invalid or expired token" }, { status: 400 });
24
+ }
25
+ await customerUserService.updatePassword(
26
+ { id: result.userId },
27
+ parsed.data.password
28
+ );
29
+ await customerSessionService.revokeAllUserSessions(result.userId);
30
+ return NextResponse.json({ ok: true });
31
+ }
32
+ const successSchema = z.object({ ok: z.literal(true) });
33
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() });
34
+ const methodDoc = {
35
+ summary: "Confirm customer password reset",
36
+ description: "Validates the reset token and sets a new password. Revokes all existing sessions.",
37
+ tags: ["Customer Authentication"],
38
+ requestBody: {
39
+ schema: passwordResetConfirmSchema,
40
+ description: "Password reset confirmation with token and new password."
41
+ },
42
+ responses: [
43
+ { status: 200, description: "Password reset successful", schema: successSchema }
44
+ ],
45
+ errors: [
46
+ { status: 400, description: "Invalid or expired token", schema: errorSchema }
47
+ ]
48
+ };
49
+ const openApi = {
50
+ summary: "Confirm customer password reset",
51
+ description: "Handles password reset confirmation for customer accounts.",
52
+ methods: { POST: methodDoc }
53
+ };
54
+ export {
55
+ POST,
56
+ metadata,
57
+ openApi
58
+ };
59
+ //# sourceMappingURL=reset-confirm.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/customer_accounts/api/password/reset-confirm.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { passwordResetConfirmSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\n\nexport const metadata: { path?: string } = {}\n\nexport async function POST(req: Request) {\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = passwordResetConfirmSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const customerTokenService = container.resolve('customerTokenService') as CustomerTokenService\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService\n\n const result = await customerTokenService.verifyPasswordResetToken(parsed.data.token)\n if (!result) {\n return NextResponse.json({ ok: false, error: 'Invalid or expired token' }, { status: 400 })\n }\n\n await customerUserService.updatePassword(\n { id: result.userId } as any,\n parsed.data.password,\n )\n\n // Revoke all existing sessions for security\n await customerSessionService.revokeAllUserSessions(result.userId)\n\n return NextResponse.json({ ok: true })\n}\n\nconst successSchema = z.object({ ok: z.literal(true) })\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Confirm customer password reset',\n description: 'Validates the reset token and sets a new password. Revokes all existing sessions.',\n tags: ['Customer Authentication'],\n requestBody: {\n schema: passwordResetConfirmSchema,\n description: 'Password reset confirmation with token and new password.',\n },\n responses: [\n { status: 200, description: 'Password reset successful', schema: successSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid or expired token', schema: errorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Confirm customer password reset',\n description: 'Handles password reset confirmation for customer accounts.',\n methods: { POST: methodDoc },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,kCAAkC;AAC3C,SAAS,8BAA8B;AAKhC,MAAM,WAA8B,CAAC;AAE5C,eAAsB,KAAK,KAAc;AACvC,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACxF;AAEA,QAAM,SAAS,2BAA2B,UAAU,IAAI;AACxD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrF;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,uBAAuB,UAAU,QAAQ,sBAAsB;AACrE,QAAM,sBAAsB,UAAU,QAAQ,qBAAqB;AACnE,QAAM,yBAAyB,UAAU,QAAQ,wBAAwB;AAEzE,QAAM,SAAS,MAAM,qBAAqB,yBAAyB,OAAO,KAAK,KAAK;AACpF,MAAI,CAAC,QAAQ;AACX,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,2BAA2B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5F;AAEA,QAAM,oBAAoB;AAAA,IACxB,EAAE,IAAI,OAAO,OAAO;AAAA,IACpB,OAAO,KAAK;AAAA,EACd;AAGA,QAAM,uBAAuB,sBAAsB,OAAO,MAAM;AAEhE,SAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AACvC;AAEA,MAAM,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;AACtD,MAAM,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,KAAK,GAAG,OAAO,EAAE,OAAO,EAAE,CAAC;AAExE,MAAM,YAA8B;AAAA,EAClC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,yBAAyB;AAAA,EAChC,aAAa;AAAA,IACX,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,6BAA6B,QAAQ,cAAc;AAAA,EACjF;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,4BAA4B,QAAQ,YAAY;AAAA,EAC9E;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,SAAS,EAAE,MAAM,UAAU;AAC7B;",
6
+ "names": []
7
+ }
@@ -0,0 +1,77 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { passwordResetRequestSchema } from "@open-mercato/core/modules/customer_accounts/data/validators";
4
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
5
+ import { rateLimitErrorSchema } from "@open-mercato/shared/lib/ratelimit/helpers";
6
+ import {
7
+ checkAuthRateLimit,
8
+ customerPasswordResetRateLimitConfig,
9
+ customerPasswordResetIpRateLimitConfig
10
+ } from "@open-mercato/core/modules/customer_accounts/lib/rateLimiter";
11
+ const metadata = {};
12
+ async function POST(req) {
13
+ const { error: rateLimitError } = await checkAuthRateLimit({
14
+ req,
15
+ ipConfig: customerPasswordResetIpRateLimitConfig,
16
+ compoundConfig: customerPasswordResetRateLimitConfig,
17
+ compoundIdentifier: ""
18
+ });
19
+ if (rateLimitError) return rateLimitError;
20
+ let body;
21
+ try {
22
+ body = await req.json();
23
+ } catch {
24
+ return NextResponse.json({ ok: true });
25
+ }
26
+ const parsed = passwordResetRequestSchema.safeParse(body);
27
+ if (!parsed.success) {
28
+ return NextResponse.json({ ok: true });
29
+ }
30
+ const { email, tenantId } = parsed.data;
31
+ if (!tenantId) {
32
+ return NextResponse.json({ ok: true });
33
+ }
34
+ const container = await createRequestContainer();
35
+ const customerUserService = container.resolve("customerUserService");
36
+ const customerTokenService = container.resolve("customerTokenService");
37
+ const user = await customerUserService.findByEmail(email, tenantId);
38
+ if (user) {
39
+ const token = await customerTokenService.createPasswordReset(user.id, tenantId);
40
+ void import("@open-mercato/core/modules/customer_accounts/events").then(
41
+ ({ emitCustomerAccountsEvent }) => emitCustomerAccountsEvent("customer_accounts.password.reset", {
42
+ userId: user.id,
43
+ email: user.email,
44
+ tenantId,
45
+ token
46
+ })
47
+ ).catch(() => void 0);
48
+ }
49
+ return NextResponse.json({ ok: true });
50
+ }
51
+ const successSchema = z.object({ ok: z.literal(true) });
52
+ const methodDoc = {
53
+ summary: "Request customer password reset",
54
+ description: "Initiates a password reset flow. Always returns 200 to prevent email enumeration.",
55
+ tags: ["Customer Authentication"],
56
+ requestBody: {
57
+ schema: passwordResetRequestSchema,
58
+ description: "Password reset request with email."
59
+ },
60
+ responses: [
61
+ { status: 200, description: "Request accepted", schema: successSchema }
62
+ ],
63
+ errors: [
64
+ { status: 429, description: "Too many requests", schema: rateLimitErrorSchema }
65
+ ]
66
+ };
67
+ const openApi = {
68
+ summary: "Customer password reset request",
69
+ description: "Handles password reset initiation for customer accounts.",
70
+ methods: { POST: methodDoc }
71
+ };
72
+ export {
73
+ POST,
74
+ metadata,
75
+ openApi
76
+ };
77
+ //# sourceMappingURL=reset-request.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/customer_accounts/api/password/reset-request.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { passwordResetRequestSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'\nimport { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport {\n checkAuthRateLimit,\n customerPasswordResetRateLimitConfig,\n customerPasswordResetIpRateLimitConfig,\n} from '@open-mercato/core/modules/customer_accounts/lib/rateLimiter'\n\nexport const metadata: { path?: string } = {}\n\nexport async function POST(req: Request) {\n const { error: rateLimitError } = await checkAuthRateLimit({\n req,\n ipConfig: customerPasswordResetIpRateLimitConfig,\n compoundConfig: customerPasswordResetRateLimitConfig,\n compoundIdentifier: '',\n })\n if (rateLimitError) return rateLimitError\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: true }) // Always 200\n }\n\n const parsed = passwordResetRequestSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: true }) // Always 200 to prevent enumeration\n }\n\n const { email, tenantId } = parsed.data\n if (!tenantId) {\n return NextResponse.json({ ok: true })\n }\n\n const container = await createRequestContainer()\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const customerTokenService = container.resolve('customerTokenService') as CustomerTokenService\n\n const user = await customerUserService.findByEmail(email, tenantId)\n if (user) {\n const token = await customerTokenService.createPasswordReset(user.id, tenantId)\n // Token would be sent via email in production; emitting event for subscribers to handle\n void import('@open-mercato/core/modules/customer_accounts/events').then(({ emitCustomerAccountsEvent }) =>\n emitCustomerAccountsEvent('customer_accounts.password.reset', {\n userId: user.id,\n email: user.email,\n tenantId,\n token,\n })\n ).catch(() => undefined)\n }\n\n // Always return 200 to prevent email enumeration\n return NextResponse.json({ ok: true })\n}\n\nconst successSchema = z.object({ ok: z.literal(true) })\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Request customer password reset',\n description: 'Initiates a password reset flow. Always returns 200 to prevent email enumeration.',\n tags: ['Customer Authentication'],\n requestBody: {\n schema: passwordResetRequestSchema,\n description: 'Password reset request with email.',\n },\n responses: [\n { status: 200, description: 'Request accepted', schema: successSchema },\n ],\n errors: [\n { status: 429, description: 'Too many requests', schema: rateLimitErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Customer password reset request',\n description: 'Handles password reset initiation for customer accounts.',\n methods: { POST: methodDoc },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,kCAAkC;AAC3C,SAAS,8BAA8B;AAGvC,SAAS,4BAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,WAA8B,CAAC;AAE5C,eAAsB,KAAK,KAAc;AACvC,QAAM,EAAE,OAAO,eAAe,IAAI,MAAM,mBAAmB;AAAA,IACzD;AAAA,IACA,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,oBAAoB;AAAA,EACtB,CAAC;AACD,MAAI,eAAgB,QAAO;AAE3B,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACvC;AAEA,QAAM,SAAS,2BAA2B,UAAU,IAAI;AACxD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACvC;AAEA,QAAM,EAAE,OAAO,SAAS,IAAI,OAAO;AACnC,MAAI,CAAC,UAAU;AACb,WAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACvC;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,sBAAsB,UAAU,QAAQ,qBAAqB;AACnE,QAAM,uBAAuB,UAAU,QAAQ,sBAAsB;AAErE,QAAM,OAAO,MAAM,oBAAoB,YAAY,OAAO,QAAQ;AAClE,MAAI,MAAM;AACR,UAAM,QAAQ,MAAM,qBAAqB,oBAAoB,KAAK,IAAI,QAAQ;AAE9E,SAAK,OAAO,qDAAqD,EAAE;AAAA,MAAK,CAAC,EAAE,0BAA0B,MACnG,0BAA0B,oCAAoC;AAAA,QAC5D,QAAQ,KAAK;AAAA,QACb,OAAO,KAAK;AAAA,QACZ;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH,EAAE,MAAM,MAAM,MAAS;AAAA,EACzB;AAGA,SAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AACvC;AAEA,MAAM,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;AAEtD,MAAM,YAA8B;AAAA,EAClC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,yBAAyB;AAAA,EAChC,aAAa;AAAA,IACX,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,oBAAoB,QAAQ,cAAc;AAAA,EACxE;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,qBAAqB;AAAA,EAChF;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,SAAS,EAAE,MAAM,UAAU;AAC7B;",
6
+ "names": []
7
+ }
@@ -0,0 +1,163 @@
1
+ import { NextResponse } from "next/server";
2
+ import { isPortalBroadcastEvent } from "@open-mercato/shared/modules/events";
3
+ import { getCustomerAuthFromRequest } from "@open-mercato/core/modules/customer_accounts/lib/customerAuth";
4
+ const metadata = {};
5
+ const HEARTBEAT_INTERVAL_MS = 3e4;
6
+ const MAX_PAYLOAD_BYTES = 4096;
7
+ function normalizeAudience(data) {
8
+ const tenantId = typeof data.tenantId === "string" ? data.tenantId : null;
9
+ const organizationScopes = /* @__PURE__ */ new Set();
10
+ if (typeof data.organizationId === "string" && data.organizationId.trim().length > 0) {
11
+ organizationScopes.add(data.organizationId.trim());
12
+ }
13
+ if (Array.isArray(data.organizationIds)) {
14
+ for (const orgId of data.organizationIds) {
15
+ if (typeof orgId === "string" && orgId.trim().length > 0) {
16
+ organizationScopes.add(orgId.trim());
17
+ }
18
+ }
19
+ }
20
+ return { tenantId, organizationScopes: Array.from(organizationScopes) };
21
+ }
22
+ function matchesAudience(conn, audience) {
23
+ if (!audience.tenantId) return false;
24
+ if (conn.tenantId !== audience.tenantId) return false;
25
+ if (audience.organizationScopes.length > 0) {
26
+ if (!audience.organizationScopes.includes(conn.organizationId)) return false;
27
+ }
28
+ return true;
29
+ }
30
+ const portalConnections = /* @__PURE__ */ new Set();
31
+ let portalTapRegistered = false;
32
+ async function broadcastPortalEvent(eventName, payload) {
33
+ if (!eventName || portalConnections.size === 0) return;
34
+ if (!isPortalBroadcastEvent(eventName)) return;
35
+ const data = payload ?? {};
36
+ const audience = normalizeAudience(data);
37
+ const organizationId = audience.organizationScopes[0] ?? "";
38
+ let ssePayload = JSON.stringify({
39
+ id: eventName,
40
+ payload: data,
41
+ timestamp: Date.now(),
42
+ organizationId
43
+ });
44
+ if (new TextEncoder().encode(ssePayload).length > MAX_PAYLOAD_BYTES) {
45
+ const entityRef = { truncated: true };
46
+ if (typeof data.id === "string" && data.id.trim().length > 0) entityRef.id = data.id.trim();
47
+ if (typeof data.entityId === "string" && data.entityId.trim().length > 0) entityRef.entityId = data.entityId.trim();
48
+ ssePayload = JSON.stringify({
49
+ id: eventName,
50
+ payload: entityRef,
51
+ timestamp: Date.now(),
52
+ organizationId
53
+ });
54
+ if (new TextEncoder().encode(ssePayload).length > MAX_PAYLOAD_BYTES) {
55
+ return;
56
+ }
57
+ }
58
+ for (const conn of portalConnections) {
59
+ if (!matchesAudience(conn, audience)) continue;
60
+ try {
61
+ conn.send(ssePayload);
62
+ } catch {
63
+ }
64
+ }
65
+ }
66
+ function ensurePortalTap() {
67
+ if (portalTapRegistered) return;
68
+ portalTapRegistered = true;
69
+ import("@open-mercato/events/bus").then(({ registerGlobalEventTap, registerCrossProcessEventListener }) => {
70
+ registerGlobalEventTap(async (eventName, payload) => {
71
+ await broadcastPortalEvent(eventName, payload ?? {});
72
+ });
73
+ registerCrossProcessEventListener(async (envelope) => {
74
+ if (envelope.originPid === process.pid) return;
75
+ await broadcastPortalEvent(
76
+ envelope.event,
77
+ envelope.payload ?? {}
78
+ );
79
+ });
80
+ }).catch(() => {
81
+ portalTapRegistered = false;
82
+ });
83
+ }
84
+ async function GET(req) {
85
+ const auth = await getCustomerAuthFromRequest(req);
86
+ if (!auth) {
87
+ return NextResponse.json({ ok: false, error: "Authentication required" }, { status: 401 });
88
+ }
89
+ ensurePortalTap();
90
+ const encoder = new TextEncoder();
91
+ let heartbeatTimer = null;
92
+ let connection = null;
93
+ const stream = new ReadableStream({
94
+ start(controller) {
95
+ const send = (data) => {
96
+ controller.enqueue(encoder.encode(`data: ${data}
97
+
98
+ `));
99
+ };
100
+ connection = {
101
+ tenantId: auth.tenantId,
102
+ organizationId: auth.orgId,
103
+ customerUserId: auth.sub,
104
+ send,
105
+ close: () => controller.close()
106
+ };
107
+ portalConnections.add(connection);
108
+ heartbeatTimer = setInterval(() => {
109
+ try {
110
+ controller.enqueue(encoder.encode(":heartbeat\n\n"));
111
+ } catch {
112
+ }
113
+ }, HEARTBEAT_INTERVAL_MS);
114
+ },
115
+ cancel() {
116
+ cleanup();
117
+ }
118
+ });
119
+ function cleanup() {
120
+ if (heartbeatTimer) {
121
+ clearInterval(heartbeatTimer);
122
+ heartbeatTimer = null;
123
+ }
124
+ if (connection) {
125
+ portalConnections.delete(connection);
126
+ connection = null;
127
+ }
128
+ }
129
+ req.signal.addEventListener("abort", cleanup);
130
+ return new Response(stream, {
131
+ status: 200,
132
+ headers: {
133
+ "Content-Type": "text/event-stream",
134
+ "Cache-Control": "no-cache, no-transform",
135
+ "Connection": "keep-alive",
136
+ "X-Accel-Buffering": "no"
137
+ }
138
+ });
139
+ }
140
+ const methodDoc = {
141
+ summary: "Subscribe to portal events via SSE (Portal Event Bridge)",
142
+ description: "Long-lived SSE connection that receives server-side events marked with portalBroadcast: true. Events are filtered by the customer's tenant and organization.",
143
+ tags: ["Customer Portal"],
144
+ responses: [
145
+ {
146
+ status: 200,
147
+ description: "Event stream (text/event-stream)"
148
+ }
149
+ ],
150
+ errors: [
151
+ { status: 401, description: "Not authenticated" }
152
+ ]
153
+ };
154
+ const openApi = {
155
+ summary: "Portal event stream",
156
+ methods: { GET: methodDoc }
157
+ };
158
+ export {
159
+ GET,
160
+ metadata,
161
+ openApi
162
+ };
163
+ //# sourceMappingURL=stream.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/customer_accounts/api/portal/events/stream.ts"],
4
+ "sourcesContent": ["/**\n * Portal SSE Event Stream \u2014 Portal Event Bridge\n *\n * Server-Sent Events endpoint that bridges server-side events to the customer portal.\n * Only events with `portalBroadcast: true` in their EventDefinition are sent.\n * Events are scoped to the authenticated customer's tenant and organization.\n *\n * Uses customer JWT auth (cookie or Bearer token) instead of staff auth.\n *\n * Client consumer: `packages/ui/src/portal/hooks/usePortalEventBridge.ts`\n */\n\nimport { NextResponse } from 'next/server'\nimport { isPortalBroadcastEvent } from '@open-mercato/shared/modules/events'\nimport { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata: { path?: string } = {}\n\nconst HEARTBEAT_INTERVAL_MS = 30_000\nconst MAX_PAYLOAD_BYTES = 4096\n\ntype PortalSseConnection = {\n tenantId: string\n organizationId: string\n customerUserId: string\n send: (data: string) => void\n close: () => void\n}\n\nfunction normalizeAudience(data: Record<string, unknown>): {\n tenantId: string | null\n organizationScopes: string[]\n} {\n const tenantId = typeof data.tenantId === 'string' ? data.tenantId : null\n const organizationScopes = new Set<string>()\n if (typeof data.organizationId === 'string' && data.organizationId.trim().length > 0) {\n organizationScopes.add(data.organizationId.trim())\n }\n if (Array.isArray(data.organizationIds)) {\n for (const orgId of data.organizationIds) {\n if (typeof orgId === 'string' && orgId.trim().length > 0) {\n organizationScopes.add(orgId.trim())\n }\n }\n }\n return { tenantId, organizationScopes: Array.from(organizationScopes) }\n}\n\nfunction matchesAudience(conn: PortalSseConnection, audience: ReturnType<typeof normalizeAudience>): boolean {\n if (!audience.tenantId) return false\n if (conn.tenantId !== audience.tenantId) return false\n if (audience.organizationScopes.length > 0) {\n if (!audience.organizationScopes.includes(conn.organizationId)) return false\n }\n return true\n}\n\nconst portalConnections = new Set<PortalSseConnection>()\n\nlet portalTapRegistered = false\n\nasync function broadcastPortalEvent(eventName: string, payload: Record<string, unknown>): Promise<void> {\n if (!eventName || portalConnections.size === 0) return\n if (!isPortalBroadcastEvent(eventName)) return\n\n const data = payload ?? {}\n const audience = normalizeAudience(data)\n const organizationId = audience.organizationScopes[0] ?? ''\n\n let ssePayload = JSON.stringify({\n id: eventName,\n payload: data,\n timestamp: Date.now(),\n organizationId,\n })\n\n if (new TextEncoder().encode(ssePayload).length > MAX_PAYLOAD_BYTES) {\n const entityRef: Record<string, unknown> = { truncated: true }\n if (typeof data.id === 'string' && data.id.trim().length > 0) entityRef.id = data.id.trim()\n if (typeof data.entityId === 'string' && data.entityId.trim().length > 0) entityRef.entityId = data.entityId.trim()\n ssePayload = JSON.stringify({\n id: eventName,\n payload: entityRef,\n timestamp: Date.now(),\n organizationId,\n })\n if (new TextEncoder().encode(ssePayload).length > MAX_PAYLOAD_BYTES) {\n return\n }\n }\n\n for (const conn of portalConnections) {\n if (!matchesAudience(conn, audience)) continue\n try {\n conn.send(ssePayload)\n } catch {\n // Connection may have been closed\n }\n }\n}\n\nfunction ensurePortalTap(): void {\n if (portalTapRegistered) return\n portalTapRegistered = true\n\n // Dynamically import to avoid circular dependency \u2014 the events bus\n // registers a global tap that fires for every emitted event.\n import('@open-mercato/events/bus').then(({ registerGlobalEventTap, registerCrossProcessEventListener }) => {\n registerGlobalEventTap(async (eventName, payload) => {\n await broadcastPortalEvent(eventName, (payload ?? {}) as Record<string, unknown>)\n })\n\n registerCrossProcessEventListener(async (envelope) => {\n if (envelope.originPid === process.pid) return\n await broadcastPortalEvent(\n envelope.event,\n (envelope.payload ?? {}) as Record<string, unknown>,\n )\n })\n }).catch(() => {\n // Silently ignore if events package is not available\n portalTapRegistered = false\n })\n}\n\nexport async function GET(req: Request): Promise<Response> {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n ensurePortalTap()\n\n const encoder = new TextEncoder()\n let heartbeatTimer: ReturnType<typeof setInterval> | null = null\n let connection: PortalSseConnection | null = null\n\n const stream = new ReadableStream({\n start(controller) {\n const send = (data: string) => {\n controller.enqueue(encoder.encode(`data: ${data}\\n\\n`))\n }\n\n connection = {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n customerUserId: auth.sub,\n send,\n close: () => controller.close(),\n }\n portalConnections.add(connection)\n\n heartbeatTimer = setInterval(() => {\n try {\n controller.enqueue(encoder.encode(':heartbeat\\n\\n'))\n } catch {\n // Stream may have been closed\n }\n }, HEARTBEAT_INTERVAL_MS)\n },\n cancel() {\n cleanup()\n },\n })\n\n function cleanup() {\n if (heartbeatTimer) {\n clearInterval(heartbeatTimer)\n heartbeatTimer = null\n }\n if (connection) {\n portalConnections.delete(connection)\n connection = null\n }\n }\n\n req.signal.addEventListener('abort', cleanup)\n\n return new Response(stream, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache, no-transform',\n 'Connection': 'keep-alive',\n 'X-Accel-Buffering': 'no',\n },\n })\n}\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Subscribe to portal events via SSE (Portal Event Bridge)',\n description: 'Long-lived SSE connection that receives server-side events marked with portalBroadcast: true. Events are filtered by the customer\\'s tenant and organization.',\n tags: ['Customer Portal'],\n responses: [\n {\n status: 200,\n description: 'Event stream (text/event-stream)',\n },\n ],\n errors: [\n { status: 401, description: 'Not authenticated' },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Portal event stream',\n methods: { GET: methodDoc },\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,57 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { getCustomerAuthFromRequest } from "@open-mercato/core/modules/customer_accounts/lib/customerAuth";
4
+ import { matchFeature } from "@open-mercato/shared/lib/auth/featureMatch";
5
+ const metadata = {};
6
+ const requestSchema = z.object({
7
+ features: z.array(z.string()).min(1).max(100)
8
+ });
9
+ async function POST(req) {
10
+ const auth = await getCustomerAuthFromRequest(req);
11
+ if (!auth) {
12
+ return NextResponse.json({ ok: false, error: "Authentication required" }, { status: 401 });
13
+ }
14
+ let body;
15
+ try {
16
+ const raw = await req.json();
17
+ body = requestSchema.parse(raw);
18
+ } catch {
19
+ return NextResponse.json({ ok: false, error: "Invalid request body" }, { status: 400 });
20
+ }
21
+ const granted = body.features.filter(
22
+ (feature) => auth.resolvedFeatures.some((g) => matchFeature(feature, g))
23
+ );
24
+ return NextResponse.json({ ok: true, granted });
25
+ }
26
+ const methodDoc = {
27
+ summary: "Check customer portal feature access",
28
+ description: "Checks which of the requested features the authenticated customer user has. Used by portal menu injection for feature-gating.",
29
+ tags: ["Customer Portal"],
30
+ requestBody: {
31
+ schema: requestSchema
32
+ },
33
+ responses: [
34
+ {
35
+ status: 200,
36
+ description: "Feature check result",
37
+ schema: z.object({
38
+ ok: z.literal(true),
39
+ granted: z.array(z.string())
40
+ })
41
+ }
42
+ ],
43
+ errors: [
44
+ { status: 401, description: "Not authenticated", schema: z.object({ ok: z.literal(false), error: z.string() }) },
45
+ { status: 400, description: "Invalid request", schema: z.object({ ok: z.literal(false), error: z.string() }) }
46
+ ]
47
+ };
48
+ const openApi = {
49
+ summary: "Portal feature check",
50
+ methods: { POST: methodDoc }
51
+ };
52
+ export {
53
+ POST,
54
+ metadata,
55
+ openApi
56
+ };
57
+ //# sourceMappingURL=feature-check.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/customer_accounts/api/portal/feature-check.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport { hasAllFeatures, matchFeature } from '@open-mercato/shared/lib/auth/featureMatch'\n\nexport const metadata: { path?: string } = {}\n\nconst requestSchema = z.object({\n features: z.array(z.string()).min(1).max(100),\n})\n\nexport async function POST(req: Request) {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n let body: z.infer<typeof requestSchema>\n try {\n const raw = await req.json()\n body = requestSchema.parse(raw)\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const granted = body.features.filter((feature) =>\n auth.resolvedFeatures.some((g) => matchFeature(feature, g)),\n )\n\n return NextResponse.json({ ok: true, granted })\n}\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Check customer portal feature access',\n description: 'Checks which of the requested features the authenticated customer user has. Used by portal menu injection for feature-gating.',\n tags: ['Customer Portal'],\n requestBody: {\n schema: requestSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Feature check result',\n schema: z.object({\n ok: z.literal(true),\n granted: z.array(z.string()),\n }),\n },\n ],\n errors: [\n { status: 401, description: 'Not authenticated', schema: z.object({ ok: z.literal(false), error: z.string() }) },\n { status: 400, description: 'Invalid request', schema: z.object({ ok: z.literal(false), error: z.string() }) },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Portal feature check',\n methods: { POST: methodDoc },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,kCAAkC;AAC3C,SAAyB,oBAAoB;AAEtC,MAAM,WAA8B,CAAC;AAE5C,MAAM,gBAAgB,EAAE,OAAO;AAAA,EAC7B,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAC9C,CAAC;AAED,eAAsB,KAAK,KAAc;AACvC,QAAM,OAAO,MAAM,2BAA2B,GAAG;AACjD,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,0BAA0B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3F;AAEA,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,MAAM,IAAI,KAAK;AAC3B,WAAO,cAAc,MAAM,GAAG;AAAA,EAChC,QAAQ;AACN,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACxF;AAEA,QAAM,UAAU,KAAK,SAAS;AAAA,IAAO,CAAC,YACpC,KAAK,iBAAiB,KAAK,CAAC,MAAM,aAAa,SAAS,CAAC,CAAC;AAAA,EAC5D;AAEA,SAAO,aAAa,KAAK,EAAE,IAAI,MAAM,QAAQ,CAAC;AAChD;AAEA,MAAM,YAA8B;AAAA,EAClC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,iBAAiB;AAAA,EACxB,aAAa;AAAA,IACX,QAAQ;AAAA,EACV;AAAA,EACA,WAAW;AAAA,IACT;AAAA,MACE,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,QAAQ,EAAE,OAAO;AAAA,QACf,IAAI,EAAE,QAAQ,IAAI;AAAA,QAClB,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,MAC7B,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,KAAK,GAAG,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,IAC/G,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,KAAK,GAAG,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,EAC/G;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,SAAS;AAAA,EACT,SAAS,EAAE,MAAM,UAAU;AAC7B;",
6
+ "names": []
7
+ }
@@ -0,0 +1,64 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
+ const metadata = {};
5
+ function readCookieFromHeader(header, name) {
6
+ if (!header) return void 0;
7
+ const parts = header.split(";");
8
+ for (const part of parts) {
9
+ const trimmed = part.trim();
10
+ if (trimmed.startsWith(`${name}=`)) {
11
+ return trimmed.slice(name.length + 1);
12
+ }
13
+ }
14
+ return void 0;
15
+ }
16
+ async function POST(req) {
17
+ const cookieHeader = req.headers.get("cookie") || "";
18
+ const sessionToken = readCookieFromHeader(cookieHeader, "customer_session_token");
19
+ if (sessionToken) {
20
+ try {
21
+ const decodedToken = decodeURIComponent(sessionToken);
22
+ const container = await createRequestContainer();
23
+ const customerSessionService = container.resolve("customerSessionService");
24
+ const session = await customerSessionService.findByToken(decodedToken);
25
+ if (session) {
26
+ await customerSessionService.revokeSession(session.id);
27
+ }
28
+ } catch {
29
+ }
30
+ }
31
+ const res = NextResponse.json({ ok: true });
32
+ res.cookies.set("customer_auth_token", "", {
33
+ httpOnly: true,
34
+ path: "/",
35
+ sameSite: "lax",
36
+ secure: process.env.NODE_ENV === "production",
37
+ maxAge: 0
38
+ });
39
+ res.cookies.set("customer_session_token", "", {
40
+ httpOnly: true,
41
+ path: "/",
42
+ sameSite: "lax",
43
+ secure: process.env.NODE_ENV === "production",
44
+ maxAge: 0
45
+ });
46
+ return res;
47
+ }
48
+ const successSchema = z.object({ ok: z.literal(true) });
49
+ const methodDoc = {
50
+ summary: "Customer logout",
51
+ description: "Revokes the current session and clears authentication cookies.",
52
+ tags: ["Customer Portal"],
53
+ responses: [{ status: 200, description: "Logged out", schema: successSchema }]
54
+ };
55
+ const openApi = {
56
+ summary: "Customer logout",
57
+ methods: { POST: methodDoc }
58
+ };
59
+ export {
60
+ POST,
61
+ metadata,
62
+ openApi
63
+ };
64
+ //# sourceMappingURL=logout.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/customer_accounts/api/portal/logout.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\n\nexport const metadata: { path?: string } = {}\n\nfunction readCookieFromHeader(header: string | null | undefined, name: string): string | undefined {\n if (!header) return undefined\n const parts = header.split(';')\n for (const part of parts) {\n const trimmed = part.trim()\n if (trimmed.startsWith(`${name}=`)) {\n return trimmed.slice(name.length + 1)\n }\n }\n return undefined\n}\n\nexport async function POST(req: Request) {\n const cookieHeader = req.headers.get('cookie') || ''\n const sessionToken = readCookieFromHeader(cookieHeader, 'customer_session_token')\n\n if (sessionToken) {\n try {\n const decodedToken = decodeURIComponent(sessionToken)\n const container = await createRequestContainer()\n const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService\n const session = await customerSessionService.findByToken(decodedToken)\n if (session) {\n await customerSessionService.revokeSession(session.id)\n }\n } catch {\n // Best effort \u2014 clear cookies regardless\n }\n }\n\n const res = NextResponse.json({ ok: true })\n\n res.cookies.set('customer_auth_token', '', {\n httpOnly: true,\n path: '/',\n sameSite: 'lax',\n secure: process.env.NODE_ENV === 'production',\n maxAge: 0,\n })\n res.cookies.set('customer_session_token', '', {\n httpOnly: true,\n path: '/',\n sameSite: 'lax',\n secure: process.env.NODE_ENV === 'production',\n maxAge: 0,\n })\n\n return res\n}\n\nconst successSchema = z.object({ ok: z.literal(true) })\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Customer logout',\n description: 'Revokes the current session and clears authentication cookies.',\n tags: ['Customer Portal'],\n responses: [{ status: 200, description: 'Logged out', schema: successSchema }],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Customer logout',\n methods: { POST: methodDoc },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AAGhC,MAAM,WAA8B,CAAC;AAE5C,SAAS,qBAAqB,QAAmC,MAAkC;AACjG,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,QAAQ,OAAO,MAAM,GAAG;AAC9B,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,QAAQ,WAAW,GAAG,IAAI,GAAG,GAAG;AAClC,aAAO,QAAQ,MAAM,KAAK,SAAS,CAAC;AAAA,IACtC;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,KAAK,KAAc;AACvC,QAAM,eAAe,IAAI,QAAQ,IAAI,QAAQ,KAAK;AAClD,QAAM,eAAe,qBAAqB,cAAc,wBAAwB;AAEhF,MAAI,cAAc;AAChB,QAAI;AACF,YAAM,eAAe,mBAAmB,YAAY;AACpD,YAAM,YAAY,MAAM,uBAAuB;AAC/C,YAAM,yBAAyB,UAAU,QAAQ,wBAAwB;AACzE,YAAM,UAAU,MAAM,uBAAuB,YAAY,YAAY;AACrE,UAAI,SAAS;AACX,cAAM,uBAAuB,cAAc,QAAQ,EAAE;AAAA,MACvD;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,MAAM,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAE1C,MAAI,QAAQ,IAAI,uBAAuB,IAAI;AAAA,IACzC,UAAU;AAAA,IACV,MAAM;AAAA,IACN,UAAU;AAAA,IACV,QAAQ,QAAQ,IAAI,aAAa;AAAA,IACjC,QAAQ;AAAA,EACV,CAAC;AACD,MAAI,QAAQ,IAAI,0BAA0B,IAAI;AAAA,IAC5C,UAAU;AAAA,IACV,MAAM;AAAA,IACN,UAAU;AAAA,IACV,QAAQ,QAAQ,IAAI,aAAa;AAAA,IACjC,QAAQ;AAAA,EACV,CAAC;AAED,SAAO;AACT;AAEA,MAAM,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;AAEtD,MAAM,YAA8B;AAAA,EAClC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,iBAAiB;AAAA,EACxB,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,cAAc,QAAQ,cAAc,CAAC;AAC/E;AAEO,MAAM,UAA2B;AAAA,EACtC,SAAS;AAAA,EACT,SAAS,EAAE,MAAM,UAAU;AAC7B;",
6
+ "names": []
7
+ }