@decocms/apps-vtex 7.31.3 → 7.31.5
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/package.json +5 -5
- package/src/utils/proxy.ts +60 -0
- package/src/utils/vtexId.ts +15 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/apps-vtex",
|
|
3
|
-
"version": "7.31.
|
|
3
|
+
"version": "7.31.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deco commerce app: VTEX integration",
|
|
6
6
|
"repository": {
|
|
@@ -51,10 +51,10 @@
|
|
|
51
51
|
"lint:unused": "knip"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@decocms/blocks": "7.31.
|
|
55
|
-
"@decocms/apps-commerce": "7.31.
|
|
56
|
-
"@decocms/apps-website": "7.31.
|
|
57
|
-
"@decocms/tanstack": "7.31.
|
|
54
|
+
"@decocms/blocks": "7.31.5",
|
|
55
|
+
"@decocms/apps-commerce": "7.31.5",
|
|
56
|
+
"@decocms/apps-website": "7.31.5",
|
|
57
|
+
"@decocms/tanstack": "7.31.5"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"@tanstack/react-query": ">=5.0.0",
|
package/src/utils/proxy.ts
CHANGED
|
@@ -45,6 +45,14 @@ export interface VtexProxyOptions {
|
|
|
45
45
|
* Custom headers to inject into every proxied request.
|
|
46
46
|
*/
|
|
47
47
|
extraHeaders?: Record<string, string>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Force `Cache-Control: private, no-store` + `CDN-Cache-Control: no-store`
|
|
51
|
+
* on authenticated proxy responses (everything except static assets), so a
|
|
52
|
+
* personalized VTEX response can never be cached and served across users.
|
|
53
|
+
* @default true
|
|
54
|
+
*/
|
|
55
|
+
hardenAuthenticatedCache?: boolean;
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
const DEFAULT_PROXY_PATHS = [
|
|
@@ -79,6 +87,42 @@ const HOP_BY_HOP_HEADERS = new Set([
|
|
|
79
87
|
"upgrade",
|
|
80
88
|
]);
|
|
81
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Path prefixes whose proxied responses are safe to cache publicly — genuinely
|
|
92
|
+
* public static assets (media files, VTEX store-theme bundles). Everything else
|
|
93
|
+
* routed through the VTEX proxy is authenticated/personalized by nature
|
|
94
|
+
* (account, checkout, /api/sessions, orders, graphql, VTEX ID) and MUST NOT be
|
|
95
|
+
* cached by shared/edge layers.
|
|
96
|
+
*/
|
|
97
|
+
const PUBLIC_PROXY_ASSET_PREFIXES = [
|
|
98
|
+
"/files/",
|
|
99
|
+
"/arquivos/",
|
|
100
|
+
"/assets/vtex",
|
|
101
|
+
"/XMLData/",
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
function isPublicProxyAsset(pathname: string): boolean {
|
|
105
|
+
return PUBLIC_PROXY_ASSET_PREFIXES.some((p) => pathname.startsWith(p));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Force authenticated proxy responses to be non-cacheable on every shared layer.
|
|
110
|
+
*
|
|
111
|
+
* VTEX marks some endpoints `no-store` (e.g. `/api/sessions`) but not all
|
|
112
|
+
* (`/account` comes back `public`), and the raw origin `Cache-Control` must
|
|
113
|
+
* never let a personalized response be cached across users. Without this, a
|
|
114
|
+
* logged-in user's response can be stored at the edge and served to another
|
|
115
|
+
* user — the root cause of a cross-customer data leak (see decocms/blocks#412).
|
|
116
|
+
* Static assets are exempt so store-theme/media caching is preserved.
|
|
117
|
+
*/
|
|
118
|
+
function hardenProxyCacheHeaders(headers: Headers, pathname: string): void {
|
|
119
|
+
if (isPublicProxyAsset(pathname)) return;
|
|
120
|
+
headers.set("Cache-Control", "private, no-store, no-cache, must-revalidate");
|
|
121
|
+
// CDN-Cache-Control takes precedence for Cloudflare's CDN layer.
|
|
122
|
+
headers.set("CDN-Cache-Control", "no-store");
|
|
123
|
+
headers.delete("Surrogate-Control");
|
|
124
|
+
}
|
|
125
|
+
|
|
82
126
|
/**
|
|
83
127
|
* Returns all path prefixes that should be proxied to VTEX.
|
|
84
128
|
*/
|
|
@@ -199,6 +243,10 @@ export async function proxyToVtex(request: Request, options?: VtexProxyOptions):
|
|
|
199
243
|
}
|
|
200
244
|
}
|
|
201
245
|
|
|
246
|
+
if (options?.hardenAuthenticatedCache !== false) {
|
|
247
|
+
hardenProxyCacheHeaders(responseHeaders, requestUrl.pathname);
|
|
248
|
+
}
|
|
249
|
+
|
|
202
250
|
return new Response(originResponse.body, {
|
|
203
251
|
status: originResponse.status,
|
|
204
252
|
statusText: originResponse.statusText,
|
|
@@ -254,6 +302,14 @@ export interface VtexCheckoutProxyConfig {
|
|
|
254
302
|
* Receives the full HTML string and should return the modified version.
|
|
255
303
|
*/
|
|
256
304
|
htmlTransform?: (html: string) => string;
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Force `Cache-Control: private, no-store` + `CDN-Cache-Control: no-store`
|
|
308
|
+
* on authenticated proxy responses (everything except static assets), so a
|
|
309
|
+
* personalized VTEX response can never be cached and served across users.
|
|
310
|
+
* @default true
|
|
311
|
+
*/
|
|
312
|
+
hardenAuthenticatedCache?: boolean;
|
|
257
313
|
}
|
|
258
314
|
|
|
259
315
|
const CF_INTERNAL_HEADERS = new Set([
|
|
@@ -414,6 +470,10 @@ export function createVtexCheckoutProxy(
|
|
|
414
470
|
}
|
|
415
471
|
}
|
|
416
472
|
|
|
473
|
+
if (config.hardenAuthenticatedCache !== false) {
|
|
474
|
+
hardenProxyCacheHeaders(resHeaders, url.pathname);
|
|
475
|
+
}
|
|
476
|
+
|
|
417
477
|
// HTML transform for checkout pages
|
|
418
478
|
const ct = originRes.headers.get("content-type") ?? "";
|
|
419
479
|
if (config.htmlTransform && ct.includes("text/html")) {
|
package/src/utils/vtexId.ts
CHANGED
|
@@ -20,9 +20,17 @@ const VTEX_AUTH_COOKIE = "VtexIdclientAutCookie";
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Extract the VtexIdclientAutCookie value from a cookie string.
|
|
23
|
+
*
|
|
24
|
+
* Matches BOTH the base cookie `VtexIdclientAutCookie=` and the account-suffixed
|
|
25
|
+
* variant `VtexIdclientAutCookie_{account}=`. VTEX frequently sets ONLY the
|
|
26
|
+
* suffixed variant on the storefront domain — matching just the base name makes
|
|
27
|
+
* genuinely logged-in users look anonymous, which defeats any logged-in cache
|
|
28
|
+
* bypass built on top of this and can leak personalized/cached responses.
|
|
23
29
|
*/
|
|
24
30
|
export function extractVtexAuthCookie(cookieHeader: string): string | null {
|
|
25
|
-
const match = cookieHeader.match(
|
|
31
|
+
const match = cookieHeader.match(
|
|
32
|
+
new RegExp(`(?:^|;\\s*)${VTEX_AUTH_COOKIE}(?:_[^=;\\s]+)?=([^;]+)`),
|
|
33
|
+
);
|
|
26
34
|
return match?.[1] ?? null;
|
|
27
35
|
}
|
|
28
36
|
|
|
@@ -48,7 +56,12 @@ function decodeJwtPayload(token: string): Record<string, unknown> | null {
|
|
|
48
56
|
export function parseVtexAuthToken(token: string): VtexAuthInfo {
|
|
49
57
|
const payload = decodeJwtPayload(token);
|
|
50
58
|
if (!payload) {
|
|
51
|
-
|
|
59
|
+
// Opaque (non-JWT) token. VTEX auth cookies are frequently opaque strings
|
|
60
|
+
// rather than JWTs — we cannot read `exp`, but the mere presence of the
|
|
61
|
+
// cookie means the user IS authenticated. Treating it as logged-out (the
|
|
62
|
+
// previous behavior) makes real sessions look anonymous and defeats any
|
|
63
|
+
// logged-in cache bypass. Fail towards "logged in" (i.e. do-not-cache).
|
|
64
|
+
return { isLoggedIn: true, isExpired: false };
|
|
52
65
|
}
|
|
53
66
|
|
|
54
67
|
const exp = typeof payload.exp === "number" ? payload.exp : undefined;
|