@open-mercato/core 0.6.1-develop.3260.1.dc719cac84 → 0.6.1-develop.3278.1.a4d26475e4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/domain_mapping/index.js +31 -0
- package/dist/generated/entities/domain_mapping/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +2 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +16 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/salesUi.js +18 -17
- package/dist/helpers/integration/salesUi.js.map +2 -2
- package/dist/modules/customer_accounts/acl.js +2 -1
- package/dist/modules/customer_accounts/acl.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.js +110 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.js +114 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings.js +271 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings.js.map +7 -0
- package/dist/modules/customer_accounts/api/domain-check.js +70 -0
- package/dist/modules/customer_accounts/api/domain-check.js.map +7 -0
- package/dist/modules/customer_accounts/api/domain-resolve/all.js +128 -0
- package/dist/modules/customer_accounts/api/domain-resolve/all.js.map +7 -0
- package/dist/modules/customer_accounts/api/domain-resolve.js +84 -0
- package/dist/modules/customer_accounts/api/domain-resolve.js.map +7 -0
- package/dist/modules/customer_accounts/api/login.js +14 -3
- package/dist/modules/customer_accounts/api/login.js.map +2 -2
- package/dist/modules/customer_accounts/api/magic-link/request.js +14 -3
- package/dist/modules/customer_accounts/api/magic-link/request.js.map +2 -2
- package/dist/modules/customer_accounts/api/password/reset-request.js +14 -3
- package/dist/modules/customer_accounts/api/password/reset-request.js.map +2 -2
- package/dist/modules/customer_accounts/api/signup.js +32 -5
- package/dist/modules/customer_accounts/api/signup.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.js +48 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +43 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.js +53 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.js +69 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.js +31 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.js +147 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.js +39 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.js +60 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.js +11 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.js +1 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js +361 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.js +28 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.js.map +7 -0
- package/dist/modules/customer_accounts/data/enrichers.js +78 -12
- package/dist/modules/customer_accounts/data/enrichers.js.map +2 -2
- package/dist/modules/customer_accounts/data/entities.js +85 -2
- package/dist/modules/customer_accounts/data/entities.js.map +2 -2
- package/dist/modules/customer_accounts/data/guards.js +130 -0
- package/dist/modules/customer_accounts/data/guards.js.map +7 -0
- package/dist/modules/customer_accounts/data/validators.js +19 -0
- package/dist/modules/customer_accounts/data/validators.js.map +2 -2
- package/dist/modules/customer_accounts/di.js +23 -1
- package/dist/modules/customer_accounts/di.js.map +2 -2
- package/dist/modules/customer_accounts/events.js +9 -2
- package/dist/modules/customer_accounts/events.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerAuthServer.js +27 -7
- package/dist/modules/customer_accounts/lib/customerAuthServer.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerUrl.js +48 -0
- package/dist/modules/customer_accounts/lib/customerUrl.js.map +7 -0
- package/dist/modules/customer_accounts/lib/hostname.js +55 -0
- package/dist/modules/customer_accounts/lib/hostname.js.map +7 -0
- package/dist/modules/customer_accounts/lib/platformDomains.js +7 -0
- package/dist/modules/customer_accounts/lib/platformDomains.js.map +7 -0
- package/dist/modules/customer_accounts/lib/proxyRanges.js +71 -0
- package/dist/modules/customer_accounts/lib/proxyRanges.js.map +7 -0
- package/dist/modules/customer_accounts/lib/rateLimiter.js +7 -0
- package/dist/modules/customer_accounts/lib/rateLimiter.js.map +2 -2
- package/dist/modules/customer_accounts/lib/resolveTenantContext.js +63 -0
- package/dist/modules/customer_accounts/lib/resolveTenantContext.js.map +7 -0
- package/dist/modules/customer_accounts/lib/secretCompare.js +13 -0
- package/dist/modules/customer_accounts/lib/secretCompare.js.map +7 -0
- package/dist/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.js +37 -0
- package/dist/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.js.map +7 -0
- package/dist/modules/customer_accounts/notifications.client.js +76 -0
- package/dist/modules/customer_accounts/notifications.client.js.map +2 -2
- package/dist/modules/customer_accounts/notifications.js +76 -0
- package/dist/modules/customer_accounts/notifications.js.map +2 -2
- package/dist/modules/customer_accounts/services/domainMappingService.js +497 -0
- package/dist/modules/customer_accounts/services/domainMappingService.js.map +7 -0
- package/dist/modules/customer_accounts/setup.js +48 -1
- package/dist/modules/customer_accounts/setup.js.map +2 -2
- package/dist/modules/customer_accounts/subscribers/invalidateDomainCache.js +29 -0
- package/dist/modules/customer_accounts/subscribers/invalidateDomainCache.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainActivated.js +29 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainActivated.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainDnsFailed.js +30 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainDnsFailed.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainTlsFailed.js +31 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainTlsFailed.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainVerified.js +29 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainVerified.js.map +7 -0
- package/dist/modules/customer_accounts/workers/domainTlsRetryWorker.js +67 -0
- package/dist/modules/customer_accounts/workers/domainTlsRetryWorker.js.map +7 -0
- package/dist/modules/customer_accounts/workers/domainVerificationWorker.js +34 -0
- package/dist/modules/customer_accounts/workers/domainVerificationWorker.js.map +7 -0
- package/dist/modules/customers/api/companies/route.js +4 -9
- package/dist/modules/customers/api/companies/route.js.map +2 -2
- package/dist/modules/customers/api/deals/route.js +4 -9
- package/dist/modules/customers/api/deals/route.js.map +2 -2
- package/dist/modules/customers/api/people/route.js +4 -9
- package/dist/modules/customers/api/people/route.js.map +2 -2
- package/dist/modules/customers/api/utils.js +0 -12
- package/dist/modules/customers/api/utils.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +361 -284
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +378 -473
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +368 -286
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/components/detail/assignableStaff.js +15 -1
- package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +188 -36
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js.map +2 -2
- package/dist/modules/customers/widgets/injection-table.js +6 -0
- package/dist/modules/customers/widgets/injection-table.js.map +2 -2
- package/dist/modules/perspectives/services/perspectiveService.js +16 -2
- package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +1 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +31 -8
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/generated/entities/domain_mapping/index.ts +14 -0
- package/generated/entities.ids.generated.ts +2 -1
- package/generated/entity-fields-registry.ts +16 -0
- package/package.json +5 -5
- package/src/helpers/integration/salesUi.ts +27 -23
- package/src/modules/customer_accounts/acl.ts +1 -0
- package/src/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.ts +119 -0
- package/src/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.ts +123 -0
- package/src/modules/customer_accounts/api/admin/domain-mappings.ts +303 -0
- package/src/modules/customer_accounts/api/domain-check.ts +83 -0
- package/src/modules/customer_accounts/api/domain-resolve/all.ts +139 -0
- package/src/modules/customer_accounts/api/domain-resolve.ts +91 -0
- package/src/modules/customer_accounts/api/login.ts +14 -3
- package/src/modules/customer_accounts/api/magic-link/request.ts +14 -3
- package/src/modules/customer_accounts/api/password/reset-request.ts +15 -3
- package/src/modules/customer_accounts/api/signup.ts +36 -5
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.tsx +70 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +66 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.tsx +78 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.tsx +90 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.tsx +43 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.tsx +178 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.tsx +56 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.tsx +69 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.ts +10 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.ts +31 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.ts +26 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.tsx +411 -0
- package/src/modules/customer_accounts/data/enrichers.ts +95 -12
- package/src/modules/customer_accounts/data/entities.ts +80 -1
- package/src/modules/customer_accounts/data/guards.ts +155 -0
- package/src/modules/customer_accounts/data/validators.ts +26 -0
- package/src/modules/customer_accounts/di.ts +35 -1
- package/src/modules/customer_accounts/events.ts +8 -1
- package/src/modules/customer_accounts/i18n/de.json +78 -0
- package/src/modules/customer_accounts/i18n/en.json +78 -0
- package/src/modules/customer_accounts/i18n/es.json +78 -0
- package/src/modules/customer_accounts/i18n/pl.json +78 -0
- package/src/modules/customer_accounts/lib/customerAuthServer.ts +44 -8
- package/src/modules/customer_accounts/lib/customerUrl.ts +72 -0
- package/src/modules/customer_accounts/lib/hostname.ts +76 -0
- package/src/modules/customer_accounts/lib/platformDomains.ts +14 -0
- package/src/modules/customer_accounts/lib/proxyRanges.ts +85 -0
- package/src/modules/customer_accounts/lib/rateLimiter.ts +10 -0
- package/src/modules/customer_accounts/lib/resolveTenantContext.ts +96 -0
- package/src/modules/customer_accounts/lib/secretCompare.ts +20 -0
- package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +341 -0
- package/src/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.ts +38 -0
- package/src/modules/customer_accounts/notifications.client.ts +76 -0
- package/src/modules/customer_accounts/notifications.ts +76 -0
- package/src/modules/customer_accounts/services/domainMappingService.ts +632 -0
- package/src/modules/customer_accounts/setup.ts +81 -1
- package/src/modules/customer_accounts/subscribers/invalidateDomainCache.ts +35 -0
- package/src/modules/customer_accounts/subscribers/notifyDomainActivated.ts +32 -0
- package/src/modules/customer_accounts/subscribers/notifyDomainDnsFailed.ts +33 -0
- package/src/modules/customer_accounts/subscribers/notifyDomainTlsFailed.ts +34 -0
- package/src/modules/customer_accounts/subscribers/notifyDomainVerified.ts +32 -0
- package/src/modules/customer_accounts/workers/domainTlsRetryWorker.ts +84 -0
- package/src/modules/customer_accounts/workers/domainVerificationWorker.ts +45 -0
- package/src/modules/customers/api/companies/route.ts +4 -9
- package/src/modules/customers/api/deals/route.ts +4 -9
- package/src/modules/customers/api/people/route.ts +4 -9
- package/src/modules/customers/api/utils.ts +0 -14
- package/src/modules/customers/backend/customers/companies/page.tsx +321 -237
- package/src/modules/customers/backend/customers/deals/page.tsx +321 -455
- package/src/modules/customers/backend/customers/people/page.tsx +331 -240
- package/src/modules/customers/components/detail/assignableStaff.ts +21 -0
- package/src/modules/customers/i18n/de.json +31 -0
- package/src/modules/customers/i18n/en.json +31 -0
- package/src/modules/customers/i18n/es.json +31 -0
- package/src/modules/customers/i18n/pl.json +31 -0
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +197 -40
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.ts +13 -7
- package/src/modules/customers/widgets/injection-table.ts +6 -0
- package/src/modules/perspectives/services/perspectiveService.ts +30 -2
- package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +1 -0
- package/src/modules/query_index/lib/engine.ts +46 -8
|
@@ -192,12 +192,90 @@
|
|
|
192
192
|
"customer_accounts.admin.unverified": "No",
|
|
193
193
|
"customer_accounts.admin.users": "Users",
|
|
194
194
|
"customer_accounts.admin.verified": "Yes",
|
|
195
|
+
"customer_accounts.domainMapping.aRecordInstruction": "For an apex domain, add an A record for {hostname} pointing to {target}",
|
|
196
|
+
"customer_accounts.domainMapping.alreadyClaimed": "This domain is not available. Please choose a different hostname.",
|
|
197
|
+
"customer_accounts.domainMapping.autoVerify.checking": "Automatically checking every 5 minutes",
|
|
198
|
+
"customer_accounts.domainMapping.autoVerify.lastChecked": "Last checked: {time}",
|
|
199
|
+
"customer_accounts.domainMapping.autoVerify.nextCheck": "Next check in ~{minutes} minutes",
|
|
200
|
+
"customer_accounts.domainMapping.changeDomain": "Change Domain",
|
|
201
|
+
"customer_accounts.domainMapping.changeDomain.description": "Your current domain ({hostname}) will stay active until the new domain is fully set up. No downtime for your customers.",
|
|
202
|
+
"customer_accounts.domainMapping.changeDomain.replacing": "Replacing {oldHostname} — old domain stays live until this one is ready",
|
|
203
|
+
"customer_accounts.domainMapping.cnameInstruction": "Add a CNAME record for {hostname} pointing to {target} in your DNS provider",
|
|
204
|
+
"customer_accounts.domainMapping.copyTarget": "Copy",
|
|
205
|
+
"customer_accounts.domainMapping.copyTarget.copied": "Copied",
|
|
206
|
+
"customer_accounts.domainMapping.description": "Map your own domain to the customer portal",
|
|
207
|
+
"customer_accounts.domainMapping.diagnostics.detectedRecords": "Detected DNS records:",
|
|
208
|
+
"customer_accounts.domainMapping.diagnostics.noRecord": "No CNAME or A record found for {hostname}",
|
|
209
|
+
"customer_accounts.domainMapping.diagnostics.proxyDetected": "Your DNS uses a proxy ({proxy}). Either disable the proxy or configure it to forward traffic to our platform.",
|
|
210
|
+
"customer_accounts.domainMapping.diagnostics.suggestion": "Suggested fix:",
|
|
211
|
+
"customer_accounts.domainMapping.diagnostics.timeout": "DNS lookup timed out — this may be a temporary issue",
|
|
212
|
+
"customer_accounts.domainMapping.diagnostics.wrongARecordTarget": "Your A record points to {detected} instead of {expected}",
|
|
213
|
+
"customer_accounts.domainMapping.diagnostics.wrongCnameTarget": "Your CNAME record points to {detected} instead of {expected}",
|
|
214
|
+
"customer_accounts.domainMapping.dialog.cancel": "Cancel",
|
|
215
|
+
"customer_accounts.domainMapping.dialog.change.submit": "Start replacement",
|
|
216
|
+
"customer_accounts.domainMapping.dialog.change.title": "Change custom domain",
|
|
217
|
+
"customer_accounts.domainMapping.dialog.register.submit": "Register",
|
|
218
|
+
"customer_accounts.domainMapping.dialog.register.title": "Register custom domain",
|
|
219
|
+
"customer_accounts.domainMapping.dns.diagnostics.detected": "Detected target",
|
|
220
|
+
"customer_accounts.domainMapping.dns.diagnostics.expected": "Expected target",
|
|
221
|
+
"customer_accounts.domainMapping.dns.diagnostics.title": "DNS configuration issue",
|
|
222
|
+
"customer_accounts.domainMapping.dns.recheckButton": "Re-check DNS",
|
|
223
|
+
"customer_accounts.domainMapping.dnsProviderHelp": "Need help? See instructions for:",
|
|
224
|
+
"customer_accounts.domainMapping.dnsProviderHelp.cloudflare": "Cloudflare",
|
|
225
|
+
"customer_accounts.domainMapping.dnsProviderHelp.godaddy": "GoDaddy",
|
|
226
|
+
"customer_accounts.domainMapping.dnsProviderHelp.googleDomains": "Google Domains",
|
|
227
|
+
"customer_accounts.domainMapping.dnsProviderHelp.namecheap": "Namecheap",
|
|
228
|
+
"customer_accounts.domainMapping.dnsProviderHelp.other": "Other providers",
|
|
229
|
+
"customer_accounts.domainMapping.empty.cta": "Register Domain",
|
|
230
|
+
"customer_accounts.domainMapping.empty.description": "Connect your own domain so customers can reach the portal at a branded URL.",
|
|
231
|
+
"customer_accounts.domainMapping.empty.title": "No custom domain yet",
|
|
232
|
+
"customer_accounts.domainMapping.error.load": "Could not load custom-domain configuration",
|
|
233
|
+
"customer_accounts.domainMapping.errors.required": "Domain is required",
|
|
234
|
+
"customer_accounts.domainMapping.hostname.label": "Domain",
|
|
235
|
+
"customer_accounts.domainMapping.hostname.placeholder": "e.g., shop.yourdomain.com",
|
|
236
|
+
"customer_accounts.domainMapping.hostname.validation.invalid": "Enter a valid domain name (no protocol, path, or port)",
|
|
237
|
+
"customer_accounts.domainMapping.loading": "Loading domain configuration…",
|
|
238
|
+
"customer_accounts.domainMapping.orgSwitcher.label": "Organization",
|
|
239
|
+
"customer_accounts.domainMapping.preview": "Test Domain",
|
|
240
|
+
"customer_accounts.domainMapping.preview.description": "Opens your portal on the custom domain in a new tab to verify it's working correctly",
|
|
241
|
+
"customer_accounts.domainMapping.preview.notReady": "Domain is not yet active. Complete the setup steps first.",
|
|
242
|
+
"customer_accounts.domainMapping.propagationNote": "DNS changes can take up to 48 hours to propagate. The system checks automatically — you'll be notified when it's ready.",
|
|
243
|
+
"customer_accounts.domainMapping.registered": "Domain registered successfully",
|
|
244
|
+
"customer_accounts.domainMapping.removeConfirm": "Are you sure? Customers using this domain will no longer be able to reach your portal.",
|
|
245
|
+
"customer_accounts.domainMapping.removeConfirm.hasReplacement": "This will also cancel the pending replacement domain ({hostname}).",
|
|
246
|
+
"customer_accounts.domainMapping.removeConfirm.title": "Remove custom domain?",
|
|
247
|
+
"customer_accounts.domainMapping.removeDomain": "Remove Domain",
|
|
248
|
+
"customer_accounts.domainMapping.removed": "Domain removed",
|
|
249
|
+
"customer_accounts.domainMapping.replacement.label": "Replacement (waiting to activate)",
|
|
250
|
+
"customer_accounts.domainMapping.status.active": "Active",
|
|
251
|
+
"customer_accounts.domainMapping.status.dns_failed": "DNS issue — see details below",
|
|
252
|
+
"customer_accounts.domainMapping.status.pending": "Checking DNS...",
|
|
253
|
+
"customer_accounts.domainMapping.status.tls_failed": "SSL certificate issue — we're retrying",
|
|
254
|
+
"customer_accounts.domainMapping.status.verified": "Setting up SSL certificate...",
|
|
255
|
+
"customer_accounts.domainMapping.stepper.step1": "Register Domain",
|
|
256
|
+
"customer_accounts.domainMapping.stepper.step2": "Configure DNS",
|
|
257
|
+
"customer_accounts.domainMapping.stepper.step3": "SSL Certificate",
|
|
258
|
+
"customer_accounts.domainMapping.stepper.step4": "Live",
|
|
259
|
+
"customer_accounts.domainMapping.title": "Custom Domain",
|
|
260
|
+
"customer_accounts.domainMapping.tls.diagnostics.operatorNote": "We are retrying automatically. If this persists, contact platform support — your DNS is fine, this is on our side.",
|
|
261
|
+
"customer_accounts.domainMapping.tls.diagnostics.retryCount": "Retry attempts: {count}",
|
|
262
|
+
"customer_accounts.domainMapping.tls.diagnostics.title": "SSL certificate issue",
|
|
263
|
+
"customer_accounts.domainMapping.tls.retryButton": "Retry SSL",
|
|
264
|
+
"customer_accounts.domainMapping.verifyNow": "Check Now",
|
|
195
265
|
"customer_accounts.nav.group": "Customers",
|
|
196
266
|
"customer_accounts.nav.role_create": "Create Role",
|
|
197
267
|
"customer_accounts.nav.role_detail": "Role Detail",
|
|
198
268
|
"customer_accounts.nav.roles": "Customer Roles",
|
|
199
269
|
"customer_accounts.nav.user_detail": "User Detail",
|
|
200
270
|
"customer_accounts.nav.users": "Customer Users",
|
|
271
|
+
"customer_accounts.notifications.domain_mapping.activated.body": "Your custom domain is now active and serving traffic.",
|
|
272
|
+
"customer_accounts.notifications.domain_mapping.activated.title": "Custom domain is live",
|
|
273
|
+
"customer_accounts.notifications.domain_mapping.dns_failed.body": "DNS verification failed for your custom domain. {reason}",
|
|
274
|
+
"customer_accounts.notifications.domain_mapping.dns_failed.title": "DNS verification failed",
|
|
275
|
+
"customer_accounts.notifications.domain_mapping.tls_failed.body": "SSL certificate provisioning is having trouble for your custom domain. We're retrying — no action needed yet.",
|
|
276
|
+
"customer_accounts.notifications.domain_mapping.tls_failed.title": "SSL provisioning issue",
|
|
277
|
+
"customer_accounts.notifications.domain_mapping.verified.body": "Your custom domain has been verified. SSL certificate is being provisioned.",
|
|
278
|
+
"customer_accounts.notifications.domain_mapping.verified.title": "Custom domain verified",
|
|
201
279
|
"customer_accounts.notifications.user.locked.body": "Customer account was locked due to too many failed login attempts.",
|
|
202
280
|
"customer_accounts.notifications.user.locked.title": "Customer account locked",
|
|
203
281
|
"customer_accounts.notifications.user.signup.body": "A new customer account was created.",
|
|
@@ -192,12 +192,90 @@
|
|
|
192
192
|
"customer_accounts.admin.unverified": "No",
|
|
193
193
|
"customer_accounts.admin.users": "Users",
|
|
194
194
|
"customer_accounts.admin.verified": "Yes",
|
|
195
|
+
"customer_accounts.domainMapping.aRecordInstruction": "For an apex domain, add an A record for {hostname} pointing to {target}",
|
|
196
|
+
"customer_accounts.domainMapping.alreadyClaimed": "This domain is not available. Please choose a different hostname.",
|
|
197
|
+
"customer_accounts.domainMapping.autoVerify.checking": "Automatically checking every 5 minutes",
|
|
198
|
+
"customer_accounts.domainMapping.autoVerify.lastChecked": "Last checked: {time}",
|
|
199
|
+
"customer_accounts.domainMapping.autoVerify.nextCheck": "Next check in ~{minutes} minutes",
|
|
200
|
+
"customer_accounts.domainMapping.changeDomain": "Change Domain",
|
|
201
|
+
"customer_accounts.domainMapping.changeDomain.description": "Your current domain ({hostname}) will stay active until the new domain is fully set up. No downtime for your customers.",
|
|
202
|
+
"customer_accounts.domainMapping.changeDomain.replacing": "Replacing {oldHostname} — old domain stays live until this one is ready",
|
|
203
|
+
"customer_accounts.domainMapping.cnameInstruction": "Add a CNAME record for {hostname} pointing to {target} in your DNS provider",
|
|
204
|
+
"customer_accounts.domainMapping.copyTarget": "Copy",
|
|
205
|
+
"customer_accounts.domainMapping.copyTarget.copied": "Copied",
|
|
206
|
+
"customer_accounts.domainMapping.description": "Map your own domain to the customer portal",
|
|
207
|
+
"customer_accounts.domainMapping.diagnostics.detectedRecords": "Detected DNS records:",
|
|
208
|
+
"customer_accounts.domainMapping.diagnostics.noRecord": "No CNAME or A record found for {hostname}",
|
|
209
|
+
"customer_accounts.domainMapping.diagnostics.proxyDetected": "Your DNS uses a proxy ({proxy}). Either disable the proxy or configure it to forward traffic to our platform.",
|
|
210
|
+
"customer_accounts.domainMapping.diagnostics.suggestion": "Suggested fix:",
|
|
211
|
+
"customer_accounts.domainMapping.diagnostics.timeout": "DNS lookup timed out — this may be a temporary issue",
|
|
212
|
+
"customer_accounts.domainMapping.diagnostics.wrongARecordTarget": "Your A record points to {detected} instead of {expected}",
|
|
213
|
+
"customer_accounts.domainMapping.diagnostics.wrongCnameTarget": "Your CNAME record points to {detected} instead of {expected}",
|
|
214
|
+
"customer_accounts.domainMapping.dialog.cancel": "Cancel",
|
|
215
|
+
"customer_accounts.domainMapping.dialog.change.submit": "Start replacement",
|
|
216
|
+
"customer_accounts.domainMapping.dialog.change.title": "Change custom domain",
|
|
217
|
+
"customer_accounts.domainMapping.dialog.register.submit": "Register",
|
|
218
|
+
"customer_accounts.domainMapping.dialog.register.title": "Register custom domain",
|
|
219
|
+
"customer_accounts.domainMapping.dns.diagnostics.detected": "Detected target",
|
|
220
|
+
"customer_accounts.domainMapping.dns.diagnostics.expected": "Expected target",
|
|
221
|
+
"customer_accounts.domainMapping.dns.diagnostics.title": "DNS configuration issue",
|
|
222
|
+
"customer_accounts.domainMapping.dns.recheckButton": "Re-check DNS",
|
|
223
|
+
"customer_accounts.domainMapping.dnsProviderHelp": "Need help? See instructions for:",
|
|
224
|
+
"customer_accounts.domainMapping.dnsProviderHelp.cloudflare": "Cloudflare",
|
|
225
|
+
"customer_accounts.domainMapping.dnsProviderHelp.godaddy": "GoDaddy",
|
|
226
|
+
"customer_accounts.domainMapping.dnsProviderHelp.googleDomains": "Google Domains",
|
|
227
|
+
"customer_accounts.domainMapping.dnsProviderHelp.namecheap": "Namecheap",
|
|
228
|
+
"customer_accounts.domainMapping.dnsProviderHelp.other": "Other providers",
|
|
229
|
+
"customer_accounts.domainMapping.empty.cta": "Register Domain",
|
|
230
|
+
"customer_accounts.domainMapping.empty.description": "Connect your own domain so customers can reach the portal at a branded URL.",
|
|
231
|
+
"customer_accounts.domainMapping.empty.title": "No custom domain yet",
|
|
232
|
+
"customer_accounts.domainMapping.error.load": "Could not load custom-domain configuration",
|
|
233
|
+
"customer_accounts.domainMapping.errors.required": "Domain is required",
|
|
234
|
+
"customer_accounts.domainMapping.hostname.label": "Domain",
|
|
235
|
+
"customer_accounts.domainMapping.hostname.placeholder": "e.g., shop.yourdomain.com",
|
|
236
|
+
"customer_accounts.domainMapping.hostname.validation.invalid": "Enter a valid domain name (no protocol, path, or port)",
|
|
237
|
+
"customer_accounts.domainMapping.loading": "Loading domain configuration…",
|
|
238
|
+
"customer_accounts.domainMapping.orgSwitcher.label": "Organization",
|
|
239
|
+
"customer_accounts.domainMapping.preview": "Test Domain",
|
|
240
|
+
"customer_accounts.domainMapping.preview.description": "Opens your portal on the custom domain in a new tab to verify it's working correctly",
|
|
241
|
+
"customer_accounts.domainMapping.preview.notReady": "Domain is not yet active. Complete the setup steps first.",
|
|
242
|
+
"customer_accounts.domainMapping.propagationNote": "DNS changes can take up to 48 hours to propagate. The system checks automatically — you'll be notified when it's ready.",
|
|
243
|
+
"customer_accounts.domainMapping.registered": "Domain registered successfully",
|
|
244
|
+
"customer_accounts.domainMapping.removeConfirm": "Are you sure? Customers using this domain will no longer be able to reach your portal.",
|
|
245
|
+
"customer_accounts.domainMapping.removeConfirm.hasReplacement": "This will also cancel the pending replacement domain ({hostname}).",
|
|
246
|
+
"customer_accounts.domainMapping.removeConfirm.title": "Remove custom domain?",
|
|
247
|
+
"customer_accounts.domainMapping.removeDomain": "Remove Domain",
|
|
248
|
+
"customer_accounts.domainMapping.removed": "Domain removed",
|
|
249
|
+
"customer_accounts.domainMapping.replacement.label": "Replacement (waiting to activate)",
|
|
250
|
+
"customer_accounts.domainMapping.status.active": "Active",
|
|
251
|
+
"customer_accounts.domainMapping.status.dns_failed": "DNS issue — see details below",
|
|
252
|
+
"customer_accounts.domainMapping.status.pending": "Checking DNS...",
|
|
253
|
+
"customer_accounts.domainMapping.status.tls_failed": "SSL certificate issue — we're retrying",
|
|
254
|
+
"customer_accounts.domainMapping.status.verified": "Setting up SSL certificate...",
|
|
255
|
+
"customer_accounts.domainMapping.stepper.step1": "Register Domain",
|
|
256
|
+
"customer_accounts.domainMapping.stepper.step2": "Configure DNS",
|
|
257
|
+
"customer_accounts.domainMapping.stepper.step3": "SSL Certificate",
|
|
258
|
+
"customer_accounts.domainMapping.stepper.step4": "Live",
|
|
259
|
+
"customer_accounts.domainMapping.title": "Custom Domain",
|
|
260
|
+
"customer_accounts.domainMapping.tls.diagnostics.operatorNote": "We are retrying automatically. If this persists, contact platform support — your DNS is fine, this is on our side.",
|
|
261
|
+
"customer_accounts.domainMapping.tls.diagnostics.retryCount": "Retry attempts: {count}",
|
|
262
|
+
"customer_accounts.domainMapping.tls.diagnostics.title": "SSL certificate issue",
|
|
263
|
+
"customer_accounts.domainMapping.tls.retryButton": "Retry SSL",
|
|
264
|
+
"customer_accounts.domainMapping.verifyNow": "Check Now",
|
|
195
265
|
"customer_accounts.nav.group": "Customers",
|
|
196
266
|
"customer_accounts.nav.role_create": "Create Role",
|
|
197
267
|
"customer_accounts.nav.role_detail": "Role Detail",
|
|
198
268
|
"customer_accounts.nav.roles": "Roles de clientes",
|
|
199
269
|
"customer_accounts.nav.user_detail": "User Detail",
|
|
200
270
|
"customer_accounts.nav.users": "Usuarios de clientes",
|
|
271
|
+
"customer_accounts.notifications.domain_mapping.activated.body": "Your custom domain is now active and serving traffic.",
|
|
272
|
+
"customer_accounts.notifications.domain_mapping.activated.title": "Custom domain is live",
|
|
273
|
+
"customer_accounts.notifications.domain_mapping.dns_failed.body": "DNS verification failed for your custom domain. {reason}",
|
|
274
|
+
"customer_accounts.notifications.domain_mapping.dns_failed.title": "DNS verification failed",
|
|
275
|
+
"customer_accounts.notifications.domain_mapping.tls_failed.body": "SSL certificate provisioning is having trouble for your custom domain. We're retrying — no action needed yet.",
|
|
276
|
+
"customer_accounts.notifications.domain_mapping.tls_failed.title": "SSL provisioning issue",
|
|
277
|
+
"customer_accounts.notifications.domain_mapping.verified.body": "Your custom domain has been verified. SSL certificate is being provisioned.",
|
|
278
|
+
"customer_accounts.notifications.domain_mapping.verified.title": "Custom domain verified",
|
|
201
279
|
"customer_accounts.notifications.user.locked.body": "Customer account was locked due to too many failed login attempts.",
|
|
202
280
|
"customer_accounts.notifications.user.locked.title": "Customer account locked",
|
|
203
281
|
"customer_accounts.notifications.user.signup.body": "A new customer account was created.",
|
|
@@ -192,12 +192,90 @@
|
|
|
192
192
|
"customer_accounts.admin.unverified": "Nie",
|
|
193
193
|
"customer_accounts.admin.users": "Użytkownicy",
|
|
194
194
|
"customer_accounts.admin.verified": "Tak",
|
|
195
|
+
"customer_accounts.domainMapping.aRecordInstruction": "For an apex domain, add an A record for {hostname} pointing to {target}",
|
|
196
|
+
"customer_accounts.domainMapping.alreadyClaimed": "This domain is not available. Please choose a different hostname.",
|
|
197
|
+
"customer_accounts.domainMapping.autoVerify.checking": "Automatically checking every 5 minutes",
|
|
198
|
+
"customer_accounts.domainMapping.autoVerify.lastChecked": "Last checked: {time}",
|
|
199
|
+
"customer_accounts.domainMapping.autoVerify.nextCheck": "Next check in ~{minutes} minutes",
|
|
200
|
+
"customer_accounts.domainMapping.changeDomain": "Change Domain",
|
|
201
|
+
"customer_accounts.domainMapping.changeDomain.description": "Your current domain ({hostname}) will stay active until the new domain is fully set up. No downtime for your customers.",
|
|
202
|
+
"customer_accounts.domainMapping.changeDomain.replacing": "Replacing {oldHostname} — old domain stays live until this one is ready",
|
|
203
|
+
"customer_accounts.domainMapping.cnameInstruction": "Add a CNAME record for {hostname} pointing to {target} in your DNS provider",
|
|
204
|
+
"customer_accounts.domainMapping.copyTarget": "Copy",
|
|
205
|
+
"customer_accounts.domainMapping.copyTarget.copied": "Copied",
|
|
206
|
+
"customer_accounts.domainMapping.description": "Map your own domain to the customer portal",
|
|
207
|
+
"customer_accounts.domainMapping.diagnostics.detectedRecords": "Detected DNS records:",
|
|
208
|
+
"customer_accounts.domainMapping.diagnostics.noRecord": "No CNAME or A record found for {hostname}",
|
|
209
|
+
"customer_accounts.domainMapping.diagnostics.proxyDetected": "Your DNS uses a proxy ({proxy}). Either disable the proxy or configure it to forward traffic to our platform.",
|
|
210
|
+
"customer_accounts.domainMapping.diagnostics.suggestion": "Suggested fix:",
|
|
211
|
+
"customer_accounts.domainMapping.diagnostics.timeout": "DNS lookup timed out — this may be a temporary issue",
|
|
212
|
+
"customer_accounts.domainMapping.diagnostics.wrongARecordTarget": "Your A record points to {detected} instead of {expected}",
|
|
213
|
+
"customer_accounts.domainMapping.diagnostics.wrongCnameTarget": "Your CNAME record points to {detected} instead of {expected}",
|
|
214
|
+
"customer_accounts.domainMapping.dialog.cancel": "Cancel",
|
|
215
|
+
"customer_accounts.domainMapping.dialog.change.submit": "Start replacement",
|
|
216
|
+
"customer_accounts.domainMapping.dialog.change.title": "Change custom domain",
|
|
217
|
+
"customer_accounts.domainMapping.dialog.register.submit": "Register",
|
|
218
|
+
"customer_accounts.domainMapping.dialog.register.title": "Register custom domain",
|
|
219
|
+
"customer_accounts.domainMapping.dns.diagnostics.detected": "Detected target",
|
|
220
|
+
"customer_accounts.domainMapping.dns.diagnostics.expected": "Expected target",
|
|
221
|
+
"customer_accounts.domainMapping.dns.diagnostics.title": "DNS configuration issue",
|
|
222
|
+
"customer_accounts.domainMapping.dns.recheckButton": "Re-check DNS",
|
|
223
|
+
"customer_accounts.domainMapping.dnsProviderHelp": "Need help? See instructions for:",
|
|
224
|
+
"customer_accounts.domainMapping.dnsProviderHelp.cloudflare": "Cloudflare",
|
|
225
|
+
"customer_accounts.domainMapping.dnsProviderHelp.godaddy": "GoDaddy",
|
|
226
|
+
"customer_accounts.domainMapping.dnsProviderHelp.googleDomains": "Google Domains",
|
|
227
|
+
"customer_accounts.domainMapping.dnsProviderHelp.namecheap": "Namecheap",
|
|
228
|
+
"customer_accounts.domainMapping.dnsProviderHelp.other": "Other providers",
|
|
229
|
+
"customer_accounts.domainMapping.empty.cta": "Register Domain",
|
|
230
|
+
"customer_accounts.domainMapping.empty.description": "Connect your own domain so customers can reach the portal at a branded URL.",
|
|
231
|
+
"customer_accounts.domainMapping.empty.title": "No custom domain yet",
|
|
232
|
+
"customer_accounts.domainMapping.error.load": "Could not load custom-domain configuration",
|
|
233
|
+
"customer_accounts.domainMapping.errors.required": "Domain is required",
|
|
234
|
+
"customer_accounts.domainMapping.hostname.label": "Domain",
|
|
235
|
+
"customer_accounts.domainMapping.hostname.placeholder": "e.g., shop.yourdomain.com",
|
|
236
|
+
"customer_accounts.domainMapping.hostname.validation.invalid": "Enter a valid domain name (no protocol, path, or port)",
|
|
237
|
+
"customer_accounts.domainMapping.loading": "Loading domain configuration…",
|
|
238
|
+
"customer_accounts.domainMapping.orgSwitcher.label": "Organization",
|
|
239
|
+
"customer_accounts.domainMapping.preview": "Test Domain",
|
|
240
|
+
"customer_accounts.domainMapping.preview.description": "Opens your portal on the custom domain in a new tab to verify it's working correctly",
|
|
241
|
+
"customer_accounts.domainMapping.preview.notReady": "Domain is not yet active. Complete the setup steps first.",
|
|
242
|
+
"customer_accounts.domainMapping.propagationNote": "DNS changes can take up to 48 hours to propagate. The system checks automatically — you'll be notified when it's ready.",
|
|
243
|
+
"customer_accounts.domainMapping.registered": "Domain registered successfully",
|
|
244
|
+
"customer_accounts.domainMapping.removeConfirm": "Are you sure? Customers using this domain will no longer be able to reach your portal.",
|
|
245
|
+
"customer_accounts.domainMapping.removeConfirm.hasReplacement": "This will also cancel the pending replacement domain ({hostname}).",
|
|
246
|
+
"customer_accounts.domainMapping.removeConfirm.title": "Remove custom domain?",
|
|
247
|
+
"customer_accounts.domainMapping.removeDomain": "Remove Domain",
|
|
248
|
+
"customer_accounts.domainMapping.removed": "Domain removed",
|
|
249
|
+
"customer_accounts.domainMapping.replacement.label": "Replacement (waiting to activate)",
|
|
250
|
+
"customer_accounts.domainMapping.status.active": "Active",
|
|
251
|
+
"customer_accounts.domainMapping.status.dns_failed": "DNS issue — see details below",
|
|
252
|
+
"customer_accounts.domainMapping.status.pending": "Checking DNS...",
|
|
253
|
+
"customer_accounts.domainMapping.status.tls_failed": "SSL certificate issue — we're retrying",
|
|
254
|
+
"customer_accounts.domainMapping.status.verified": "Setting up SSL certificate...",
|
|
255
|
+
"customer_accounts.domainMapping.stepper.step1": "Register Domain",
|
|
256
|
+
"customer_accounts.domainMapping.stepper.step2": "Configure DNS",
|
|
257
|
+
"customer_accounts.domainMapping.stepper.step3": "SSL Certificate",
|
|
258
|
+
"customer_accounts.domainMapping.stepper.step4": "Live",
|
|
259
|
+
"customer_accounts.domainMapping.title": "Custom Domain",
|
|
260
|
+
"customer_accounts.domainMapping.tls.diagnostics.operatorNote": "We are retrying automatically. If this persists, contact platform support — your DNS is fine, this is on our side.",
|
|
261
|
+
"customer_accounts.domainMapping.tls.diagnostics.retryCount": "Retry attempts: {count}",
|
|
262
|
+
"customer_accounts.domainMapping.tls.diagnostics.title": "SSL certificate issue",
|
|
263
|
+
"customer_accounts.domainMapping.tls.retryButton": "Retry SSL",
|
|
264
|
+
"customer_accounts.domainMapping.verifyNow": "Check Now",
|
|
195
265
|
"customer_accounts.nav.group": "Klienci",
|
|
196
266
|
"customer_accounts.nav.role_create": "Utwórz rolę",
|
|
197
267
|
"customer_accounts.nav.role_detail": "Szczegóły roli",
|
|
198
268
|
"customer_accounts.nav.roles": "Role klientów",
|
|
199
269
|
"customer_accounts.nav.user_detail": "Szczegóły użytkownika",
|
|
200
270
|
"customer_accounts.nav.users": "Użytkownicy klientów",
|
|
271
|
+
"customer_accounts.notifications.domain_mapping.activated.body": "Your custom domain is now active and serving traffic.",
|
|
272
|
+
"customer_accounts.notifications.domain_mapping.activated.title": "Custom domain is live",
|
|
273
|
+
"customer_accounts.notifications.domain_mapping.dns_failed.body": "DNS verification failed for your custom domain. {reason}",
|
|
274
|
+
"customer_accounts.notifications.domain_mapping.dns_failed.title": "DNS verification failed",
|
|
275
|
+
"customer_accounts.notifications.domain_mapping.tls_failed.body": "SSL certificate provisioning is having trouble for your custom domain. We're retrying — no action needed yet.",
|
|
276
|
+
"customer_accounts.notifications.domain_mapping.tls_failed.title": "SSL provisioning issue",
|
|
277
|
+
"customer_accounts.notifications.domain_mapping.verified.body": "Your custom domain has been verified. SSL certificate is being provisioned.",
|
|
278
|
+
"customer_accounts.notifications.domain_mapping.verified.title": "Custom domain verified",
|
|
201
279
|
"customer_accounts.notifications.user.locked.body": "Customer account was locked due to too many failed login attempts.",
|
|
202
280
|
"customer_accounts.notifications.user.locked.title": "Customer account locked",
|
|
203
281
|
"customer_accounts.notifications.user.signup.body": "A new customer account was created.",
|
|
@@ -6,8 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { cookies } from 'next/headers'
|
|
9
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
9
10
|
import { verifyAudienceJwt } from '@open-mercato/shared/lib/auth/jwt'
|
|
10
|
-
import { CUSTOMER_JWT_AUDIENCE } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'
|
|
11
|
+
import { CUSTOMER_JWT_AUDIENCE, CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'
|
|
12
|
+
import { DomainMappingService } from '@open-mercato/core/modules/customer_accounts/services/domainMappingService'
|
|
13
|
+
import { tryNormalizeHostname } from '@open-mercato/core/modules/customer_accounts/lib/hostname'
|
|
14
|
+
import { platformDomains } from '@open-mercato/core/modules/customer_accounts/lib/platformDomains'
|
|
11
15
|
import { validateUserState } from './customerAuth'
|
|
12
16
|
import type { CustomerAuthContext } from './customerAuth'
|
|
13
17
|
|
|
@@ -15,10 +19,6 @@ export type { CustomerAuthContext }
|
|
|
15
19
|
|
|
16
20
|
async function assertSessionStillActive(sessionId: string): Promise<boolean> {
|
|
17
21
|
try {
|
|
18
|
-
const [{ createRequestContainer }, { CustomerSessionService }] = await Promise.all([
|
|
19
|
-
import('@open-mercato/shared/lib/di/container'),
|
|
20
|
-
import('@open-mercato/core/modules/customer_accounts/services/customerSessionService'),
|
|
21
|
-
])
|
|
22
22
|
const container = await createRequestContainer()
|
|
23
23
|
const service = container.resolve('customerSessionService') as InstanceType<typeof CustomerSessionService>
|
|
24
24
|
const session = await service.findActiveSessionById(sessionId)
|
|
@@ -43,7 +43,9 @@ async function assertSessionStillActive(sessionId: string): Promise<boolean> {
|
|
|
43
43
|
* if (!customerAuth) redirect('/login')
|
|
44
44
|
* ```
|
|
45
45
|
*/
|
|
46
|
-
export async function getCustomerAuthFromCookies(
|
|
46
|
+
export async function getCustomerAuthFromCookies(
|
|
47
|
+
options?: { expectedTenantId?: string },
|
|
48
|
+
): Promise<CustomerAuthContext | null> {
|
|
47
49
|
const cookieStore = await cookies()
|
|
48
50
|
const token = cookieStore.get('customer_auth_token')?.value
|
|
49
51
|
if (!token) return null
|
|
@@ -54,12 +56,19 @@ export async function getCustomerAuthFromCookies(): Promise<CustomerAuthContext
|
|
|
54
56
|
if (payload.type !== 'customer') return null
|
|
55
57
|
const sid = typeof payload.sid === 'string' ? payload.sid : ''
|
|
56
58
|
if (!sid) return null
|
|
59
|
+
const tenantId = String(payload.tenantId)
|
|
60
|
+
if (options?.expectedTenantId && options.expectedTenantId !== tenantId) {
|
|
61
|
+
// Cross-host JWT replay defense. See spec rev 5 Customer Authentication
|
|
62
|
+
// section: the host-resolved tenant is the authoritative scope; mismatched
|
|
63
|
+
// JWTs are rejected as if unauthenticated.
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
57
66
|
const stillActive = await assertSessionStillActive(sid)
|
|
58
67
|
if (!stillActive) return null
|
|
59
68
|
|
|
60
69
|
const userState = await validateUserState(
|
|
61
70
|
String(payload.sub),
|
|
62
|
-
|
|
71
|
+
tenantId,
|
|
63
72
|
String(payload.orgId),
|
|
64
73
|
payload.iat,
|
|
65
74
|
)
|
|
@@ -69,7 +78,7 @@ export async function getCustomerAuthFromCookies(): Promise<CustomerAuthContext
|
|
|
69
78
|
sub: String(payload.sub),
|
|
70
79
|
sid,
|
|
71
80
|
type: 'customer',
|
|
72
|
-
tenantId
|
|
81
|
+
tenantId,
|
|
73
82
|
orgId: String(payload.orgId),
|
|
74
83
|
email: String(payload.email || ''),
|
|
75
84
|
displayName: String(payload.displayName || ''),
|
|
@@ -82,3 +91,30 @@ export async function getCustomerAuthFromCookies(): Promise<CustomerAuthContext
|
|
|
82
91
|
return null
|
|
83
92
|
}
|
|
84
93
|
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Convenience wrapper for custom-domain pages: resolves the host, calls
|
|
97
|
+
* getCustomerAuthFromCookies with the host-resolved tenant as expectedTenantId.
|
|
98
|
+
*
|
|
99
|
+
* Use from server components rendered under custom domains.
|
|
100
|
+
*/
|
|
101
|
+
export async function getCustomerAuthForHost(
|
|
102
|
+
host: string | null | undefined,
|
|
103
|
+
): Promise<CustomerAuthContext | null> {
|
|
104
|
+
if (!host) return getCustomerAuthFromCookies()
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const hostname = tryNormalizeHostname(host)
|
|
108
|
+
if (!hostname) return getCustomerAuthFromCookies()
|
|
109
|
+
if (platformDomains().includes(hostname)) return getCustomerAuthFromCookies()
|
|
110
|
+
|
|
111
|
+
const container = await createRequestContainer()
|
|
112
|
+
const service = container.resolve('domainMappingService') as InstanceType<typeof DomainMappingService>
|
|
113
|
+
const resolved = await service.resolveByHostname(hostname)
|
|
114
|
+
if (!resolved || resolved.status !== 'active') return null
|
|
115
|
+
return getCustomerAuthFromCookies({ expectedTenantId: resolved.tenantId })
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.warn('[customer_accounts] getCustomerAuthForHost: domain resolve failed; falling back to platform cookie', err)
|
|
118
|
+
return getCustomerAuthFromCookies()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Build customer-portal URLs that honor the active custom domain when present.
|
|
2
|
+
// All customer-facing email senders (signup welcome, magic link, password reset,
|
|
3
|
+
// notification digests) MUST construct links via this helper instead of
|
|
4
|
+
// hard-coding PLATFORM_PORTAL_BASE_URL — see customer_accounts/AGENTS.md.
|
|
5
|
+
|
|
6
|
+
import type { AppContainer } from '@open-mercato/shared/lib/di/container'
|
|
7
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
8
|
+
|
|
9
|
+
type DomainMappingService = {
|
|
10
|
+
resolveActiveByOrg(orgId: string): Promise<{ hostname: string } | null>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type OrgService = {
|
|
14
|
+
findById(orgId: string): Promise<{ id: string; slug: string | null } | null>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function platformBaseUrl(): string {
|
|
18
|
+
const fromEnv = process.env.PLATFORM_PORTAL_BASE_URL?.trim()
|
|
19
|
+
if (fromEnv && fromEnv.length > 0) return fromEnv.replace(/\/+$/, '')
|
|
20
|
+
// Outbound emails (welcome, magic link, password reset) embed this URL —
|
|
21
|
+
// localhost in production silently sends broken links to real customers.
|
|
22
|
+
if (process.env.NODE_ENV === 'production') {
|
|
23
|
+
throw new Error(
|
|
24
|
+
'PLATFORM_PORTAL_BASE_URL is required in production. Configure it before sending customer-portal emails.',
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
return 'http://localhost:3000'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resolveContainer(container?: AppContainer): Promise<AppContainer> {
|
|
31
|
+
if (container) return Promise.resolve(container)
|
|
32
|
+
return createRequestContainer()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function urlForCustomerOrg(
|
|
36
|
+
orgId: string,
|
|
37
|
+
path: string,
|
|
38
|
+
options?: { container?: AppContainer },
|
|
39
|
+
): Promise<string> {
|
|
40
|
+
const safePath = path.startsWith('/') ? path : `/${path}`
|
|
41
|
+
const container = await resolveContainer(options?.container)
|
|
42
|
+
|
|
43
|
+
let active: { hostname: string } | null = null
|
|
44
|
+
try {
|
|
45
|
+
const service = container.resolve('domainMappingService') as DomainMappingService
|
|
46
|
+
if (service && typeof service.resolveActiveByOrg === 'function') {
|
|
47
|
+
active = await service.resolveActiveByOrg(orgId)
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
// domainMappingService not registered yet (fresh installs, tests) — fall through to platform URL.
|
|
51
|
+
active = null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (active && active.hostname) {
|
|
55
|
+
return `https://${active.hostname}${safePath}`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let orgSlug: string | null = null
|
|
59
|
+
try {
|
|
60
|
+
const orgService = container.resolve('orgService') as OrgService | undefined
|
|
61
|
+
if (orgService && typeof orgService.findById === 'function') {
|
|
62
|
+
const org = await orgService.findById(orgId)
|
|
63
|
+
orgSlug = org?.slug ?? null
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
orgSlug = null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const base = platformBaseUrl()
|
|
70
|
+
if (orgSlug) return `${base}/${orgSlug}/portal${safePath}`
|
|
71
|
+
return `${base}${safePath}`
|
|
72
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Hostname normalization for the custom-domain routing feature.
|
|
2
|
+
// Single canonical form (lowercase, no trailing dot, IDN→Punycode) is essential —
|
|
3
|
+
// the UNIQUE constraint on `domain_mappings.hostname` is meaningless without it.
|
|
4
|
+
|
|
5
|
+
const MAX_HOSTNAME_LENGTH = 253
|
|
6
|
+
const HOSTNAME_LABEL = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/
|
|
7
|
+
|
|
8
|
+
export class HostnameNormalizationError extends Error {
|
|
9
|
+
constructor(message: string) {
|
|
10
|
+
super(message)
|
|
11
|
+
this.name = 'HostnameNormalizationError'
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function normalizeHostname(input: string): string {
|
|
16
|
+
if (typeof input !== 'string') {
|
|
17
|
+
throw new HostnameNormalizationError('Hostname must be a string')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let host = input.trim().toLowerCase()
|
|
21
|
+
if (!host) throw new HostnameNormalizationError('Hostname is empty')
|
|
22
|
+
|
|
23
|
+
host = host
|
|
24
|
+
.replace(/^https?:\/\//, '')
|
|
25
|
+
.replace(/\/.*$/, '')
|
|
26
|
+
.replace(/\?.*$/, '')
|
|
27
|
+
.replace(/#.*$/, '')
|
|
28
|
+
.replace(/:\d+$/, '')
|
|
29
|
+
|
|
30
|
+
if (host.endsWith('.')) host = host.slice(0, -1)
|
|
31
|
+
|
|
32
|
+
if (!host) throw new HostnameNormalizationError('Hostname is empty after stripping')
|
|
33
|
+
|
|
34
|
+
// Convert IDN (Unicode) to Punycode (ASCII) via the URL constructor — this
|
|
35
|
+
// matches what browsers and Let's Encrypt use, without depending on the
|
|
36
|
+
// deprecated `node:punycode` module or pulling in `tr46`.
|
|
37
|
+
try {
|
|
38
|
+
const url = new URL(`https://${host}`)
|
|
39
|
+
host = url.hostname
|
|
40
|
+
} catch {
|
|
41
|
+
throw new HostnameNormalizationError(`Hostname is not a valid host: ${input}`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!host || host.length > MAX_HOSTNAME_LENGTH) {
|
|
45
|
+
throw new HostnameNormalizationError(
|
|
46
|
+
`Hostname must be between 1 and ${MAX_HOSTNAME_LENGTH} characters after normalization`,
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const labels = host.split('.')
|
|
51
|
+
if (labels.length < 2) {
|
|
52
|
+
throw new HostnameNormalizationError('Hostname must have at least two labels (e.g. "example.com")')
|
|
53
|
+
}
|
|
54
|
+
for (const label of labels) {
|
|
55
|
+
if (!HOSTNAME_LABEL.test(label)) {
|
|
56
|
+
throw new HostnameNormalizationError(`Invalid DNS label: "${label}"`)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Reject IP-address literals (e.g. "127.0.0.1"): a custom domain must be a
|
|
60
|
+
// registered DNS name, and a TLD is never all-numeric. This also keeps
|
|
61
|
+
// loopback/test hosts (127.0.0.1) from being mistaken for a tenant's
|
|
62
|
+
// branded domain by the routing layer.
|
|
63
|
+
if (/^\d+$/.test(labels[labels.length - 1])) {
|
|
64
|
+
throw new HostnameNormalizationError(`Hostname looks like an IP address, not a DNS name: ${input}`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return host
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function tryNormalizeHostname(input: string): string | null {
|
|
71
|
+
try {
|
|
72
|
+
return normalizeHostname(input)
|
|
73
|
+
} catch {
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse the `PLATFORM_DOMAINS` env into a normalized list of platform hostnames.
|
|
3
|
+
*
|
|
4
|
+
* Platform hosts are the deployment's own domains (admin app, marketing site,
|
|
5
|
+
* loopback). Custom-domain routing MUST treat platform hosts as non-tenant
|
|
6
|
+
* traffic, so domain registration, resolver lookups, and tenant-context
|
|
7
|
+
* fallbacks all share the same view of which hosts are platform-owned.
|
|
8
|
+
*/
|
|
9
|
+
export function platformDomains(): string[] {
|
|
10
|
+
return (process.env.PLATFORM_DOMAINS ?? 'localhost,openmercato.com')
|
|
11
|
+
.split(',')
|
|
12
|
+
.map((s) => s.trim().toLowerCase())
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// CIDR matching for known reverse-proxy IP ranges (Cloudflare, Fastly, custom).
|
|
2
|
+
// Used by the DNS verification fallback: if a domain's A record lands inside a
|
|
3
|
+
// known proxy range we fall back to a reverse-resolve over HTTPS, because the
|
|
4
|
+
// proxy hides the real origin from us.
|
|
5
|
+
|
|
6
|
+
const DEFAULT_CLOUDFLARE_RANGES = [
|
|
7
|
+
'173.245.48.0/20',
|
|
8
|
+
'103.21.244.0/22',
|
|
9
|
+
'103.22.200.0/22',
|
|
10
|
+
'103.31.4.0/22',
|
|
11
|
+
'141.101.64.0/18',
|
|
12
|
+
'108.162.192.0/18',
|
|
13
|
+
'190.93.240.0/20',
|
|
14
|
+
'188.114.96.0/20',
|
|
15
|
+
'197.234.240.0/22',
|
|
16
|
+
'198.41.128.0/17',
|
|
17
|
+
'162.158.0.0/15',
|
|
18
|
+
'104.16.0.0/13',
|
|
19
|
+
'104.24.0.0/14',
|
|
20
|
+
'172.64.0.0/13',
|
|
21
|
+
'131.0.72.0/22',
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
type ParsedCidr = { network: number; mask: number; raw: string }
|
|
25
|
+
|
|
26
|
+
let cachedRanges: ParsedCidr[] | null = null
|
|
27
|
+
|
|
28
|
+
function parseIpv4(ip: string): number | null {
|
|
29
|
+
const parts = ip.split('.')
|
|
30
|
+
if (parts.length !== 4) return null
|
|
31
|
+
let result = 0
|
|
32
|
+
for (const part of parts) {
|
|
33
|
+
if (!/^\d+$/.test(part)) return null
|
|
34
|
+
const value = Number(part)
|
|
35
|
+
if (value < 0 || value > 255) return null
|
|
36
|
+
result = (result << 8) | value
|
|
37
|
+
}
|
|
38
|
+
// Force unsigned 32-bit
|
|
39
|
+
return result >>> 0
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseCidr(cidr: string): ParsedCidr | null {
|
|
43
|
+
const [ip, prefixStr] = cidr.split('/')
|
|
44
|
+
if (!ip || !prefixStr) return null
|
|
45
|
+
const prefix = Number(prefixStr)
|
|
46
|
+
if (!Number.isFinite(prefix) || prefix < 0 || prefix > 32) return null
|
|
47
|
+
const ipNum = parseIpv4(ip)
|
|
48
|
+
if (ipNum === null) return null
|
|
49
|
+
const mask = prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0
|
|
50
|
+
const network = (ipNum & mask) >>> 0
|
|
51
|
+
return { network, mask, raw: cidr }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function loadRanges(): ParsedCidr[] {
|
|
55
|
+
if (cachedRanges) return cachedRanges
|
|
56
|
+
const fromEnv = (process.env.KNOWN_PROXY_IP_RANGES ?? '').trim()
|
|
57
|
+
const list = fromEnv
|
|
58
|
+
? fromEnv.split(',').map((s) => s.trim()).filter(Boolean)
|
|
59
|
+
: DEFAULT_CLOUDFLARE_RANGES
|
|
60
|
+
cachedRanges = list.map(parseCidr).filter((v): v is ParsedCidr => v !== null)
|
|
61
|
+
return cachedRanges
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isInKnownProxyRange(ip: string): boolean {
|
|
65
|
+
const ipNum = parseIpv4(ip)
|
|
66
|
+
if (ipNum === null) return false
|
|
67
|
+
for (const range of loadRanges()) {
|
|
68
|
+
if (((ipNum & range.mask) >>> 0) === range.network) return true
|
|
69
|
+
}
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function detectProxy(ip: string): string | null {
|
|
74
|
+
if (!isInKnownProxyRange(ip)) return null
|
|
75
|
+
// For now we only ship Cloudflare's published ranges by default. Operators
|
|
76
|
+
// using a different proxy can override KNOWN_PROXY_IP_RANGES; we still
|
|
77
|
+
// return 'unknown' for those so callers can phrase the diagnostic correctly.
|
|
78
|
+
const fromEnv = process.env.KNOWN_PROXY_IP_RANGES
|
|
79
|
+
if (!fromEnv) return 'cloudflare'
|
|
80
|
+
return 'unknown'
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function resetProxyRangeCacheForTests(): void {
|
|
84
|
+
cachedRanges = null
|
|
85
|
+
}
|
|
@@ -33,4 +33,14 @@ export const customerMagicLinkIpRateLimitConfig = readEndpointRateLimitConfig('C
|
|
|
33
33
|
points: 10, duration: 60, blockDuration: 120, keyPrefix: 'customer-magic-link-ip',
|
|
34
34
|
})
|
|
35
35
|
|
|
36
|
+
// Bulk custom-domain warm-up endpoint (`/api/customer_accounts/domain-resolve/all`).
|
|
37
|
+
// Single shared `DOMAIN_RESOLVE_SECRET` gates a payload that lists every active
|
|
38
|
+
// custom-domain mapping in the deployment, so we cap requests per IP to make
|
|
39
|
+
// brute-force or post-leak enumeration noisy. 30 req/min/IP comfortably covers
|
|
40
|
+
// the Node middleware warm-up cadence (one call per process boot) while
|
|
41
|
+
// staying well below useful enumeration throughput.
|
|
42
|
+
export const domainResolveAllIpRateLimitConfig = readEndpointRateLimitConfig('DOMAIN_RESOLVE_ALL_IP', {
|
|
43
|
+
points: 30, duration: 60, blockDuration: 60, keyPrefix: 'domain-resolve-all',
|
|
44
|
+
})
|
|
45
|
+
|
|
36
46
|
export { checkAuthRateLimit, resetAuthRateLimit }
|