@doswiftly/storefront-sdk 22.0.0 → 22.1.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 +20 -0
- package/README.md +39 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/middleware/forwarded-ip.d.ts +82 -0
- package/dist/core/middleware/forwarded-ip.d.ts.map +1 -0
- package/dist/core/middleware/forwarded-ip.js +109 -0
- package/dist/react/server/get-storefront-client.d.ts +19 -1
- package/dist/react/server/get-storefront-client.d.ts.map +1 -1
- package/dist/react/server/get-storefront-client.js +36 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 22.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 32ee745: Forward the real buyer IP from server-rendered storefronts (per-buyer rate limiting)
|
|
8
|
+
|
|
9
|
+
A server-rendered (BFF) storefront fetches from its own server, so the API sees one
|
|
10
|
+
source IP for every buyer and per-IP rate limits collapse onto that single address.
|
|
11
|
+
The server client now forwards the real buyer IP to the API so rate limiting applies
|
|
12
|
+
per buyer again.
|
|
13
|
+
|
|
14
|
+
**Automatic on the server — no wiring, fully backward-compatible.** Call
|
|
15
|
+
`getStorefrontClient({ apiUrl, shopSlug })` as before; forwarding configures itself
|
|
16
|
+
and stays inert when it cannot apply, so existing setups are unaffected. Server-only.
|
|
17
|
+
|
|
18
|
+
Non-Next server runtimes can supply the buyer IP explicitly via `getBuyerIp` (and
|
|
19
|
+
`getForwardedIpSecret`). The lower-level `forwardedIpMiddleware` is also exported.
|
|
20
|
+
|
|
21
|
+
(`@doswiftly/storefront-operations` is a version-sync bump — no code change.)
|
|
22
|
+
|
|
3
23
|
## 22.0.0
|
|
4
24
|
|
|
5
25
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -772,6 +772,11 @@ Middleware that reads mutable state takes a **lazy getter**
|
|
|
772
772
|
(`authMiddleware(() => store.getState().accessToken)`) so rotated values are
|
|
773
773
|
picked up without rebuilding the client.
|
|
774
774
|
|
|
775
|
+
A server-only `forwardedIpMiddleware` is also available for server-rendered (BFF)
|
|
776
|
+
storefronts — it forwards the real buyer IP to the backend so per-IP rate limits
|
|
777
|
+
do not collapse onto the storefront server's address. See
|
|
778
|
+
[Server-side](#server-side-reactserver).
|
|
779
|
+
|
|
775
780
|
## Core API
|
|
776
781
|
|
|
777
782
|
### createStorefrontClient
|
|
@@ -1070,6 +1075,40 @@ middleware: [
|
|
|
1070
1075
|
],
|
|
1071
1076
|
```
|
|
1072
1077
|
|
|
1078
|
+
### Forwarding the buyer IP for rate limiting
|
|
1079
|
+
|
|
1080
|
+
When a storefront fetches on the server, the API sees the storefront server's IP for
|
|
1081
|
+
every buyer, so per-IP rate limits collapse onto a single address. The server client
|
|
1082
|
+
forwards the buyer's real IP so rate limiting applies per buyer again.
|
|
1083
|
+
|
|
1084
|
+
**Automatic on the server — nothing to wire.** Call `getStorefrontClient` as usual:
|
|
1085
|
+
|
|
1086
|
+
```typescript
|
|
1087
|
+
const client = getStorefrontClient({
|
|
1088
|
+
apiUrl: process.env.DOSWIFTLY_API_URL!,
|
|
1089
|
+
shopSlug: process.env.DOSWIFTLY_SHOP_SLUG!,
|
|
1090
|
+
});
|
|
1091
|
+
```
|
|
1092
|
+
|
|
1093
|
+
It applies only inside a server request with forwarding configured by your
|
|
1094
|
+
deployment, and is otherwise an inert pass-through — existing setups are unaffected.
|
|
1095
|
+
**Server-only**: nothing IP-related reaches the browser.
|
|
1096
|
+
|
|
1097
|
+
**Non-Next server runtimes** — supply the buyer IP yourself (e.g. from
|
|
1098
|
+
`cf-connecting-ip`), plus the signing secret if your runtime has no `process.env`:
|
|
1099
|
+
|
|
1100
|
+
```typescript
|
|
1101
|
+
const client = getStorefrontClient({
|
|
1102
|
+
apiUrl,
|
|
1103
|
+
shopSlug,
|
|
1104
|
+
getBuyerIp: () => incomingRequest.headers.get('cf-connecting-ip'),
|
|
1105
|
+
// getForwardedIpSecret: () => ...
|
|
1106
|
+
});
|
|
1107
|
+
```
|
|
1108
|
+
|
|
1109
|
+
The lower-level `forwardedIpMiddleware({ getBuyerIp, getSecret })` is also exported
|
|
1110
|
+
for fully custom clients.
|
|
1111
|
+
|
|
1073
1112
|
## Caching
|
|
1074
1113
|
|
|
1075
1114
|
```typescript
|
package/dist/core/index.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export { authMiddleware } from './middleware/auth';
|
|
|
41
41
|
export { cartSecretMiddleware, serverCartSecretMiddleware, CART_SECRET_HEADER, } from './middleware/cart-secret';
|
|
42
42
|
export { currencyMiddleware } from './middleware/currency';
|
|
43
43
|
export { languageMiddleware } from './middleware/language';
|
|
44
|
+
export { forwardedIpMiddleware, forwardedIpSignedMessage, FORWARDED_IP_HEADER, FORWARDED_IP_TS_HEADER, FORWARDED_IP_SIG_HEADER, type ForwardedIpMiddlewareOptions, } from './middleware/forwarded-ip';
|
|
44
45
|
export { botProtectionMiddleware, BOT_PROTECTION_HEADER, type BotProtectionTokenProvider, type BotProtectionConfig, type BotProtectionProviderConfig, type BotProtectionMiddlewareOptions, type FailStrategy, } from './middleware/bot-protection';
|
|
45
46
|
export { retryMiddleware, type RetryOptions } from './middleware/retry';
|
|
46
47
|
export { timeoutMiddleware, type TimeoutOptions } from './middleware/timeout';
|
package/dist/core/index.d.ts.map
CHANGED
|
@@ -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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"}
|
package/dist/core/index.js
CHANGED
|
@@ -43,6 +43,7 @@ export { authMiddleware } from './middleware/auth';
|
|
|
43
43
|
export { cartSecretMiddleware, serverCartSecretMiddleware, CART_SECRET_HEADER, } from './middleware/cart-secret';
|
|
44
44
|
export { currencyMiddleware } from './middleware/currency';
|
|
45
45
|
export { languageMiddleware } from './middleware/language';
|
|
46
|
+
export { forwardedIpMiddleware, forwardedIpSignedMessage, FORWARDED_IP_HEADER, FORWARDED_IP_TS_HEADER, FORWARDED_IP_SIG_HEADER, } from './middleware/forwarded-ip';
|
|
46
47
|
export { botProtectionMiddleware, BOT_PROTECTION_HEADER, } from './middleware/bot-protection';
|
|
47
48
|
export { retryMiddleware } from './middleware/retry';
|
|
48
49
|
export { timeoutMiddleware } from './middleware/timeout';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forwarded-IP middleware — lets a SERVER-SIDE (BFF) storefront forward the real
|
|
3
|
+
* buyer IP to the backend, so per-buyer rate limiting works even though the
|
|
4
|
+
* backend's connection comes from the storefront server, not the browser.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: when a storefront renders/fetches on the server (server
|
|
7
|
+
* components, route handlers, server actions), the backend sees ONE source IP —
|
|
8
|
+
* the storefront server's — for every buyer. Per-IP limits then collapse onto
|
|
9
|
+
* that single address. This middleware carries the buyer's real IP (read from the
|
|
10
|
+
* incoming request, e.g. the `cf-connecting-ip` header) in a signed header the
|
|
11
|
+
* backend can trust.
|
|
12
|
+
*
|
|
13
|
+
* Trust model: the header is signed with HMAC-SHA256 over `${ip}.${ts}.${shopSlug}`
|
|
14
|
+
* using a shared platform secret. The backend recomputes the signature and only
|
|
15
|
+
* trusts the IP when it matches and the timestamp is fresh. Binding the shop slug
|
|
16
|
+
* stops a signature from one shop being replayed against another; the timestamp
|
|
17
|
+
* bounds replay. A spoofed header without a valid signature is ignored by the
|
|
18
|
+
* backend (it falls back to the connection IP).
|
|
19
|
+
*
|
|
20
|
+
* SERVER-ONLY: the secret must never reach the browser. Use this middleware only
|
|
21
|
+
* in server-side clients (`getStorefrontClient`), never in the browser pipeline.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import { forwardedIpMiddleware } from '@doswiftly/storefront-sdk';
|
|
26
|
+
*
|
|
27
|
+
* // `buyerIp` comes from your server framework's incoming request — typically the
|
|
28
|
+
* // `cf-connecting-ip` request header. `serverSecret` is a server-only env value.
|
|
29
|
+
* getStorefrontClient({
|
|
30
|
+
* apiUrl,
|
|
31
|
+
* shopSlug,
|
|
32
|
+
* middleware: [
|
|
33
|
+
* forwardedIpMiddleware({
|
|
34
|
+
* getBuyerIp: () => buyerIp,
|
|
35
|
+
* getSecret: () => serverSecret,
|
|
36
|
+
* }),
|
|
37
|
+
* ],
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
import type { Middleware } from '../client/types';
|
|
42
|
+
/** Header carrying the buyer's real IP, forwarded by a server-side storefront. */
|
|
43
|
+
export declare const FORWARDED_IP_HEADER = "x-doswiftly-buyer-ip";
|
|
44
|
+
/** Header carrying the signature timestamp (unix milliseconds) — bounds replay. */
|
|
45
|
+
export declare const FORWARDED_IP_TS_HEADER = "x-doswiftly-fwd-ts";
|
|
46
|
+
/** Header carrying the hex HMAC-SHA256 signature over `${ip}.${ts}.${shopSlug}`. */
|
|
47
|
+
export declare const FORWARDED_IP_SIG_HEADER = "x-doswiftly-fwd-sig";
|
|
48
|
+
/**
|
|
49
|
+
* Canonical signed message. The backend MUST build this identically before
|
|
50
|
+
* verifying the signature. Order and separators are load-bearing.
|
|
51
|
+
*/
|
|
52
|
+
export declare function forwardedIpSignedMessage(ip: string, timestamp: string, shopSlug: string): string;
|
|
53
|
+
/** A getter that may be sync or async (e.g. `async () => (await headers()).get('cf-connecting-ip')`). */
|
|
54
|
+
type MaybeAsyncGetter = () => string | null | undefined | Promise<string | null | undefined>;
|
|
55
|
+
export interface ForwardedIpMiddlewareOptions {
|
|
56
|
+
/** Real buyer IP from the incoming request (e.g. the `cf-connecting-ip` header). */
|
|
57
|
+
getBuyerIp: MaybeAsyncGetter;
|
|
58
|
+
/** Shared platform secret used to sign. Server-side only — never expose to the browser. */
|
|
59
|
+
getSecret: MaybeAsyncGetter;
|
|
60
|
+
/**
|
|
61
|
+
* OPTIONAL override for the shop slug bound into the signature. Defaults to the
|
|
62
|
+
* `X-Shop-Slug` header the client already sends — so you normally don't pass it,
|
|
63
|
+
* and the signed slug is guaranteed to match what the backend verifies. Provide
|
|
64
|
+
* only for non-standard transports that don't set that header.
|
|
65
|
+
*/
|
|
66
|
+
getShopSlug?: MaybeAsyncGetter;
|
|
67
|
+
/** Clock source (unix milliseconds). Injectable for tests; defaults to `Date.now`. */
|
|
68
|
+
now?: () => number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Server-side forwarded-IP middleware. On every request it reads the buyer IP,
|
|
72
|
+
* shop slug and secret (lazy getters — a rotated secret is picked up without
|
|
73
|
+
* rebuilding the pipeline), signs `${ip}.${ts}.${shopSlug}` with HMAC-SHA256 and
|
|
74
|
+
* adds the three forwarded-IP headers. When any input is missing it adds nothing
|
|
75
|
+
* — the backend then keys the connection IP (the server-side default).
|
|
76
|
+
*
|
|
77
|
+
* The HMAC key is imported once per distinct secret value and reused across
|
|
78
|
+
* requests (re-imported only on rotation).
|
|
79
|
+
*/
|
|
80
|
+
export declare function forwardedIpMiddleware(options: ForwardedIpMiddlewareOptions): Middleware;
|
|
81
|
+
export {};
|
|
82
|
+
//# sourceMappingURL=forwarded-ip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forwarded-ip.d.ts","sourceRoot":"","sources":["../../../src/core/middleware/forwarded-ip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,kFAAkF;AAClF,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAC1D,mFAAmF;AACnF,eAAO,MAAM,sBAAsB,uBAAuB,CAAC;AAC3D,oFAAoF;AACpF,eAAO,MAAM,uBAAuB,wBAAwB,CAAC;AAQ7D;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEhG;AAED,yGAAyG;AACzG,KAAK,gBAAgB,GAAG,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAE7F,MAAM,WAAW,4BAA4B;IAC3C,oFAAoF;IACpF,UAAU,EAAE,gBAAgB,CAAC;IAC7B,2FAA2F;IAC3F,SAAS,EAAE,gBAAgB,CAAC;IAC5B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAeD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,UAAU,CAiCvF"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forwarded-IP middleware — lets a SERVER-SIDE (BFF) storefront forward the real
|
|
3
|
+
* buyer IP to the backend, so per-buyer rate limiting works even though the
|
|
4
|
+
* backend's connection comes from the storefront server, not the browser.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: when a storefront renders/fetches on the server (server
|
|
7
|
+
* components, route handlers, server actions), the backend sees ONE source IP —
|
|
8
|
+
* the storefront server's — for every buyer. Per-IP limits then collapse onto
|
|
9
|
+
* that single address. This middleware carries the buyer's real IP (read from the
|
|
10
|
+
* incoming request, e.g. the `cf-connecting-ip` header) in a signed header the
|
|
11
|
+
* backend can trust.
|
|
12
|
+
*
|
|
13
|
+
* Trust model: the header is signed with HMAC-SHA256 over `${ip}.${ts}.${shopSlug}`
|
|
14
|
+
* using a shared platform secret. The backend recomputes the signature and only
|
|
15
|
+
* trusts the IP when it matches and the timestamp is fresh. Binding the shop slug
|
|
16
|
+
* stops a signature from one shop being replayed against another; the timestamp
|
|
17
|
+
* bounds replay. A spoofed header without a valid signature is ignored by the
|
|
18
|
+
* backend (it falls back to the connection IP).
|
|
19
|
+
*
|
|
20
|
+
* SERVER-ONLY: the secret must never reach the browser. Use this middleware only
|
|
21
|
+
* in server-side clients (`getStorefrontClient`), never in the browser pipeline.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import { forwardedIpMiddleware } from '@doswiftly/storefront-sdk';
|
|
26
|
+
*
|
|
27
|
+
* // `buyerIp` comes from your server framework's incoming request — typically the
|
|
28
|
+
* // `cf-connecting-ip` request header. `serverSecret` is a server-only env value.
|
|
29
|
+
* getStorefrontClient({
|
|
30
|
+
* apiUrl,
|
|
31
|
+
* shopSlug,
|
|
32
|
+
* middleware: [
|
|
33
|
+
* forwardedIpMiddleware({
|
|
34
|
+
* getBuyerIp: () => buyerIp,
|
|
35
|
+
* getSecret: () => serverSecret,
|
|
36
|
+
* }),
|
|
37
|
+
* ],
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
/** Header carrying the buyer's real IP, forwarded by a server-side storefront. */
|
|
42
|
+
export const FORWARDED_IP_HEADER = 'x-doswiftly-buyer-ip';
|
|
43
|
+
/** Header carrying the signature timestamp (unix milliseconds) — bounds replay. */
|
|
44
|
+
export const FORWARDED_IP_TS_HEADER = 'x-doswiftly-fwd-ts';
|
|
45
|
+
/** Header carrying the hex HMAC-SHA256 signature over `${ip}.${ts}.${shopSlug}`. */
|
|
46
|
+
export const FORWARDED_IP_SIG_HEADER = 'x-doswiftly-fwd-sig';
|
|
47
|
+
/**
|
|
48
|
+
* Shop routing header the client already attaches to every request (see
|
|
49
|
+
* `create-client.ts`). The signature binds to THIS slug by default, so the signed
|
|
50
|
+
* value always matches what the backend verifies from the same header.
|
|
51
|
+
*/
|
|
52
|
+
const SHOP_SLUG_HEADER = 'X-Shop-Slug';
|
|
53
|
+
/**
|
|
54
|
+
* Canonical signed message. The backend MUST build this identically before
|
|
55
|
+
* verifying the signature. Order and separators are load-bearing.
|
|
56
|
+
*/
|
|
57
|
+
export function forwardedIpSignedMessage(ip, timestamp, shopSlug) {
|
|
58
|
+
return `${ip}.${timestamp}.${shopSlug}`;
|
|
59
|
+
}
|
|
60
|
+
async function importHmacKey(secret) {
|
|
61
|
+
return crypto.subtle.importKey('raw', new TextEncoder().encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, [
|
|
62
|
+
'sign',
|
|
63
|
+
]);
|
|
64
|
+
}
|
|
65
|
+
async function signHex(key, message) {
|
|
66
|
+
const signature = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(message));
|
|
67
|
+
return Array.from(new Uint8Array(signature))
|
|
68
|
+
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
69
|
+
.join('');
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Server-side forwarded-IP middleware. On every request it reads the buyer IP,
|
|
73
|
+
* shop slug and secret (lazy getters — a rotated secret is picked up without
|
|
74
|
+
* rebuilding the pipeline), signs `${ip}.${ts}.${shopSlug}` with HMAC-SHA256 and
|
|
75
|
+
* adds the three forwarded-IP headers. When any input is missing it adds nothing
|
|
76
|
+
* — the backend then keys the connection IP (the server-side default).
|
|
77
|
+
*
|
|
78
|
+
* The HMAC key is imported once per distinct secret value and reused across
|
|
79
|
+
* requests (re-imported only on rotation).
|
|
80
|
+
*/
|
|
81
|
+
export function forwardedIpMiddleware(options) {
|
|
82
|
+
const { getBuyerIp, getShopSlug, getSecret, now = () => Date.now() } = options;
|
|
83
|
+
let cachedSecret = null;
|
|
84
|
+
let cachedKey = null;
|
|
85
|
+
return async (request, next) => {
|
|
86
|
+
// Getters may be async (e.g. reading the IP from `headers()` per request);
|
|
87
|
+
// `await` resolves sync values unchanged, so sync getters keep working.
|
|
88
|
+
const ip = await getBuyerIp();
|
|
89
|
+
// Slug defaults to the routing header the client already sends — so it always
|
|
90
|
+
// matches what the backend verifies. The optional getter only overrides it.
|
|
91
|
+
const shopSlug = (await getShopSlug?.()) ?? request.headers[SHOP_SLUG_HEADER];
|
|
92
|
+
const secret = await getSecret();
|
|
93
|
+
// Forward only when everything needed for a trusted, shop-bound signature is
|
|
94
|
+
// present. Missing any piece -> send nothing (backend falls back to conn IP).
|
|
95
|
+
if (ip && shopSlug && secret) {
|
|
96
|
+
const timestamp = String(now());
|
|
97
|
+
if (cachedSecret !== secret || cachedKey === null) {
|
|
98
|
+
cachedSecret = secret;
|
|
99
|
+
cachedKey = importHmacKey(secret);
|
|
100
|
+
}
|
|
101
|
+
const key = await cachedKey;
|
|
102
|
+
const signature = await signHex(key, forwardedIpSignedMessage(ip, timestamp, shopSlug));
|
|
103
|
+
request.headers[FORWARDED_IP_HEADER] = ip;
|
|
104
|
+
request.headers[FORWARDED_IP_TS_HEADER] = timestamp;
|
|
105
|
+
request.headers[FORWARDED_IP_SIG_HEADER] = signature;
|
|
106
|
+
}
|
|
107
|
+
return next(request);
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -40,11 +40,29 @@ export interface ServerClientOptions extends Omit<StorefrontClientConfig, 'middl
|
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
42
|
middleware?: Middleware[];
|
|
43
|
+
/**
|
|
44
|
+
* OPTIONAL override for the buyer-IP source. Forwarded-IP signing is
|
|
45
|
+
* auto-configured: by default the SDK reads the request's `cf-connecting-ip`
|
|
46
|
+
* via `next/headers` (a server-rendered storefront would otherwise collapse
|
|
47
|
+
* every buyer onto its own server IP for rate limiting). Provide this only for
|
|
48
|
+
* non-Next server runtimes where `next/headers` is unavailable. May be async.
|
|
49
|
+
* Server-side only.
|
|
50
|
+
*/
|
|
51
|
+
getBuyerIp?: () => string | null | undefined | Promise<string | null | undefined>;
|
|
52
|
+
/**
|
|
53
|
+
* OPTIONAL override for the forwarded-IP signing secret. By default it is read
|
|
54
|
+
* from `process.env.DOSWIFTLY_FORWARDED_IP_SECRET`, set in your DoSwiftly
|
|
55
|
+
* deployment environment. Provide this getter only to override the env source —
|
|
56
|
+
* e.g. a runtime that does not expose the secret on `process.env`. Lazy getter —
|
|
57
|
+
* a rotated secret is picked up without rebuilding the client. NEVER expose this
|
|
58
|
+
* to the browser. Sync or async.
|
|
59
|
+
*/
|
|
60
|
+
getForwardedIpSecret?: () => string | null | undefined | Promise<string | null | undefined>;
|
|
43
61
|
}
|
|
44
62
|
/**
|
|
45
63
|
* Create a StorefrontClient for server-side use.
|
|
46
64
|
*
|
|
47
|
-
* Includes default middleware: retry → timeout → errors.
|
|
65
|
+
* Includes default middleware: forwarded-IP → retry → timeout → errors.
|
|
48
66
|
* Does NOT include auth/currency middleware (server has no Zustand stores).
|
|
49
67
|
* Pass headers via config.defaultHeaders or getHeaders option.
|
|
50
68
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-storefront-client.d.ts","sourceRoot":"","sources":["../../../src/react/server/get-storefront-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;
|
|
1
|
+
{"version":3,"file":"get-storefront-client.d.ts","sourceRoot":"","sources":["../../../src/react/server/get-storefront-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAOH,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAEpG,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,sBAAsB,EAAE,YAAY,CAAC;IACrF;;;;;;;;;;;;;;;;;OAiBG;IACH,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC;IAE1B;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IAElF;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CAC7F;AAqBD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,GAAG,gBAAgB,CA4BlF"}
|
|
@@ -23,18 +23,52 @@ import { createStorefrontClient } from '../../core/client/create-client';
|
|
|
23
23
|
import { retryMiddleware } from '../../core/middleware/retry';
|
|
24
24
|
import { timeoutMiddleware } from '../../core/middleware/timeout';
|
|
25
25
|
import { errorMiddleware } from '../../core/middleware/errors';
|
|
26
|
+
import { forwardedIpMiddleware } from '../../core/middleware/forwarded-ip';
|
|
27
|
+
/**
|
|
28
|
+
* Default buyer-IP source: the request's `cf-connecting-ip` header, read via
|
|
29
|
+
* `next/headers` (the same dynamic-import pattern used to read request cookies — a
|
|
30
|
+
* graceful no-op outside a Next request scope or in runtimes without
|
|
31
|
+
* `next/headers`). Override via `getBuyerIp` for other server frameworks.
|
|
32
|
+
*/
|
|
33
|
+
async function readCfConnectingIp() {
|
|
34
|
+
try {
|
|
35
|
+
const { headers } = await import('next/headers');
|
|
36
|
+
const store = await headers();
|
|
37
|
+
// Prefer a forwarded client-IP header when present: if the request was
|
|
38
|
+
// proxied, the direct `cf-connecting-ip` is the proxy's address while this
|
|
39
|
+
// header carries the original client IP. Fall back to the direct connection IP.
|
|
40
|
+
return store.get('x-doswiftly-client-ip') ?? store.get('cf-connecting-ip');
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
26
46
|
/**
|
|
27
47
|
* Create a StorefrontClient for server-side use.
|
|
28
48
|
*
|
|
29
|
-
* Includes default middleware: retry → timeout → errors.
|
|
49
|
+
* Includes default middleware: forwarded-IP → retry → timeout → errors.
|
|
30
50
|
* Does NOT include auth/currency middleware (server has no Zustand stores).
|
|
31
51
|
* Pass headers via config.defaultHeaders or getHeaders option.
|
|
32
52
|
*/
|
|
33
53
|
export function getStorefrontClient(options) {
|
|
34
|
-
const { middleware: customMiddleware = [], ...config } = options;
|
|
54
|
+
const { middleware: customMiddleware = [], getBuyerIp, getForwardedIpSecret, ...config } = options;
|
|
55
|
+
// Forward the real buyer IP for per-buyer rate limiting — fully self-configured,
|
|
56
|
+
// nothing for the storefront to wire. Buyer IP defaults to the request's
|
|
57
|
+
// `cf-connecting-ip` (via next/headers); the signing secret defaults to
|
|
58
|
+
// `process.env.DOSWIFTLY_FORWARDED_IP_SECRET` (set in the DoSwiftly deployment
|
|
59
|
+
// environment). The slug comes from the `X-Shop-Slug` header the client already
|
|
60
|
+
// sends, so the
|
|
61
|
+
// signed value matches what the backend verifies. The middleware signs ONLY when
|
|
62
|
+
// BOTH a buyer IP and a secret resolve at request time — so with no secret
|
|
63
|
+
// configured (or outside a Next request) it is an inert pass-through. Both
|
|
64
|
+
// getters can be overridden for non-Next runtimes.
|
|
65
|
+
const resolveBuyerIp = getBuyerIp ?? readCfConnectingIp;
|
|
66
|
+
const resolveSecret = getForwardedIpSecret ??
|
|
67
|
+
(() => (typeof process !== 'undefined' ? process.env?.DOSWIFTLY_FORWARDED_IP_SECRET : undefined));
|
|
35
68
|
return createStorefrontClient({
|
|
36
69
|
...config,
|
|
37
70
|
middleware: [
|
|
71
|
+
forwardedIpMiddleware({ getBuyerIp: resolveBuyerIp, getSecret: resolveSecret }),
|
|
38
72
|
...customMiddleware,
|
|
39
73
|
retryMiddleware({ maxRetries: 2 }),
|
|
40
74
|
timeoutMiddleware({ timeout: 10000 }), // Server-side: 10s timeout
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doswiftly/storefront-sdk",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.1.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,
|