@doswiftly/storefront-sdk 11.4.0 → 11.5.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,78 @@
1
1
  # Changelog
2
2
 
3
+ ## 11.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 190fd9d: `CartClient.complete()` now returns `Order.accessToken` — unblocks guest order summary flow end-to-end.
8
+
9
+ **What changed:**
10
+
11
+ Internal `ORDER_FRAGMENT` selection set now includes `accessToken: String!`. Before this version the SDK omitted the field defensively (the backend resolver was incomplete in 11.4.0 — selecting `accessToken` crashed the entire response with a non-nullable violation). The backend resolver is now fixed; this version restores `accessToken` selection so `cartComplete.order.accessToken` returns the token that you pass to `OrderByToken` for guest summary pages.
12
+
13
+ **Upgrade impact:**
14
+ - After upgrade, `cart.complete()` response includes `order.accessToken` (non-empty UUID v4 string). Use it to build guest summary URLs (e.g. `/order/summary?token=${order.accessToken}`).
15
+ - No breaking change — existing code that destructures the `order` object continues to work; the new field is additive.
16
+
17
+ **Backward compatibility:** SDK consumers on the previous version that already destructure `order` keep working. The new `accessToken` field is added; existing fields (`id`, `orderNumber`, `totals`, `status`, etc.) are unchanged.
18
+
19
+ **Companion**: `@doswiftly/storefront-operations` ships the same field in its `Order` fragment and the new `OrderByToken` query (see that changelog entry for full security model — rate limit, email guard, Cache-Control).
20
+
21
+ - e6c80ce: Auth route handlers (`createSetTokenHandler`, `createClearTokenHandler`, `createWhoamiHandler`) now accept an optional `isTrustedOrigin` predicate. This unblocks the BFF auth flow when the storefront runs behind a reverse proxy (DoSwiftly hosting, Vercel, custom edge proxy) that rewrites or strips the `Host` header — since 11.3.0 the strict `Origin host = Host` comparison returned 403 for every login, logout, and page-load hydration in that topology, leaving the auth cookie unset and every auth-gated route redirecting to login.
22
+
23
+ ### Fix
24
+
25
+ Each handler accepts a new `isTrustedOrigin` callback:
26
+
27
+ ```ts
28
+ type OriginValidator = (ctx: {
29
+ origin: string;
30
+ originHost: string;
31
+ request: Request;
32
+ }) => boolean | Promise<boolean>;
33
+ ```
34
+
35
+ When the predicate returns truthy the strict `Origin host = Host header` comparison is bypassed; when it returns falsy (or is not configured) the existing strict check applies. A thrown predicate fails closed — the error is logged and the strict check applies as if the predicate returned `false`.
36
+
37
+ Two pre-built predicates are exported:
38
+ - `trustedForwardedHostValidator` — passes when `Origin host` equals `X-Forwarded-Host` (falling back to `X-Original-Host`). Use this when a reverse proxy you control sets one of those headers per request. The DoSwiftly hosting platform and Vercel both qualify.
39
+ - `originAllowlistValidator(['https://shop.example.com', 'other-shop.example'])` — passes when `Origin` matches an entry in a static list. Useful when one storefront is hosted on multiple hostnames (custom apex + platform subdomain) and you do not want to depend on forwarded-host headers.
40
+
41
+ ### Upgrade impact
42
+ - New storefronts scaffolded via `doswiftly init` ship with `isTrustedOrigin: trustedForwardedHostValidator` configured by default.
43
+ - Existing storefronts using the SDK behind a reverse proxy need a 3-line change to each route handler under `app/api/auth/`:
44
+ ```ts
45
+ import {
46
+ createSetTokenHandler,
47
+ trustedForwardedHostValidator,
48
+ } from "@doswiftly/storefront-sdk";
49
+ export const POST = createSetTokenHandler({
50
+ isTrustedOrigin: trustedForwardedHostValidator,
51
+ });
52
+ ```
53
+ - Storefronts deployed without a reverse proxy (single-tier hosting where the Next.js process receives traffic directly) need no changes — the default strict check still works because the Host header arrives intact.
54
+
55
+ ### Security
56
+
57
+ The predicate is invoked AFTER the Origin header is parsed (rejecting malformed origins) and BEFORE the strict Host comparison. Forwarded-host validation is safe because the trusted intermediary overwrites those headers on every inbound request (`headers.set(...)`, not `append`), so an attacker cannot forge them via a browser `fetch()` — browsers cannot set `X-Forwarded-*` from JavaScript (they are forbidden request headers per the fetch spec).
58
+
59
+ Defense-in-depth layers continue to apply: CORS preflight at the backend, `SameSite=Lax` on the auth cookie, `HttpOnly` + `Secure` cookie attributes, and the strict Origin URL parse (still rejects malformed origin and the `?host=trusted` query-string bypass).
60
+
61
+ - 6b9f907: `createWhoamiHandler` now allows missing `Origin` header for safe (GET) requests — fixes "user logged out after refresh" regression introduced in 11.3.0.
62
+
63
+ **Background:**
64
+
65
+ Per fetch spec § 3.5.6, browsers OMIT the `Origin` header for same-origin GET/HEAD requests. The hardened `validateOrigin()` introduced in 11.3.0 required `Origin` for every call, so every page-load hydration via `GET /api/auth/whoami` returned 403 — the in-memory auth store stayed empty even though the httpOnly cookie was valid, and the UI reverted to "Sign in" on refresh. Auth-protected mutations kept working (the cookie was sent automatically), only the UI hydration was broken.
66
+
67
+ **Fix:**
68
+
69
+ `validateOrigin()` accepts an optional `{ allowMissingOrigin: true }` flag. When set, missing `Origin` is allowed (typical same-origin GET); when `Origin` is present, mismatch is still rejected (defense-in-depth for cross-origin GET edge cases — e.g. `fetch(..., { mode: 'cors' })` does send `Origin`). `createWhoamiHandler` opts into the lenient check because `GET /whoami` is CSRF-safe (idempotent, no state change, response is JSON same-origin attackers cannot read anyway). `createSetTokenHandler` and `createClearTokenHandler` keep the strict check — they are POST and browsers always send `Origin` for state-changing methods.
70
+
71
+ **Upgrade impact:**
72
+ - After upgrade, `GET /api/auth/whoami` succeeds for same-origin browser fetch with no `Origin` header — the auth store hydrates correctly on page-load, the UI shows the signed-in customer instead of "Sign in".
73
+ - No code change required in your storefront — the SDK's BFF handler is the only thing that needs updating.
74
+ - POST handlers (`createSetTokenHandler`, `createClearTokenHandler`) are unchanged; existing strict `Origin` validation stays in place for mutations.
75
+
3
76
  ## 11.4.0
