Skip to content

feat(vtex): forward the full request cookie for session-scoped account reads and writes - #10

Merged
marcoferreiradev merged 2 commits into
main-altfrom
feat/telesales-session-forwarding
Jul 30, 2026
Merged

marcoferreiradev merged 2 commits into
main-altfrom
feat/telesales-session-forwarding

Conversation

@marcoferreiradev

@marcoferreiradev marcoferreiradev commented Jul 30, 2026 •

Copy link
Copy Markdown
Collaborator

Why

parseCookie forwards only the VtexId cookies and drops vtex_session. store-graphql's
@withCurrentProfile resolves the profile from that session — so without it, every account
loader/action resolves the cookie holder instead of the session-scoped shopper.

That makes telesales impersonation impossible: an operator browsing on a customer's behalf
carries an admin token plus a session that names the customer, and these nine endpoints only
ever saw the admin.

What changed

New forwardCookie(headers) in vtex/utils/vtexId.ts returns the full request cookie. The
nine account loaders/actions below now use it in place of parseCookie().cookie, matching the
cookie forwarding the cart actions already do inline:

kind files
loaders user.ts, profile/getCurrentProfile.ts, address/getUserAddresses.ts, payment/userPayments.ts
actions profile/updateProfile.ts, address/{create,update,delete}.ts, payment/deletePaymentToken.ts

parseCookie is still used for the JWT payload (sub, userId) — only the forwarded cookie
string changed. The forwarded cookie is what targets the write; no request field does.

10 files, +46/−20.

Known risks, deliberately not addressed here

Both were found by review and left alone on purpose — this app should change as little as
possible, and neither is reachable-and-harmful today.

  1. A stale session can now resolve to null where it previously fell back to the cookie.
    In withCurrentProfile, session takes precedence, and getCurrentProfileFromSession only
    throws when !session || !session.id — a valid session without a profile returns
    currentProfile: null successfully, so the cookie fallback never runs. Before this change
    getSession always threw (no vtex_session forwarded) and the fallback always ran.
    Reachability of that window is unproven; an ordinary logged-in shopper was verified live
    through these endpoints with the change active. If it ever bites, the targeted fix is to
    gate forwardCookie on payload.audience === "admin", which comes from the signed JWT.

  2. newsletter/updateNewsletterOptIn still subscribes with the cookie holder's email.
    It is not in the nine, and under a telesales session it would name the operator. Left as-is
    because it is inert: checkUserAuthorization gates the write on storeUserAuthToken, which
    an operator's admin token is not, so the mutation is a silent no-op with either email —
    fixing it changes no observable behaviour and would add a round-trip plus a throw path to an
    endpoint with no consumer.

Downstream

oficina-reserva on feat/callcenter-operator pins this branch at deno.json:48. The pin
points at a commit that exists only on this branch, so the site build depends on this landing
on main-alt and being repinned to the merged SHA.

🤖 Generated with Claude Code

marcoferreiradev and others added 2 commits July 23, 2026 14:20
…ad/writes

profile/address/payment/user store-graphql calls forwarded only the VtexId cookies (parseCookie), dropping vtex_session — so @withCurrentProfile could not resolve a session-scoped (e.g. telesales-impersonated) profile and writes misdirected. Forward the full request cookie, matching the cart actions.

updateProfile also injected payload.sub as the profile email; under an operator admin token that is the OPERATOR's email, which would re-key the impersonated customer's account. Derive the target email from the impersonated-customer session cookie when the token audience is admin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The block read `vtex-impersonated-customer-email` as a COOKIE and threw
"Could not resolve the target profile email" when it was absent. Nothing ever
writes that cookie — impersonation sets it as a session public property via
editSession — so the throw fired on every profile update under a telesales
session.

It was also unnecessary: store-graphql discards the field
(node/resolvers/profile/services.ts, `const { email, ...profileWithoutEmail }`,
since 2.173.2) and writes to the session-resolved currentProfile. The forwarded
cookie is what targets the write; this field is only present because
ProfileInput.email is String! in the schema.

So `email: payload.sub` is restored with a one-line why, the client-writable
cookie read is gone, and the block's security argument goes with it — there is
nothing left to defend.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@marcoferreiradev
marcoferreiradev merged commit 377a615 into main-alt Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant