Skip to content

Commit 69cfe1f

Browse files
committed
feat: implement logout functionality with Keycloak iframe handling
- Added a new handleLogout function to manage user logout via an iframe for Keycloak. - Removed the previous inline logout logic from the MainMenu.Item component. - Enhanced error handling and session invalidation upon logout completion.
1 parent ddb6f8c commit 69cfe1f

File tree

1 file changed

+64
-27
lines changed

1 file changed

+64
-27
lines changed

src/frontend/src/ui/MainMenu.tsx

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,69 @@ export const MainMenuConfig: React.FC<MainMenuConfigProps> = ({
166166
});
167167
};
168168

169+
const handleLogout = async () => {
170+
capture('logout_clicked');
171+
172+
try {
173+
// Call the logout endpoint and get the logout_url
174+
const response = await fetch('/auth/logout', {
175+
method: 'GET',
176+
credentials: 'include'
177+
});
178+
const data = await response.json();
179+
const keycloakLogoutUrl = data.logout_url;
180+
181+
// Create a function to create an iframe and return a promise that resolves when it loads or times out
182+
const createIframeLoader = (url: string, debugName: string): Promise<void> => {
183+
return new Promise<void>((resolve) => {
184+
const iframe = document.createElement("iframe");
185+
iframe.style.display = "none";
186+
iframe.src = url;
187+
console.debug(`[pad.ws] (Silently) Priming ${debugName} logout for ${url}`);
188+
189+
const cleanup = () => {
190+
if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
191+
resolve();
192+
};
193+
194+
iframe.onload = cleanup;
195+
// Fallback: remove iframe after 2s if onload doesn't fire
196+
const timeoutId = window.setTimeout(cleanup, 2000);
197+
198+
// Also clean up if the iframe errors
199+
iframe.onerror = () => {
200+
clearTimeout(timeoutId);
201+
cleanup();
202+
};
203+
204+
// Add the iframe to the DOM
205+
document.body.appendChild(iframe);
206+
});
207+
};
208+
209+
// Create a promise for Keycloak logout iframe
210+
const promises = [];
211+
212+
// Add Keycloak logout iframe
213+
promises.push(createIframeLoader(keycloakLogoutUrl, "Keycloak"));
214+
215+
// Wait for both iframes to complete
216+
await Promise.all(promises);
217+
218+
// Wait for the iframe to complete
219+
await Promise.all(promises);
220+
221+
// Invalidate auth query to show the AuthModal
222+
queryClient.invalidateQueries({ queryKey: ['auth'] });
223+
queryClient.invalidateQueries({ queryKey: ['userProfile'] });
224+
225+
// No need to redirect to the logout URL since we're already handling it via iframe
226+
console.log("Logged out successfully");
227+
} catch (error) {
228+
console.error("Logout failed:", error);
229+
}
230+
};
231+
169232
return (
170233
<>
171234
{showAccountModal && (
@@ -282,33 +345,7 @@ export const MainMenuConfig: React.FC<MainMenuConfigProps> = ({
282345

283346
<MainMenu.Item
284347
icon={<LogOut />}
285-
onClick={async () => {
286-
capture('logout_clicked');
287-
288-
try {
289-
// Call the logout endpoint and get the session_id
290-
const response = await fetch('/auth/logout', {
291-
method: 'GET',
292-
credentials: 'include'
293-
});
294-
const data = await response.json();
295-
const logoutUrl = data.logout_url;
296-
297-
// Clear the session_id cookie client-side
298-
document.cookie = "session_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
299-
300-
// Invalidate auth query to show the AuthModal
301-
queryClient.invalidateQueries({ queryKey: ['auth'] });
302-
queryClient.invalidateQueries({ queryKey: ['userProfile'] });
303-
304-
// Redirect to the logout URL
305-
window.location.replace(logoutUrl);
306-
307-
console.log("Logged out successfully");
308-
} catch (error) {
309-
console.error("Logout failed:", error);
310-
}
311-
}}
348+
onClick={handleLogout}
312349
>
313350
Logout
314351
</MainMenu.Item>

0 commit comments

Comments
 (0)