4
77
 
5
78
  ### Minor Changes
@@ -12,14 +12,119 @@
12
12
  * export const POST = createSetTokenHandler();
13
13
  * ```
14
14
  */
15
+ /**
16
+ * Context passed to {@link OriginValidator} predicate callbacks.
17
+ *
18
+ * Includes the already-parsed `originHost` so factory helpers do not
19
+ * re-parse, and the full `Request` so a predicate can inspect any header
20
+ * set by a trusted intermediary (e.g. `X-Forwarded-Host`).
21
+ */
22
+ export interface OriginValidatorContext {
23
+ /** Raw `Origin` header value (e.g. `https://shop.example.com`). */
24
+ origin: string;
25
+ /** Parsed `host` portion of the origin URL (e.g. `shop.example.com`). */
26
+ originHost: string;
27
+ /** Full incoming request — read additional headers as needed. */
28
+ request: Request;
29
+ }
30
+ /**
31
+ * Predicate that decides whether an incoming origin should be trusted
32
+ * even when the strict `Origin host = Host header` comparison would fail.
33
+ *
34
+ * When the predicate returns truthy, `validateOrigin` returns `null` (pass).
35
+ * When it returns falsy, the strict Host check applies (current behavior).
36
+ * When the predicate throws, `validateOrigin` fails closed — the throw is
37
+ * logged and the strict Host check applies as if the predicate returned `false`.
38
+ *
39
+ * Use {@link trustedForwardedHostValidator} for reverse-proxy deployments
40
+ * (Cloudflare Workers dispatch, Vercel, NGINX, etc.) or
41
+ * {@link originAllowlistValidator} for explicit hostname allowlists.
42
+ */
43
+ export type OriginValidator = (ctx: OriginValidatorContext) => boolean | Promise<boolean>;
15
44
  interface SetTokenHandlerOptions {
45
+ /** Cookie `Max-Age` override (seconds). */
16
46
  maxAge?: number;
47
+ /** Optional predicate that bypasses strict Host check when truthy. */
48
+ isTrustedOrigin?: OriginValidator | null;
49
+ }
50
+ interface ClearTokenHandlerOptions {
51
+ /** Optional predicate that bypasses strict Host check when truthy. */
52
+ isTrustedOrigin?: OriginValidator | null;
17
53
  }
54
+ /**
55
+ * Pre-built {@link OriginValidator} that passes when the parsed `Origin` host
56
+ * matches a hostname set by a trusted reverse proxy.
57
+ *
58
+ * Reads `X-Forwarded-Host` first (RFC 7239 de-facto standard, multiple values
59
+ * are comma-separated — the first entry is the original client-facing host).
60
+ * Falls back to `X-Original-Host` (a header the DoSwiftly dispatch worker
61
+ * emits alongside `X-Forwarded-Host`).
62
+ *
63
+ * Safety: this predicate is only spoof-resistant when invoked inside a runtime
64
+ * that sits behind a trusted intermediary which OVERWRITES these headers on
65
+ * every inbound request (the dispatch worker calls `headers.set(...)`, not
66
+ * `append`, so a client-sent value is replaced). Browsers cannot set
67
+ * `X-Forwarded-*` from JS — these are forbidden request headers per fetch
68
+ * spec — so CSRF requests from a malicious origin cannot forge them.
69
+ *
70
+ * Use this predicate when deploying behind the DoSwiftly dispatch worker,
71
+ * Vercel, NGINX, or any reverse proxy that strips/rewrites `Host` while
72
+ * preserving `Origin`.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * // app/api/auth/set-token/route.ts
77
+ * import {
78
+ * createSetTokenHandler,
79
+ * trustedForwardedHostValidator,
80
+ * } from '@doswiftly/storefront-sdk';
81
+ *
82
+ * export const POST = createSetTokenHandler({
83
+ * isTrustedOrigin: trustedForwardedHostValidator,
84
+ * });
85
+ * ```
86
+ */
87
+ export declare const trustedForwardedHostValidator: OriginValidator;
88
+ /**
89
+ * Factory that returns an {@link OriginValidator} which passes when the
90
+ * parsed `Origin` host matches one of the entries in `allowedOrigins`.
91
+ *
92
+ * Each entry may be a full origin URL (`https://shop.example.com`) or a bare
93
+ * hostname (`shop.example.com`). The factory normalizes both forms to the
94
+ * hostname for comparison.
95
+ *
96
+ * Use this predicate when deploying without a reverse proxy that sets
97
+ * `X-Forwarded-Host`, or when hosting one storefront on multiple hostnames
98
+ * (custom apex + platform subdomain). When a custom domain is added to the
99
+ * merchant's shop, the integrator must redeploy with the new hostname in the
100
+ * list — for runtime-synced allowlists prefer {@link trustedForwardedHostValidator}
101
+ * behind a reverse proxy.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * // app/api/auth/set-token/route.ts
106
+ * import {
107
+ * createSetTokenHandler,
108
+ * originAllowlistValidator,
109
+ * } from '@doswiftly/storefront-sdk';
110
+ *
111
+ * export const POST = createSetTokenHandler({
112
+ * isTrustedOrigin: originAllowlistValidator([
113
+ * 'https://shop.example.com',
114
+ * 'shop-example.doswiftly.pl',
115
+ * ]),
116
+ * });
117
+ * ```
118
+ */
119
+ export declare function originAllowlistValidator(allowedOrigins: string[]): OriginValidator;
18
120
  /**
19
121
  * Create a POST handler that sets the auth token in an httpOnly cookie.
20
122
  *
21
123
  * Security: origin validation, Content-Type check, CSRF (SameSite=Lax),
22
124
  * httpOnly (XSS protection), Secure in production.
125
+ *
126
+ * Pass `isTrustedOrigin` when running behind a reverse proxy that strips or
127
+ * rewrites the `Host` header — see {@link trustedForwardedHostValidator}.
23
128
  */
