@open-mercato/core 0.6.1-develop.3276.1.1fcd145078 → 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
|
@@ -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
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/customer_accounts/api/domain-resolve.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\nexport async function GET(req: Request) {\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 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 const supplied = req.headers.get('x-domain-resolve-secret')\n if (!secretEqual(supplied, expected)) {\n return withOriginHeader(NextResponse.json({ ok: false, error: 'Forbidden' }, { status: 403 }))\n }\n\n const url = new URL(req.url)\n const rawHost = url.searchParams.get('host')\n if (!rawHost) return withOriginHeader(NextResponse.json({ ok: false, error: 'host param required' }, { status: 400 }))\n const hostname = tryNormalizeHostname(rawHost)\n if (!hostname) return withOriginHeader(NextResponse.json({ ok: false, error: 'invalid host' }, { status: 400 }))\n\n const container = await createRequestContainer()\n const service = container.resolve('domainMappingService') as DomainMappingService\n const result = await service.resolveByHostname(hostname)\n if (!result || result.status !== 'active') {\n return withOriginHeader(NextResponse.json({ ok: false, error: 'Not found' }, { status: 404 }))\n }\n\n return withOriginHeader(\n NextResponse.json({\n ok: true,\n tenantId: result.tenantId,\n organizationId: result.organizationId,\n orgSlug: result.orgSlug,\n status: result.status,\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: 'Resolve a custom domain to its tenant and organization',\n methods: {\n GET: {\n summary: 'Single-host resolve for the Node middleware',\n description:\n 'Internal endpoint used by the portal middleware to populate its in-memory cache. 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 tenantId: z.string().uuid(),\n organizationId: z.string().uuid(),\n orgSlug: z.string().nullable(),\n status: z.literal('active'),\n }),\n },\n ],\n errors: [\n { status: 400, description: 'Bad request', schema: errorSchema },\n { status: 403, description: 'Forbidden', schema: errorSchema },\n { status: 404, description: 'Not found', schema: errorSchema },\n { status: 503, description: 'Server misconfiguration', schema: errorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
customerLoginRateLimitConfig,
|
|
12
12
|
customerLoginIpRateLimitConfig
|
|
13
13
|
} from "@open-mercato/core/modules/customer_accounts/lib/rateLimiter";
|
|
14
|
+
import {
|
|
15
|
+
resolveTenantContext,
|
|
16
|
+
TenantResolutionError
|
|
17
|
+
} from "@open-mercato/core/modules/customer_accounts/lib/resolveTenantContext";
|
|
14
18
|
const metadata = { requireAuth: false };
|
|
15
19
|
async function POST(req) {
|
|
16
20
|
let body;
|
|
@@ -23,9 +27,16 @@ async function POST(req) {
|
|
|
23
27
|
if (!parsed.success) {
|
|
24
28
|
return NextResponse.json({ ok: false, error: "Invalid credentials" }, { status: 400 });
|
|
25
29
|
}
|
|
26
|
-
const { email, password
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
const { email, password } = parsed.data;
|
|
31
|
+
let tenantId;
|
|
32
|
+
try {
|
|
33
|
+
const context = await resolveTenantContext(req, parsed.data.tenantId);
|
|
34
|
+
tenantId = context.tenantId;
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (err instanceof TenantResolutionError) {
|
|
37
|
+
return NextResponse.json({ ok: false, error: err.message }, { status: err.status });
|
|
38
|
+
}
|
|
39
|
+
throw err;
|
|
29
40
|
}
|
|
30
41
|
const { error: rateLimitError, compoundKey } = await checkAuthRateLimit({
|
|
31
42
|
req,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customer_accounts/api/login.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { loginSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\nimport { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\nimport { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport { getClientIp } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport {\n checkAuthRateLimit,\n resetAuthRateLimit,\n customerLoginRateLimitConfig,\n customerLoginIpRateLimitConfig,\n} from '@open-mercato/core/modules/customer_accounts/lib/rateLimiter'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nexport async function POST(req: Request) {\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = loginSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Invalid credentials' }, { status: 400 })\n }\n\n const { email, password
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,mBAAmB;AAC5B,SAAS,8BAA8B;AAIvC,SAAS,iCAAiC;AAC1C,SAAS,4BAA4B;AACrC,SAAS,mBAAmB;AAC5B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,WAAqD,EAAE,aAAa,MAAM;AAEvF,eAAsB,KAAK,KAAc;AACvC,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACxF;AAEA,QAAM,SAAS,YAAY,UAAU,IAAI;AACzC,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,sBAAsB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvF;AAEA,QAAM,EAAE,OAAO,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { loginSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\nimport { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\nimport { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport { getClientIp } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport {\n checkAuthRateLimit,\n resetAuthRateLimit,\n customerLoginRateLimitConfig,\n customerLoginIpRateLimitConfig,\n} from '@open-mercato/core/modules/customer_accounts/lib/rateLimiter'\nimport {\n resolveTenantContext,\n TenantResolutionError,\n} from '@open-mercato/core/modules/customer_accounts/lib/resolveTenantContext'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nexport async function POST(req: Request) {\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = loginSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Invalid credentials' }, { status: 400 })\n }\n\n const { email, password } = parsed.data\n let tenantId: string\n try {\n const context = await resolveTenantContext(req, parsed.data.tenantId)\n tenantId = context.tenantId\n } catch (err) {\n if (err instanceof TenantResolutionError) {\n return NextResponse.json({ ok: false, error: err.message }, { status: err.status })\n }\n throw err\n }\n\n const { error: rateLimitError, compoundKey } = await checkAuthRateLimit({\n req,\n ipConfig: customerLoginIpRateLimitConfig,\n compoundConfig: customerLoginRateLimitConfig,\n compoundIdentifier: email,\n })\n if (rateLimitError) return rateLimitError\n\n const container = await createRequestContainer()\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService\n const customerRbacService = container.resolve('customerRbacService') as CustomerRbacService\n\n const user = await customerUserService.findByEmail(email, tenantId)\n if (!user || !user.passwordHash) {\n void emitCustomerAccountsEvent('customer_accounts.login.failed', { email, reason: 'invalid_credentials', tenantId }).catch(() => undefined)\n return NextResponse.json({ ok: false, error: 'Invalid email or password' }, { status: 401 })\n }\n\n if (!user.isActive) {\n return NextResponse.json({ ok: false, error: 'Account is deactivated' }, { status: 401 })\n }\n\n if (customerUserService.checkLockout(user)) {\n void emitCustomerAccountsEvent('customer_accounts.login.failed', { email, reason: 'locked', tenantId }).catch(() => undefined)\n return NextResponse.json({ ok: false, error: 'Account is temporarily locked. Please try again later.' }, { status: 423 })\n }\n\n const passwordValid = await customerUserService.verifyPassword(user, password)\n if (!passwordValid) {\n await customerUserService.incrementFailedAttempts(user)\n void emitCustomerAccountsEvent('customer_accounts.login.failed', { email, reason: 'invalid_password', tenantId }).catch(() => undefined)\n return NextResponse.json({ ok: false, error: 'Invalid email or password' }, { status: 401 })\n }\n\n if (!user.emailVerifiedAt) {\n void emitCustomerAccountsEvent('customer_accounts.login.failed', {\n email,\n reason: 'email_not_verified',\n tenantId,\n }).catch(() => undefined)\n return NextResponse.json({ ok: false, error: 'Invalid email or password' }, { status: 401 })\n }\n\n await customerUserService.resetFailedAttempts(user)\n await customerUserService.updateLastLoginAt(user)\n\n if (compoundKey) {\n await resetAuthRateLimit(compoundKey, customerLoginRateLimitConfig)\n }\n\n const acl = await customerRbacService.loadAcl(user.id, { tenantId, organizationId: user.organizationId })\n const resolvedFeatures = acl.features\n\n const ip = getClientIp(req, 0)\n const userAgent = req.headers.get('user-agent') || null\n const { rawToken, jwt, session } = await customerSessionService.createSession(user, resolvedFeatures, ip, userAgent)\n\n void emitCustomerAccountsEvent('customer_accounts.login.success', {\n id: user.id,\n email: user.email,\n tenantId,\n organizationId: user.organizationId,\n }).catch(() => undefined)\n\n const res = NextResponse.json({\n ok: true,\n user: {\n id: user.id,\n email: user.email,\n displayName: user.displayName,\n emailVerified: !!user.emailVerifiedAt,\n },\n resolvedFeatures,\n })\n\n res.cookies.set('customer_auth_token', jwt, {\n httpOnly: true,\n path: '/',\n sameSite: 'lax',\n secure: process.env.NODE_ENV === 'production',\n maxAge: 60 * 60 * 8,\n })\n res.cookies.set('customer_session_token', rawToken, {\n httpOnly: true,\n path: '/',\n sameSite: 'lax',\n secure: process.env.NODE_ENV === 'production',\n maxAge: 60 * 60 * 24 * 30,\n })\n\n return res\n}\n\nconst loginSuccessSchema = z.object({\n ok: z.literal(true),\n user: z.object({\n id: z.string().uuid(),\n email: z.string().email(),\n displayName: z.string(),\n emailVerified: z.boolean(),\n }),\n resolvedFeatures: z.array(z.string()),\n})\n\nconst errorSchema = z.object({\n ok: z.literal(false),\n error: z.string(),\n})\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Authenticate customer credentials',\n description: 'Validates customer credentials and issues JWT + session cookies.',\n tags: ['Customer Authentication'],\n requestBody: {\n schema: loginSchema,\n description: 'Login payload with email and password.',\n },\n responses: [\n { status: 200, description: 'Login successful', schema: loginSuccessSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: errorSchema },\n { status: 401, description: 'Invalid credentials', schema: errorSchema },\n { status: 423, description: 'Account locked', schema: errorSchema },\n { status: 429, description: 'Too many login attempts', schema: rateLimitErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Customer login',\n description: 'Handles customer authentication and session issuance.',\n methods: { POST: methodDoc },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { magicLinkRequestSchema } from "@open-mercato/core/modules/customer_accounts/data/validators";
|
|
4
|
+
import {
|
|
5
|
+
resolveTenantContext,
|
|
6
|
+
TenantResolutionError
|
|
7
|
+
} from "@open-mercato/core/modules/customer_accounts/lib/resolveTenantContext";
|
|
4
8
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
9
|
import { rateLimitErrorSchema } from "@open-mercato/shared/lib/ratelimit/helpers";
|
|
6
10
|
import {
|
|
@@ -29,9 +33,16 @@ async function POST(req) {
|
|
|
29
33
|
if (!parsed.success) {
|
|
30
34
|
return NextResponse.json({ ok: true });
|
|
31
35
|
}
|
|
32
|
-
const { email
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
const { email } = parsed.data;
|
|
37
|
+
let tenantId;
|
|
38
|
+
try {
|
|
39
|
+
const context = await resolveTenantContext(req, parsed.data.tenantId);
|
|
40
|
+
tenantId = context.tenantId;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
if (err instanceof TenantResolutionError) {
|
|
43
|
+
return NextResponse.json({ ok: true });
|
|
44
|
+
}
|
|
45
|
+
throw err;
|
|
35
46
|
}
|
|
36
47
|
const container = await createRequestContainer();
|
|
37
48
|
const customerUserService = container.resolve("customerUserService");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customer_accounts/api/magic-link/request.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { magicLinkRequestSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'\nimport { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport {\n checkAuthRateLimit,\n customerMagicLinkRateLimitConfig,\n customerMagicLinkIpRateLimitConfig,\n} from '@open-mercato/core/modules/customer_accounts/lib/rateLimiter'\nimport { readNormalizedEmailFromJsonRequest } from '@open-mercato/core/modules/customer_accounts/lib/rateLimitIdentifier'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nexport async function POST(req: Request) {\n const rateLimitEmail = await readNormalizedEmailFromJsonRequest(req)\n const { error: rateLimitError } = await checkAuthRateLimit({\n req,\n ipConfig: customerMagicLinkIpRateLimitConfig,\n compoundConfig: customerMagicLinkRateLimitConfig,\n compoundIdentifier: rateLimitEmail,\n })\n if (rateLimitError) return rateLimitError\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: true })\n }\n\n const parsed = magicLinkRequestSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: true })\n }\n\n const { email
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC,SAAS,8BAA8B;AAGvC,SAAS,4BAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,0CAA0C;AAE5C,MAAM,WAAqD,EAAE,aAAa,MAAM;AAEvF,eAAsB,KAAK,KAAc;AACvC,QAAM,iBAAiB,MAAM,mCAAmC,GAAG;AACnE,QAAM,EAAE,OAAO,eAAe,IAAI,MAAM,mBAAmB;AAAA,IACzD;AAAA,IACA,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,oBAAoB;AAAA,EACtB,CAAC;AACD,MAAI,eAAgB,QAAO;AAE3B,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACvC;AAEA,QAAM,SAAS,uBAAuB,UAAU,IAAI;AACpD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACvC;AAEA,QAAM,EAAE,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { magicLinkRequestSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport {\n resolveTenantContext,\n TenantResolutionError,\n} from '@open-mercato/core/modules/customer_accounts/lib/resolveTenantContext'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'\nimport { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport {\n checkAuthRateLimit,\n customerMagicLinkRateLimitConfig,\n customerMagicLinkIpRateLimitConfig,\n} from '@open-mercato/core/modules/customer_accounts/lib/rateLimiter'\nimport { readNormalizedEmailFromJsonRequest } from '@open-mercato/core/modules/customer_accounts/lib/rateLimitIdentifier'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nexport async function POST(req: Request) {\n const rateLimitEmail = await readNormalizedEmailFromJsonRequest(req)\n const { error: rateLimitError } = await checkAuthRateLimit({\n req,\n ipConfig: customerMagicLinkIpRateLimitConfig,\n compoundConfig: customerMagicLinkRateLimitConfig,\n compoundIdentifier: rateLimitEmail,\n })\n if (rateLimitError) return rateLimitError\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: true })\n }\n\n const parsed = magicLinkRequestSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: true })\n }\n\n const { email } = parsed.data\n let tenantId: string\n try {\n const context = await resolveTenantContext(req, parsed.data.tenantId)\n tenantId = context.tenantId\n } catch (err) {\n if (err instanceof TenantResolutionError) {\n return NextResponse.json({ ok: true })\n }\n throw err\n }\n\n const container = await createRequestContainer()\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const customerTokenService = container.resolve('customerTokenService') as CustomerTokenService\n\n const user = await customerUserService.findByEmail(email, tenantId)\n if (user) {\n await customerTokenService.createMagicLink(user.id, tenantId)\n // Token is stored in DB; email delivery should be handled by a direct service call,\n // NOT via the event bus \u2014 raw tokens must never travel through events.\n void import('@open-mercato/core/modules/customer_accounts/events').then(({ emitCustomerAccountsEvent }) =>\n emitCustomerAccountsEvent('customer_accounts.magic_link.requested', {\n userId: user.id,\n tenantId,\n organizationId: user.organizationId,\n })\n ).catch(() => undefined)\n }\n\n return NextResponse.json({ ok: true })\n}\n\nconst successSchema = z.object({ ok: z.literal(true) })\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Request magic link login',\n description: 'Sends a magic link to the customer email. Always returns 200 to prevent enumeration.',\n tags: ['Customer Authentication'],\n requestBody: {\n schema: magicLinkRequestSchema,\n description: 'Magic link request with email.',\n },\n responses: [\n { status: 200, description: 'Request accepted', schema: successSchema },\n ],\n errors: [\n { status: 429, description: 'Too many requests', schema: rateLimitErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Request customer magic link',\n description: 'Handles magic link login requests for customer accounts.',\n methods: { POST: methodDoc },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,8BAA8B;AAGvC,SAAS,4BAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,0CAA0C;AAE5C,MAAM,WAAqD,EAAE,aAAa,MAAM;AAEvF,eAAsB,KAAK,KAAc;AACvC,QAAM,iBAAiB,MAAM,mCAAmC,GAAG;AACnE,QAAM,EAAE,OAAO,eAAe,IAAI,MAAM,mBAAmB;AAAA,IACzD;AAAA,IACA,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,oBAAoB;AAAA,EACtB,CAAC;AACD,MAAI,eAAgB,QAAO;AAE3B,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACvC;AAEA,QAAM,SAAS,uBAAuB,UAAU,IAAI;AACpD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACvC;AAEA,QAAM,EAAE,MAAM,IAAI,OAAO;AACzB,MAAI;AACJ,MAAI;AACF,UAAM,UAAU,MAAM,qBAAqB,KAAK,OAAO,KAAK,QAAQ;AACpE,eAAW,QAAQ;AAAA,EACrB,SAAS,KAAK;AACZ,QAAI,eAAe,uBAAuB;AACxC,aAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,IACvC;AACA,UAAM;AAAA,EACR;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,sBAAsB,UAAU,QAAQ,qBAAqB;AACnE,QAAM,uBAAuB,UAAU,QAAQ,sBAAsB;AAErE,QAAM,OAAO,MAAM,oBAAoB,YAAY,OAAO,QAAQ;AAClE,MAAI,MAAM;AACR,UAAM,qBAAqB,gBAAgB,KAAK,IAAI,QAAQ;AAG5D,SAAK,OAAO,qDAAqD,EAAE;AAAA,MAAK,CAAC,EAAE,0BAA0B,MACnG,0BAA0B,0CAA0C;AAAA,QAClE,QAAQ,KAAK;AAAA,QACb;AAAA,QACA,gBAAgB,KAAK;AAAA,MACvB,CAAC;AAAA,IACH,EAAE,MAAM,MAAM,MAAS;AAAA,EACzB;AAEA,SAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AACvC;AAEA,MAAM,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;AAEtD,MAAM,YAA8B;AAAA,EAClC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,yBAAyB;AAAA,EAChC,aAAa;AAAA,IACX,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,oBAAoB,QAAQ,cAAc;AAAA,EACxE;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,qBAAqB;AAAA,EAChF;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,SAAS,EAAE,MAAM,UAAU;AAC7B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { passwordResetRequestSchema } from "@open-mercato/core/modules/customer_accounts/data/validators";
|
|
4
|
+
import {
|
|
5
|
+
resolveTenantContext,
|
|
6
|
+
TenantResolutionError
|
|
7
|
+
} from "@open-mercato/core/modules/customer_accounts/lib/resolveTenantContext";
|
|
4
8
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
9
|
import { rateLimitErrorSchema } from "@open-mercato/shared/lib/ratelimit/helpers";
|
|
6
10
|
import {
|
|
@@ -29,9 +33,16 @@ async function POST(req) {
|
|
|
29
33
|
if (!parsed.success) {
|
|
30
34
|
return NextResponse.json({ ok: true });
|
|
31
35
|
}
|
|
32
|
-
const { email
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
const { email } = parsed.data;
|
|
37
|
+
let tenantId;
|
|
38
|
+
try {
|
|
39
|
+
const context = await resolveTenantContext(req, parsed.data.tenantId);
|
|
40
|
+
tenantId = context.tenantId;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
if (err instanceof TenantResolutionError) {
|
|
43
|
+
return NextResponse.json({ ok: true });
|
|
44
|
+
}
|
|
45
|
+
throw err;
|
|
35
46
|
}
|
|
36
47
|
const container = await createRequestContainer();
|
|
37
48
|
const customerUserService = container.resolve("customerUserService");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customer_accounts/api/password/reset-request.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { passwordResetRequestSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'\nimport { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport {\n checkAuthRateLimit,\n customerPasswordResetRateLimitConfig,\n customerPasswordResetIpRateLimitConfig,\n} from '@open-mercato/core/modules/customer_accounts/lib/rateLimiter'\nimport { readNormalizedEmailFromJsonRequest } from '@open-mercato/core/modules/customer_accounts/lib/rateLimitIdentifier'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nexport async function POST(req: Request) {\n const rateLimitEmail = await readNormalizedEmailFromJsonRequest(req)\n const { error: rateLimitError } = await checkAuthRateLimit({\n req,\n ipConfig: customerPasswordResetIpRateLimitConfig,\n compoundConfig: customerPasswordResetRateLimitConfig,\n compoundIdentifier: rateLimitEmail,\n })\n if (rateLimitError) return rateLimitError\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: true }) // Always 200\n }\n\n const parsed = passwordResetRequestSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: true }) // Always 200 to prevent enumeration\n }\n\n const { email
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,kCAAkC;AAC3C,SAAS,8BAA8B;AAGvC,SAAS,4BAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,0CAA0C;AAE5C,MAAM,WAAqD,EAAE,aAAa,MAAM;AAEvF,eAAsB,KAAK,KAAc;AACvC,QAAM,iBAAiB,MAAM,mCAAmC,GAAG;AACnE,QAAM,EAAE,OAAO,eAAe,IAAI,MAAM,mBAAmB;AAAA,IACzD;AAAA,IACA,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,oBAAoB;AAAA,EACtB,CAAC;AACD,MAAI,eAAgB,QAAO;AAE3B,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACvC;AAEA,QAAM,SAAS,2BAA2B,UAAU,IAAI;AACxD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACvC;AAEA,QAAM,EAAE,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { passwordResetRequestSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport {\n resolveTenantContext,\n TenantResolutionError,\n} from '@open-mercato/core/modules/customer_accounts/lib/resolveTenantContext'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'\nimport { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport {\n checkAuthRateLimit,\n customerPasswordResetRateLimitConfig,\n customerPasswordResetIpRateLimitConfig,\n} from '@open-mercato/core/modules/customer_accounts/lib/rateLimiter'\nimport { readNormalizedEmailFromJsonRequest } from '@open-mercato/core/modules/customer_accounts/lib/rateLimitIdentifier'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nexport async function POST(req: Request) {\n const rateLimitEmail = await readNormalizedEmailFromJsonRequest(req)\n const { error: rateLimitError } = await checkAuthRateLimit({\n req,\n ipConfig: customerPasswordResetIpRateLimitConfig,\n compoundConfig: customerPasswordResetRateLimitConfig,\n compoundIdentifier: rateLimitEmail,\n })\n if (rateLimitError) return rateLimitError\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: true }) // Always 200\n }\n\n const parsed = passwordResetRequestSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: true }) // Always 200 to prevent enumeration\n }\n\n const { email } = parsed.data\n let tenantId: string\n try {\n const context = await resolveTenantContext(req, parsed.data.tenantId)\n tenantId = context.tenantId\n } catch (err) {\n if (err instanceof TenantResolutionError) {\n // Always return 200 to avoid leaking whether a tenant exists for this host.\n return NextResponse.json({ ok: true })\n }\n throw err\n }\n\n const container = await createRequestContainer()\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const customerTokenService = container.resolve('customerTokenService') as CustomerTokenService\n\n const user = await customerUserService.findByEmail(email, tenantId)\n if (user) {\n await customerTokenService.createPasswordReset(user.id, tenantId)\n // Token is stored in DB; email delivery should be handled by a direct service call,\n // NOT via the event bus \u2014 raw tokens must never travel through events.\n void import('@open-mercato/core/modules/customer_accounts/events').then(({ emitCustomerAccountsEvent }) =>\n emitCustomerAccountsEvent('customer_accounts.password_reset.requested', {\n userId: user.id,\n tenantId,\n })\n ).catch(() => undefined)\n }\n\n // Always return 200 to prevent email enumeration\n return NextResponse.json({ ok: true })\n}\n\nconst successSchema = z.object({ ok: z.literal(true) })\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Request customer password reset',\n description: 'Initiates a password reset flow. Always returns 200 to prevent email enumeration.',\n tags: ['Customer Authentication'],\n requestBody: {\n schema: passwordResetRequestSchema,\n description: 'Password reset request with email.',\n },\n responses: [\n { status: 200, description: 'Request accepted', schema: successSchema },\n ],\n errors: [\n { status: 429, description: 'Too many requests', schema: rateLimitErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Customer password reset request',\n description: 'Handles password reset initiation for customer accounts.',\n methods: { POST: methodDoc },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,kCAAkC;AAC3C;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,8BAA8B;AAGvC,SAAS,4BAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,0CAA0C;AAE5C,MAAM,WAAqD,EAAE,aAAa,MAAM;AAEvF,eAAsB,KAAK,KAAc;AACvC,QAAM,iBAAiB,MAAM,mCAAmC,GAAG;AACnE,QAAM,EAAE,OAAO,eAAe,IAAI,MAAM,mBAAmB;AAAA,IACzD;AAAA,IACA,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,oBAAoB;AAAA,EACtB,CAAC;AACD,MAAI,eAAgB,QAAO;AAE3B,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACvC;AAEA,QAAM,SAAS,2BAA2B,UAAU,IAAI;AACxD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACvC;AAEA,QAAM,EAAE,MAAM,IAAI,OAAO;AACzB,MAAI;AACJ,MAAI;AACF,UAAM,UAAU,MAAM,qBAAqB,KAAK,OAAO,KAAK,QAAQ;AACpE,eAAW,QAAQ;AAAA,EACrB,SAAS,KAAK;AACZ,QAAI,eAAe,uBAAuB;AAExC,aAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,IACvC;AACA,UAAM;AAAA,EACR;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,sBAAsB,UAAU,QAAQ,qBAAqB;AACnE,QAAM,uBAAuB,UAAU,QAAQ,sBAAsB;AAErE,QAAM,OAAO,MAAM,oBAAoB,YAAY,OAAO,QAAQ;AAClE,MAAI,MAAM;AACR,UAAM,qBAAqB,oBAAoB,KAAK,IAAI,QAAQ;AAGhE,SAAK,OAAO,qDAAqD,EAAE;AAAA,MAAK,CAAC,EAAE,0BAA0B,MACnG,0BAA0B,8CAA8C;AAAA,QACtE,QAAQ,KAAK;AAAA,QACb;AAAA,MACF,CAAC;AAAA,IACH,EAAE,MAAM,MAAM,MAAS;AAAA,EACzB;AAGA,SAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AACvC;AAEA,MAAM,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;AAEtD,MAAM,YAA8B;AAAA,EAClC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,yBAAyB;AAAA,EAChC,aAAa;AAAA,IACX,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,oBAAoB,QAAQ,cAAc;AAAA,EACxE;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,qBAAqB;AAAA,EAChF;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,SAAS,EAAE,MAAM,UAAU;AAC7B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -18,6 +18,11 @@ import {
|
|
|
18
18
|
import { readNormalizedEmailFromJsonRequest } from "@open-mercato/core/modules/customer_accounts/lib/rateLimitIdentifier";
|
|
19
19
|
import { findOrganizationInTenant } from "@open-mercato/core/modules/customer_accounts/lib/organizationLookup";
|
|
20
20
|
import { getSecurityEmailBaseUrl, mapSecurityEmailUrlError } from "@open-mercato/shared/lib/url";
|
|
21
|
+
import {
|
|
22
|
+
resolveTenantContext,
|
|
23
|
+
TenantResolutionError
|
|
24
|
+
} from "@open-mercato/core/modules/customer_accounts/lib/resolveTenantContext";
|
|
25
|
+
import { urlForCustomerOrg } from "@open-mercato/core/modules/customer_accounts/lib/customerUrl";
|
|
21
26
|
const metadata = { requireAuth: false };
|
|
22
27
|
const TIMING_EQUALIZATION_HASH = "$2b$10$.F2A6UHFzk.d8trNdfqt4OLz05Nf3IOuMmN6VJKflhD4.rz.prR8i";
|
|
23
28
|
function resolvePortalLoginUrl(baseUrl, organizationSlug) {
|
|
@@ -46,9 +51,21 @@ async function POST(req) {
|
|
|
46
51
|
if (!parsed.success) {
|
|
47
52
|
return NextResponse.json({ ok: false, error: "Validation failed", details: parsed.error.flatten().fieldErrors }, { status: 400 });
|
|
48
53
|
}
|
|
49
|
-
const { email, password, displayName
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
const { email, password, displayName } = parsed.data;
|
|
55
|
+
let tenantId;
|
|
56
|
+
let organizationId;
|
|
57
|
+
try {
|
|
58
|
+
const context = await resolveTenantContext(req, parsed.data.tenantId);
|
|
59
|
+
tenantId = context.tenantId;
|
|
60
|
+
organizationId = context.organizationId ?? parsed.data.organizationId ?? null;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if (err instanceof TenantResolutionError) {
|
|
63
|
+
return NextResponse.json({ ok: false, error: err.message }, { status: err.status });
|
|
64
|
+
}
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
if (!organizationId) {
|
|
68
|
+
return NextResponse.json({ ok: false, error: "organizationId is required" }, { status: 400 });
|
|
52
69
|
}
|
|
53
70
|
let baseUrl;
|
|
54
71
|
try {
|
|
@@ -74,7 +91,11 @@ async function POST(req) {
|
|
|
74
91
|
if (existing) {
|
|
75
92
|
await bcryptCompare(password, TIMING_EQUALIZATION_HASH);
|
|
76
93
|
const existingOrg = await findOrganizationInTenant(em, existing.organizationId, tenantId);
|
|
77
|
-
|
|
94
|
+
let loginUrl = resolvePortalLoginUrl(baseUrl, existingOrg?.slug ?? null);
|
|
95
|
+
try {
|
|
96
|
+
loginUrl = await urlForCustomerOrg(existing.organizationId, "/login", { container });
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
78
99
|
const subject2 = translate("customer_accounts.signup.existing.subject", "You already have a portal account");
|
|
79
100
|
const copy2 = {
|
|
80
101
|
preview: translate("customer_accounts.signup.existing.preview", "A sign-up attempt was made for an email that already has a portal account."),
|
|
@@ -114,7 +135,13 @@ async function POST(req) {
|
|
|
114
135
|
}
|
|
115
136
|
await em.persist(user).flush();
|
|
116
137
|
const verificationToken = await customerTokenService.createEmailVerification(user.id, tenantId);
|
|
117
|
-
|
|
138
|
+
let verifyUrl = resolvePortalVerifyUrl(baseUrl, verificationToken, orgRow.slug);
|
|
139
|
+
try {
|
|
140
|
+
verifyUrl = await urlForCustomerOrg(organizationId, `/verify?token=${encodeURIComponent(verificationToken)}`, {
|
|
141
|
+
container
|
|
142
|
+
});
|
|
143
|
+
} catch {
|
|
144
|
+
}
|
|
118
145
|
const subject = translate("customer_accounts.signup.created.subject", "Verify your portal account");
|
|
119
146
|
const copy = {
|
|
120
147
|
preview: translate("customer_accounts.signup.created.preview", "Verify your portal account to finish sign-up."),
|