@ecomiq/storefront 2.1.4 → 3.0.1
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/LICENSE +21 -0
- package/README.md +377 -76
- package/dist/account-Dx4e3syi.js +258 -0
- package/dist/account-forms-AVk71ko7.js +39 -0
- package/dist/account-forms.d.ts +13 -0
- package/dist/account.d.ts +240 -0
- package/dist/address-defaults-BqjRhS82.js +14 -0
- package/dist/address-defaults.d.ts +11 -0
- package/dist/analytics/analytics-CG1Qagwl.js +148 -0
- package/dist/analytics/analytics.d.ts +1 -0
- package/dist/analytics/config-DJr-GRhs.js +62 -0
- package/dist/analytics/config.d.ts +22 -0
- package/dist/analytics/consent-B13rBpBs.js +57 -0
- package/dist/analytics/consent-banner-9QvRRy8v.js +239 -0
- package/dist/analytics/consent-banner.d.ts +20 -0
- package/dist/analytics/cookies-pLozllyB.js +100 -0
- package/dist/analytics/cookies.d.ts +6 -0
- package/dist/analytics/destinations-iECrlLIr.js +144 -0
- package/dist/analytics/events-Dt81tIp1.js +102 -0
- package/dist/analytics/events.d.ts +51 -0
- package/dist/analytics/tracker-p4yaoOyk.js +51 -0
- package/dist/api-cache-DsuIn5hX.js +159 -0
- package/dist/bank-accounts-DlEczQQ5.js +81 -0
- package/dist/bank-accounts.d.ts +4 -0
- package/dist/captcha-DAqaIx8m.js +117 -0
- package/dist/captcha.d.ts +34 -0
- package/dist/cart-D7DxZ2lD.js +30 -0
- package/dist/cart-actions-Bsub_FFG.js +116 -0
- package/dist/cart-actions.d.ts +15 -0
- package/dist/cart-drawer-B7PPsR69.js +22 -0
- package/dist/cart-drawer.d.ts +9 -0
- package/dist/cart-form-y9P1r_EX.js +81 -0
- package/dist/cart-form.d.ts +24 -0
- package/dist/cart-summary-ur2VWKr-.js +89 -0
- package/dist/cart-summary.d.ts +31 -0
- package/dist/cart.d.ts +4 -0
- package/dist/catalog-7gJ-l5hW.js +67 -0
- package/dist/catalog-hooks-B7ZP4wYY.js +312 -0
- package/dist/catalog-hooks.d.ts +111 -0
- package/dist/catalog.d.ts +5 -0
- package/dist/checkout-DVWSXANi.js +474 -0
- package/dist/checkout-form-CbOIdENC.js +131 -0
- package/dist/checkout-form.d.ts +20 -0
- package/dist/checkout.d.ts +86 -0
- package/dist/complaint-book-BvwLvfx0.js +117 -0
- package/dist/complaint-book.d.ts +62 -0
- package/dist/culqi-authentication-BGr20vAJ.js +204 -0
- package/dist/culqi-authentication.d.ts +15 -0
- package/dist/culqi-checkout-CTXpKgp_.js +55 -0
- package/dist/culqi-three-ds-HP3mfrvV.js +33 -0
- package/dist/culqi-three-ds.d.ts +29 -0
- package/dist/customer-avatar-CJXSDroK.js +12 -0
- package/dist/customer-avatar.d.ts +5 -0
- package/dist/customer-cookies-DHOgFAMV.js +45 -0
- package/dist/customer-csrf-C1oDX-Py.js +17 -0
- package/dist/customer-csrf.d.ts +3 -0
- package/dist/customer-document-fields-C1K-_DAD.js +70 -0
- package/dist/customer-document-fields.d.ts +9 -0
- package/dist/customer-login-form-C7AC6vXd.js +144 -0
- package/dist/customer-login-form.d.ts +17 -0
- package/dist/customer-registration-form-BEm_NEfP.js +93 -0
- package/dist/customer-registration-form.d.ts +8 -0
- package/dist/document-url-CGsAl5Tf.js +9 -0
- package/dist/documents-T1mzc_j8.js +38 -0
- package/dist/documents.d.ts +16 -0
- package/dist/entries/account.d.ts +11 -0
- package/dist/entries/account.js +10 -0
- package/dist/entries/analytics.d.ts +6 -0
- package/dist/entries/analytics.js +5 -0
- package/dist/entries/cart.d.ts +8 -0
- package/dist/entries/cart.js +7 -0
- package/dist/entries/checkout.d.ts +12 -0
- package/dist/entries/checkout.js +11 -0
- package/dist/entries/order.d.ts +11 -0
- package/dist/entries/order.js +11 -0
- package/dist/favicon-YpyhVJ9H.js +28 -0
- package/dist/favicon.d.ts +4 -0
- package/dist/form-BtQcbXlJ.js +18 -0
- package/dist/form-contracts-BNpHaTGJ.js +166 -0
- package/dist/form-contracts.d.ts +42 -0
- package/dist/form.d.ts +3 -0
- package/dist/hidden-CCWtOWKR.js +3 -0
- package/dist/hidden.d.ts +4 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +50 -0
- package/dist/invoice-fields-BG0HjL6T.js +103 -0
- package/dist/invoice-fields.d.ts +39 -0
- package/dist/links-kFxBZfdb.js +96 -0
- package/dist/links.d.ts +21 -0
- package/dist/mercadopago-checkout-DC7RXa8k.js +27 -0
- package/dist/mercadopago-dialog-UTQev0MG.js +180 -0
- package/dist/mercadopago-dialog.d.ts +21 -0
- package/dist/money-DtGOtDri.js +32 -0
- package/dist/money.d.ts +1 -0
- package/dist/optimistic-cart-BveVOHuh.js +53 -0
- package/dist/optimistic-cart.d.ts +16 -0
- package/dist/order-display-DTt5WBbc.js +112 -0
- package/dist/order-display.d.ts +20 -0
- package/dist/order-draft-DxV1Uhm3.js +38 -0
- package/dist/order-draft.d.ts +16 -0
- package/dist/order-pages-Dm6l5m0m.js +38 -0
- package/dist/order-pages.d.ts +12 -0
- package/dist/order-payment-continuation-D_d5PGA4.js +212 -0
- package/dist/order-payment-continuation.d.ts +10 -0
- package/dist/order-payment-in-review-x4gdLFHZ.js +65 -0
- package/dist/order-payment-in-review.d.ts +5 -0
- package/dist/order-status-BJXLJwxq.js +162 -0
- package/dist/order-status.d.ts +89 -0
- package/dist/order-view-9IlBg5AF.js +62 -0
- package/dist/order-view.d.ts +12 -0
- package/dist/page-action-Ctkf4xC6.js +7 -0
- package/dist/page-seo-a5FJl8x5.js +262 -0
- package/dist/page-seo.d.ts +34 -0
- package/dist/pages-Ca_Gt6mq.js +150 -0
- package/dist/pages.d.ts +45 -0
- package/dist/payment-dialog-frame-B-Q_xQ94.js +57 -0
- package/dist/payment-logo-BoPaGJc_.js +46 -0
- package/dist/payment-logo-svgs-By9lDTLK.js +20 -0
- package/dist/payment-logo.d.ts +11 -0
- package/dist/payment-logos-BYvdwYyJ.js +23 -0
- package/dist/payment-methods-B8ocTgFZ.js +73 -0
- package/dist/payment-methods.d.ts +58 -0
- package/dist/payment-return-5cjypwIw.js +7 -0
- package/dist/place-CmW4hBWY.js +13 -0
- package/dist/place.d.ts +4 -0
- package/dist/preview-BiIBif3C.js +427 -0
- package/dist/preview-address-Cf0sJ-9p.js +21 -0
- package/dist/preview-cart-Dg2CVktS.js +376 -0
- package/dist/product-preview-BspMAecJ.js +51 -0
- package/dist/product-preview.d.ts +5 -0
- package/dist/product-variants-BNDHvs1y.js +46 -0
- package/dist/product-variants.d.ts +12 -0
- package/dist/public-pages-XtwlaLwt.js +194 -0
- package/dist/public-pages.d.ts +67 -0
- package/dist/registration-CpDcv8ql.js +91 -0
- package/dist/registration.d.ts +12 -0
- package/dist/request-security-CRsRMgwj.js +76 -0
- package/dist/rich-text-M-Ju8RL4.js +9 -0
- package/dist/rich-text.d.ts +5 -0
- package/dist/route-text-DcJw2SsK.js +9 -0
- package/dist/route-text.d.ts +4 -0
- package/dist/section-preview-CmsMl3yu.js +83 -0
- package/dist/section-preview.d.ts +12 -0
- package/dist/sections/account-addresses.d.ts +18 -0
- package/dist/sections/account-addresses.js +375 -0
- package/dist/sections/account-order.d.ts +12 -0
- package/dist/sections/account-order.js +592 -0
- package/dist/sections/account-orders.d.ts +8 -0
- package/dist/sections/account-orders.js +359 -0
- package/dist/sections/account-profile.d.ts +16 -0
- package/dist/sections/account-profile.js +123 -0
- package/dist/sections/checkout.d.ts +24 -0
- package/dist/sections/checkout.js +963 -0
- package/dist/sections/complaint-book.d.ts +8 -0
- package/dist/sections/complaint-book.js +538 -0
- package/dist/sections/design-system.d.ts +1 -0
- package/dist/sections/design-system.js +310 -0
- package/dist/sections/draft-invoice.d.ts +20 -0
- package/dist/sections/draft-invoice.js +158 -0
- package/dist/sections/order-tracking.d.ts +19 -0
- package/dist/sections/order-tracking.js +141 -0
- package/dist/sections-CNUMWTF_.js +77 -0
- package/dist/sections.d.ts +19 -0
- package/dist/seo-GpDRhAAA.js +78 -0
- package/dist/session-CKvmFJyg.js +379 -0
- package/dist/session.d.ts +24 -0
- package/dist/settings-B0NO2A6Y.js +78 -0
- package/dist/settings.d.ts +51 -0
- package/dist/source-marker-DhqSkW7n.js +4 -0
- package/dist/sources-B-3JFJfb.js +46 -0
- package/dist/sources.d.ts +7 -0
- package/dist/store-8Q-_LFrQ.js +152 -0
- package/dist/store-contact--hY6CoFb.js +19 -0
- package/dist/store-contact.d.ts +6 -0
- package/dist/store.d.ts +17 -0
- package/dist/storefront-language-DrMjVbmk.js +29 -0
- package/dist/storefront-language.d.ts +14 -0
- package/dist/storefront-text-D25ae1qf.js +11 -0
- package/dist/storefront-text-en-ljjjSS4q.js +1030 -0
- package/dist/storefront-text-pt-BtHaJLcp.js +1030 -0
- package/dist/storefront-text.d.ts +4 -0
- package/dist/stripe-dialog-Ev9s3Z62.js +396 -0
- package/dist/stripe-dialog.d.ts +30 -0
- package/dist/telemetry-HuZXWBPi.js +114 -0
- package/dist/telemetry.d.ts +10 -0
- package/dist/templates-CAqoNyTW.js +40 -0
- package/dist/templates.d.ts +15 -0
- package/dist/urls-CpGQPxvf.js +71 -0
- package/dist/urls.d.ts +18 -0
- package/dist/use-checkout-CZcxXJXi.js +42 -0
- package/dist/use-checkout-payment-9R-zacrJ.js +80 -0
- package/dist/use-checkout-payment.d.ts +18 -0
- package/dist/use-checkout.d.ts +36 -0
- package/dist/use-culqi-checkout-CrjLAipU.js +109 -0
- package/dist/use-order-payment-BKIz_IO0.js +45 -0
- package/dist/use-order-payment.d.ts +18 -0
- package/dist/use-payment-token-CErx-tN-.js +105 -0
- package/dist/vite-payment-logos-kziu7wKb.js +27 -0
- package/dist/vite-payment-logos.d.ts +24 -0
- package/dist/vite-public-assets-DW2fgtVb.js +209 -0
- package/dist/vite-public-assets.d.ts +29 -0
- package/dist/vite-sections-DTBr0-Kg.js +464 -0
- package/dist/vite-sections.d.ts +21 -0
- package/dist/vite-theme-ui-BMO-CDi3.js +41 -0
- package/dist/vite-theme-ui.d.ts +10 -0
- package/dist/vite.d.ts +36 -0
- package/dist/vite.js +127 -0
- package/dist/voucher-payment-CVFDZ1L8.js +66 -0
- package/dist/worker-CNi-gzCu.js +86 -0
- package/dist/worker.d.ts +27 -0
- package/package.json +100 -76
- package/src/sections/account-addresses.tsx +215 -0
- package/src/sections/account-order.tsx +424 -0
- package/src/sections/account-orders.tsx +259 -0
- package/src/sections/account-profile.tsx +70 -0
- package/src/sections/checkout.tsx +586 -0
- package/src/sections/complaint-book.tsx +325 -0
- package/src/sections/design-system.tsx +146 -0
- package/src/sections/draft-invoice.tsx +105 -0
- package/src/sections/order-tracking.tsx +81 -0
- package/src/vite-client.d.ts +4 -0
- package/bin/ecomiq.mjs +0 -179
- package/public/design-bridge.js +0 -318
- package/src/api-proxy.ts +0 -194
- package/src/auth/create-storefront-auth.ts +0 -94
- package/src/auth/ecomiq-oauth-password-client.ts +0 -26
- package/src/auth/ecomiq-oauth-password-constants.ts +0 -24
- package/src/auth/ecomiq-oauth-password-plugin.ts +0 -294
- package/src/auth/ecomiq-oauth-password-types.ts +0 -24
- package/src/auth/ecomiq-storefront-sign-in.ts +0 -44
- package/src/auth/env.ts +0 -7
- package/src/auth/secure-cookies.ts +0 -23
- package/src/auth/storefront-auth-base-url.ts +0 -117
- package/src/auth/storefront-auth.ts +0 -7
- package/src/cart-session.ts +0 -239
- package/src/client.ts +0 -48
- package/src/codegen.mjs +0 -801
- package/src/components/account/customer-addresses-panel.tsx +0 -678
- package/src/components/account/customer-order-detail-panel.tsx +0 -285
- package/src/components/account/customer-orders-panel.tsx +0 -192
- package/src/components/account/customer-profile-panel.tsx +0 -379
- package/src/components/checkout/cart-sync.ts +0 -143
- package/src/components/checkout/checkout-contact-section.tsx +0 -76
- package/src/components/checkout/checkout-external-update-banner.tsx +0 -15
- package/src/components/checkout/checkout-form-controls.tsx +0 -139
- package/src/components/checkout/checkout-line-items.tsx +0 -43
- package/src/components/checkout/checkout-order-summary.tsx +0 -165
- package/src/components/checkout/checkout-payment-section.tsx +0 -212
- package/src/components/checkout/checkout-session-view.tsx +0 -564
- package/src/components/checkout/checkout-shipping-address-section.tsx +0 -400
- package/src/components/checkout/checkout-shipping-section.tsx +0 -98
- package/src/components/checkout/checkout-states.tsx +0 -73
- package/src/components/checkout/checkout-step-card.tsx +0 -101
- package/src/components/checkout/checkout-steps.ts +0 -111
- package/src/components/checkout/form-schema.ts +0 -281
- package/src/components/checkout/format.ts +0 -15
- package/src/components/checkout/index.ts +0 -5
- package/src/components/checkout/map-customer-to-checkout.ts +0 -188
- package/src/components/checkout/types.ts +0 -41
- package/src/components/checkout/use-checkout-customer-prefill.ts +0 -409
- package/src/components/checkout/use-checkout-geo.ts +0 -50
- package/src/components/checkout/use-checkout-session.ts +0 -640
- package/src/components/hero.tsx +0 -291
- package/src/config.d.ts +0 -94
- package/src/config.mjs +0 -146
- package/src/format-money.ts +0 -31
- package/src/health.ts +0 -90
- package/src/hooks/index.ts +0 -9
- package/src/hooks/recently-viewed.ts +0 -125
- package/src/index.ts +0 -349
- package/src/link.tsx +0 -2
- package/src/pages/account/account-page-skeleton.tsx +0 -69
- package/src/pages/account/account-page-view.tsx +0 -270
- package/src/pages/account/account-shell.tsx +0 -101
- package/src/pages/account/parse-account-route.ts +0 -36
- package/src/pages/account.tsx +0 -16
- package/src/pages/api.$.ts +0 -20
- package/src/pages/api.auth.$.ts +0 -16
- package/src/pages/checkout.$checkoutId.tsx +0 -60
- package/src/pages/checkout.tsx +0 -31
- package/src/pages/pages.$pageSlug.tsx +0 -74
- package/src/pages/thank-you.tsx +0 -53
- package/src/providers/auth.tsx +0 -96
- package/src/providers/cart.tsx +0 -275
- package/src/providers/ecomiq.ts +0 -137
- package/src/providers/index.ts +0 -26
- package/src/proxy-api.ts +0 -20
- package/src/public-config.ts +0 -24
- package/src/review-submission.ts +0 -89
- package/src/runtime/context.ts +0 -2
- package/src/runtime/default-layout.tsx +0 -64
- package/src/runtime/head.ts +0 -149
- package/src/runtime/page.tsx +0 -143
- package/src/runtime/postcss-browser-stub.mjs +0 -5
- package/src/server-config-store.ts +0 -35
- package/src/server.ts +0 -286
- package/src/styles.css +0 -76
- package/src/ui/badge.tsx +0 -32
- package/src/ui/button.tsx +0 -31
- package/src/ui/card.tsx +0 -31
- package/src/ui/carousel.tsx +0 -218
- package/src/ui/cn.ts +0 -6
- package/src/ui/index.ts +0 -8
- package/src/ui/separator.tsx +0 -6
- package/src/ui/skeleton.tsx +0 -6
- package/src/ui/storefront-error.tsx +0 -49
- package/src/utils.ts +0 -36
- package/src/vite-inline.mjs +0 -263
- package/templates/__root.tsx +0 -83
- package/templates/router.tsx +0 -19
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { betterAuth } from 'better-auth'
|
|
2
|
-
import type { BetterAuthPlugin } from 'better-auth'
|
|
3
|
-
import { createCookieGetter, getCookies } from 'better-auth/cookies'
|
|
4
|
-
import { tanstackStartCookies } from 'better-auth/tanstack-start'
|
|
5
|
-
import { getStorefrontClient } from '../client'
|
|
6
|
-
import { ecomiqOAuthPassword } from './ecomiq-oauth-password-plugin'
|
|
7
|
-
import { readProcessEnv } from './env'
|
|
8
|
-
import { shouldUseSecureCookies } from './secure-cookies'
|
|
9
|
-
import {
|
|
10
|
-
mergeSettingsHostsIntoBaseURL,
|
|
11
|
-
resolveStorefrontAuthBaseURL,
|
|
12
|
-
type StorefrontDynamicBaseURLConfig,
|
|
13
|
-
} from './storefront-auth-base-url'
|
|
14
|
-
|
|
15
|
-
export type CreateStorefrontAuthOptions = {
|
|
16
|
-
secret?: string
|
|
17
|
-
/** Opciones adicionales pasadas a `betterAuth` (p. ej. `database`). */
|
|
18
|
-
[key: string]: unknown
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Instancia Better Auth del storefront (servidor).
|
|
23
|
-
*
|
|
24
|
-
* allowedHosts = `BETTER_AUTH_TRUSTED_HOSTS` (defaults) + dominio(s) de settings.
|
|
25
|
-
* Cookies `Secure` se recalculan por request (HTTP vs HTTPS).
|
|
26
|
-
*/
|
|
27
|
-
export function createStorefrontAuth(options: CreateStorefrontAuthOptions = {}) {
|
|
28
|
-
const { secret, advanced, ...rest } = options
|
|
29
|
-
|
|
30
|
-
const resolvedBaseURL: StorefrontDynamicBaseURLConfig = resolveStorefrontAuthBaseURL()
|
|
31
|
-
|
|
32
|
-
const auth = betterAuth({
|
|
33
|
-
...rest,
|
|
34
|
-
secret: (secret as string | undefined) ?? readProcessEnv('BETTER_AUTH_SECRET'),
|
|
35
|
-
baseURL: resolvedBaseURL,
|
|
36
|
-
advanced: {
|
|
37
|
-
disableCSRFCheck: true,
|
|
38
|
-
useSecureCookies: false,
|
|
39
|
-
...(typeof advanced === 'object' && advanced !== null ? advanced : {}),
|
|
40
|
-
},
|
|
41
|
-
plugins: [
|
|
42
|
-
ecomiqOAuthPassword() as BetterAuthPlugin,
|
|
43
|
-
tanstackStartCookies(),
|
|
44
|
-
],
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
const originalHandler = auth.handler.bind(auth)
|
|
48
|
-
|
|
49
|
-
return Object.assign(auth, {
|
|
50
|
-
handler: async (request: Request) => {
|
|
51
|
-
await syncAllowedHostsFromSettings(resolvedBaseURL)
|
|
52
|
-
await rehydrateAuthCookiesForRequest(auth, request)
|
|
53
|
-
return originalHandler(request)
|
|
54
|
-
},
|
|
55
|
-
})
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async function syncAllowedHostsFromSettings(
|
|
59
|
-
baseURL: StorefrontDynamicBaseURLConfig,
|
|
60
|
-
): Promise<void> {
|
|
61
|
-
try {
|
|
62
|
-
const settings = await getStorefrontClient().getSettings()
|
|
63
|
-
mergeSettingsHostsIntoBaseURL(baseURL, settings)
|
|
64
|
-
} catch {
|
|
65
|
-
mergeSettingsHostsIntoBaseURL(baseURL, null)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Better Auth crea cookies al init; con `protocol: 'auto'` cae en `isProduction`
|
|
71
|
-
* y marca Secure. En cada request las regeneramos según el protocolo real.
|
|
72
|
-
*/
|
|
73
|
-
async function rehydrateAuthCookiesForRequest(
|
|
74
|
-
auth: { $context: Promise<Record<string, unknown>> | Record<string, unknown> },
|
|
75
|
-
request: Request,
|
|
76
|
-
): Promise<void> {
|
|
77
|
-
const ctx = await auth.$context
|
|
78
|
-
const useSecure = shouldUseSecureCookies(request.url)
|
|
79
|
-
const origin = new URL(request.url).origin
|
|
80
|
-
const options = (ctx.options ?? {}) as Record<string, unknown>
|
|
81
|
-
const advanced = {
|
|
82
|
-
...((options.advanced as object | undefined) ?? {}),
|
|
83
|
-
useSecureCookies: useSecure,
|
|
84
|
-
}
|
|
85
|
-
const cookieOptions = {
|
|
86
|
-
...options,
|
|
87
|
-
baseURL: origin,
|
|
88
|
-
advanced,
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
ctx.authCookies = getCookies(cookieOptions as never)
|
|
92
|
-
ctx.createAuthCookie = createCookieGetter(cookieOptions as never)
|
|
93
|
-
options.advanced = advanced
|
|
94
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { BetterAuthClientPlugin } from 'better-auth/client'
|
|
2
|
-
import { PLUGIN_ID } from './ecomiq-oauth-password-constants'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Cliente Better Auth para el plugin `ecomiqOAuthPassword` (login `/v1/customer/login`).
|
|
6
|
-
* Tras añadirlo a `createAuthClient`, refresca la sesión al llamar al sign-in.
|
|
7
|
-
*
|
|
8
|
-
* Ejemplo:
|
|
9
|
-
* `authClient.$fetch("/ecomiq-oauth/sign-in", { method: "POST", headers, body: { email, password } })`
|
|
10
|
-
*
|
|
11
|
-
* Importante: no anotar el retorno como `BetterAuthClientPlugin` (rompe la inferencia de sesión).
|
|
12
|
-
*/
|
|
13
|
-
export function ecomiqOAuthPasswordClient() {
|
|
14
|
-
return {
|
|
15
|
-
id: PLUGIN_ID,
|
|
16
|
-
pathMethods: {
|
|
17
|
-
'/ecomiq-oauth/sign-in': 'POST',
|
|
18
|
-
},
|
|
19
|
-
atomListeners: [
|
|
20
|
-
{
|
|
21
|
-
matcher: (path: string) => path === '/ecomiq-oauth/sign-in',
|
|
22
|
-
signal: '$sessionSignal' as const,
|
|
23
|
-
},
|
|
24
|
-
],
|
|
25
|
-
} satisfies BetterAuthClientPlugin
|
|
26
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_API_BASE_URL, DEFAULT_API_VERSION } from '@ecomiq/core'
|
|
2
|
-
|
|
3
|
-
/** Id del plugin servidor/cliente Better Auth (debe coincidir en ambos lados). */
|
|
4
|
-
export const PLUGIN_ID = 'ecomiq-oauth-password'
|
|
5
|
-
|
|
6
|
-
/** Base storefront (override con `STOREFRONT_API_URL`). */
|
|
7
|
-
export const DEFAULT_STOREFRONT_API_URL = DEFAULT_API_BASE_URL
|
|
8
|
-
|
|
9
|
-
/** Prefijo de versión en storefront API (`/v1/...`). */
|
|
10
|
-
export const STOREFRONT_API_PATH_PREFIX = DEFAULT_API_VERSION
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Cookie HttpOnly con el bearer de customer login.
|
|
14
|
-
* Debe coincidir con el nombre que lee el proxy BFF (`api-proxy`).
|
|
15
|
-
*/
|
|
16
|
-
export const STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME = 'ecomiq_sf_oauth_at'
|
|
17
|
-
|
|
18
|
-
/** Path de la cookie (debe llegar a `/api/*` del proxy). */
|
|
19
|
-
export const STOREFRONT_ECOMIQ_OAUTH_COOKIE_PATH = '/api'
|
|
20
|
-
|
|
21
|
-
/** Header de identidad para login storefront (`POST /v1/customer/login`). */
|
|
22
|
-
export const STOREFRONT_HEADERS = {
|
|
23
|
-
storeId: 'x-Store-Id',
|
|
24
|
-
} as const
|
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createAuthEndpoint,
|
|
3
|
-
createAuthMiddleware,
|
|
4
|
-
formCsrfMiddleware,
|
|
5
|
-
} from "better-auth/api";
|
|
6
|
-
import { APIError, type BetterAuthPlugin } from "better-auth";
|
|
7
|
-
import { setSessionCookie } from "better-auth/cookies";
|
|
8
|
-
import { parseUserOutput } from "better-auth/db";
|
|
9
|
-
|
|
10
|
-
import { readProcessEnv } from "./env";
|
|
11
|
-
import {
|
|
12
|
-
DEFAULT_STOREFRONT_API_URL,
|
|
13
|
-
PLUGIN_ID,
|
|
14
|
-
STOREFRONT_API_PATH_PREFIX,
|
|
15
|
-
STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME,
|
|
16
|
-
STOREFRONT_ECOMIQ_OAUTH_COOKIE_PATH,
|
|
17
|
-
STOREFRONT_HEADERS,
|
|
18
|
-
} from "./ecomiq-oauth-password-constants";
|
|
19
|
-
import {
|
|
20
|
-
customerLoginResponseSchema,
|
|
21
|
-
signInBody,
|
|
22
|
-
} from "./ecomiq-oauth-password-types";
|
|
23
|
-
import { shouldUseSecureCookiesFromBaseURL } from "./secure-cookies";
|
|
24
|
-
|
|
25
|
-
type StoreIdentityContext = {
|
|
26
|
-
storeId?: string | null
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Inicia sesión contra `POST /v1/customer/login` del storefront Ecomiq,
|
|
31
|
-
* crea/actualiza el usuario en Better Auth y guarda el bearer en cookie HttpOnly
|
|
32
|
-
* acotada a `/api` (proxy BFF).
|
|
33
|
-
*/
|
|
34
|
-
export function ecomiqOAuthPassword(): BetterAuthPlugin {
|
|
35
|
-
const clearStorefrontOAuthCookieMiddleware = createAuthMiddleware(async (ctx) => {
|
|
36
|
-
ctx.setCookie(STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME, "", {
|
|
37
|
-
path: STOREFRONT_ECOMIQ_OAUTH_COOKIE_PATH,
|
|
38
|
-
httpOnly: true,
|
|
39
|
-
sameSite: "lax",
|
|
40
|
-
secure: shouldUseSecureCookiesFromBaseURL(
|
|
41
|
-
ctx.context.baseURL,
|
|
42
|
-
ctx.request?.url,
|
|
43
|
-
),
|
|
44
|
-
maxAge: 0,
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
id: PLUGIN_ID,
|
|
50
|
-
schema: {
|
|
51
|
-
user: {
|
|
52
|
-
fields: {
|
|
53
|
-
firstName: { type: "string", required: false },
|
|
54
|
-
lastName: { type: "string", required: false },
|
|
55
|
-
phone: { type: "string", required: false },
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
hooks: {
|
|
60
|
-
after: [
|
|
61
|
-
{
|
|
62
|
-
matcher: (hookCtx) => hookCtx.path === "/sign-out",
|
|
63
|
-
handler: clearStorefrontOAuthCookieMiddleware,
|
|
64
|
-
},
|
|
65
|
-
],
|
|
66
|
-
},
|
|
67
|
-
endpoints: {
|
|
68
|
-
signIn: createAuthEndpoint(
|
|
69
|
-
"/ecomiq-oauth/sign-in",
|
|
70
|
-
{
|
|
71
|
-
method: "POST",
|
|
72
|
-
use: [formCsrfMiddleware],
|
|
73
|
-
body: signInBody,
|
|
74
|
-
},
|
|
75
|
-
async (ctx) => {
|
|
76
|
-
const { email: loginEmail, password, callbackURL, rememberMe } = ctx.body;
|
|
77
|
-
|
|
78
|
-
const login = await customerPasswordLogin(loginEmail, password, {
|
|
79
|
-
storeId: ctx.getHeader(STOREFRONT_HEADERS.storeId),
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const canonicalEmail = loginEmail.toLowerCase().trim();
|
|
83
|
-
const displayName = canonicalEmail.split("@")[0] || "Cliente";
|
|
84
|
-
|
|
85
|
-
const existing = await ctx.context.internalAdapter.findUserByEmail(canonicalEmail);
|
|
86
|
-
let user = existing?.user;
|
|
87
|
-
if (!user) {
|
|
88
|
-
user = await ctx.context.internalAdapter.createUser({
|
|
89
|
-
email: canonicalEmail,
|
|
90
|
-
name: displayName,
|
|
91
|
-
emailVerified: login.customer.isVerified,
|
|
92
|
-
});
|
|
93
|
-
} else if (user.name !== displayName) {
|
|
94
|
-
await ctx.context.internalAdapter.updateUser(user.id, { name: displayName });
|
|
95
|
-
user = { ...user, name: displayName };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const oauthSessionExpires =
|
|
99
|
-
login.expiresIn !== undefined && login.expiresIn > 0
|
|
100
|
-
? new Date(Date.now() + login.expiresIn * 1000)
|
|
101
|
-
: undefined;
|
|
102
|
-
|
|
103
|
-
const session = await ctx.context.internalAdapter.createSession(
|
|
104
|
-
user.id,
|
|
105
|
-
rememberMe === false,
|
|
106
|
-
oauthSessionExpires ? { expiresAt: oauthSessionExpires } : {},
|
|
107
|
-
Boolean(oauthSessionExpires),
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
if (!session) {
|
|
111
|
-
throw APIError.from("INTERNAL_SERVER_ERROR", {
|
|
112
|
-
code: "ECOMIQ_SESSION_FAILED",
|
|
113
|
-
message: "No se pudo crear la sesión",
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
await setSessionCookie(ctx, { session, user }, rememberMe === false);
|
|
118
|
-
|
|
119
|
-
const oauthMaxAgeSec =
|
|
120
|
-
oauthSessionExpires !== undefined
|
|
121
|
-
? Math.max(60, Math.ceil((oauthSessionExpires.getTime() - Date.now()) / 1000))
|
|
122
|
-
: login.expiresIn !== undefined && login.expiresIn > 0
|
|
123
|
-
? login.expiresIn
|
|
124
|
-
: 60 * 60 * 24;
|
|
125
|
-
|
|
126
|
-
const secureCookie = shouldUseSecureCookiesFromBaseURL(
|
|
127
|
-
ctx.context.baseURL,
|
|
128
|
-
ctx.request?.url,
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
await ctx.setCookie(STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME, login.accessToken, {
|
|
132
|
-
path: STOREFRONT_ECOMIQ_OAUTH_COOKIE_PATH,
|
|
133
|
-
httpOnly: true,
|
|
134
|
-
sameSite: "lax",
|
|
135
|
-
secure: secureCookie,
|
|
136
|
-
maxAge: oauthMaxAgeSec,
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
if (callbackURL) ctx.setHeader("Location", callbackURL);
|
|
140
|
-
|
|
141
|
-
return ctx.json({
|
|
142
|
-
redirect: !!callbackURL,
|
|
143
|
-
token: session.token,
|
|
144
|
-
url: callbackURL,
|
|
145
|
-
user: parseUserOutput(ctx.context.options, user),
|
|
146
|
-
});
|
|
147
|
-
},
|
|
148
|
-
),
|
|
149
|
-
},
|
|
150
|
-
} satisfies BetterAuthPlugin;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function resolveCustomerLoginUrl(): string {
|
|
154
|
-
const base = (readProcessEnv("STOREFRONT_API_URL") ?? DEFAULT_STOREFRONT_API_URL).replace(
|
|
155
|
-
/\/$/,
|
|
156
|
-
"",
|
|
157
|
-
);
|
|
158
|
-
return `${base}/${STOREFRONT_API_PATH_PREFIX}/customer/login`;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
type ApiErrorBody = {
|
|
162
|
-
error?: string;
|
|
163
|
-
message?: string;
|
|
164
|
-
title?: string;
|
|
165
|
-
detail?: string;
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
async function customerPasswordLogin(
|
|
169
|
-
email: string,
|
|
170
|
-
password: string,
|
|
171
|
-
context: StoreIdentityContext,
|
|
172
|
-
): Promise<ReturnType<typeof customerLoginResponseSchema.parse>> {
|
|
173
|
-
const headers = new Headers({
|
|
174
|
-
"content-type": "application/json",
|
|
175
|
-
accept: "application/json",
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
const storeId = context.storeId?.trim();
|
|
179
|
-
if (storeId) {
|
|
180
|
-
headers.set(STOREFRONT_HEADERS.storeId, storeId);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
let res: Response;
|
|
184
|
-
let text: string;
|
|
185
|
-
try {
|
|
186
|
-
res = await fetch(resolveCustomerLoginUrl(), {
|
|
187
|
-
method: "POST",
|
|
188
|
-
headers,
|
|
189
|
-
body: JSON.stringify({ email, password }),
|
|
190
|
-
});
|
|
191
|
-
text = await res.text();
|
|
192
|
-
} catch (e) {
|
|
193
|
-
throw mapCustomerLoginFetchFailure(e);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const json = safeParseJson(text);
|
|
197
|
-
if (!res.ok) {
|
|
198
|
-
throwBadRequestFromLoginApi(json, res.statusText || text || `HTTP ${res.status}`);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const parsed = customerLoginResponseSchema.safeParse(json);
|
|
202
|
-
if (!parsed.success) {
|
|
203
|
-
throw APIError.from("UNAUTHORIZED", {
|
|
204
|
-
code: "ECOMIQ_LOGIN_SHAPE",
|
|
205
|
-
message: "Respuesta de login con formato inválido",
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (parsed.data.customer.status !== "active") {
|
|
210
|
-
throw APIError.from("UNAUTHORIZED", {
|
|
211
|
-
code: "ECOMIQ_CUSTOMER_INACTIVE",
|
|
212
|
-
message: "La cuenta del cliente no está activa",
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return parsed.data;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function safeParseJson(text: string): unknown {
|
|
220
|
-
try {
|
|
221
|
-
return text ? JSON.parse(text) : {};
|
|
222
|
-
} catch {
|
|
223
|
-
return {};
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function throwBadRequestFromLoginApi(payload: unknown, fallbackMessage: string): never {
|
|
228
|
-
const err = payload as ApiErrorBody;
|
|
229
|
-
const errorCode =
|
|
230
|
-
typeof err.error === "string" && err.error.trim() ? err.error.trim() : "invalid_credentials";
|
|
231
|
-
const errorMessage =
|
|
232
|
-
(typeof err.message === "string" && err.message.trim()) ||
|
|
233
|
-
(typeof err.detail === "string" && err.detail.trim()) ||
|
|
234
|
-
(typeof err.title === "string" && err.title.trim()) ||
|
|
235
|
-
(typeof err.error === "string" && err.error.trim()) ||
|
|
236
|
-
fallbackMessage.trim() ||
|
|
237
|
-
"Credenciales inválidas.";
|
|
238
|
-
throw APIError.from("BAD_REQUEST", {
|
|
239
|
-
code: errorCode,
|
|
240
|
-
message: errorMessage.slice(0, 500),
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function mapCustomerLoginFetchFailure(e: unknown): never {
|
|
245
|
-
const codes = collectNetworkErrorCodes(e);
|
|
246
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
247
|
-
|
|
248
|
-
const isDnsError = codes.some((c) => c === "ENOTFOUND" || c === "EAI_AGAIN");
|
|
249
|
-
if (isDnsError) {
|
|
250
|
-
throw APIError.from("BAD_GATEWAY", {
|
|
251
|
-
code: "ECOMIQ_LOGIN_DNS",
|
|
252
|
-
message: "No se pudo resolver el servidor de la tienda.",
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const isConnectionRefused = codes.includes("ECONNREFUSED");
|
|
257
|
-
if (isConnectionRefused) {
|
|
258
|
-
throw APIError.from("BAD_GATEWAY", {
|
|
259
|
-
code: "ECOMIQ_LOGIN_REFUSED",
|
|
260
|
-
message: "Conexión rechazada al servidor de la tienda.",
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const looksLikeAbortOrSocketClose =
|
|
265
|
-
codes.some((c) =>
|
|
266
|
-
["UND_ERR_SOCKET", "ECONNRESET", "EPIPE", "ECONNABORTED"].includes(c),
|
|
267
|
-
) || /terminated|abort|closed|FIN|socket/i.test(msg);
|
|
268
|
-
|
|
269
|
-
if (looksLikeAbortOrSocketClose) {
|
|
270
|
-
throwBadRequestFromLoginApi({}, "Credenciales inválidas o conexión cerrada por el servidor.");
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
throw APIError.from("INTERNAL_SERVER_ERROR", {
|
|
274
|
-
code: "ECOMIQ_LOGIN_REQUEST_FAILED",
|
|
275
|
-
message: msg.slice(0, 280) || "Error al contactar el servidor de la tienda.",
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function collectNetworkErrorCodes(e: unknown): string[] {
|
|
280
|
-
const out: string[] = [];
|
|
281
|
-
const visit = (x: unknown): void => {
|
|
282
|
-
if (!x || typeof x !== "object") return;
|
|
283
|
-
const code =
|
|
284
|
-
"code" in x && typeof (x as { code?: unknown }).code === "string"
|
|
285
|
-
? (x as { code: string }).code
|
|
286
|
-
: undefined;
|
|
287
|
-
if (code) out.push(code);
|
|
288
|
-
if ("cause" in x && (x as { cause?: unknown }).cause) {
|
|
289
|
-
visit((x as { cause: unknown }).cause);
|
|
290
|
-
}
|
|
291
|
-
};
|
|
292
|
-
visit(e);
|
|
293
|
-
return out;
|
|
294
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
export const customerLoginCustomerSchema = z.object({
|
|
4
|
-
id: z.coerce.number(),
|
|
5
|
-
status: z.string(),
|
|
6
|
-
isVerified: z.boolean(),
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
/** Respuesta de `POST /v1/customer/login`. */
|
|
10
|
-
export const customerLoginResponseSchema = z.object({
|
|
11
|
-
accessToken: z.string().min(1),
|
|
12
|
-
refreshToken: z.string().optional(),
|
|
13
|
-
/** Segundos hasta expiración (ej. `86399`). Sincroniza la sesión Better Auth con el bearer. */
|
|
14
|
-
expiresIn: z.coerce.number().int().positive().optional(),
|
|
15
|
-
tokenType: z.string().optional(),
|
|
16
|
-
customer: customerLoginCustomerSchema,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
export const signInBody = z.object({
|
|
20
|
-
email: z.string().email(),
|
|
21
|
-
password: z.string().min(1),
|
|
22
|
-
callbackURL: z.string().optional(),
|
|
23
|
-
rememberMe: z.boolean().optional().default(true),
|
|
24
|
-
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { StorefrontSettings } from '@ecomiq/core'
|
|
2
|
-
import { STOREFRONT_HEADERS } from './ecomiq-oauth-password-constants'
|
|
3
|
-
|
|
4
|
-
export type EcomiqSignInCredentials = {
|
|
5
|
-
email: string
|
|
6
|
-
password: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/** Cliente Better Auth con `$fetch` (p. ej. resultado de `createStorefrontAuthClient`). */
|
|
10
|
-
export type StorefrontAuthFetchClient = {
|
|
11
|
-
$fetch: (
|
|
12
|
-
path: string,
|
|
13
|
-
init: {
|
|
14
|
-
method: string
|
|
15
|
-
headers: Record<string, string | undefined>
|
|
16
|
-
body: EcomiqSignInCredentials
|
|
17
|
-
},
|
|
18
|
-
) => Promise<{ error?: { message?: string } | null }>
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Inicia sesión con email/contraseña vía plugin `ecomiqOAuthPassword`
|
|
23
|
-
* (Better Auth → `/v1/customer/login`).
|
|
24
|
-
*/
|
|
25
|
-
export async function signInWithEcomiqCredentials(
|
|
26
|
-
authClient: StorefrontAuthFetchClient,
|
|
27
|
-
settings: StorefrontSettings | null | undefined,
|
|
28
|
-
credentials: EcomiqSignInCredentials,
|
|
29
|
-
) {
|
|
30
|
-
const response = await authClient.$fetch('/ecomiq-oauth/sign-in', {
|
|
31
|
-
method: 'POST',
|
|
32
|
-
headers: {
|
|
33
|
-
'Content-Type': 'application/json',
|
|
34
|
-
[STOREFRONT_HEADERS.storeId]: settings?.store.id,
|
|
35
|
-
},
|
|
36
|
-
body: credentials,
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
if (response.error) {
|
|
40
|
-
throw new Error(response.error.message ?? 'Error al iniciar sesión')
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return response
|
|
44
|
-
}
|
package/src/auth/env.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/** Lee una variable de entorno sin depender de `@types/node`. */
|
|
2
|
-
export function readProcessEnv(name: string): string | undefined {
|
|
3
|
-
const env = (globalThis as { process?: { env?: Record<string, string | undefined> } })
|
|
4
|
-
.process?.env
|
|
5
|
-
const value = env?.[name]
|
|
6
|
-
return typeof value === 'string' && value.length > 0 ? value : undefined
|
|
7
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Secure cookies solo en HTTPS. En localhost HTTP el browser las descarta.
|
|
3
|
-
*/
|
|
4
|
-
export function shouldUseSecureCookies(requestUrl: string | URL): boolean {
|
|
5
|
-
try {
|
|
6
|
-
const url = typeof requestUrl === 'string' ? new URL(requestUrl) : requestUrl
|
|
7
|
-
return url.protocol === 'https:'
|
|
8
|
-
} catch {
|
|
9
|
-
return false
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function shouldUseSecureCookiesFromBaseURL(
|
|
14
|
-
baseURL: string | undefined,
|
|
15
|
-
fallbackRequestUrl?: string,
|
|
16
|
-
): boolean {
|
|
17
|
-
if (typeof baseURL === 'string' && baseURL.length > 0) {
|
|
18
|
-
if (baseURL.startsWith('https://')) return true
|
|
19
|
-
if (baseURL.startsWith('http://')) return false
|
|
20
|
-
}
|
|
21
|
-
if (fallbackRequestUrl) return shouldUseSecureCookies(fallbackRequestUrl)
|
|
22
|
-
return false
|
|
23
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { readProcessEnv } from './env'
|
|
2
|
-
|
|
3
|
-
/** Config dinámica de Better Auth (`baseURL: { allowedHosts }`). */
|
|
4
|
-
export type StorefrontDynamicBaseURLConfig = {
|
|
5
|
-
allowedHosts: string[]
|
|
6
|
-
fallback?: string
|
|
7
|
-
protocol?: 'http' | 'https' | 'auto'
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/** Env CSV con hosts por defecto (antes de settings). */
|
|
11
|
-
export const BETTER_AUTH_TRUSTED_HOSTS_ENV = 'BETTER_AUTH_TRUSTED_HOSTS'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Defaults si `BETTER_AUTH_TRUSTED_HOSTS` no está definido.
|
|
15
|
-
* Incluye host sin puerto (`localhost`) y con puerto (`localhost:*`).
|
|
16
|
-
*/
|
|
17
|
-
export const DEFAULT_STOREFRONT_AUTH_ALLOWED_HOSTS = [
|
|
18
|
-
'localhost',
|
|
19
|
-
'localhost:*',
|
|
20
|
-
'127.0.0.1',
|
|
21
|
-
'127.0.0.1:*',
|
|
22
|
-
'*.ecomiq.app',
|
|
23
|
-
'ecomiq.app',
|
|
24
|
-
] as const
|
|
25
|
-
|
|
26
|
-
export type StoreSettingsHostsSource = {
|
|
27
|
-
store: {
|
|
28
|
-
domain: string | null
|
|
29
|
-
domains: string[]
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Hosts por defecto: env CSV o fallback hardcodeado. */
|
|
34
|
-
export function defaultAllowedHostsFromEnv(): string[] {
|
|
35
|
-
const fromEnv = splitCsv(readProcessEnv(BETTER_AUTH_TRUSTED_HOSTS_ENV))
|
|
36
|
-
return uniqueHosts(
|
|
37
|
-
fromEnv.length > 0 ? fromEnv : [...DEFAULT_STOREFRONT_AUTH_ALLOWED_HOSTS],
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Extrae hosts desde settings API (`store.domain` + `store.domains`).
|
|
43
|
-
*/
|
|
44
|
-
export function hostsFromStoreSettings(
|
|
45
|
-
settings: StoreSettingsHostsSource | null | undefined,
|
|
46
|
-
): string[] {
|
|
47
|
-
if (!settings?.store) return []
|
|
48
|
-
const raw = [settings.store.domain, ...settings.store.domains]
|
|
49
|
-
return uniqueHosts(
|
|
50
|
-
raw.flatMap((value) => {
|
|
51
|
-
const host = normalizeHost(value)
|
|
52
|
-
return host ? [host] : []
|
|
53
|
-
}),
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* `baseURL` dinámico: solo hosts del env (defaults).
|
|
59
|
-
* El dominio final se fusiona luego desde settings API.
|
|
60
|
-
*/
|
|
61
|
-
export function resolveStorefrontAuthBaseURL(): StorefrontDynamicBaseURLConfig {
|
|
62
|
-
return {
|
|
63
|
-
protocol: 'auto',
|
|
64
|
-
allowedHosts: defaultAllowedHostsFromEnv(),
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* allowedHosts = defaults (env) + dominio(s) de settings.
|
|
70
|
-
*/
|
|
71
|
-
export function mergeSettingsHostsIntoBaseURL(
|
|
72
|
-
baseURL: StorefrontDynamicBaseURLConfig,
|
|
73
|
-
settings: StoreSettingsHostsSource | null | undefined,
|
|
74
|
-
): void {
|
|
75
|
-
baseURL.allowedHosts = uniqueHosts([
|
|
76
|
-
...defaultAllowedHostsFromEnv(),
|
|
77
|
-
...hostsFromStoreSettings(settings),
|
|
78
|
-
])
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function splitCsv(value: string | undefined): string[] {
|
|
82
|
-
if (!value) return []
|
|
83
|
-
return value
|
|
84
|
-
.split(',')
|
|
85
|
-
.map((part) => part.trim())
|
|
86
|
-
.filter(Boolean)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function normalizeHost(value: string | null | undefined): string | null {
|
|
90
|
-
if (!value) return null
|
|
91
|
-
const trimmed = value.trim()
|
|
92
|
-
if (!trimmed) return null
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
if (trimmed.includes('://')) {
|
|
96
|
-
const host = new URL(trimmed).host
|
|
97
|
-
return host || null
|
|
98
|
-
}
|
|
99
|
-
} catch {
|
|
100
|
-
return null
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const withoutPath = trimmed.split('/')[0]?.trim()
|
|
104
|
-
return withoutPath || null
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function uniqueHosts(hosts: readonly string[]): string[] {
|
|
108
|
-
const seen = new Set<string>()
|
|
109
|
-
const result: string[] = []
|
|
110
|
-
for (const host of hosts) {
|
|
111
|
-
const normalized = host.trim().toLowerCase()
|
|
112
|
-
if (!normalized || seen.has(normalized)) continue
|
|
113
|
-
seen.add(normalized)
|
|
114
|
-
result.push(host.trim())
|
|
115
|
-
}
|
|
116
|
-
return result
|
|
117
|
-
}
|