Skip to content
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0e199ed
fix: Updates the path of authentication cookies to “/api/vtexid/refre…
marcoferreiradev Sep 4, 2025
0f5f80f
WIP: fix segment in headless login/logout
marcoferreiradev Sep 10, 2025
642c16c
WIP
marcoferreiradev Sep 10, 2025
b55ac96
refactor: remove unused cookie handling and logging in authentication…
marcoferreiradev Sep 11, 2025
da2605b
feat: implement refresh token handling in authentication actions; upd…
marcoferreiradev Sep 12, 2025
c5e23fd
fix: updating the sessions' setCookie so that the segment is not dupl…
marcoferreiradev Sep 18, 2025
ff91240
Merge branch 'main' into fix/pathFromRefreshToken
marcoferreiradev Sep 25, 2025
fc1c5ba
Merge branch 'main' into fix/pathFromRefreshToken
marcoferreiradev Sep 25, 2025
b127448
Merge branch 'main' into fix/pathFromRefreshToken
marcoferreiradev Sep 25, 2025
59e1533
Merge branch 'main' into fix/pathFromRefreshToken
marcoferreiradev Sep 25, 2025
35046f4
feat(vtex): add support for preserving UTM characters in segmentBag
marcoferreiradev Sep 30, 2025
1d87ac0
Merge branch 'main' into fix/pathFromRefreshToken
marcoferreiradev Oct 1, 2025
7b16396
feat(cache): add no cache extension
marcoferreiradev Oct 7, 2025
a04c090
Merge branch 'main' into fix/pathFromRefreshToken
marcoferreiradev Oct 7, 2025
fe3fcac
feat(cache): add no cache extension
marcoferreiradev Oct 7, 2025
84fc153
feat(cache): add no cache extension
marcoferreiradev Oct 7, 2025
9f56d67
Merge branch 'main' into fix/pathFromRefreshToken
marcoferreiradev Oct 7, 2025
2495519
refactor(segment): remove preserveUtmChars parameter and simplify ser…
marcoferreiradev Oct 7, 2025
65a9a41
Merge branch 'main' into fix/pathFromRefreshToken
marcoferreiradev Oct 8, 2025
db21a39
Merge branch 'main' into fix/pathFromRefreshToken
marcoferreiradev Oct 10, 2025
141dbd6
feat(analytics): add extra params prop to gtm
marcoferreiradev Oct 10, 2025
134fefa
feat(verified-reviews): add fullReview loader and update manifest imp…
marcoferreiradev Oct 13, 2025
8e36460
fix: update publicProperties and items in sessions actions and loaders
marcoferreiradev Oct 14, 2025
00a9ff5
feat(session): add new properties 'priceTables' and 'profilePriceTabl…
marcoferreiradev Oct 14, 2025
ad85bab
refactor(session): update session actions and loaders to return new S…
marcoferreiradev Oct 14, 2025
3a5a9a2
feat(cart): enhance updateUser action to handle errors and improve lo…
marcoferreiradev Oct 23, 2025
9807fc1
refactor(authentication): conditionally invoke refreshToken and valid…
marcoferreiradev Oct 23, 2025
e34db75
Merge branch 'main' into fix/pathFromRefreshToken
marcoferreiradev Oct 23, 2025
11c29d9
Merge branch 'main' into fix/pathFromRefreshToken
marcoferreiradev Nov 11, 2025
6b1da03
Merge branch 'main' into fix/pathFromRefreshToken
marcoferreiradev Nov 12, 2025
e8063df
fix canonical
marcoferreiradev Nov 12, 2025
ef77710
Merge branch 'main' into fix/pathFromRefreshToken
marcoferreiradev Nov 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions commerce/loaders/product/extensions/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ export default function ProductsExt(
): Promise<Product[] | null> {
return extend(props);
}

export const cache = "no-cache";
2 changes: 2 additions & 0 deletions commerce/loaders/product/extensions/suggestions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ export default function ProductDetailsExt(
): Promise<Suggestion | null> {
return extend(props);
}

