@open-mercato/core 0.6.1-develop.3276.1.1fcd145078 → 0.6.1-develop.3287.1.450f4ffb56

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 (266) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/domain_mapping/index.js +31 -0
  3. package/dist/generated/entities/domain_mapping/index.js.map +7 -0
  4. package/dist/generated/entities.ids.generated.js +2 -1
  5. package/dist/generated/entities.ids.generated.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +16 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/helpers/integration/auth.js +1 -1
  9. package/dist/helpers/integration/auth.js.map +2 -2
  10. package/dist/helpers/integration/authUi.js +1 -1
  11. package/dist/helpers/integration/authUi.js.map +2 -2
  12. package/dist/helpers/integration/salesUi.js +18 -17
  13. package/dist/helpers/integration/salesUi.js.map +2 -2
  14. package/dist/modules/auth/frontend/login.js +4 -4
  15. package/dist/modules/auth/frontend/login.js.map +2 -2
  16. package/dist/modules/auth/frontend/reset/[token]/page.js +5 -5
  17. package/dist/modules/auth/frontend/reset/[token]/page.js.map +2 -2
  18. package/dist/modules/auth/frontend/reset.js +2 -2
  19. package/dist/modules/auth/frontend/reset.js.map +2 -2
  20. package/dist/modules/customer_accounts/acl.js +2 -1
  21. package/dist/modules/customer_accounts/acl.js.map +2 -2
  22. package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.js +110 -0
  23. package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.js.map +7 -0
  24. package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.js +114 -0
  25. package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.js.map +7 -0
  26. package/dist/modules/customer_accounts/api/admin/domain-mappings.js +271 -0
  27. package/dist/modules/customer_accounts/api/admin/domain-mappings.js.map +7 -0
  28. package/dist/modules/customer_accounts/api/domain-check.js +70 -0
  29. package/dist/modules/customer_accounts/api/domain-check.js.map +7 -0
  30. package/dist/modules/customer_accounts/api/domain-resolve/all.js +128 -0
  31. package/dist/modules/customer_accounts/api/domain-resolve/all.js.map +7 -0
  32. package/dist/modules/customer_accounts/api/domain-resolve.js +84 -0
  33. package/dist/modules/customer_accounts/api/domain-resolve.js.map +7 -0
  34. package/dist/modules/customer_accounts/api/login.js +14 -3
  35. package/dist/modules/customer_accounts/api/login.js.map +2 -2
  36. package/dist/modules/customer_accounts/api/magic-link/request.js +14 -3
  37. package/dist/modules/customer_accounts/api/magic-link/request.js.map +2 -2
  38. package/dist/modules/customer_accounts/api/password/reset-request.js +14 -3
  39. package/dist/modules/customer_accounts/api/password/reset-request.js.map +2 -2
  40. package/dist/modules/customer_accounts/api/signup.js +32 -5
  41. package/dist/modules/customer_accounts/api/signup.js.map +2 -2
  42. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.js +48 -0
  43. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.js.map +7 -0
  44. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +43 -0
  45. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +7 -0
  46. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.js +53 -0
  47. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.js.map +7 -0
  48. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.js +69 -0
  49. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.js.map +7 -0
  50. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.js +31 -0
  51. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.js.map +7 -0
  52. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.js +147 -0
  53. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.js.map +7 -0
  54. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.js +39 -0
  55. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.js.map +7 -0
  56. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.js +60 -0
  57. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.js.map +7 -0
  58. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.js +11 -0
  59. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.js.map +7 -0
  60. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.js +1 -0
  61. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.js.map +7 -0
  62. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js +361 -0
  63. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js.map +7 -0
  64. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.js +28 -0
  65. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.js.map +7 -0
  66. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +4 -3
  67. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  68. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +6 -5
  69. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  70. package/dist/modules/customer_accounts/data/enrichers.js +78 -12
  71. package/dist/modules/customer_accounts/data/enrichers.js.map +2 -2
  72. package/dist/modules/customer_accounts/data/entities.js +85 -2
  73. package/dist/modules/customer_accounts/data/entities.js.map +2 -2
  74. package/dist/modules/customer_accounts/data/guards.js +130 -0
  75. package/dist/modules/customer_accounts/data/guards.js.map +7 -0
  76. package/dist/modules/customer_accounts/data/validators.js +19 -0
  77. package/dist/modules/customer_accounts/data/validators.js.map +2 -2
  78. package/dist/modules/customer_accounts/di.js +23 -1
  79. package/dist/modules/customer_accounts/di.js.map +2 -2
  80. package/dist/modules/customer_accounts/events.js +9 -2
  81. package/dist/modules/customer_accounts/events.js.map +2 -2
  82. package/dist/modules/customer_accounts/lib/customerAuthServer.js +27 -7
  83. package/dist/modules/customer_accounts/lib/customerAuthServer.js.map +2 -2
  84. package/dist/modules/customer_accounts/lib/customerUrl.js +48 -0
  85. package/dist/modules/customer_accounts/lib/customerUrl.js.map +7 -0
  86. package/dist/modules/customer_accounts/lib/hostname.js +55 -0
  87. package/dist/modules/customer_accounts/lib/hostname.js.map +7 -0
  88. package/dist/modules/customer_accounts/lib/platformDomains.js +7 -0
  89. package/dist/modules/customer_accounts/lib/platformDomains.js.map +7 -0
  90. package/dist/modules/customer_accounts/lib/proxyRanges.js +71 -0
  91. package/dist/modules/customer_accounts/lib/proxyRanges.js.map +7 -0
  92. package/dist/modules/customer_accounts/lib/rateLimiter.js +7 -0
  93. package/dist/modules/customer_accounts/lib/rateLimiter.js.map +2 -2
  94. package/dist/modules/customer_accounts/lib/resolveTenantContext.js +63 -0
  95. package/dist/modules/customer_accounts/lib/resolveTenantContext.js.map +7 -0
  96. package/dist/modules/customer_accounts/lib/secretCompare.js +13 -0
  97. package/dist/modules/customer_accounts/lib/secretCompare.js.map +7 -0
  98. package/dist/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.js +37 -0
  99. package/dist/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.js.map +7 -0
  100. package/dist/modules/customer_accounts/notifications.client.js +76 -0
  101. package/dist/modules/customer_accounts/notifications.client.js.map +2 -2
  102. package/dist/modules/customer_accounts/notifications.js +76 -0
  103. package/dist/modules/customer_accounts/notifications.js.map +2 -2
  104. package/dist/modules/customer_accounts/services/domainMappingService.js +497 -0
  105. package/dist/modules/customer_accounts/services/domainMappingService.js.map +7 -0
  106. package/dist/modules/customer_accounts/setup.js +48 -1
  107. package/dist/modules/customer_accounts/setup.js.map +2 -2
  108. package/dist/modules/customer_accounts/subscribers/invalidateDomainCache.js +29 -0
  109. package/dist/modules/customer_accounts/subscribers/invalidateDomainCache.js.map +7 -0
  110. package/dist/modules/customer_accounts/subscribers/notifyDomainActivated.js +29 -0
  111. package/dist/modules/customer_accounts/subscribers/notifyDomainActivated.js.map +7 -0
  112. package/dist/modules/customer_accounts/subscribers/notifyDomainDnsFailed.js +30 -0
  113. package/dist/modules/customer_accounts/subscribers/notifyDomainDnsFailed.js.map +7 -0
  114. package/dist/modules/customer_accounts/subscribers/notifyDomainTlsFailed.js +31 -0
  115. package/dist/modules/customer_accounts/subscribers/notifyDomainTlsFailed.js.map +7 -0
  116. package/dist/modules/customer_accounts/subscribers/notifyDomainVerified.js +29 -0
  117. package/dist/modules/customer_accounts/subscribers/notifyDomainVerified.js.map +7 -0
  118. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -2
  119. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  120. package/dist/modules/customer_accounts/workers/domainTlsRetryWorker.js +67 -0
  121. package/dist/modules/customer_accounts/workers/domainTlsRetryWorker.js.map +7 -0
  122. package/dist/modules/customer_accounts/workers/domainVerificationWorker.js +34 -0
  123. package/dist/modules/customer_accounts/workers/domainVerificationWorker.js.map +7 -0
  124. package/dist/modules/customers/api/companies/route.js +4 -9
  125. package/dist/modules/customers/api/companies/route.js.map +2 -2
  126. package/dist/modules/customers/api/deals/route.js +4 -9
  127. package/dist/modules/customers/api/deals/route.js.map +2 -2
  128. package/dist/modules/customers/api/people/route.js +4 -9
  129. package/dist/modules/customers/api/people/route.js.map +2 -2
  130. package/dist/modules/customers/api/utils.js +0 -12
  131. package/dist/modules/customers/api/utils.js.map +2 -2
  132. package/dist/modules/customers/backend/customers/companies/page.js +361 -284
  133. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  134. package/dist/modules/customers/backend/customers/deals/page.js +378 -473
  135. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  136. package/dist/modules/customers/backend/customers/people/page.js +368 -286
  137. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  138. package/dist/modules/customers/components/detail/assignableStaff.js +15 -1
  139. package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
  140. package/dist/modules/customers/components/formConfig.js +2 -2
  141. package/dist/modules/customers/components/formConfig.js.map +2 -2
  142. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +188 -36
  143. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
  144. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js +2 -2
  145. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js.map +2 -2
  146. package/dist/modules/customers/widgets/injection-table.js +6 -0
  147. package/dist/modules/customers/widgets/injection-table.js.map +2 -2
  148. package/dist/modules/integrations/backend/integrations/[id]/page.js +2 -3
  149. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  150. package/dist/modules/perspectives/services/perspectiveService.js +16 -2
  151. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  152. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +5 -3
  153. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
  154. package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js +3 -3
  155. package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js.map +2 -2
  156. package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js +4 -2
  157. package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js.map +2 -2
  158. package/dist/modules/query_index/lib/engine.js +31 -8
  159. package/dist/modules/query_index/lib/engine.js.map +2 -2
  160. package/dist/modules/sales/backend/sales/documents/[id]/page.js +3 -5
  161. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  162. package/dist/modules/sales/components/ProviderFieldInput.js +2 -2
  163. package/dist/modules/sales/components/ProviderFieldInput.js.map +2 -2
  164. package/dist/modules/sales/components/documents/SalesDocumentForm.js +3 -4
  165. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  166. package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js +3 -4
  167. package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js.map +2 -2
  168. package/dist/modules/workflows/backend/tasks/[id]/page.js +2 -2
  169. package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
  170. package/dist/modules/workflows/frontend/checkout-demo/page.js +2 -2
  171. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  172. package/generated/entities/domain_mapping/index.ts +14 -0
  173. package/generated/entities.ids.generated.ts +2 -1
  174. package/generated/entity-fields-registry.ts +16 -0
  175. package/package.json +5 -5
  176. package/src/helpers/integration/auth.ts +1 -1
  177. package/src/helpers/integration/authUi.ts +1 -1
  178. package/src/helpers/integration/salesUi.ts +27 -23
  179. package/src/modules/auth/frontend/login.tsx +4 -3
  180. package/src/modules/auth/frontend/reset/[token]/page.tsx +4 -4
  181. package/src/modules/auth/frontend/reset.tsx +2 -2
  182. package/src/modules/customer_accounts/acl.ts +1 -0
  183. package/src/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.ts +119 -0
  184. package/src/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.ts +123 -0
  185. package/src/modules/customer_accounts/api/admin/domain-mappings.ts +303 -0
  186. package/src/modules/customer_accounts/api/domain-check.ts +83 -0
  187. package/src/modules/customer_accounts/api/domain-resolve/all.ts +139 -0
  188. package/src/modules/customer_accounts/api/domain-resolve.ts +91 -0
  189. package/src/modules/customer_accounts/api/login.ts +14 -3
  190. package/src/modules/customer_accounts/api/magic-link/request.ts +14 -3
  191. package/src/modules/customer_accounts/api/password/reset-request.ts +15 -3
  192. package/src/modules/customer_accounts/api/signup.ts +36 -5
  193. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.tsx +70 -0
  194. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +66 -0
  195. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.tsx +78 -0
  196. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.tsx +90 -0
  197. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.tsx +43 -0
  198. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.tsx +178 -0
  199. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.tsx +56 -0
  200. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.tsx +69 -0
  201. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.ts +10 -0
  202. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.ts +31 -0
  203. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.ts +26 -0
  204. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.tsx +411 -0
  205. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +3 -2
  206. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +5 -4
  207. package/src/modules/customer_accounts/data/enrichers.ts +95 -12
  208. package/src/modules/customer_accounts/data/entities.ts +80 -1
  209. package/src/modules/customer_accounts/data/guards.ts +155 -0
  210. package/src/modules/customer_accounts/data/validators.ts +26 -0
  211. package/src/modules/customer_accounts/di.ts +35 -1
  212. package/src/modules/customer_accounts/events.ts +8 -1
  213. package/src/modules/customer_accounts/i18n/de.json +78 -0
  214. package/src/modules/customer_accounts/i18n/en.json +78 -0
  215. package/src/modules/customer_accounts/i18n/es.json +78 -0
  216. package/src/modules/customer_accounts/i18n/pl.json +78 -0
  217. package/src/modules/customer_accounts/lib/customerAuthServer.ts +44 -8
  218. package/src/modules/customer_accounts/lib/customerUrl.ts +72 -0
  219. package/src/modules/customer_accounts/lib/hostname.ts +76 -0
  220. package/src/modules/customer_accounts/lib/platformDomains.ts +14 -0
  221. package/src/modules/customer_accounts/lib/proxyRanges.ts +85 -0
  222. package/src/modules/customer_accounts/lib/rateLimiter.ts +10 -0
  223. package/src/modules/customer_accounts/lib/resolveTenantContext.ts +96 -0
  224. package/src/modules/customer_accounts/lib/secretCompare.ts +20 -0
  225. package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +341 -0
  226. package/src/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.ts +38 -0
  227. package/src/modules/customer_accounts/notifications.client.ts +76 -0
  228. package/src/modules/customer_accounts/notifications.ts +76 -0
  229. package/src/modules/customer_accounts/services/domainMappingService.ts +632 -0
  230. package/src/modules/customer_accounts/setup.ts +81 -1
  231. package/src/modules/customer_accounts/subscribers/invalidateDomainCache.ts +35 -0
  232. package/src/modules/customer_accounts/subscribers/notifyDomainActivated.ts +32 -0
  233. package/src/modules/customer_accounts/subscribers/notifyDomainDnsFailed.ts +33 -0
  234. package/src/modules/customer_accounts/subscribers/notifyDomainTlsFailed.ts +34 -0
  235. package/src/modules/customer_accounts/subscribers/notifyDomainVerified.ts +32 -0
  236. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +2 -2
  237. package/src/modules/customer_accounts/workers/domainTlsRetryWorker.ts +84 -0
  238. package/src/modules/customer_accounts/workers/domainVerificationWorker.ts +45 -0
  239. package/src/modules/customers/api/companies/route.ts +4 -9
  240. package/src/modules/customers/api/deals/route.ts +4 -9
  241. package/src/modules/customers/api/people/route.ts +4 -9
  242. package/src/modules/customers/api/utils.ts +0 -14
  243. package/src/modules/customers/backend/customers/companies/page.tsx +321 -237
  244. package/src/modules/customers/backend/customers/deals/page.tsx +321 -455
  245. package/src/modules/customers/backend/customers/people/page.tsx +331 -240
  246. package/src/modules/customers/components/detail/assignableStaff.ts +21 -0
  247. package/src/modules/customers/components/formConfig.tsx +2 -2
  248. package/src/modules/customers/i18n/de.json +31 -0
  249. package/src/modules/customers/i18n/en.json +31 -0
  250. package/src/modules/customers/i18n/es.json +31 -0
  251. package/src/modules/customers/i18n/pl.json +31 -0
  252. package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +197 -40
  253. package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.ts +13 -7
  254. package/src/modules/customers/widgets/injection-table.ts +6 -0
  255. package/src/modules/integrations/backend/integrations/[id]/page.tsx +2 -2
  256. package/src/modules/perspectives/services/perspectiveService.ts +30 -2
  257. package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +5 -3
  258. package/src/modules/portal/frontend/[orgSlug]/portal/reset-password/page.tsx +3 -3
  259. package/src/modules/portal/frontend/[orgSlug]/portal/signup/page.tsx +4 -2
  260. package/src/modules/query_index/lib/engine.ts +46 -8
  261. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +3 -5
  262. package/src/modules/sales/components/ProviderFieldInput.tsx +2 -2
  263. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +2 -3
  264. package/src/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.tsx +3 -4
  265. package/src/modules/workflows/backend/tasks/[id]/page.tsx +2 -2
  266. package/src/modules/workflows/frontend/checkout-demo/page.tsx +2 -2