24
129
  export declare function createSetTokenHandler(overrides?: SetTokenHandlerOptions): (request: Request) => Promise<Response>;
25
130
  interface WhoamiHandlerOptions {
@@ -27,6 +132,8 @@ interface WhoamiHandlerOptions {
27
132
  apiUrl?: string;
28
133
  /** Shop slug header value (X-Shop-Slug). */
29
134
  shopSlug?: string;
135
+ /** Optional predicate that bypasses strict Host check when truthy. */
136
+ isTrustedOrigin?: OriginValidator | null;
30
137
  }
31
138
  /**
32
139
  * Create a GET handler that hydrates customer state z httpOnly cookie.
@@ -41,6 +148,8 @@ interface WhoamiHandlerOptions {
41
148
  * 4. Klient setAuth(customer, '') — token NIE w body (cookie kontynuuje per-request auth)
42
149
  *
43
150
  * Security: origin validation, fetch via Bearer (cookie nie crossuje API boundary).
151
+ * Pass `isTrustedOrigin` when running behind a reverse proxy — see
152
+ * {@link trustedForwardedHostValidator}.
44
153
  *
45
154
  * @example
46
155
  * ```ts
@@ -57,7 +166,10 @@ export declare function createWhoamiHandler(options?: WhoamiHandlerOptions): (re
57
166
  * Create a POST handler that clears the auth token cookie.
58
167
  *
59
168
  * Security: origin validation, immediate expiration (maxAge=0).
169
+ *
170
+ * Pass `isTrustedOrigin` when running behind a reverse proxy that strips or
171
+ * rewrites the `Host` header — see {@link trustedForwardedHostValidator}.
60
172
  */
61
- export declare function createClearTokenHandler(): (request: Request) => Promise<Response>;
173
+ export declare function createClearTokenHandler(options?: ClearTokenHandlerOptions): (request: Request) => Promise<Response>;
62
174
  export {};
63
175
  //# sourceMappingURL=handlers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../../src/core/auth/handlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,UAAU,sBAAsB;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAoED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,CAAC,EAAE,sBAAsB,GACjC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAgEzC;AAED,UAAU,oBAAoB;IAC5B,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,oBAAyB,GACjC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA6DzC;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAkCjF"}
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../../src/core/auth/handlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACrC,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,eAAe,GAAG,CAC5B,GAAG,EAAE,sBAAsB,KACxB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,UAAU,sBAAsB;IAC9B,2CAA2C;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CAC1C;AAED,UAAU,wBAAwB;IAChC,sEAAsE;IACtE,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CAC1C;AAqHD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,6BAA6B,EAAE,eAa3C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,wBAAwB,CACtC,cAAc,EAAE,MAAM,EAAE,GACvB,eAAe,CAajB;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,CAAC,EAAE,sBAAsB,GACjC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAiEzC;AAED,UAAU,oBAAoB;IAC5B,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,oBAAyB,GACjC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAwEzC;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,CAAC,EAAE,wBAAwB,GACjC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAoCzC"}
@@ -37,15 +37,31 @@ function serializeCookie(name, value, options) {
37
37
  * - `origin = "https://attacker.example/?host=trusted"` → substring match
38
38
  * passes for any trusted host name.
39
39
  *
40
- * Both Origin and Host MUST be present (typical browser fetch sends Origin
41
- * for cross-origin and same-origin POSTs; server-to-server callers without
42
- * an Origin header should hit the GraphQL API directly, not these BFF
43
- * handlers).
40
+ * For state-changing methods (POST/PUT/PATCH/DELETE) browsers always send
41
+ * Origin (fetch spec § 3.5.6 "method is neither GET nor HEAD"). For safe
42
+ * methods (GET/HEAD) browsers OMIT Origin for same-origin requests; passing
43
+ * `allowMissingOrigin: true` skips the "Origin required" check while still
44
+ * validating mismatch when Origin happens to be present (defense-in-depth
45
+ * for cross-origin GET edge cases).
46
+ *
47
+ * When `isTrustedOrigin` is provided, the predicate is evaluated AFTER the
48
+ * Origin is parsed but BEFORE the strict Host comparison. If the predicate
49
+ * returns truthy, the request passes — useful when running behind a reverse
50
+ * proxy that rewrites or strips the `Host` header (Cloudflare Workers
51
+ * dispatch, Vercel, etc.) while preserving the customer-facing host in
52
+ * `X-Forwarded-Host`. The predicate is consulted instead of (not in addition
53
+ * to) the strict check.
54
+ *
55
+ * Server-to-server callers without an Origin header should hit the GraphQL
56
+ * API directly, not these BFF handlers.
44
57
  */
45
- function validateOrigin(request) {
58
+ async function validateOrigin(request, options) {
46
59
  const origin = request.headers.get('origin');
47
- const host = request.headers.get('host');
48
- if (!origin || !host) {
60
+ // GET/HEAD same-origin: browser omits Origin per fetch spec — pass through.
61
+ // Mutating methods (POST/etc.) MUST have Origin (browsers always send it).
62
+ if (!origin) {
63
+ if (options?.allowMissingOrigin)
64
+ return null;
49
65
  return new Response(JSON.stringify({ error: 'Origin and Host headers are required' }), { status: 403, headers: { 'Content-Type': 'application/json' } });
50
66
  }
51
67
  let originHost;
@@ -58,6 +74,31 @@ function validateOrigin(request) {
58
74
  headers: { 'Content-Type': 'application/json' },
59
75
  });
60
76
  }
77
+ // Predicate gate (bypasses strict Host check for reverse-proxy deployments).
78
+ // Fail-closed: predicate throw is logged + fall through to strict check.
79
+ if (options?.isTrustedOrigin) {
80
+ try {
81
+ const trusted = await options.isTrustedOrigin({
82
+ origin,
83
+ originHost,
84
+ request,
85
+ });
86
+ if (trusted)
87
+ return null;
88
+ }
89
+ catch (err) {
90
+ console.warn('isTrustedOrigin predicate threw:', err);
91
+ // continue to strict Host check
92
+ }
93
+ }
94
+ // Strict Host check — backward compatible fallback when no predicate is
95
+ // configured or predicate returned false. Reverse proxies that drop the
96
+ // Host header MUST use a predicate; bare-metal deploys without a proxy
97
+ // see the original Host and pass strict.
98
+ const host = request.headers.get('host');
99
+ if (!host) {
100
+ return new Response(JSON.stringify({ error: 'Origin and Host headers are required' }), { status: 403, headers: { 'Content-Type': 'application/json' } });
101
+ }
61
102
  if (originHost !== host) {
62
103
  return new Response(JSON.stringify({ error: 'Origin mismatch' }), {
63
104
  status: 403,
@@ -66,18 +107,115 @@ function validateOrigin(request) {
66
107
  }
67
108
  return null;
68
109
  }
110
+ /**
111
+ * Pre-built {@link OriginValidator} that passes when the parsed `Origin` host
112
+ * matches a hostname set by a trusted reverse proxy.
113
+ *
114
+ * Reads `X-Forwarded-Host` first (RFC 7239 de-facto standard, multiple values
115
+ * are comma-separated — the first entry is the original client-facing host).
116
+ * Falls back to `X-Original-Host` (a header the DoSwiftly dispatch worker
117
+ * emits alongside `X-Forwarded-Host`).
118
+ *
119
+ * Safety: this predicate is only spoof-resistant when invoked inside a runtime
120
+ * that sits behind a trusted intermediary which OVERWRITES these headers on
121
+ * every inbound request (the dispatch worker calls `headers.set(...)`, not
122
+ * `append`, so a client-sent value is replaced). Browsers cannot set
123
+ * `X-Forwarded-*` from JS — these are forbidden request headers per fetch
124
+ * spec — so CSRF requests from a malicious origin cannot forge them.
125
+ *
126
+ * Use this predicate when deploying behind the DoSwiftly dispatch worker,
127
+ * Vercel, NGINX, or any reverse proxy that strips/rewrites `Host` while
128
+ * preserving `Origin`.
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * // app/api/auth/set-token/route.ts
133
+ * import {
134
+ * createSetTokenHandler,
135
+ * trustedForwardedHostValidator,
136
+ * } from '@doswiftly/storefront-sdk';
137
+ *
138
+ * export const POST = createSetTokenHandler({
139
+ * isTrustedOrigin: trustedForwardedHostValidator,
140
+ * });
141
+ * ```
142
+ */
143
+ export const trustedForwardedHostValidator = ({ originHost, request, }) => {
144
+ const xfh = request.headers.get('x-forwarded-host');
145
+ if (xfh) {
146
+ // RFC 7239 — chain of proxies appends, first value is the original host.
147
+ const original = xfh.split(',')[0]?.trim();
148
+ if (original && original === originHost)
149
+ return true;
150
+ }
151
+ const xoh = request.headers.get('x-original-host');
152
+ if (xoh && xoh.trim() === originHost)
153
+ return true;
154
+ return false;
155
+ };
156
+ /**
157
+ * Factory that returns an {@link OriginValidator} which passes when the
158
+ * parsed `Origin` host matches one of the entries in `allowedOrigins`.
159
+ *
160
+ * Each entry may be a full origin URL (`https://shop.example.com`) or a bare
161
+ * hostname (`shop.example.com`). The factory normalizes both forms to the
162
+ * hostname for comparison.
163
+ *
164
+ * Use this predicate when deploying without a reverse proxy that sets
165
+ * `X-Forwarded-Host`, or when hosting one storefront on multiple hostnames
166
+ * (custom apex + platform subdomain). When a custom domain is added to the
167
+ * merchant's shop, the integrator must redeploy with the new hostname in the
168
+ * list — for runtime-synced allowlists prefer {@link trustedForwardedHostValidator}
169
+ * behind a reverse proxy.
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * // app/api/auth/set-token/route.ts
174
+ * import {
175
+ * createSetTokenHandler,
176
+ * originAllowlistValidator,
177
+ * } from '@doswiftly/storefront-sdk';
178
+ *
179
+ * export const POST = createSetTokenHandler({
180
+ * isTrustedOrigin: originAllowlistValidator([
181
+ * 'https://shop.example.com',
182
+ * 'shop-example.doswiftly.pl',
183
+ * ]),
184
+ * });
185
+ * ```
186
+ */
187
+ export function originAllowlistValidator(allowedOrigins) {
188
+ const allowedHosts = new Set();
189
+ for (const entry of allowedOrigins) {
190
+ const trimmed = entry.trim();
191
+ if (!trimmed)
192
+ continue;
193
+ // Support both `https://host[:port]` and bare `host[:port]` entries.
194
+ try {
195
+ allowedHosts.add(new URL(trimmed).host);
196
+ }
197
+ catch {
198
+ allowedHosts.add(trimmed);
199
+ }
200
+ }
201
+ return ({ originHost }) => allowedHosts.has(originHost);
202
+ }
69
203
  /**
70
204
  * Create a POST handler that sets the auth token in an httpOnly cookie.
71
205
  *
72
206
  * Security: origin validation, Content-Type check, CSRF (SameSite=Lax),
73
207
  * httpOnly (XSS protection), Secure in production.
208
+ *
209
+ * Pass `isTrustedOrigin` when running behind a reverse proxy that strips or
210
+ * rewrites the `Host` header — see {@link trustedForwardedHostValidator}.
74
211
  */
75
212
  export function createSetTokenHandler(overrides) {
76
213
  const maxAge = overrides?.maxAge ?? AUTH_COOKIE_DEFAULTS.maxAge;
214
+ const isTrustedOrigin = overrides?.isTrustedOrigin ?? null;
77
215
  return async (request) => {
78
216
  try {
79
217
  // 1. CSRF: validate origin
80
- const originError = validateOrigin(request);
218
+ const originError = await validateOrigin(request, { isTrustedOrigin });
81
219
  if (originError)
82
220
  return originError;
83
221
  // 2. Validate Content-Type
@@ -132,6 +270,8 @@ export function createSetTokenHandler(overrides) {
132
270
  * 4. Klient setAuth(customer, '') — token NIE w body (cookie kontynuuje per-request auth)
133
271
  *
134
272
  * Security: origin validation, fetch via Bearer (cookie nie crossuje API boundary).
273
+ * Pass `isTrustedOrigin` when running behind a reverse proxy — see
274
+ * {@link trustedForwardedHostValidator}.
135
275
  *
136
276
  * @example
137
277
  * ```ts
@@ -144,9 +284,19 @@ export function createSetTokenHandler(overrides) {
144
284
  * ```
145
285
  */
146
286
  export function createWhoamiHandler(options = {}) {
287
+ const isTrustedOrigin = options.isTrustedOrigin ?? null;
147
288
  return async (request) => {
148
289
  try {
149
- const originError = validateOrigin(request);
290
+ // GET /whoami is CSRF-safe (idempotent, no state change). Browsers OMIT
291
+ // Origin header for same-origin GET per fetch spec § 3.5.6 — strict
292
+ // Origin requirement would 403 every page load on storefronts using
293
+ // same-origin BFF setup (Next.js `/api/auth/whoami` Route Handler).
294
+ // We still validate mismatch when Origin is present (cross-origin GET
295
+ // edge case — fetch with `mode: 'cors'` does send Origin).
296
+ const originError = await validateOrigin(request, {
297
+ allowMissingOrigin: true,
298
+ isTrustedOrigin,
299
+ });
150
300
  if (originError)
151
301
  return originError;
152
302
  const cookieHeader = request.headers.get('cookie') ?? '';
@@ -192,12 +342,16 @@ export function createWhoamiHandler(options = {}) {
192
342
  * Create a POST handler that clears the auth token cookie.
193
343
  *
194
344
  * Security: origin validation, immediate expiration (maxAge=0).
345
+ *
346
+ * Pass `isTrustedOrigin` when running behind a reverse proxy that strips or
347
+ * rewrites the `Host` header — see {@link trustedForwardedHostValidator}.
195
348
  */
196
- export function createClearTokenHandler() {
349
+ export function createClearTokenHandler(options) {
350
+ const isTrustedOrigin = options?.isTrustedOrigin ?? null;
197
351
  return async (request) => {
198
352
  try {
199
353
  // 1. CSRF: validate origin
200
- const originError = validateOrigin(request);
354
+ const originError = await validateOrigin(request, { isTrustedOrigin });
201
355
  if (originError)
202
356
  return originError;
203
357
  // 2. Clear cookie (maxAge=0)
@@ -62,7 +62,8 @@ export { LANGUAGE_COOKIE_NAME, LANGUAGE_COOKIE_MAX_AGE, LANGUAGE_HEADER_NAME } f
62
62
  export { CURRENCY_COOKIE_NAME, CURRENCY_COOKIE_MAX_AGE, CURRENCY_HEADER_NAME } from './currency/cookie-config';
63
63
  export { CART_COOKIE_NAME, CART_COOKIE_MAX_AGE } from './cart/cookie-config';
64
64
  export { matchesRoute, type RouteProtectionConfig } from './auth/routes';
65
- export { createSetTokenHandler, createClearTokenHandler, createWhoamiHandler } from './auth/handlers';
65
+ export { createSetTokenHandler, createClearTokenHandler, createWhoamiHandler, originAllowlistValidator, trustedForwardedHostValidator, } from './auth/handlers';
66
+ export type { OriginValidator, OriginValidatorContext } from './auth/handlers';
66
67
  export { createAuthTokenClient, type AuthTokenClient } from './auth/token-client';
67
68
  export { type ImageData, thumbHashToDataURL } from './image';
68
69
  export { getOperationName } from './client/operation-name';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"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"}
@@ -73,7 +73,7 @@ export { CART_COOKIE_NAME, CART_COOKIE_MAX_AGE } from './cart/cookie-config';
73
73
  // Auth route matching
74
74
  export { matchesRoute } from './auth/routes';
75
75
  // Auth cookie handlers (API route factories)
76
- export { createSetTokenHandler, createClearTokenHandler, createWhoamiHandler } from './auth/handlers';
76
+ export { createSetTokenHandler, createClearTokenHandler, createWhoamiHandler, originAllowlistValidator, trustedForwardedHostValidator, } from './auth/handlers';
77
77
  // Auth token client (client-side fetch helpers)
78
78
  export { createAuthTokenClient } from './auth/token-client';
79
79
  // Image types (loaders removed — GraphQL returns ready-to-use CDN URLs with transform params)
@@ -1 +1 @@
1
- {"version":3,"file":"cart.d.ts","sourceRoot":"","sources":["../../../src/core/operations/cart.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAuSH,eAAO,MAAM,UAAU,QAOrB,CAAC;AAMH,eAAO,MAAM,WAAW,QAWtB,CAAC;AAEH,eAAO,MAAM,cAAc,QAWzB,CAAC;AAEH,eAAO,MAAM,iBAAiB,QAW5B,CAAC;AAEH,eAAO,MAAM,iBAAiB,QAW5B,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAEH,eAAO,MAAM,gBAAgB,QAW3B,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAMH,eAAO,MAAM,yBAAyB,QAWpC,CAAC;AAEH,eAAO,MAAM,wBAAwB,QAWnC,CAAC;AAEH,eAAO,MAAM,2BAA2B,QAWtC,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAEH,eAAO,MAAM,oBAAoB,QAW/B,CAAC;AAEH,eAAO,MAAM,qBAAqB,QAWhC,CAAC;AAEH,eAAO,MAAM,+BAA+B,QAW1C,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,QAWxB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QASzB,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,QAoBtC,CAAC"}
1
+ {"version":3,"file":"cart.d.ts","sourceRoot":"","sources":["../../../src/core/operations/cart.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAwSH,eAAO,MAAM,UAAU,QAOrB,CAAC;AAMH,eAAO,MAAM,WAAW,QAWtB,CAAC;AAEH,eAAO,MAAM,cAAc,QAWzB,CAAC;AAEH,eAAO,MAAM,iBAAiB,QAW5B,CAAC;AAEH,eAAO,MAAM,iBAAiB,QAW5B,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAEH,eAAO,MAAM,gBAAgB,QAW3B,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAMH,eAAO,MAAM,yBAAyB,QAWpC,CAAC;AAEH,eAAO,MAAM,wBAAwB,QAWnC,CAAC;AAEH,eAAO,MAAM,2BAA2B,QAWtC,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAEH,eAAO,MAAM,oBAAoB,QAW/B,CAAC;AAEH,eAAO,MAAM,qBAAqB,QAWhC,CAAC;AAEH,eAAO,MAAM,+BAA+B,QAW1C,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,QAWxB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QASzB,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,QAoBtC,CAAC"}
@@ -229,6 +229,7 @@ const ORDER_FRAGMENT = `
229
229
  fragment Order on Order {
230
230
  id
231
231
  orderNumber
232
+ accessToken
232
233
  totals {
233
234
  total { ...Money }
234
235
  subtotal { ...Money }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doswiftly/storefront-sdk",
3
- "version": "11.4.0",
3
+ "version": "11.5.0",
4
4
  "description": "Storefront runtime SDK for DoSwiftly Commerce — layered transport, middleware pipeline, React providers, Zustand stores, cache strategies. 0 runtime dependencies in core.",
5
5
  "type": "module",
6
6
  "sideEffects": false,