export const cache = "no-cache";
2 changes: 2 additions & 0 deletions commerce/loaders/product/productListingPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export default function ProductDetailsExt(
): Promise<ProductListingPage | null> {
return extend(props);
}

export const cache = "no-cache";
2 changes: 2 additions & 0 deletions commerce/loaders/product/products.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export default function ProductsExt(
): Promise<Product[] | null> {
return extend(props);
}

export const cache = "no-cache";
50 changes: 50 additions & 0 deletions verified-reviews/loaders/fullReview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { logger } from "@deco/deco/o11y";
import { AppContext } from "../mod.ts";
import { createClient, PaginationOptions } from "../utils/client.ts";
import { VerifiedReviewsFullReview } from "../utils/types.ts";

export type Props = PaginationOptions & {
productId: string | string[];
};

export default async function loader(
props: Props,
_req: Request,
ctx: AppContext,
): Promise<VerifiedReviewsFullReview> {
try {
const client = createClient({ ...ctx });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify the context spreading pattern.

Spreading the entire AppContext into createClient may pass unnecessary properties. According to the relevant code snippet, createClient expects ConfigVerifiedReviews | undefined. Please verify that the AppContext structure is compatible with this expectation, or extract only the necessary properties.


🏁 Script executed:

#!/bin/bash
# Description: Verify the createClient function signature and AppContext structure

# Check createClient function signature
ast-grep --pattern $'export $_ createClient($PARAM) {
  $$$
}'

# Check AppContext type definition
rg -nP "export\s+(type|interface)\s+AppContext" verified-reviews/

Length of output: 79


🏁 Script executed:

#!/bin/bash
# Locate createClient definition and usage
rg -nP "createClient" -R verified-reviews/

# Show import context in fullReview.ts
sed -n '1,10p' verified-reviews/loaders/fullReview.ts

# Extract createClient signature from mod.ts
rg -nP "export .*createClient" -C3 verified-reviews/mod.ts

Length of output: 736


🏁 Script executed:

#!/bin/bash
# Extract createClient definition and its parameter type
rg -nP "export\s+(async\s+)?(function|const)\s+createClient" -C5 verified-reviews/utils/client.ts

Length of output: 542


Avoid passing entire AppContext into createClient.
createClient accepts only a ConfigVerifiedReviews object (it only uses idWebsite), so replace

createClient({ ...ctx });

with something like

createClient({ idWebsite: ctx.idWebsite });
🤖 Prompt for AI Agents
In verified-reviews/loaders/fullReview.ts around line 16, createClient is being
called with the entire AppContext ({ ...ctx }) even though it only expects a
ConfigVerifiedReviews object (it only uses idWebsite); change the call to pass
only the required shape by extracting idWebsite from ctx and calling
createClient with that single property (e.g., createClient({ idWebsite:
ctx.idWebsite })), ensuring the rest of ctx is not spread into the client
creation.

if (!client) {
return {
aggregateRating: undefined,
review: [],
};
}

const data = await client.fullReview(props);
return data;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error("Error getting full review", message);
return {
aggregateRating: undefined,
review: [],
};
}
}

export const cache = "stale-while-revalidate";

export const cacheKey = (props: Props) => {
const normalized = {
productId: Array.isArray(props.productId)
? [...props.productId].sort().join(",")
: props.productId,
count: props.count ?? 10,
offset: props.offset ?? 0,
order: props.order ?? "date_desc",
customizeOrder: props.customizeOrder ?? false,
};

return `${normalized.productId}-${normalized.count}-${normalized.offset}-${normalized.order}-${normalized.customizeOrder}`;
};
Comment on lines +38 to +50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent default values between cacheKey and client implementation.

The cacheKey function uses count: 10 as the default (line 43), but according to the relevant code snippet from client.ts, client.fullReview uses count = 5 as its default. This mismatch could cause cache keys to differ from actual request parameters, leading to unnecessary cache misses.

Apply this diff to align the default with the client implementation:

