From 6177194f23603ddabb1b595db4b0d6b4fe901e8f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:16:56 +0000 Subject: [PATCH] fix(dashboard): dynamically expire all refresh cookie variants in impersonation snippet Co-Authored-By: mantra --- .../dashboard/src/components/user-dialogs.tsx | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/apps/dashboard/src/components/user-dialogs.tsx b/apps/dashboard/src/components/user-dialogs.tsx index 9ab9aafa8..e066ceb1b 100644 --- a/apps/dashboard/src/components/user-dialogs.tsx +++ b/apps/dashboard/src/components/user-dialogs.tsx @@ -36,20 +36,34 @@ export function generateImpersonateSnippet( refreshToken: string, expiresAtDate: Date, ): string { - // Clear ALL cookie variants (both hexclave- and legacy stack- prefixes, with - // and without __Host-) so no stale token takes precedence over the one we set. - // Set the cookie under the primary hexclave-refresh- name the SDK reads first; - // using the legacy stack-refresh- name caused impersonation to silently fail on - // production because not all deployed SDK versions fall back to it. + // Dynamically expire EVERY refresh cookie for this project before setting ours. + // The SDK selects the refresh token by scanning all cookies whose name starts + // with `hexclave-refresh-{pid}--` / `stack-refresh-{pid}--` (with or without the + // __Host- prefix) and keeping the one with the newest `updated_at_millis`. A + // hardcoded list of names misses structured variants like the cross-subdomain + // `--custom-*` cookies, so a stale/future-dated one could still win the selection + // and keep impersonation from taking effect. We instead iterate document.cookie + // and delete any name matching the two bases (bare legacy, `--*` structured, and + // __Host- prefixed) so nothing but the cookie we set below survives. + // We set the new token under the primary hexclave-refresh- name the SDK reads + // first; using the legacy stack-refresh- name caused impersonation to silently + // fail on production because not all deployed SDK versions fall back to it. const pid = encodeURIComponent(projectId); return deindent` document.cookie = 'hexclave-access=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/'; document.cookie = 'stack-access=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/'; - document.cookie = 'hexclave-refresh-${pid}--default=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/'; - document.cookie = '__Host-hexclave-refresh-${pid}--default=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/' + (location.protocol === 'https:' ? '; secure' : ''); - document.cookie = 'stack-refresh-${pid}--default=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/'; - document.cookie = '__Host-stack-refresh-${pid}--default=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/' + (location.protocol === 'https:' ? '; secure' : ''); - document.cookie = 'stack-refresh-${pid}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/'; + var impersonationRefreshBases = ['hexclave-refresh-${pid}', 'stack-refresh-${pid}']; + document.cookie.split(';').forEach(function (rawCookie) { + var cookieName = rawCookie.split('=')[0].trim(); + if (!cookieName) return; + var bareName = cookieName.replace(/^__Host-/, ''); + var matchesBase = impersonationRefreshBases.some(function (base) { + return bareName === base || bareName.indexOf(base + '--') === 0; + }); + if (matchesBase) { + document.cookie = cookieName + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/' + (location.protocol === 'https:' ? '; secure' : ''); + } + }); document.cookie = (location.protocol === 'https:' ? '__Host-' : '') + 'hexclave-refresh-${pid}--default=' + encodeURIComponent(JSON.stringify({ refresh_token: ${JSON.stringify(refreshToken)}, updated_at_millis: Date.now() })) + '; expires=${expiresAtDate.toUTCString()}; path=/' + (location.protocol === 'https:' ? '; secure' : ''); window.location.reload(); `;