@@ -0,0 +1,271 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { UniqueConstraintViolationException } from "@mikro-orm/core";
4
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
5
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
6
+ import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
7
+ import {
8
+ validateCrudMutationGuard,
9
+ runCrudMutationGuardAfterSuccess
10
+ } from "@open-mercato/shared/lib/crud/mutation-guard";
11
+ import { registerDomainSchema } from "@open-mercato/core/modules/customer_accounts/data/validators";
12
+ import { DomainMapping } from "@open-mercato/core/modules/customer_accounts/data/entities";
13
+ const FEATURE = "customer_accounts.domain.manage";
14
+ const metadata = {
15
+ GET: { requireAuth: true, requireFeatures: [FEATURE] },
16
+ POST: { requireAuth: true, requireFeatures: [FEATURE] },
17
+ DELETE: { requireAuth: true, requireFeatures: [FEATURE] }
18
+ };
19
+ function isUniqueViolation(error) {
20
+ if (error instanceof UniqueConstraintViolationException) return true;
21
+ if (!error || typeof error !== "object") return false;
22
+ const code = error.code;
23
+ if (code === "23505") return true;
24
+ const messageRaw = error.message;
25
+ const message = typeof messageRaw === "string" ? messageRaw : "";
26
+ return message.toLowerCase().includes("duplicate key");
27
+ }
28
+ function serializeRecord(record) {
29
+ return {
30
+ id: record.id,
31
+ hostname: record.hostname,
32
+ organizationId: record.organizationId,
33
+ tenantId: record.tenantId,
34
+ provider: record.provider,
35
+ status: record.status,
36
+ verifiedAt: record.verifiedAt?.toISOString() ?? null,
37
+ lastDnsCheckAt: record.lastDnsCheckAt?.toISOString() ?? null,
38
+ dnsFailureReason: record.dnsFailureReason ?? null,
39
+ tlsFailureReason: record.tlsFailureReason ?? null,
40
+ tlsRetryCount: record.tlsRetryCount,
41
+ cnameTarget: process.env.CUSTOM_DOMAIN_CNAME_TARGET ?? null,
42
+ aRecordTarget: process.env.CUSTOM_DOMAIN_A_RECORD_TARGET ?? null,
43
+ createdAt: record.createdAt.toISOString(),
44
+ updatedAt: record.updatedAt?.toISOString() ?? null
45
+ };
46
+ }
47
+ async function GET(req) {
48
+ const auth = await getAuthFromRequest(req);
49
+ if (!auth) return NextResponse.json({ ok: false, error: "Unauthorized" }, { status: 401 });
50
+ const container = await createRequestContainer();
51
+ const rbac = container.resolve("rbacService");
52
+ const allowed = await rbac.userHasAllFeatures(auth.sub, [FEATURE], {
53
+ tenantId: auth.tenantId,
54
+ organizationId: auth.orgId
55
+ });
56
+ if (!allowed) return NextResponse.json({ ok: false, error: "Forbidden" }, { status: 403 });
57
+ const url = new URL(req.url);
58
+ const orgFilter = url.searchParams.get("organizationId");
59
+ const em = container.resolve("em");
60
+ const where = { tenantId: auth.tenantId };
61
+ if (orgFilter) where.organizationId = orgFilter;
62
+ const records = await em.find(DomainMapping, where, { orderBy: { createdAt: "desc" } });
63
+ return NextResponse.json({
64
+ ok: true,
65
+ domainMappings: records.map(serializeRecord),
66
+ config: {
67
+ cnameTarget: process.env.CUSTOM_DOMAIN_CNAME_TARGET ?? null,
68
+ aRecordTarget: process.env.CUSTOM_DOMAIN_A_RECORD_TARGET ?? null
69
+ }
70
+ });
71
+ }
72
+ async function POST(req) {
73
+ const auth = await getAuthFromRequest(req);
74
+ if (!auth || !auth.tenantId) return NextResponse.json({ ok: false, error: "Unauthorized" }, { status: 401 });
75
+ const container = await createRequestContainer();
76
+ const rbac = container.resolve("rbacService");
77
+ const allowed = await rbac.userHasAllFeatures(auth.sub, [FEATURE], {
78
+ tenantId: auth.tenantId,
79
+ organizationId: auth.orgId
80
+ });
81
+ if (!allowed) return NextResponse.json({ ok: false, error: "Forbidden" }, { status: 403 });
82
+ const body = await readJsonSafe(req, {});
83
+ const parsed = registerDomainSchema.safeParse(body);
84
+ if (!parsed.success) {
85
+ return NextResponse.json(
86
+ { ok: false, error: "Invalid request", issues: parsed.error.flatten() },
87
+ { status: 400 }
88
+ );
89
+ }
90
+ const guardResult = await validateCrudMutationGuard(container, {
91
+ tenantId: auth.tenantId,
92
+ organizationId: parsed.data.organizationId,
93
+ userId: auth.sub,
94
+ resourceKind: "customer_accounts.domain_mapping",
95
+ resourceId: parsed.data.organizationId,
96
+ operation: "create",
97
+ requestMethod: req.method,
98
+ requestHeaders: req.headers,
99
+ mutationPayload: parsed.data
100
+ });
101
+ if (guardResult && !guardResult.ok) {
102
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
103
+ }
104
+ const service = container.resolve("domainMappingService");
105
+ let entity;
106
+ try {
107
+ entity = await service.register({
108
+ hostname: parsed.data.hostname,
109
+ organizationId: parsed.data.organizationId,
110
+ tenantId: auth.tenantId,
111
+ replacesDomainId: parsed.data.replacesDomainId
112
+ });
113
+ } catch (err) {
114
+ if (isUniqueViolation(err)) {
115
+ return NextResponse.json(
116
+ { ok: false, error: "This domain is not available. Please choose a different hostname." },
117
+ { status: 409 }
118
+ );
119
+ }
120
+ const message = err instanceof Error ? err.message : "Failed to register domain";
121
+ return NextResponse.json({ ok: false, error: message }, { status: 500 });
122
+ }
123
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
124
+ await runCrudMutationGuardAfterSuccess(container, {
125
+ tenantId: auth.tenantId,
126
+ organizationId: parsed.data.organizationId,
127
+ userId: auth.sub,
128
+ resourceKind: "customer_accounts.domain_mapping",
129
+ resourceId: entity.id,
130
+ operation: "create",
131
+ requestMethod: req.method,
132
+ requestHeaders: req.headers,
133
+ metadata: guardResult.metadata ?? null
134
+ });
135
+ }
136
+ return NextResponse.json({ ok: true, domainMapping: serializeRecord(entity) }, { status: 201 });
137
+ }
138
+ async function DELETE(req) {
139
+ const auth = await getAuthFromRequest(req);
140
+ if (!auth || !auth.tenantId) return NextResponse.json({ ok: false, error: "Unauthorized" }, { status: 401 });
141
+ const container = await createRequestContainer();
142
+ const rbac = container.resolve("rbacService");
143
+ const allowed = await rbac.userHasAllFeatures(auth.sub, [FEATURE], {
144
+ tenantId: auth.tenantId,
145
+ organizationId: auth.orgId
146
+ });
147
+ if (!allowed) return NextResponse.json({ ok: false, error: "Forbidden" }, { status: 403 });
148
+ const url = new URL(req.url);
149
+ const id = url.searchParams.get("id");
150
+ if (!id) return NextResponse.json({ ok: false, error: "id is required" }, { status: 400 });
151
+ const service = container.resolve("domainMappingService");
152
+ const existing = await service.findById(id, { tenantId: auth.tenantId });
153
+ if (!existing) return NextResponse.json({ ok: false, error: "Not found" }, { status: 404 });
154
+ const guardResult = await validateCrudMutationGuard(container, {
155
+ tenantId: auth.tenantId,
156
+ organizationId: existing.organizationId,
157
+ userId: auth.sub,
158
+ resourceKind: "customer_accounts.domain_mapping",
159
+ resourceId: id,
160
+ operation: "delete",
161
+ requestMethod: req.method,
162
+ requestHeaders: req.headers
163
+ });
164
+ if (guardResult && !guardResult.ok) {
165
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
166
+ }
167
+ await service.remove(id, { tenantId: auth.tenantId });
168
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
169
+ await runCrudMutationGuardAfterSuccess(container, {
170
+ tenantId: auth.tenantId,
171
+ organizationId: existing.organizationId,
172
+ userId: auth.sub,
173
+ resourceKind: "customer_accounts.domain_mapping",
174
+ resourceId: id,
175
+ operation: "delete",
176
+ requestMethod: req.method,
177
+ requestHeaders: req.headers,
178
+ metadata: guardResult.metadata ?? null
179
+ });
180
+ }
181
+ return NextResponse.json({ ok: true });
182
+ }
183
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() });
184
+ const domainMappingSchema = z.object({
185
+ id: z.string().uuid(),
186
+ hostname: z.string(),
187
+ organizationId: z.string().uuid(),
188
+ tenantId: z.string().uuid(),
189
+ provider: z.literal("traefik"),
190
+ status: z.enum(["pending", "verified", "active", "dns_failed", "tls_failed"]),
191
+ verifiedAt: z.string().nullable(),
192
+ lastDnsCheckAt: z.string().nullable(),
193
+ dnsFailureReason: z.string().nullable(),
194
+ tlsFailureReason: z.string().nullable(),
195
+ tlsRetryCount: z.number().int().nonnegative(),
196
+ cnameTarget: z.string().nullable(),
197
+ aRecordTarget: z.string().nullable(),
198
+ createdAt: z.string(),
199
+ updatedAt: z.string().nullable()
200
+ });
201
+ const openApi = {
202
+ tag: "CustomerAccounts",
203
+ summary: "Custom portal domain mappings (admin)",
204
+ methods: {
205
+ GET: {
206
+ summary: "List domain mappings",
207
+ description: "Returns all custom-domain mappings for the current tenant, optionally filtered by organization.",
208
+ responses: [
209
+ {
210
+ status: 200,
211
+ description: "OK",
212
+ schema: z.object({
213
+ ok: z.literal(true),
214
+ domainMappings: z.array(domainMappingSchema),
215
+ config: z.object({
216
+ cnameTarget: z.string().nullable(),
217
+ aRecordTarget: z.string().nullable()
218
+ })
219
+ })
220
+ }
221
+ ],
222
+ errors: [
223
+ { status: 401, description: "Unauthorized", schema: errorSchema },
224
+ { status: 403, description: "Forbidden", schema: errorSchema }
225
+ ]
226
+ },
227
+ POST: {
228
+ summary: "Register a custom domain",
229
+ description: "Registers a new custom domain mapping for an organization. Verifies via DNS asynchronously.",
230
+ requestBody: {
231
+ contentType: "application/json",
232
+ schema: z.object({
233
+ hostname: z.string(),
234
+ organizationId: z.string().uuid(),
235
+ replacesDomainId: z.string().uuid().optional()
236
+ })
237
+ },
238
+ responses: [
239
+ {
240
+ status: 201,
241
+ description: "Created",
242
+ schema: z.object({ ok: z.literal(true), domainMapping: domainMappingSchema })
243
+ }
244
+ ],
245
+ errors: [
246
+ { status: 400, description: "Validation error", schema: errorSchema },
247
+ { status: 401, description: "Unauthorized", schema: errorSchema },
248
+ { status: 403, description: "Forbidden", schema: errorSchema },
249
+ { status: 409, description: "Conflict", schema: errorSchema }
250
+ ]
251
+ },
252
+ DELETE: {
253
+ summary: "Remove a custom domain",
254
+ description: "Removes the domain mapping identified by ?id=. Cache and Traefik routing drain within TTL.",
255
+ responses: [{ status: 200, description: "OK", schema: z.object({ ok: z.literal(true) }) }],
256
+ errors: [
257
+ { status: 400, description: "Bad request", schema: errorSchema },
258
+ { status: 401, description: "Unauthorized", schema: errorSchema },
259
+ { status: 403, description: "Forbidden", schema: errorSchema }
260
+ ]
261
+ }
262
+ }
263
+ };
264
+ export {
265
+ DELETE,
266
+ GET,
267
+ POST,
268
+ metadata,
269
+ openApi
270
+ };
271
+ //# sourceMappingURL=domain-mappings.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/customer_accounts/api/admin/domain-mappings.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { UniqueConstraintViolationException } from '@mikro-orm/core'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport {\n validateCrudMutationGuard,\n runCrudMutationGuardAfterSuccess,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { registerDomainSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport {\n DomainMappingService,\n type ResolveResult,\n} from '@open-mercato/core/modules/customer_accounts/services/domainMappingService'\nimport { DomainMapping } from '@open-mercato/core/modules/customer_accounts/data/entities'\n\nconst FEATURE = 'customer_accounts.domain.manage'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: [FEATURE] },\n POST: { requireAuth: true, requireFeatures: [FEATURE] },\n DELETE: { requireAuth: true, requireFeatures: [FEATURE] },\n}\n\nfunction isUniqueViolation(error: unknown): boolean {\n if (error instanceof UniqueConstraintViolationException) return true\n if (!error || typeof error !== 'object') return false\n const code = (error as { code?: string }).code\n if (code === '23505') return true\n const messageRaw = (error as { message?: string }).message\n const message = typeof messageRaw === 'string' ? messageRaw : ''\n return message.toLowerCase().includes('duplicate key')\n}\n\nfunction serializeRecord(record: DomainMapping) {\n return {\n id: record.id,\n hostname: record.hostname,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n provider: record.provider,\n status: record.status,\n verifiedAt: record.verifiedAt?.toISOString() ?? null,\n lastDnsCheckAt: record.lastDnsCheckAt?.toISOString() ?? null,\n dnsFailureReason: record.dnsFailureReason ?? null,\n tlsFailureReason: record.tlsFailureReason ?? null,\n tlsRetryCount: record.tlsRetryCount,\n cnameTarget: process.env.CUSTOM_DOMAIN_CNAME_TARGET ?? null,\n aRecordTarget: process.env.CUSTOM_DOMAIN_A_RECORD_TARGET ?? null,\n createdAt: record.createdAt.toISOString(),\n updatedAt: record.updatedAt?.toISOString() ?? null,\n }\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ ok: false, error: 'Unauthorized' }, { status: 401 })\n\n const container = await createRequestContainer()\n const rbac = container.resolve('rbacService') as RbacService\n const allowed = await rbac.userHasAllFeatures(auth.sub, [FEATURE], {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n if (!allowed) return NextResponse.json({ ok: false, error: 'Forbidden' }, { status: 403 })\n\n const url = new URL(req.url)\n const orgFilter = url.searchParams.get('organizationId')\n\n const em = container.resolve('em') as EntityManager\n const where: Record<string, unknown> = { tenantId: auth.tenantId }\n if (orgFilter) where.organizationId = orgFilter\n const records = await em.find(DomainMapping, where as never, { orderBy: { createdAt: 'desc' } })\n\n return NextResponse.json({\n ok: true,\n domainMappings: records.map(serializeRecord),\n config: {\n cnameTarget: process.env.CUSTOM_DOMAIN_CNAME_TARGET ?? null,\n aRecordTarget: process.env.CUSTOM_DOMAIN_A_RECORD_TARGET ?? null,\n },\n })\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ ok: false, error: 'Unauthorized' }, { status: 401 })\n\n const container = await createRequestContainer()\n const rbac = container.resolve('rbacService') as RbacService\n const allowed = await rbac.userHasAllFeatures(auth.sub, [FEATURE], {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n if (!allowed) return NextResponse.json({ ok: false, error: 'Forbidden' }, { status: 403 })\n\n const body = await readJsonSafe(req, {})\n const parsed = registerDomainSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json(\n { ok: false, error: 'Invalid request', issues: parsed.error.flatten() },\n { status: 400 },\n )\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: parsed.data.organizationId,\n userId: auth.sub,\n resourceKind: 'customer_accounts.domain_mapping',\n resourceId: parsed.data.organizationId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data as unknown as Record<string, unknown>,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const service = container.resolve('domainMappingService') as DomainMappingService\n\n let entity: DomainMapping\n try {\n // service.register normalizes hostname internally \u2014 guards may have\n // returned modifiedPayload but the typed runner doesn't expose it,\n // so we rely on the service for the canonical form.\n entity = await service.register({\n hostname: parsed.data.hostname,\n organizationId: parsed.data.organizationId,\n tenantId: auth.tenantId,\n replacesDomainId: parsed.data.replacesDomainId,\n })\n } catch (err: unknown) {\n if (isUniqueViolation(err)) {\n return NextResponse.json(\n { ok: false, error: 'This domain is not available. Please choose a different hostname.' },\n { status: 409 },\n )\n }\n const message = err instanceof Error ? err.message : 'Failed to register domain'\n return NextResponse.json({ ok: false, error: message }, { status: 500 })\n }\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: parsed.data.organizationId,\n userId: auth.sub,\n resourceKind: 'customer_accounts.domain_mapping',\n resourceId: entity.id,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true, domainMapping: serializeRecord(entity) }, { status: 201 })\n}\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ ok: false, error: 'Unauthorized' }, { status: 401 })\n\n const container = await createRequestContainer()\n const rbac = container.resolve('rbacService') as RbacService\n const allowed = await rbac.userHasAllFeatures(auth.sub, [FEATURE], {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n if (!allowed) return NextResponse.json({ ok: false, error: 'Forbidden' }, { status: 403 })\n\n const url = new URL(req.url)\n const id = url.searchParams.get('id')\n if (!id) return NextResponse.json({ ok: false, error: 'id is required' }, { status: 400 })\n\n const service = container.resolve('domainMappingService') as DomainMappingService\n const existing = await service.findById(id, { tenantId: auth.tenantId })\n if (!existing) return NextResponse.json({ ok: false, error: 'Not found' }, { status: 404 })\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: existing.organizationId,\n userId: auth.sub,\n resourceKind: 'customer_accounts.domain_mapping',\n resourceId: id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n await service.remove(id, { tenantId: auth.tenantId })\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: existing.organizationId,\n userId: auth.sub,\n resourceKind: 'customer_accounts.domain_mapping',\n resourceId: id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true })\n}\n\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\nconst domainMappingSchema = z.object({\n id: z.string().uuid(),\n hostname: z.string(),\n organizationId: z.string().uuid(),\n tenantId: z.string().uuid(),\n provider: z.literal('traefik'),\n status: z.enum(['pending', 'verified', 'active', 'dns_failed', 'tls_failed']),\n verifiedAt: z.string().nullable(),\n lastDnsCheckAt: z.string().nullable(),\n dnsFailureReason: z.string().nullable(),\n tlsFailureReason: z.string().nullable(),\n tlsRetryCount: z.number().int().nonnegative(),\n cnameTarget: z.string().nullable(),\n aRecordTarget: z.string().nullable(),\n createdAt: z.string(),\n updatedAt: z.string().nullable(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CustomerAccounts',\n summary: 'Custom portal domain mappings (admin)',\n methods: {\n GET: {\n summary: 'List domain mappings',\n description: 'Returns all custom-domain mappings for the current tenant, optionally filtered by organization.',\n responses: [\n {\n status: 200,\n description: 'OK',\n schema: z.object({\n ok: z.literal(true),\n domainMappings: z.array(domainMappingSchema),\n config: z.object({\n cnameTarget: z.string().nullable(),\n aRecordTarget: z.string().nullable(),\n }),\n }),\n },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Forbidden', schema: errorSchema },\n ],\n },\n POST: {\n summary: 'Register a custom domain',\n description: 'Registers a new custom domain mapping for an organization. Verifies via DNS asynchronously.',\n requestBody: {\n contentType: 'application/json',\n schema: z.object({\n hostname: z.string(),\n organizationId: z.string().uuid(),\n replacesDomainId: z.string().uuid().optional(),\n }),\n },\n responses: [\n {\n status: 201,\n description: 'Created',\n schema: z.object({ ok: z.literal(true), domainMapping: domainMappingSchema }),\n },\n ],\n errors: [\n { status: 400, description: 'Validation error', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Forbidden', schema: errorSchema },\n { status: 409, description: 'Conflict', schema: errorSchema },\n ],\n },\n DELETE: {\n summary: 'Remove a custom domain',\n description: 'Removes the domain mapping identified by ?id=. Cache and Traefik routing drain within TTL.',\n responses: [{ status: 200, description: 'OK', schema: z.object({ ok: z.literal(true) }) }],\n errors: [\n { status: 400, description: 'Bad request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Forbidden', schema: errorSchema },\n ],\n },\n },\n}\n\n// Re-export resolve type so tests can import without indirect lookup\nexport type { ResolveResult }\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,70 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
+ import { tryNormalizeHostname } from "@open-mercato/core/modules/customer_accounts/lib/hostname";
5
+ import { secretEqual } from "@open-mercato/core/modules/customer_accounts/lib/secretCompare";
6
+ const ORIGIN_HEADER_NAME = process.env.CUSTOMER_DOMAIN_ORIGIN_HEADER ?? "X-Open-Mercato-Origin";
7
+ const metadata = {
8
+ GET: { requireAuth: false }
9
+ };
10
+ function withOriginHeader(response) {
11
+ response.headers.set(ORIGIN_HEADER_NAME, "1");
12
+ return response;
13
+ }
14
+ function unauthorized(message) {
15
+ return withOriginHeader(NextResponse.json({ ok: false, error: message }, { status: 403 }));
16
+ }
17
+ function notFound() {
18
+ return withOriginHeader(NextResponse.json({ ok: false, error: "Not found" }, { status: 404 }));
19
+ }
20
+ function ok() {
21
+ return withOriginHeader(NextResponse.json({ ok: true }));
22
+ }
23
+ async function GET(req) {
24
+ const expected = process.env.DOMAIN_CHECK_SECRET;
25
+ if (!expected) {
26
+ return withOriginHeader(
27
+ NextResponse.json(
28
+ { ok: false, error: "DOMAIN_CHECK_SECRET is not configured on the server" },
29
+ { status: 503 }
30
+ )
31
+ );
32
+ }
33
+ const supplied = req.headers.get("x-domain-check-secret");
34
+ if (!secretEqual(supplied, expected)) return unauthorized("Forbidden");
35
+ const url = new URL(req.url);
36
+ const rawHost = url.searchParams.get("host") ?? req.headers.get("x-forwarded-host");
37
+ if (!rawHost) return notFound();
38
+ const hostname = tryNormalizeHostname(rawHost);
39
+ if (!hostname) return notFound();
40
+ const container = await createRequestContainer();
41
+ const service = container.resolve("domainMappingService");
42
+ const result = await service.isAllowedForTls(hostname);
43
+ if (!result) return notFound();
44
+ return ok();
45
+ }
46
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() });
47
+ const openApi = {
48
+ tag: "CustomerAccounts",
49
+ summary: "Traefik domain-check gate",
50
+ methods: {
51
+ GET: {
52
+ summary: "Verify a hostname is allowed for TLS provisioning",
53
+ description: "Called by Traefik before issuing a Let's Encrypt certificate. Requires the X-Domain-Check-Secret header to match DOMAIN_CHECK_SECRET.",
54
+ responses: [
55
+ { status: 200, description: "Hostname allowed", schema: z.object({ ok: z.literal(true) }) }
56
+ ],
57
+ errors: [
58
+ { status: 403, description: "Forbidden", schema: errorSchema },
59
+ { status: 404, description: "Hostname not registered or not yet verified", schema: errorSchema },
60
+ { status: 503, description: "Server misconfiguration", schema: errorSchema }
61
+ ]
62
+ }
63
+ }
64
+ };
65
+ export {
66
+ GET,
67
+ metadata,
68
+ openApi
69
+ };
70
+ //# sourceMappingURL=domain-check.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customer_accounts/api/domain-check.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { tryNormalizeHostname } from '@open-mercato/core/modules/customer_accounts/lib/hostname'\nimport { secretEqual } from '@open-mercato/core/modules/customer_accounts/lib/secretCompare'\nimport { DomainMappingService } from '@open-mercato/core/modules/customer_accounts/services/domainMappingService'\n\nconst ORIGIN_HEADER_NAME = process.env.CUSTOMER_DOMAIN_ORIGIN_HEADER ?? 'X-Open-Mercato-Origin'\n\nexport const metadata = {\n GET: { requireAuth: false },\n}\n\nfunction withOriginHeader(response: NextResponse): NextResponse {\n response.headers.set(ORIGIN_HEADER_NAME, '1')\n return response\n}\n\nfunction unauthorized(message: string): NextResponse {\n return withOriginHeader(NextResponse.json({ ok: false, error: message }, { status: 403 }))\n}\n\nfunction notFound(): NextResponse {\n return withOriginHeader(NextResponse.json({ ok: false, error: 'Not found' }, { status: 404 }))\n}\n\nfunction ok(): NextResponse {\n return withOriginHeader(NextResponse.json({ ok: true }))\n}\n\nexport async function GET(req: Request) {\n const expected = process.env.DOMAIN_CHECK_SECRET\n if (!expected) {\n // Fail closed when the gating secret is not configured on the server.\n return withOriginHeader(\n NextResponse.json(\n { ok: false, error: 'DOMAIN_CHECK_SECRET is not configured on the server' },\n { status: 503 },\n ),\n )\n }\n const supplied = req.headers.get('x-domain-check-secret')\n if (!secretEqual(supplied, expected)) return unauthorized('Forbidden')\n\n const url = new URL(req.url)\n // Traefik's ForwardAuth middleware cannot template the auth URL with the\n // original Host, so it forwards the original request via X-Forwarded-Host.\n // Manual / direct callers can still pass `?host=` explicitly.\n const rawHost =\n url.searchParams.get('host') ?? req.headers.get('x-forwarded-host')\n if (!rawHost) return notFound()\n const hostname = tryNormalizeHostname(rawHost)\n if (!hostname) return notFound()\n\n const container = await createRequestContainer()\n const service = container.resolve('domainMappingService') as DomainMappingService\n const result = await service.isAllowedForTls(hostname)\n if (!result) return notFound()\n return ok()\n}\n\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CustomerAccounts',\n summary: 'Traefik domain-check gate',\n methods: {\n GET: {\n summary: 'Verify a hostname is allowed for TLS provisioning',\n description:\n 'Called by Traefik before issuing a Let\\'s Encrypt certificate. Requires the X-Domain-Check-Secret header to match DOMAIN_CHECK_SECRET.',\n responses: [\n { status: 200, description: 'Hostname allowed', schema: z.object({ ok: z.literal(true) }) },\n ],\n errors: [\n { status: 403, description: 'Forbidden', schema: errorSchema },\n { status: 404, description: 'Hostname not registered or not yet verified', schema: errorSchema },\n { status: 503, description: 'Server misconfiguration', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC,SAAS,4BAA4B;AACrC,SAAS,mBAAmB;AAG5B,MAAM,qBAAqB,QAAQ,IAAI,iCAAiC;AAEjE,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM;AAC5B;AAEA,SAAS,iBAAiB,UAAsC;AAC9D,WAAS,QAAQ,IAAI,oBAAoB,GAAG;AAC5C,SAAO;AACT;AAEA,SAAS,aAAa,SAA+B;AACnD,SAAO,iBAAiB,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC,CAAC;AAC3F;AAEA,SAAS,WAAyB;AAChC,SAAO,iBAAiB,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,YAAY,GAAG,EAAE,QAAQ,IAAI,CAAC,CAAC;AAC/F;AAEA,SAAS,KAAmB;AAC1B,SAAO,iBAAiB,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC,CAAC;AACzD;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,CAAC,UAAU;AAEb,WAAO;AAAA,MACL,aAAa;AAAA,QACX,EAAE,IAAI,OAAO,OAAO,sDAAsD;AAAA,QAC1E,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AACA,QAAM,WAAW,IAAI,QAAQ,IAAI,uBAAuB;AACxD,MAAI,CAAC,YAAY,UAAU,QAAQ,EAAG,QAAO,aAAa,WAAW;AAErE,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAI3B,QAAM,UACJ,IAAI,aAAa,IAAI,MAAM,KAAK,IAAI,QAAQ,IAAI,kBAAkB;AACpE,MAAI,CAAC,QAAS,QAAO,SAAS;AAC9B,QAAM,WAAW,qBAAqB,OAAO;AAC7C,MAAI,CAAC,SAAU,QAAO,SAAS;AAE/B,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,UAAU,UAAU,QAAQ,sBAAsB;AACxD,QAAM,SAAS,MAAM,QAAQ,gBAAgB,QAAQ;AACrD,MAAI,CAAC,OAAQ,QAAO,SAAS;AAC7B,SAAO,GAAG;AACZ;AAEA,MAAM,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,KAAK,GAAG,OAAO,EAAE,OAAO,EAAE,CAAC;AAEjE,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aACE;AAAA,MACF,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,oBAAoB,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC,EAAE;AAAA,MAC5F;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,aAAa,QAAQ,YAAY;AAAA,QAC7D,EAAE,QAAQ,KAAK,aAAa,+CAA+C,QAAQ,YAAY;AAAA,QAC/F,EAAE,QAAQ,KAAK,aAAa,2BAA2B,QAAQ,YAAY;AAAA,MAC7E;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,128 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
+ import { secretEqual } from "@open-mercato/core/modules/customer_accounts/lib/secretCompare";
5
+ import { rateLimitErrorSchema } from "@open-mercato/shared/lib/ratelimit/helpers";
6
+ import {
7
+ checkAuthRateLimit,
8
+ domainResolveAllIpRateLimitConfig
9
+ } from "@open-mercato/core/modules/customer_accounts/lib/rateLimiter";
10
+ const ORIGIN_HEADER_NAME = process.env.CUSTOMER_DOMAIN_ORIGIN_HEADER ?? "X-Open-Mercato-Origin";
11
+ const LOG_PREFIX = "[domain-resolve/all]";
12
+ const ENDPOINT_PATH = "/api/customer_accounts/domain-resolve/all";
13
+ const metadata = {
14
+ GET: { requireAuth: false }
15
+ };
16
+ function withOriginHeader(response) {
17
+ response.headers.set(ORIGIN_HEADER_NAME, "1");
18
+ return response;
19
+ }
20
+ function readCallerIp(req) {
21
+ const forwardedFor = req.headers.get("x-forwarded-for");
22
+ if (forwardedFor) {
23
+ const firstHop = forwardedFor.split(",")[0]?.trim();
24
+ if (firstHop) return firstHop;
25
+ }
26
+ const realIp = req.headers.get("x-real-ip");
27
+ if (realIp && realIp.trim().length > 0) return realIp.trim();
28
+ return "unknown";
29
+ }
30
+ async function GET(req) {
31
+ const callerIp = readCallerIp(req);
32
+ const expected = process.env.DOMAIN_RESOLVE_SECRET;
33
+ if (!expected) {
34
+ console.warn(`${LOG_PREFIX} rejected request \u2014 secret not configured`, {
35
+ endpoint: ENDPOINT_PATH,
36
+ ip: callerIp,
37
+ outcome: "misconfigured"
38
+ });
39
+ return withOriginHeader(
40
+ NextResponse.json(
41
+ { ok: false, error: "DOMAIN_RESOLVE_SECRET is not configured on the server" },
42
+ { status: 503 }
43
+ )
44
+ );
45
+ }
46
+ const { error: rateLimitError } = await checkAuthRateLimit({
47
+ req,
48
+ ipConfig: domainResolveAllIpRateLimitConfig
49
+ });
50
+ if (rateLimitError) {
51
+ console.warn(`${LOG_PREFIX} rate-limited`, {
52
+ endpoint: ENDPOINT_PATH,
53
+ ip: callerIp,
54
+ outcome: "rate_limited"
55
+ });
56
+ return withOriginHeader(rateLimitError);
57
+ }
58
+ const supplied = req.headers.get("x-domain-resolve-secret");
59
+ if (!secretEqual(supplied, expected)) {
60
+ console.warn(`${LOG_PREFIX} rejected request \u2014 invalid secret`, {
61
+ endpoint: ENDPOINT_PATH,
62
+ ip: callerIp,
63
+ outcome: "forbidden"
64
+ });
65
+ return withOriginHeader(NextResponse.json({ ok: false, error: "Forbidden" }, { status: 403 }));
66
+ }
67
+ const container = await createRequestContainer();
68
+ const service = container.resolve("domainMappingService");
69
+ const records = await service.resolveAll();
70
+ console.info(`${LOG_PREFIX} authorized`, {
71
+ endpoint: ENDPOINT_PATH,
72
+ ip: callerIp,
73
+ outcome: "ok",
74
+ domains: records.length
75
+ });
76
+ return withOriginHeader(
77
+ NextResponse.json({
78
+ ok: true,
79
+ domains: records.map((r) => ({
80
+ hostname: r.hostname,
81
+ tenantId: r.tenantId,
82
+ organizationId: r.organizationId,
83
+ orgSlug: r.orgSlug,
84
+ status: r.status
85
+ }))
86
+ })
87
+ );
88
+ }
89
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() });
90
+ const openApi = {
91
+ tag: "CustomerAccounts",
92
+ summary: "Batch resolve all active custom domains",
93
+ methods: {
94
+ GET: {
95
+ summary: "Batch warm-up for the Node middleware",
96
+ description: "Returns every active domain mapping in a single payload so the middleware can populate its cache on process start. Requires X-Domain-Resolve-Secret header.",
97
+ responses: [
98
+ {
99
+ status: 200,
100
+ description: "OK",
101
+ schema: z.object({
102
+ ok: z.literal(true),
103
+ domains: z.array(
104
+ z.object({
105
+ hostname: z.string(),
106
+ tenantId: z.string().uuid(),
107
+ organizationId: z.string().uuid(),
108
+ orgSlug: z.string().nullable(),
109
+ status: z.literal("active")
110
+ })
111
+ )
112
+ })
113
+ }
114
+ ],
115
+ errors: [
116
+ { status: 403, description: "Forbidden", schema: errorSchema },
117
+ { status: 429, description: "Too many requests", schema: rateLimitErrorSchema },
118
+ { status: 503, description: "Server misconfiguration", schema: errorSchema }
119
+ ]
120
+ }
121
+ }
122
+ };
123
+ export {
124
+ GET,
125
+ metadata,
126
+ openApi
127
+ };
128
+ //# sourceMappingURL=all.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/customer_accounts/api/domain-resolve/all.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { secretEqual } from '@open-mercato/core/modules/customer_accounts/lib/secretCompare'\nimport { DomainMappingService } from '@open-mercato/core/modules/customer_accounts/services/domainMappingService'\nimport { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport {\n checkAuthRateLimit,\n domainResolveAllIpRateLimitConfig,\n} from '@open-mercato/core/modules/customer_accounts/lib/rateLimiter'\n\nconst ORIGIN_HEADER_NAME = process.env.CUSTOMER_DOMAIN_ORIGIN_HEADER ?? 'X-Open-Mercato-Origin'\nconst LOG_PREFIX = '[domain-resolve/all]'\nconst ENDPOINT_PATH = '/api/customer_accounts/domain-resolve/all'\n\nexport const metadata = {\n GET: { requireAuth: false },\n}\n\nfunction withOriginHeader(response: NextResponse): NextResponse {\n response.headers.set(ORIGIN_HEADER_NAME, '1')\n return response\n}\n\nfunction readCallerIp(req: Request): string {\n const forwardedFor = req.headers.get('x-forwarded-for')\n if (forwardedFor) {\n const firstHop = forwardedFor.split(',')[0]?.trim()\n if (firstHop) return firstHop\n }\n const realIp = req.headers.get('x-real-ip')\n if (realIp && realIp.trim().length > 0) return realIp.trim()\n return 'unknown'\n}\n\nexport async function GET(req: Request) {\n const callerIp = readCallerIp(req)\n const expected = process.env.DOMAIN_RESOLVE_SECRET\n if (!expected) {\n // Fail closed when the gating secret is not configured on the server.\n console.warn(`${LOG_PREFIX} rejected request \u2014 secret not configured`, {\n endpoint: ENDPOINT_PATH,\n ip: callerIp,\n outcome: 'misconfigured',\n })\n return withOriginHeader(\n NextResponse.json(\n { ok: false, error: 'DOMAIN_RESOLVE_SECRET is not configured on the server' },\n { status: 503 },\n ),\n )\n }\n\n // Rate-limit by caller IP before the secret comparison to make leak-driven\n // enumeration noisy and bounded. Fails closed (returns the helper's 429\n // response) when the limit is exceeded.\n const { error: rateLimitError } = await checkAuthRateLimit({\n req,\n ipConfig: domainResolveAllIpRateLimitConfig,\n })\n if (rateLimitError) {\n console.warn(`${LOG_PREFIX} rate-limited`, {\n endpoint: ENDPOINT_PATH,\n ip: callerIp,\n outcome: 'rate_limited',\n })\n return withOriginHeader(rateLimitError)\n }\n\n const supplied = req.headers.get('x-domain-resolve-secret')\n if (!secretEqual(supplied, expected)) {\n console.warn(`${LOG_PREFIX} rejected request \u2014 invalid secret`, {\n endpoint: ENDPOINT_PATH,\n ip: callerIp,\n outcome: 'forbidden',\n })\n return withOriginHeader(NextResponse.json({ ok: false, error: 'Forbidden' }, { status: 403 }))\n }\n\n const container = await createRequestContainer()\n const service = container.resolve('domainMappingService') as DomainMappingService\n const records = await service.resolveAll()\n console.info(`${LOG_PREFIX} authorized`, {\n endpoint: ENDPOINT_PATH,\n ip: callerIp,\n outcome: 'ok',\n domains: records.length,\n })\n return withOriginHeader(\n NextResponse.json({\n ok: true,\n domains: records.map((r) => ({\n hostname: r.hostname,\n tenantId: r.tenantId,\n organizationId: r.organizationId,\n orgSlug: r.orgSlug,\n status: r.status,\n })),\n }),\n )\n}\n\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CustomerAccounts',\n summary: 'Batch resolve all active custom domains',\n methods: {\n GET: {\n summary: 'Batch warm-up for the Node middleware',\n description:\n 'Returns every active domain mapping in a single payload so the middleware can populate its cache on process start. Requires X-Domain-Resolve-Secret header.',\n responses: [\n {\n status: 200,\n description: 'OK',\n schema: z.object({\n ok: z.literal(true),\n domains: z.array(\n z.object({\n hostname: z.string(),\n tenantId: z.string().uuid(),\n organizationId: z.string().uuid(),\n orgSlug: z.string().nullable(),\n status: z.literal('active'),\n }),\n ),\n }),\n },\n ],\n errors: [\n { status: 403, description: 'Forbidden', schema: errorSchema },\n { status: 429, description: 'Too many requests', schema: rateLimitErrorSchema },\n { status: 503, description: 'Server misconfiguration', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,84 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
+ import { tryNormalizeHostname } from "@open-mercato/core/modules/customer_accounts/lib/hostname";
5
+ import { secretEqual } from "@open-mercato/core/modules/customer_accounts/lib/secretCompare";
6
+ const ORIGIN_HEADER_NAME = process.env.CUSTOMER_DOMAIN_ORIGIN_HEADER ?? "X-Open-Mercato-Origin";
7
+ const metadata = {
8
+ GET: { requireAuth: false }
9
+ };
10
+ function withOriginHeader(response) {
11
+ response.headers.set(ORIGIN_HEADER_NAME, "1");
12
+ return response;
13
+ }
14
+ async function GET(req) {
15
+ const expected = process.env.DOMAIN_RESOLVE_SECRET;
16
+ if (!expected) {
17
+ return withOriginHeader(
18
+ NextResponse.json(
19
+ { ok: false, error: "DOMAIN_RESOLVE_SECRET is not configured on the server" },
20
+ { status: 503 }
21
+ )
22
+ );
23
+ }
24
+ const supplied = req.headers.get("x-domain-resolve-secret");
25
+ if (!secretEqual(supplied, expected)) {
26
+ return withOriginHeader(NextResponse.json({ ok: false, error: "Forbidden" }, { status: 403 }));
27
+ }
28
+ const url = new URL(req.url);
29
+ const rawHost = url.searchParams.get("host");
30
+ if (!rawHost) return withOriginHeader(NextResponse.json({ ok: false, error: "host param required" }, { status: 400 }));
31
+ const hostname = tryNormalizeHostname(rawHost);
32
+ if (!hostname) return withOriginHeader(NextResponse.json({ ok: false, error: "invalid host" }, { status: 400 }));
33
+ const container = await createRequestContainer();
34
+ const service = container.resolve("domainMappingService");
35
+ const result = await service.resolveByHostname(hostname);
36
+ if (!result || result.status !== "active") {
37
+ return withOriginHeader(NextResponse.json({ ok: false, error: "Not found" }, { status: 404 }));
38
+ }
39
+ return withOriginHeader(
40
+ NextResponse.json({
41
+ ok: true,
42
+ tenantId: result.tenantId,
43
+ organizationId: result.organizationId,
44
+ orgSlug: result.orgSlug,
45
+ status: result.status
46
+ })
47
+ );
48
+ }
49
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() });
50
+ const openApi = {
51
+ tag: "CustomerAccounts",
52
+ summary: "Resolve a custom domain to its tenant and organization",
53
+ methods: {
54
+ GET: {
55
+ summary: "Single-host resolve for the Node middleware",
56
+ description: "Internal endpoint used by the portal middleware to populate its in-memory cache. Requires X-Domain-Resolve-Secret header.",
57
+ responses: [
58
+ {
59
+ status: 200,
60
+ description: "OK",
61
+ schema: z.object({
62
+ ok: z.literal(true),
63
+ tenantId: z.string().uuid(),
64
+ organizationId: z.string().uuid(),
65
+ orgSlug: z.string().nullable(),
66
+ status: z.literal("active")
67
+ })
68
+ }
69
+ ],
70
+ errors: [
71
+ { status: 400, description: "Bad request", schema: errorSchema },
72
+ { status: 403, description: "Forbidden", schema: errorSchema },
73
+ { status: 404, description: "Not found", schema: errorSchema },
74
+ { status: 503, description: "Server misconfiguration", schema: errorSchema }
75
+ ]
76
+ }
77
+ }
78
+ };
79
+ export {
80
+ GET,
81
+ metadata,
82
+ openApi
83
+ };
84
+ //# sourceMappingURL=domain-resolve.js.map