-    count: props.count ?? 10,
+    count: props.count ?? 5,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const cacheKey = (props: Props) => {
const normalized = {
productId: Array.isArray(props.productId)
? [...props.productId].sort().join(",")
: props.productId,
count: props.count ?? 10,
offset: props.offset ?? 0,
order: props.order ?? "date_desc",
customizeOrder: props.customizeOrder ?? false,
};
return `${normalized.productId}-${normalized.count}-${normalized.offset}-${normalized.order}-${normalized.customizeOrder}`;
};
export const cacheKey = (props: Props) => {
const normalized = {
productId: Array.isArray(props.productId)
? [...props.productId].sort().join(",")
: props.productId,
count: props.count ?? 5,
offset: props.offset ?? 0,
order: props.order ?? "date_desc",
customizeOrder: props.customizeOrder ?? false,
};
return `${normalized.productId}-${normalized.count}-${normalized.offset}-${normalized.order}-${normalized.customizeOrder}`;
};
🤖 Prompt for AI Agents
In verified-reviews/loaders/fullReview.ts around lines 38 to 50, the cacheKey
function sets a default count of 10 which is inconsistent with
client.fullReview's default count of 5; update the normalized.count default from
10 to 5 so the cache key reflects the actual request default and prevents
unnecessary cache misses.

4 changes: 3 additions & 1 deletion verified-reviews/loaders/productDetailsPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default function productDetailsPage(
];
}

const fullReview = await client.fullReview({
const fullReview = await ctx.invoke["verified-reviews"].loaders.fullReview({
productId: productsToGetReviews,
count: config?.count,
offset: config?.offset,
Expand All @@ -52,3 +52,5 @@ export default function productDetailsPage(
};
};
}

export const cache = "no-cache";
2 changes: 2 additions & 0 deletions verified-reviews/loaders/productList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ export default function productList(
});
};
}

export const cache = "no-cache";
2 changes: 2 additions & 0 deletions verified-reviews/loaders/productListingPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,5 @@ export default function productListingPage(
};
};
}

export const cache = "no-cache";
22 changes: 12 additions & 10 deletions verified-reviews/manifest.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@
// This file SHOULD be checked into source version control.
// This file is automatically updated during development when running `dev.ts`.

import * as $$$0 from "./loaders/productDetailsPage.ts";
import * as $$$1 from "./loaders/productList.ts";
import * as $$$2 from "./loaders/productListingPage.ts";
import * as $$$3 from "./loaders/productReviews.ts";
import * as $$$4 from "./loaders/storeReview.ts";
import * as $$$0 from "./loaders/fullReview.ts";
import * as $$$1 from "./loaders/productDetailsPage.ts";
import * as $$$2 from "./loaders/productList.ts";
import * as $$$3 from "./loaders/productListingPage.ts";
import * as $$$4 from "./loaders/productReviews.ts";
import * as $$$5 from "./loaders/storeReview.ts";

