@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.
Files changed (310) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +377 -76
  3. package/dist/account-Dx4e3syi.js +258 -0
  4. package/dist/account-forms-AVk71ko7.js +39 -0
  5. package/dist/account-forms.d.ts +13 -0
  6. package/dist/account.d.ts +240 -0
  7. package/dist/address-defaults-BqjRhS82.js +14 -0
  8. package/dist/address-defaults.d.ts +11 -0
  9. package/dist/analytics/analytics-CG1Qagwl.js +148 -0
  10. package/dist/analytics/analytics.d.ts +1 -0
  11. package/dist/analytics/config-DJr-GRhs.js +62 -0
  12. package/dist/analytics/config.d.ts +22 -0
  13. package/dist/analytics/consent-B13rBpBs.js +57 -0
  14. package/dist/analytics/consent-banner-9QvRRy8v.js +239 -0
  15. package/dist/analytics/consent-banner.d.ts +20 -0
  16. package/dist/analytics/cookies-pLozllyB.js +100 -0
  17. package/dist/analytics/cookies.d.ts +6 -0
  18. package/dist/analytics/destinations-iECrlLIr.js +144 -0
  19. package/dist/analytics/events-Dt81tIp1.js +102 -0
  20. package/dist/analytics/events.d.ts +51 -0
  21. package/dist/analytics/tracker-p4yaoOyk.js +51 -0
  22. package/dist/api-cache-DsuIn5hX.js +159 -0
  23. package/dist/bank-accounts-DlEczQQ5.js +81 -0
  24. package/dist/bank-accounts.d.ts +4 -0
  25. package/dist/captcha-DAqaIx8m.js +117 -0
  26. package/dist/captcha.d.ts +34 -0
  27. package/dist/cart-D7DxZ2lD.js +30 -0
  28. package/dist/cart-actions-Bsub_FFG.js +116 -0
  29. package/dist/cart-actions.d.ts +15 -0
  30. package/dist/cart-drawer-B7PPsR69.js +22 -0
  31. package/dist/cart-drawer.d.ts +9 -0
  32. package/dist/cart-form-y9P1r_EX.js +81 -0
  33. package/dist/cart-form.d.ts +24 -0
  34. package/dist/cart-summary-ur2VWKr-.js +89 -0
  35. package/dist/cart-summary.d.ts +31 -0
  36. package/dist/cart.d.ts +4 -0
  37. package/dist/catalog-7gJ-l5hW.js +67 -0
  38. package/dist/catalog-hooks-B7ZP4wYY.js +312 -0
  39. package/dist/catalog-hooks.d.ts +111 -0
  40. package/dist/catalog.d.ts +5 -0
  41. package/dist/checkout-DVWSXANi.js +474 -0
  42. package/dist/checkout-form-CbOIdENC.js +131 -0
  43. package/dist/checkout-form.d.ts +20 -0
  44. package/dist/checkout.d.ts +86 -0
  45. package/dist/complaint-book-BvwLvfx0.js +117 -0
  46. package/dist/complaint-book.d.ts +62 -0
  47. package/dist/culqi-authentication-BGr20vAJ.js +204 -0
  48. package/dist/culqi-authentication.d.ts +15 -0
  49. package/dist/culqi-checkout-CTXpKgp_.js +55 -0
  50. package/dist/culqi-three-ds-HP3mfrvV.js +33 -0
  51. package/dist/culqi-three-ds.d.ts +29 -0
  52. package/dist/customer-avatar-CJXSDroK.js +12 -0
  53. package/dist/customer-avatar.d.ts +5 -0
  54. package/dist/customer-cookies-DHOgFAMV.js +45 -0
  55. package/dist/customer-csrf-C1oDX-Py.js +17 -0
  56. package/dist/customer-csrf.d.ts +3 -0
  57. package/dist/customer-document-fields-C1K-_DAD.js +70 -0
  58. package/dist/customer-document-fields.d.ts +9 -0
  59. package/dist/customer-login-form-C7AC6vXd.js +144 -0
  60. package/dist/customer-login-form.d.ts +17 -0
  61. package/dist/customer-registration-form-BEm_NEfP.js +93 -0
  62. package/dist/customer-registration-form.d.ts +8 -0
  63. package/dist/document-url-CGsAl5Tf.js +9 -0
  64. package/dist/documents-T1mzc_j8.js +38 -0
  65. package/dist/documents.d.ts +16 -0
  66. package/dist/entries/account.d.ts +11 -0
  67. package/dist/entries/account.js +10 -0
  68. package/dist/entries/analytics.d.ts +6 -0
  69. package/dist/entries/analytics.js +5 -0
  70. package/dist/entries/cart.d.ts +8 -0
  71. package/dist/entries/cart.js +7 -0
  72. package/dist/entries/checkout.d.ts +12 -0
  73. package/dist/entries/checkout.js +11 -0
  74. package/dist/entries/order.d.ts +11 -0
  75. package/dist/entries/order.js +11 -0
  76. package/dist/favicon-YpyhVJ9H.js +28 -0
  77. package/dist/favicon.d.ts +4 -0
  78. package/dist/form-BtQcbXlJ.js +18 -0
  79. package/dist/form-contracts-BNpHaTGJ.js +166 -0
  80. package/dist/form-contracts.d.ts +42 -0
  81. package/dist/form.d.ts +3 -0
  82. package/dist/hidden-CCWtOWKR.js +3 -0
  83. package/dist/hidden.d.ts +4 -0
  84. package/dist/index.d.ts +50 -0
  85. package/dist/index.js +50 -0
  86. package/dist/invoice-fields-BG0HjL6T.js +103 -0
  87. package/dist/invoice-fields.d.ts +39 -0
  88. package/dist/links-kFxBZfdb.js +96 -0
  89. package/dist/links.d.ts +21 -0
  90. package/dist/mercadopago-checkout-DC7RXa8k.js +27 -0
  91. package/dist/mercadopago-dialog-UTQev0MG.js +180 -0
  92. package/dist/mercadopago-dialog.d.ts +21 -0
  93. package/dist/money-DtGOtDri.js +32 -0
  94. package/dist/money.d.ts +1 -0
  95. package/dist/optimistic-cart-BveVOHuh.js +53 -0
  96. package/dist/optimistic-cart.d.ts +16 -0
  97. package/dist/order-display-DTt5WBbc.js +112 -0
  98. package/dist/order-display.d.ts +20 -0
  99. package/dist/order-draft-DxV1Uhm3.js +38 -0
  100. package/dist/order-draft.d.ts +16 -0
  101. package/dist/order-pages-Dm6l5m0m.js +38 -0
  102. package/dist/order-pages.d.ts +12 -0
  103. package/dist/order-payment-continuation-D_d5PGA4.js +212 -0
  104. package/dist/order-payment-continuation.d.ts +10 -0
  105. package/dist/order-payment-in-review-x4gdLFHZ.js +65 -0
  106. package/dist/order-payment-in-review.d.ts +5 -0
  107. package/dist/order-status-BJXLJwxq.js +162 -0
  108. package/dist/order-status.d.ts +89 -0
  109. package/dist/order-view-9IlBg5AF.js +62 -0
  110. package/dist/order-view.d.ts +12 -0
  111. package/dist/page-action-Ctkf4xC6.js +7 -0
  112. package/dist/page-seo-a5FJl8x5.js +262 -0
  113. package/dist/page-seo.d.ts +34 -0
  114. package/dist/pages-Ca_Gt6mq.js +150 -0
  115. package/dist/pages.d.ts +45 -0
  116. package/dist/payment-dialog-frame-B-Q_xQ94.js +57 -0
  117. package/dist/payment-logo-BoPaGJc_.js +46 -0
  118. package/dist/payment-logo-svgs-By9lDTLK.js +20 -0
  119. package/dist/payment-logo.d.ts +11 -0
  120. package/dist/payment-logos-BYvdwYyJ.js +23 -0
  121. package/dist/payment-methods-B8ocTgFZ.js +73 -0
  122. package/dist/payment-methods.d.ts +58 -0
  123. package/dist/payment-return-5cjypwIw.js +7 -0
  124. package/dist/place-CmW4hBWY.js +13 -0
  125. package/dist/place.d.ts +4 -0
  126. package/dist/preview-BiIBif3C.js +427 -0
  127. package/dist/preview-address-Cf0sJ-9p.js +21 -0
  128. package/dist/preview-cart-Dg2CVktS.js +376 -0
  129. package/dist/product-preview-BspMAecJ.js +51 -0
  130. package/dist/product-preview.d.ts +5 -0
  131. package/dist/product-variants-BNDHvs1y.js +46 -0
  132. package/dist/product-variants.d.ts +12 -0
  133. package/dist/public-pages-XtwlaLwt.js +194 -0
  134. package/dist/public-pages.d.ts +67 -0
  135. package/dist/registration-CpDcv8ql.js +91 -0
  136. package/dist/registration.d.ts +12 -0
  137. package/dist/request-security-CRsRMgwj.js +76 -0
  138. package/dist/rich-text-M-Ju8RL4.js +9 -0
  139. package/dist/rich-text.d.ts +5 -0
  140. package/dist/route-text-DcJw2SsK.js +9 -0
  141. package/dist/route-text.d.ts +4 -0
  142. package/dist/section-preview-CmsMl3yu.js +83 -0
  143. package/dist/section-preview.d.ts +12 -0
  144. package/dist/sections/account-addresses.d.ts +18 -0
  145. package/dist/sections/account-addresses.js +375 -0
  146. package/dist/sections/account-order.d.ts +12 -0
  147. package/dist/sections/account-order.js +592 -0
  148. package/dist/sections/account-orders.d.ts +8 -0
  149. package/dist/sections/account-orders.js +359 -0
  150. package/dist/sections/account-profile.d.ts +16 -0
  151. package/dist/sections/account-profile.js +123 -0
  152. package/dist/sections/checkout.d.ts +24 -0
  153. package/dist/sections/checkout.js +963 -0
  154. package/dist/sections/complaint-book.d.ts +8 -0
  155. package/dist/sections/complaint-book.js +538 -0
  156. package/dist/sections/design-system.d.ts +1 -0
  157. package/dist/sections/design-system.js +310 -0
  158. package/dist/sections/draft-invoice.d.ts +20 -0
  159. package/dist/sections/draft-invoice.js +158 -0
  160. package/dist/sections/order-tracking.d.ts +19 -0
  161. package/dist/sections/order-tracking.js +141 -0
  162. package/dist/sections-CNUMWTF_.js +77 -0
  163. package/dist/sections.d.ts +19 -0
  164. package/dist/seo-GpDRhAAA.js +78 -0
  165. package/dist/session-CKvmFJyg.js +379 -0
  166. package/dist/session.d.ts +24 -0
  167. package/dist/settings-B0NO2A6Y.js +78 -0
  168. package/dist/settings.d.ts +51 -0
  169. package/dist/source-marker-DhqSkW7n.js +4 -0
  170. package/dist/sources-B-3JFJfb.js +46 -0
  171. package/dist/sources.d.ts +7 -0
  172. package/dist/store-8Q-_LFrQ.js +152 -0
  173. package/dist/store-contact--hY6CoFb.js +19 -0
  174. package/dist/store-contact.d.ts +6 -0
  175. package/dist/store.d.ts +17 -0
  176. package/dist/storefront-language-DrMjVbmk.js +29 -0
  177. package/dist/storefront-language.d.ts +14 -0
  178. package/dist/storefront-text-D25ae1qf.js +11 -0
  179. package/dist/storefront-text-en-ljjjSS4q.js +1030 -0
  180. package/dist/storefront-text-pt-BtHaJLcp.js +1030 -0
  181. package/dist/storefront-text.d.ts +4 -0
  182. package/dist/stripe-dialog-Ev9s3Z62.js +396 -0
  183. package/dist/stripe-dialog.d.ts +30 -0
  184. package/dist/telemetry-HuZXWBPi.js +114 -0
  185. package/dist/telemetry.d.ts +10 -0
  186. package/dist/templates-CAqoNyTW.js +40 -0
  187. package/dist/templates.d.ts +15 -0
  188. package/dist/urls-CpGQPxvf.js +71 -0
  189. package/dist/urls.d.ts +18 -0
  190. package/dist/use-checkout-CZcxXJXi.js +42 -0
  191. package/dist/use-checkout-payment-9R-zacrJ.js +80 -0
  192. package/dist/use-checkout-payment.d.ts +18 -0
  193. package/dist/use-checkout.d.ts +36 -0
  194. package/dist/use-culqi-checkout-CrjLAipU.js +109 -0
  195. package/dist/use-order-payment-BKIz_IO0.js +45 -0
  196. package/dist/use-order-payment.d.ts +18 -0
  197. package/dist/use-payment-token-CErx-tN-.js +105 -0
  198. package/dist/vite-payment-logos-kziu7wKb.js +27 -0
  199. package/dist/vite-payment-logos.d.ts +24 -0
  200. package/dist/vite-public-assets-DW2fgtVb.js +209 -0
  201. package/dist/vite-public-assets.d.ts +29 -0
  202. package/dist/vite-sections-DTBr0-Kg.js +464 -0
  203. package/dist/vite-sections.d.ts +21 -0
  204. package/dist/vite-theme-ui-BMO-CDi3.js +41 -0
  205. package/dist/vite-theme-ui.d.ts +10 -0
  206. package/dist/vite.d.ts +36 -0
  207. package/dist/vite.js +127 -0
  208. package/dist/voucher-payment-CVFDZ1L8.js +66 -0
  209. package/dist/worker-CNi-gzCu.js +86 -0
  210. package/dist/worker.d.ts +27 -0
  211. package/package.json +100 -76
  212. package/src/sections/account-addresses.tsx +215 -0
  213. package/src/sections/account-order.tsx +424 -0
  214. package/src/sections/account-orders.tsx +259 -0
  215. package/src/sections/account-profile.tsx +70 -0
  216. package/src/sections/checkout.tsx +586 -0
  217. package/src/sections/complaint-book.tsx +325 -0
  218. package/src/sections/design-system.tsx +146 -0
  219. package/src/sections/draft-invoice.tsx +105 -0
  220. package/src/sections/order-tracking.tsx +81 -0
  221. package/src/vite-client.d.ts +4 -0
  222. package/bin/ecomiq.mjs +0 -179
  223. package/public/design-bridge.js +0 -318
  224. package/src/api-proxy.ts +0 -194
  225. package/src/auth/create-storefront-auth.ts +0 -94
  226. package/src/auth/ecomiq-oauth-password-client.ts +0 -26
  227. package/src/auth/ecomiq-oauth-password-constants.ts +0 -24
  228. package/src/auth/ecomiq-oauth-password-plugin.ts +0 -294
  229. package/src/auth/ecomiq-oauth-password-types.ts +0 -24
  230. package/src/auth/ecomiq-storefront-sign-in.ts +0 -44
  231. package/src/auth/env.ts +0 -7
  232. package/src/auth/secure-cookies.ts +0 -23
  233. package/src/auth/storefront-auth-base-url.ts +0 -117
  234. package/src/auth/storefront-auth.ts +0 -7
  235. package/src/cart-session.ts +0 -239
  236. package/src/client.ts +0 -48
  237. package/src/codegen.mjs +0 -801
  238. package/src/components/account/customer-addresses-panel.tsx +0 -678
  239. package/src/components/account/customer-order-detail-panel.tsx +0 -285
  240. package/src/components/account/customer-orders-panel.tsx +0 -192
  241. package/src/components/account/customer-profile-panel.tsx +0 -379
  242. package/src/components/checkout/cart-sync.ts +0 -143
  243. package/src/components/checkout/checkout-contact-section.tsx +0 -76
  244. package/src/components/checkout/checkout-external-update-banner.tsx +0 -15
  245. package/src/components/checkout/checkout-form-controls.tsx +0 -139
  246. package/src/components/checkout/checkout-line-items.tsx +0 -43
  247. package/src/components/checkout/checkout-order-summary.tsx +0 -165
  248. package/src/components/checkout/checkout-payment-section.tsx +0 -212
  249. package/src/components/checkout/checkout-session-view.tsx +0 -564
  250. package/src/components/checkout/checkout-shipping-address-section.tsx +0 -400
  251. package/src/components/checkout/checkout-shipping-section.tsx +0 -98
  252. package/src/components/checkout/checkout-states.tsx +0 -73
  253. package/src/components/checkout/checkout-step-card.tsx +0 -101
  254. package/src/components/checkout/checkout-steps.ts +0 -111
  255. package/src/components/checkout/form-schema.ts +0 -281
  256. package/src/components/checkout/format.ts +0 -15
  257. package/src/components/checkout/index.ts +0 -5
  258. package/src/components/checkout/map-customer-to-checkout.ts +0 -188
  259. package/src/components/checkout/types.ts +0 -41
  260. package/src/components/checkout/use-checkout-customer-prefill.ts +0 -409
  261. package/src/components/checkout/use-checkout-geo.ts +0 -50
  262. package/src/components/checkout/use-checkout-session.ts +0 -640
  263. package/src/components/hero.tsx +0 -291
  264. package/src/config.d.ts +0 -94
  265. package/src/config.mjs +0 -146
  266. package/src/format-money.ts +0 -31
  267. package/src/health.ts +0 -90
  268. package/src/hooks/index.ts +0 -9
  269. package/src/hooks/recently-viewed.ts +0 -125
  270. package/src/index.ts +0 -349
  271. package/src/link.tsx +0 -2
  272. package/src/pages/account/account-page-skeleton.tsx +0 -69
  273. package/src/pages/account/account-page-view.tsx +0 -270
  274. package/src/pages/account/account-shell.tsx +0 -101
  275. package/src/pages/account/parse-account-route.ts +0 -36
  276. package/src/pages/account.tsx +0 -16
  277. package/src/pages/api.$.ts +0 -20
  278. package/src/pages/api.auth.$.ts +0 -16
  279. package/src/pages/checkout.$checkoutId.tsx +0 -60
  280. package/src/pages/checkout.tsx +0 -31
  281. package/src/pages/pages.$pageSlug.tsx +0 -74
  282. package/src/pages/thank-you.tsx +0 -53
  283. package/src/providers/auth.tsx +0 -96
  284. package/src/providers/cart.tsx +0 -275
  285. package/src/providers/ecomiq.ts +0 -137
  286. package/src/providers/index.ts +0 -26
  287. package/src/proxy-api.ts +0 -20
  288. package/src/public-config.ts +0 -24
  289. package/src/review-submission.ts +0 -89
  290. package/src/runtime/context.ts +0 -2
  291. package/src/runtime/default-layout.tsx +0 -64
  292. package/src/runtime/head.ts +0 -149
  293. package/src/runtime/page.tsx +0 -143
  294. package/src/runtime/postcss-browser-stub.mjs +0 -5
  295. package/src/server-config-store.ts +0 -35
  296. package/src/server.ts +0 -286
  297. package/src/styles.css +0 -76
  298. package/src/ui/badge.tsx +0 -32
  299. package/src/ui/button.tsx +0 -31
  300. package/src/ui/card.tsx +0 -31
  301. package/src/ui/carousel.tsx +0 -218
  302. package/src/ui/cn.ts +0 -6
  303. package/src/ui/index.ts +0 -8
  304. package/src/ui/separator.tsx +0 -6
  305. package/src/ui/skeleton.tsx +0 -6
  306. package/src/ui/storefront-error.tsx +0 -49
  307. package/src/utils.ts +0 -36
  308. package/src/vite-inline.mjs +0 -263
  309. package/templates/__root.tsx +0 -83
  310. 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
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * Instancia Better Auth del storefront (servidor).
3
- * Singleton usado por la ruta cerrada `/api/auth/$`.
4
- */
5
- import { createStorefrontAuth } from './create-storefront-auth'
6
-
7
- export const storefrontAuth = createStorefrontAuth()