From 4608330413ac73ad14dbd6d4c91cbf980ff4eb89 Mon Sep 17 00:00:00 2001 From: swarnendusahu10-dotcom Date: Sun, 25 Oct 2026 20:31:34 +0530 Subject: [PATCH] fix: namespace auth token storage key to prevent cross-instance session collisions --- packages/react/src/lib/auth.js | 34 +++++++++++++++++------- packages/react/src/views/EmbeddedChat.js | 18 ++++++++++++- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/packages/react/src/lib/auth.js b/packages/react/src/lib/auth.js index c3ac9ca586..8035f58cc8 100644 --- a/packages/react/src/lib/auth.js +++ b/packages/react/src/lib/auth.js @@ -1,19 +1,33 @@ -async function saveTokenLocalStorage(token) { +async function saveTokenLocalStorage(token, key = 'ec_token') { if (typeof localStorage !== 'undefined') { - localStorage.setItem('ec_token', token); + localStorage.setItem(key, token); } } -async function getTokenLocalStorage() { +async function getTokenLocalStorage(key = 'ec_token') { if (typeof localStorage !== 'undefined') { - return localStorage.getItem('ec_token'); + const token = localStorage.getItem(key); + if (token) { + return token; + } + // Backward compatibility: fallback and migrate legacy 'ec_token' if namespaced key is empty + if (key !== 'ec_token') { + const legacyToken = localStorage.getItem('ec_token'); + if (legacyToken) { + localStorage.setItem(key, legacyToken); + return legacyToken; + } + } } return null; } -async function deleteTokenLocalStorage() { +async function deleteTokenLocalStorage(key = 'ec_token') { if (typeof localStorage !== 'undefined') { - localStorage.removeItem('ec_token'); + localStorage.removeItem(key); + if (key !== 'ec_token') { + localStorage.removeItem('ec_token'); + } } } @@ -30,7 +44,7 @@ async function deleteTokenSecure() { this.handleSecureLogin('delete'); } -export function getTokenStorage(secure = false) { +export function getTokenStorage(secure = false, key = 'ec_token') { if (secure) { return { saveToken: saveTokenSecure, @@ -39,8 +53,8 @@ export function getTokenStorage(secure = false) { }; } return { - saveToken: saveTokenLocalStorage, - getToken: getTokenLocalStorage, - deleteToken: deleteTokenLocalStorage, + saveToken: (token) => saveTokenLocalStorage(token, key), + getToken: () => getTokenLocalStorage(key), + deleteToken: () => deleteTokenLocalStorage(key), }; } diff --git a/packages/react/src/views/EmbeddedChat.js b/packages/react/src/views/EmbeddedChat.js index 287e5e8bf1..cfb40244bd 100644 --- a/packages/react/src/views/EmbeddedChat.js +++ b/packages/react/src/views/EmbeddedChat.js @@ -65,6 +65,7 @@ const EmbeddedChat = (props) => { dark = false, remoteOpt = false, layoutMode = 'bubble', + tokenStorageKey = null, } = config; const auth = useMemo( @@ -79,7 +80,21 @@ const EmbeddedChat = (props) => { const { classNames, styleOverrides } = useComponentOverrides('EmbeddedChat'); const [fullScreen, setFullScreen] = useState(false); const [isSynced, setIsSynced] = useState(!remoteOpt); - const { getToken, saveToken, deleteToken } = getTokenStorage(secure); + const storageKey = useMemo(() => { + if (tokenStorageKey) { + return tokenStorageKey; + } + if (host) { + const normalizedHost = host.trim().replace(/\/+$/, ''); + return `ec_token_${normalizedHost}`; + } + return 'ec_token'; + }, [tokenStorageKey, host]); + + const { getToken, saveToken, deleteToken } = useMemo( + () => getTokenStorage(secure, storageKey), + [secure, storageKey] + ); const { setIsUserAuthenticated, setUsername: setAuthenticatedUsername, @@ -356,6 +371,7 @@ EmbeddedChat.propTypes = { style: PropTypes.object, hideHeader: PropTypes.bool, dark: PropTypes.bool, + tokenStorageKey: PropTypes.string, aiAdapter: PropTypes.shape({ name: PropTypes.string, sendPrompt: PropTypes.func.isRequired,