const manifest = {
"loaders": {
"verified-reviews/loaders/productDetailsPage.ts": $$$0,
"verified-reviews/loaders/productList.ts": $$$1,
"verified-reviews/loaders/productListingPage.ts": $$$2,
"verified-reviews/loaders/productReviews.ts": $$$3,
"verified-reviews/loaders/storeReview.ts": $$$4,
"verified-reviews/loaders/fullReview.ts": $$$0,
"verified-reviews/loaders/productDetailsPage.ts": $$$1,
"verified-reviews/loaders/productList.ts": $$$2,
"verified-reviews/loaders/productListingPage.ts": $$$3,
"verified-reviews/loaders/productReviews.ts": $$$4,
"verified-reviews/loaders/storeReview.ts": $$$5,
},
"name": "verified-reviews",
"baseUrl": import.meta.url,
Expand Down
33 changes: 29 additions & 4 deletions vtex/actions/authentication/accessKeySignIn.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { getCookies, getSetCookies } from "std/http/cookie.ts";
import { getCookies, getSetCookies, setCookie } from "std/http/cookie.ts";
import { AppContext } from "../../mod.ts";
import { AuthResponse } from "../../utils/types.ts";
import setLoginCookies from "../../utils/login/setLoginCookies.ts";
import {
buildCookieJar,
proxySetCookie,
REFRESH_TOKEN_COOKIE,
} from "../../utils/cookies.ts";

export interface Props {
email: string;
Expand All @@ -24,6 +28,9 @@ export default async function action(
}

const cookies = getCookies(req.headers);
const startSetCookies = getSetCookies(ctx.response.headers);
const { header: cookie } = buildCookieJar(req.headers, startSetCookies);

const VtexSessionToken = cookies?.["VtexSessionToken"] ?? null;

if (!VtexSessionToken) {
Expand All @@ -43,6 +50,7 @@ export default async function action(
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
cookie,
},
},
);
Expand All @@ -54,8 +62,25 @@ export default async function action(
}

const data: AuthResponse = await response.json();
const setCookies = getSetCookies(response.headers);
await setLoginCookies(data, ctx, setCookies);
proxySetCookie(response.headers, ctx.response.headers, req.url);
const dataRefreshToken = await ctx.invoke.vtex.actions.authentication
.refreshToken();
await ctx.invoke.vtex.actions.session.validateSession({
publicProperties: {
refreshAfter: { value: dataRefreshToken.refreshAfter },
},
});

// TODO: REMOVE THIS AFTER TESTING AND VALIDATE IF NEEDED REWRITE REFRESH_TOKEN_COOKIE
const setCookies = getSetCookies(ctx.response.headers);
for (const cookie of setCookies) {
if (cookie.name === REFRESH_TOKEN_COOKIE) {
setCookie(ctx.response.headers, {
...cookie,
path: "/", // default path is /api/vtexid/refreshtoken/webstore, but browser dont send to backend headers
});
}
}

return data;
}
57 changes: 42 additions & 15 deletions vtex/actions/authentication/classicSignIn.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { AppContext } from "../../mod.ts";
import { AuthResponse } from "../../utils/types.ts";
import setLoginCookies from "../../utils/login/setLoginCookies.ts";
import { getSetCookies } from "std/http/cookie.ts";
// import setLoginCookies from "../../utils/login/setLoginCookies.ts";
import { getSetCookies, setCookie } from "std/http/cookie.ts";
import {
buildCookieJar,
proxySetCookie,
REFRESH_TOKEN_COOKIE,
} from "../../utils/cookies.ts";
Comment on lines 1 to +9
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Unify strategy for vid_rt path across the codebase

PR intent highlights path mismatch issues. Decide one policy (keep VTEX default path vs. rewrite to “/”) and apply consistently (proxy, resetPassword, accessKeySignIn, login utils). I can help extract a utility and update call sites.

🤖 Prompt for AI Agents
In vtex/actions/authentication/classicSignIn.ts lines 1-9: the PR notes
inconsistent handling of the vid_rt cookie path across the codebase; pick one
policy (either preserve VTEX default path or always rewrite to "/") and apply it
consistently by extracting a small cookie-path utility (e.g.,
normalizeVidRtPath(cookie: Cookie | SetCookieOptions) -> SetCookieOptions) and
using it wherever vid_rt is set or proxied. Update this file and the other call
sites (proxy logic, resetPassword, accessKeySignIn, and the login cookie
utilities) to call the new utility so vid_rt path behavior is unified across
proxy, resetPassword, accessKeySignIn, and login utils.


export interface Props {
email: string;
Expand All @@ -14,7 +19,7 @@ export interface Props {
*/
export default async function action(
props: Props,
_req: Request,
req: Request,
ctx: AppContext,
): Promise<AuthResponse> {
const { vcsDeprecated } = ctx;
Expand All @@ -26,6 +31,8 @@ export default async function action(
const startAuthentication = await ctx.invoke.vtex.actions.authentication
.startAuthentication({});

const startSetCookies = getSetCookies(ctx.response.headers);
const { header: cookieHeader } = buildCookieJar(req.headers, startSetCookies);
const authenticationToken = startAuthentication?.authenticationToken;

if (!authenticationToken) {
Expand All @@ -39,17 +46,19 @@ export default async function action(
urlencoded.append("password", props.password);
urlencoded.append("authenticationToken", authenticationToken);

const response = await vcsDeprecated
["POST /api/vtexid/pub/authentication/classic/validate"](
{},
{
body: urlencoded,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
},
const response = await vcsDeprecated[
"POST /api/vtexid/pub/authentication/classic/validate"
](
{},
{
body: urlencoded,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
cookie: cookieHeader,
},
);
},
);

if (!response.ok) {
throw new Error(
Expand All @@ -58,8 +67,26 @@ export default async function action(
}

const data: AuthResponse = await response.json();
const setCookies = getSetCookies(response.headers);
await setLoginCookies(data, ctx, setCookies);
proxySetCookie(response.headers, ctx.response.headers, req.url);
// First login was made, but we need to call the refresh token at least once to get the refreshAfter for the first time
const dataRefreshToken = await ctx.invoke.vtex.actions.authentication
.refreshToken();
await ctx.invoke.vtex.actions.session.validateSession({
publicProperties: {
refreshAfter: { value: dataRefreshToken.refreshAfter },
},
});

// TODO: REMOVE THIS AFTER TESTING AND VALIDATE IF NEEDED REWRITE REFRESH_TOKEN_COOKIE
const setCookies = getSetCookies(ctx.response.headers);
for (const cookie of setCookies) {
if (cookie.name === REFRESH_TOKEN_COOKIE) {
setCookie(ctx.response.headers, {
...cookie,
path: "/", // default path is /api/vtexid/refreshtoken/webstore, but browser dont send to backend headers
});
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stop reissuing vid_rt at "/" without expiring the default path

This can leave two vid_rt cookies ("/" and "/api/.../webstore"), causing nondeterministic behavior on logout/session.

Apply:

-  // TODO: REMOVE THIS AFTER TESTING AND VALIDATE IF NEEDED REWRITE REFRESH_TOKEN_COOKIE
-  const setCookies = getSetCookies(ctx.response.headers);
-  for (const cookie of setCookies) {
-    if (cookie.name === REFRESH_TOKEN_COOKIE) {
-      setCookie(ctx.response.headers, {
-        ...cookie,
-        path: "/", // default path is /api/vtexid/refreshtoken/webstore, but browser dont send to backend headers
-      });
-    }
-  }
+  // If rewriting is required, expire the legacy path first to avoid duplicates
+  const setCookies = getSetCookies(ctx.response.headers);
+  const rt = setCookies.find((c) => c.name === REFRESH_TOKEN_COOKIE);
+  if (rt) {
+    if (rt.path && rt.path !== "/") {
+      setCookie(ctx.response.headers, {
+        name: rt.name,
+        value: "",
+        domain: rt.domain,
+        path: rt.path,
+        secure: rt.secure,
+        httpOnly: rt.httpOnly,
+        sameSite: rt.sameSite,
+        maxAge: 0,
+        expires: new Date(0),
+      });
+    }
+    setCookie(ctx.response.headers, { ...rt, path: "/" });
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// TODO: REMOVE THIS AFTER TESTING AND VALIDATE IF NEEDED REWRITE REFRESH_TOKEN_COOKIE
const setCookies = getSetCookies(ctx.response.headers);
for (const cookie of setCookies) {
if (cookie.name === REFRESH_TOKEN_COOKIE) {
setCookie(ctx.response.headers, {
...cookie,
path: "/", // default path is /api/vtexid/refreshtoken/webstore, but browser dont send to backend headers
});
}
}
// If rewriting is required, expire the legacy path first to avoid duplicates
const setCookies = getSetCookies(ctx.response.headers);
const rt = setCookies.find((c) => c.name === REFRESH_TOKEN_COOKIE);
if (rt) {
if (rt.path && rt.path !== "/") {
setCookie(ctx.response.headers, {
name: rt.name,
value: "",
domain: rt.domain,
path: rt.path,
secure: rt.secure,
httpOnly: rt.httpOnly,
sameSite: rt.sameSite,
maxAge: 0,
expires: new Date(0),
});
}
setCookie(ctx.response.headers, { ...rt, path: "/" });
}
🤖 Prompt for AI Agents
In vtex/actions/authentication/classicSignIn.ts around lines 73 to 82, the code
forces REFRESH_TOKEN_COOKIE to path "/" which can create a second vid_rt cookie
("/" plus the original "/api/.../webstore") and cause nondeterministic
logout/session behavior; remove the hardcoded path override and instead preserve
the cookie's original path when calling setCookie (or, if you must promote it to
"/", explicitly expire the original cookie at its original path before setting
the "/" cookie) and delete the temporary TODO comment.


return data;
}
71 changes: 47 additions & 24 deletions vtex/actions/authentication/logout.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,57 @@
import { getCookies, setCookie } from "std/http/mod.ts";
import { AppContext } from "../../mod.ts";
import { parseCookie } from "../../utils/vtexId.ts";
// import { getCookies, getSetCookies } from "std/http/cookie.ts";
import type { AppContext } from "../../mod.ts";
import { proxySetCookie, REFRESH_TOKEN_COOKIE } from "../../utils/cookies.ts";
// import { setSegmentBag } from "../../utils/segment.ts";
import { redirect } from "@deco/deco";
import { LogoutResponse } from "../../utils/types.ts";
import { deleteCookie } from "std/http/cookie.ts";

export interface Props {
/**
* URL to redirect after logout
* @default "/"
*/
returnUrl?: string;
}

/**
* @title Logout
* @description Logout the user
* @title VTEX Integration - Logout
* @description Performs user logout and cleans session data
*/
export default async function action(
_: unknown,
{ returnUrl = "/" }: Props,
req: Request,
ctx: AppContext,
) {
const cookies = getCookies(req.headers);
const { payload } = parseCookie(req.headers, ctx.account);

for (const cookieName in cookies) {
if (cookieName.startsWith("VtexIdclientAutCookie")) {
setCookie(ctx.response.headers, {
name: cookieName,
value: "",
expires: new Date(0),
path: "/",
});
}
}
): Promise<LogoutResponse> {
const { account, vcsDeprecated } = ctx;

const response = await vcsDeprecated["GET /api/vtexid/pub/logout"]({
scope: account,
}, {
headers: {
"cookie": req.headers.get("cookie") || "",
},
});

const sessionId = payload?.sess;
if (!sessionId) {
return;
if (!response.ok) {
throw new Error(
`Logout request failed: ${response.status} ${response.statusText}`,
);
}

await ctx.invoke.vtex.actions.session.deleteSession({ sessionId });
const data = await response.json();

deleteCookie(ctx.response.headers, REFRESH_TOKEN_COOKIE, {
path: "/",
domain: new URL(req.url).hostname,
});
Comment on lines +44 to +47
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix: delete refresh-token cookie using VTEX path, not "/"

The refresh token cookie (vid_rt) is scoped to /api/vtexid/refreshtoken/webstore. Deleting it at path "/" won’t clear the scoped cookie.

Apply:

-  deleteCookie(ctx.response.headers, REFRESH_TOKEN_COOKIE, {
-    path: "/",
-    domain: new URL(req.url).hostname,
-  });
+  const VTEX_REFRESH_PATH = "/api/vtexid/refreshtoken/webstore";
+  const targetHost = new URL(req.url).hostname;
+  deleteCookie(ctx.response.headers, REFRESH_TOKEN_COOKIE, {
+    path: VTEX_REFRESH_PATH,
+    domain: targetHost,
+  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
deleteCookie(ctx.response.headers, REFRESH_TOKEN_COOKIE, {
path: "/",
domain: new URL(req.url).hostname,
});
// Ensure we delete the refresh-token cookie at its scoped VTEX path
const VTEX_REFRESH_PATH = "/api/vtexid/refreshtoken/webstore";
const targetHost = new URL(req.url).hostname;
deleteCookie(ctx.response.headers, REFRESH_TOKEN_COOKIE, {
path: VTEX_REFRESH_PATH,
domain: targetHost,
});
🤖 Prompt for AI Agents
In vtex/actions/authentication/logout.ts around lines 44 to 47, the code deletes
the refresh-token cookie using path "/" which won’t remove the cookie scoped to
/api/vtexid/refreshtoken/webstore; update the deleteCookie call to use path:
"/api/vtexid/refreshtoken/webstore" (keeping domain: new URL(req.url).hostname
and any other existing options) so the scoped vid_rt cookie is properly cleared.


proxySetCookie(response.headers, ctx.response.headers, req.url);

Comment on lines 42 to 50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Normalize auth cookie Path on logout as well.

Logout relies on Set-Cookie expirations; if Path isn’t the refresh-token path, cookies may linger for that scope. Mirror the sign-in fix here.

Apply within this hunk:

-  const data = await response.json();
-  proxySetCookie(response.headers, ctx.response.headers, req.url);
+  const contentType = response.headers.get("content-type") ?? "";
+  const data: LogoutResponse =
+    contentType.includes("application/json")
+      ? await response.json()
+      : { cookieName: null, accountCookieName: null };
+  // Proxy all cookies (domain rewrite)
+  proxySetCookie(response.headers, ctx.response.headers, req.url);
+  // Normalize Path for VTEX auth cookies
+  const targetHost = new URL(req.url).hostname;
+  const VTEX_REFRESH_PATH = "/api/vtexid/refreshtoken/webstore";
+  for (const c of getSetCookies(response.headers)) {
+    const needsAuthPath =
+      c.name === "vid_rt" || c.name.startsWith("VtexIdclientAutCookie");
+    if (needsAuthPath) {
+      setCookie(ctx.response.headers, {
+        ...c,
+        domain: targetHost,
+        path: VTEX_REFRESH_PATH,
+      });
+    }
+  }

Add imports:

-// import { getCookies, getSetCookies } from "std/http/cookie.ts";
+import { getSetCookies, setCookie } from "std/http/cookie.ts";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const data = await response.json();
proxySetCookie(response.headers, ctx.response.headers, req.url);
import { getSetCookies, setCookie } from "std/http/cookie.ts";
const contentType = response.headers.get("content-type") ?? "";
const data: LogoutResponse =
contentType.includes("application/json")
? await response.json()
: { cookieName: null, accountCookieName: null };
// Proxy all cookies (domain rewrite)
proxySetCookie(response.headers, ctx.response.headers, req.url);
// Normalize Path for VTEX auth cookies
const targetHost = new URL(req.url).hostname;
const VTEX_REFRESH_PATH = "/api/vtexid/refreshtoken/webstore";
for (const c of getSetCookies(response.headers)) {
const needsAuthPath =
c.name === "vid_rt" || c.name.startsWith("VtexIdclientAutCookie");
if (needsAuthPath) {
setCookie(ctx.response.headers, {
...c,
domain: targetHost,
path: VTEX_REFRESH_PATH,
});
}
}
🤖 Prompt for AI Agents
In vtex/actions/authentication/logout.ts around lines 41 to 43, the logout flow
currently calls proxySetCookie(response.headers, ctx.response.headers, req.url)
but does not normalize the cookie Path, so cookies may persist for the wrong
scope; mirror the sign-in fix by updating the proxySetCookie call to include the
refresh-token path (e.g. proxySetCookie(response.headers, ctx.response.headers,
req.url, getRefreshTokenPath(req.url)) or by normalizing the Set-Cookie Path
after proxying using normalizeCookiePath on ctx.response.headers['set-cookie']),
and add the necessary imports at the top of the file (import getRefreshTokenPath
and/or normalizeCookiePath from the auth/cookie utilities used in sign-in).

await ctx.invoke.vtex.actions.cart.updateUser();
await ctx.invoke.vtex.actions.session
.validateSession();
Comment on lines +51 to +53
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the invocation order and error handling.

The code invokes updateUser and validateSession sequentially without awaiting error handling. If either fails, the user is still redirected. Consider whether partial failure should prevent the redirect or be logged.


🏁 Script executed:

#!/bin/bash
# Description: Check error handling patterns in other authentication flows

# Search for similar invoke patterns to understand error handling conventions
rg -nP --type=ts -C5 'invoke\.vtex\.actions\.(cart|session)\.' | rg -A3 -B3 'await.*invoke'

Length of output: 3960



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for try-catch blocks in authentication flows to see if errors are handled

rg -nP --type=ts 'try\s*\{' vtex/actions/authentication/ -A10 | head -50

Length of output: 870


🏁 Script executed:

#!/bin/bash
# Search for error handling patterns around invoke calls specifically

rg -nP --type=ts 'catch.*invoke|\.catch\(' vtex/actions/authentication/ -B5 -A5

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if there's a higher-level error handler or middleware for actions

rg -nP --type=ts 'export.*default|handler|middleware' vtex/actions/authentication/logout.ts -B2 -A5

Length of output: 262



---

🏁 Script executed:

```shell
#!/bin/bash
# View the full logout.ts file to see complete error handling context

cat vtex/actions/authentication/logout.ts

Length of output: 1482


Add error handling for invoke calls or document the intended failure behavior.

The code inconsistently handles errors: the initial logout API call (line 30) checks if (!response.ok), but the invoke calls at lines 51–53 lack error handling. If updateUser() or validateSession() fails, the function still redirects, leaving session state potentially inconsistent. Either wrap these invokes in a try-catch to handle partial failures explicitly, or document that framework-level error handling catches these failures.

🤖 Prompt for AI Agents
In vtex/actions/authentication/logout.ts around lines 51–53, the calls to
ctx.invoke.vtex.actions.cart.updateUser() and
ctx.invoke.vtex.actions.session.validateSession() lack error handling which can
leave session state inconsistent; wrap these invoke calls in a try-catch (cover
both calls together), on error log the exception with context and decide
behavior: either propagate the error (rethrow) to prevent the redirect or record
a recoverable warning and continue; ensure the chosen path is consistent with
the earlier response.ok check (i.e., if partial failure should abort logout,
rethrow after logging; if it can be tolerated, log a warning and proceed).


redirect(returnUrl);
return data;
Comment on lines +56 to +57
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent open redirects in returnUrl.

returnUrl appears user-controlled (derived from the request in the section). Restrict to same-origin paths to avoid phishing/open-redirect.

-  redirect(returnUrl);
-  return data;
+  const safeLocation = (() => {
+    try {
+      if (!returnUrl) return "/";
+      if (returnUrl.startsWith("/")) return returnUrl.startsWith("//") ? "/" : returnUrl;
+      const dest = new URL(returnUrl, req.url);
+      const origin = new URL(req.url).origin;
+      return dest.origin === origin ? (dest.pathname + dest.search + dest.hash) : "/";
+    } catch {
+      return "/";
+    }
+  })();
+  redirect(safeLocation);
+  return data;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
redirect(returnUrl);
return data;
const safeLocation = (() => {
try {
if (!returnUrl) return "/";
if (returnUrl.startsWith("/")) return returnUrl.startsWith("//") ? "/" : returnUrl;
const dest = new URL(returnUrl, req.url);
const origin = new URL(req.url).origin;
return dest.origin === origin ? (dest.pathname + dest.search + dest.hash) : "/";
} catch {
return "/";
}
})();
redirect(safeLocation);
return data;
🤖 Prompt for AI Agents
In vtex/actions/authentication/logout.ts around lines 47-48, the redirect uses a
user-controlled returnUrl which enables open-redirects; ensure returnUrl is
validated and restricted to same-origin paths only by treating only relative
paths as safe (e.g., require it to start with a single '/' and not with '//' and
disallow any ':' or protocol components), or parse it with URL and compare
origin to the current origin; if validation fails, replace with a safe default
path before calling redirect.

}
Loading
Loading