From 9d96787da43c396a6fe5c6199431d36348757fac Mon Sep 17 00:00:00 2001 From: Adrienne Rio Date: Tue, 31 Dec 2024 11:45:53 +0800 Subject: [PATCH 1/3] chore: added function to remove user state --- src/hooks/useOAuth2.ts | 9 +++++++-- src/oidc/error.ts | 1 + src/oidc/oidc.ts | 32 +++++++++++++++++++++++++++++++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/hooks/useOAuth2.ts b/src/hooks/useOAuth2.ts index eb63337..40d21c6 100644 --- a/src/hooks/useOAuth2.ts +++ b/src/hooks/useOAuth2.ts @@ -27,7 +27,11 @@ const LOGOUT_TIMEOUT = 10000; * @returns {{ OAuth2Logout: () => Promise }} - Object containing the OAuth2Logout function. * @deprecated Please use OAuth2Logout function instead of this hook from the `@deriv-com/auth-client` package. */ -export const useOAuth2 = (OAuth2GrowthBookConfig: OAuth2GBConfig, WSLogoutAndRedirect: () => Promise) => { +export const useOAuth2 = ( + OAuth2GrowthBookConfig: OAuth2GBConfig, + WSLogoutAndRedirect: () => Promise, + isGBLoaded?: boolean +) => { const { OAuth2EnabledApps, OAuth2EnabledAppsInitialised } = OAuth2GrowthBookConfig; const isOAuth2Enabled = useIsOAuth2Enabled(OAuth2EnabledApps, OAuth2EnabledAppsInitialised); @@ -41,13 +45,14 @@ export const useOAuth2 = (OAuth2GrowthBookConfig: OAuth2GBConfig, WSLogoutAndRed }; const OAuth2Logout = useCallback(async () => { + if (!isGBLoaded) return if (!isOAuth2Enabled) return WSLogoutAndRedirect(); const onMessage = (event: MessageEvent) => { if (event.data === 'logout_complete') { const domains = ['deriv.com', 'binary.sx', 'pages.dev', 'localhost']; const currentDomain = window.location.hostname.split('.').slice(-2).join('.'); - if (domains.includes(currentDomain)) { + if (domains.includes(currentDomain) && isOAuth2Enabled) { Cookies.set('logged_state', 'false', { expires: 30, path: '/', diff --git a/src/oidc/error.ts b/src/oidc/error.ts index edf3e05..66acda8 100644 --- a/src/oidc/error.ts +++ b/src/oidc/error.ts @@ -5,6 +5,7 @@ export enum OIDCErrorType { LegacyTokenRequestFailed = 'LegacyTokenRequestFailed', UserManagerCreationFailed = 'UserManagerCreationFailed', OneTimeCodeMissing = 'OneTimeCodeMissing', + FailedToRemoveSession = 'FailedToRemoveSession', } export class OIDCError extends Error { diff --git a/src/oidc/oidc.ts b/src/oidc/oidc.ts index 6a89899..b815001 100644 --- a/src/oidc/oidc.ts +++ b/src/oidc/oidc.ts @@ -41,6 +41,16 @@ type CreateUserManagerOptions = { postLogoutRedirectUri?: string; }; +type OAuth2LogoutOptions = { + isGBEnabled?: boolean; + clearOIDCStorageOptions?: ClearOIDCStorageOptions; +}; + +type ClearOIDCStorageOptions = { + redirectCallbackUri: string; + postLogoutRedirectUri: string; +}; + /** * Fetches the OIDC configuration for the given serverUrl. * @returns {Promise} - A promise resolving to the OIDC configuration. @@ -268,7 +278,7 @@ export const createUserManager = async (options: CreateUserManagerOptions) => { * Logs out the user from the auth server and calls the callback function when the logout is complete. * @param WSLogoutAndRedirect - The callback function to call after the logout is complete */ -export const OAuth2Logout = (WSLogoutAndRedirect: () => void) => { +export const OAuth2Logout = (WSLogoutAndRedirect: () => void, options: OAuth2LogoutOptions) => { const oidcEndpoints = localStorage.getItem('config.oidc_endpoints') || '{}'; const logoutUrl = getOAuthLogoutUrl() || JSON.parse(oidcEndpoints).end_session_endpoint; @@ -276,6 +286,8 @@ export const OAuth2Logout = (WSLogoutAndRedirect: () => void) => { const cleanup = () => { const iframe = document.getElementById('logout-iframe') as HTMLIFrameElement; if (iframe) iframe.remove(); + // NOTE: this will resolve issues where once you are logged out, OIDC may reuse the previous user's OIDC ID token / session data + if (options.clearOIDCStorageOptions) clearOIDCStorage(options.clearOIDCStorageOptions); }; const onMessage = (event: MessageEvent) => { if (event.data === 'logout_complete') { @@ -351,6 +363,24 @@ export const oidcLogout = async (options: RequestOidcAuthenticationOptions): Pro } }; +/** + * Clears any OIDC-related storage (localStorage, sessionStorage, etc) about the current user. + * There's 2 types of storage `oidc-client-ts` uses: state store, and user store + * User store is used for storing the access token and ID token + * Use this function when your application has logged out successfully but the session data is still there, otherwise use `OAuth2Logout` function which already handles this scenario + * + */ +export const clearOIDCStorage = async (options: ClearOIDCStorageOptions) => { + try { + const userManager = await createUserManager(options); + + await userManager.removeUser(); + } catch (error) { + if (error instanceof Error) throw new OIDCError(OIDCErrorType.FailedToRemoveSession, error.message); + throw new OIDCError(OIDCErrorType.FailedToRemoveSession, 'unable to remove OIDC session'); + } +}; + /** * Checks if the user has completed the logout flow and calls the callback function from the consumer. * At this point the user is already logged out from the auth server. This function is just to clear the FE session. From 0373f2055b3f9b590659302bbcbdb8d7bf1a23d9 Mon Sep 17 00:00:00 2001 From: Adrienne Rio Date: Tue, 31 Dec 2024 11:48:26 +0800 Subject: [PATCH 2/3] chore: removed unrelated changes --- src/hooks/useOAuth2.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/hooks/useOAuth2.ts b/src/hooks/useOAuth2.ts index 40d21c6..eb63337 100644 --- a/src/hooks/useOAuth2.ts +++ b/src/hooks/useOAuth2.ts @@ -27,11 +27,7 @@ const LOGOUT_TIMEOUT = 10000; * @returns {{ OAuth2Logout: () => Promise }} - Object containing the OAuth2Logout function. * @deprecated Please use OAuth2Logout function instead of this hook from the `@deriv-com/auth-client` package. */ -export const useOAuth2 = ( - OAuth2GrowthBookConfig: OAuth2GBConfig, - WSLogoutAndRedirect: () => Promise, - isGBLoaded?: boolean -) => { +export const useOAuth2 = (OAuth2GrowthBookConfig: OAuth2GBConfig, WSLogoutAndRedirect: () => Promise) => { const { OAuth2EnabledApps, OAuth2EnabledAppsInitialised } = OAuth2GrowthBookConfig; const isOAuth2Enabled = useIsOAuth2Enabled(OAuth2EnabledApps, OAuth2EnabledAppsInitialised); @@ -45,14 +41,13 @@ export const useOAuth2 = ( }; const OAuth2Logout = useCallback(async () => { - if (!isGBLoaded) return if (!isOAuth2Enabled) return WSLogoutAndRedirect(); const onMessage = (event: MessageEvent) => { if (event.data === 'logout_complete') { const domains = ['deriv.com', 'binary.sx', 'pages.dev', 'localhost']; const currentDomain = window.location.hostname.split('.').slice(-2).join('.'); - if (domains.includes(currentDomain) && isOAuth2Enabled) { + if (domains.includes(currentDomain)) { Cookies.set('logged_state', 'false', { expires: 30, path: '/', From 7d50561114f815c0af1e45f8961c95ec1bd93a83 Mon Sep 17 00:00:00 2001 From: Adrienne Rio Date: Tue, 31 Dec 2024 12:01:51 +0800 Subject: [PATCH 3/3] chore: make clearing user state mandatory --- src/oidc/oidc.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/oidc/oidc.ts b/src/oidc/oidc.ts index b815001..7504ec8 100644 --- a/src/oidc/oidc.ts +++ b/src/oidc/oidc.ts @@ -42,8 +42,9 @@ type CreateUserManagerOptions = { }; type OAuth2LogoutOptions = { - isGBEnabled?: boolean; - clearOIDCStorageOptions?: ClearOIDCStorageOptions; + WSLogoutAndRedirect: () => void; + redirectCallbackUri: string; + postLogoutRedirectUri: string; }; type ClearOIDCStorageOptions = { @@ -278,7 +279,7 @@ export const createUserManager = async (options: CreateUserManagerOptions) => { * Logs out the user from the auth server and calls the callback function when the logout is complete. * @param WSLogoutAndRedirect - The callback function to call after the logout is complete */ -export const OAuth2Logout = (WSLogoutAndRedirect: () => void, options: OAuth2LogoutOptions) => { +export const OAuth2Logout = (options: OAuth2LogoutOptions) => { const oidcEndpoints = localStorage.getItem('config.oidc_endpoints') || '{}'; const logoutUrl = getOAuthLogoutUrl() || JSON.parse(oidcEndpoints).end_session_endpoint; @@ -287,7 +288,10 @@ export const OAuth2Logout = (WSLogoutAndRedirect: () => void, options: OAuth2Log const iframe = document.getElementById('logout-iframe') as HTMLIFrameElement; if (iframe) iframe.remove(); // NOTE: this will resolve issues where once you are logged out, OIDC may reuse the previous user's OIDC ID token / session data - if (options.clearOIDCStorageOptions) clearOIDCStorage(options.clearOIDCStorageOptions); + clearOIDCStorage({ + redirectCallbackUri: options.redirectCallbackUri, + postLogoutRedirectUri: options.postLogoutRedirectUri, + }); }; const onMessage = (event: MessageEvent) => { if (event.data === 'logout_complete') { @@ -301,7 +305,7 @@ export const OAuth2Logout = (WSLogoutAndRedirect: () => void, options: OAuth2Log secure: true, }); } - WSLogoutAndRedirect(); + options.WSLogoutAndRedirect(); cleanup(); window.removeEventListener('message', onMessage); }