Skip to content

Commit 3baff18

Browse files
committed
feat: enhance authentication flow with popup support and improved logout handling
- Added support for popup authentication in the login process. - Updated the callback endpoint to handle popup state and return a specific response. - Improved logout functionality to clear session cookies without redirecting. - Invalidated authentication queries on logout to refresh the UI state.
1 parent 42a3450 commit 3baff18

File tree

3 files changed

+73
-17
lines changed

3 files changed

+73
-17
lines changed

src/backend/routers/auth.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,32 @@
22
import jwt
33
import httpx
44
from fastapi import APIRouter, Request, HTTPException, Depends
5-
from fastapi.responses import RedirectResponse
5+
from fastapi.responses import RedirectResponse, FileResponse
6+
import os
67

7-
from config import get_auth_url, get_token_url, OIDC_CONFIG, sessions
8+
from config import get_auth_url, get_token_url, OIDC_CONFIG, sessions, STATIC_DIR
89
from dependencies import SessionData, require_auth
910
from coder import CoderAPI
1011

1112
auth_router = APIRouter()
1213
coder_api = CoderAPI()
1314

1415
@auth_router.get("/login")
15-
async def login(request: Request, kc_idp_hint: str = None):
16+
async def login(request: Request, kc_idp_hint: str = None, popup: str = None):
1617
session_id = secrets.token_urlsafe(32)
1718
auth_url = get_auth_url()
19+
state = "popup" if popup == "1" else "default"
1820
if kc_idp_hint:
1921
auth_url = f"{auth_url}&kc_idp_hint={kc_idp_hint}"
22+
# Add state param to OIDC URL
23+
auth_url = f"{auth_url}&state={state}"
2024
response = RedirectResponse(auth_url)
2125
response.set_cookie('session_id', session_id)
2226

2327
return response
2428

2529
@auth_router.get("/callback")
26-
async def callback(request: Request, code: str):
30+
async def callback(request: Request, code: str, state: str = "default"):
2731
session_id = request.cookies.get('session_id')
2832
if not session_id:
2933
raise HTTPException(status_code=400, detail="No session")
@@ -53,19 +57,32 @@ async def callback(request: Request, code: str):
5357
user_info
5458
)
5559
coder_api.ensure_workspace_exists(user_data['username'])
56-
57-
5860
except Exception as e:
5961
print(f"Error in user/workspace setup: {str(e)}")
6062
# Continue with login even if Coder API fails
61-
62-
return RedirectResponse('/')
63+
64+
if state == "popup":
65+
return FileResponse(os.path.join(STATIC_DIR, "auth/popup-close.html"))
66+
else:
67+
return RedirectResponse('/')
6368

6469
@auth_router.get("/logout")
6570
async def logout(request: Request):
6671
session_id = request.cookies.get('session_id')
6772
if session_id in sessions:
6873
del sessions[session_id]
69-
response = RedirectResponse('/')
70-
response.delete_cookie('session_id')
74+
75+
# Create a response that doesn't redirect but still clears the cookie
76+
from fastapi.responses import JSONResponse
77+
response = JSONResponse({"status": "success", "message": "Logged out successfully"})
78+
79+
# Clear the session_id cookie with all necessary parameters
80+
response.delete_cookie(
81+
key="session_id",
82+
path="/",
83+
domain=None, # Use None to match the current domain
84+
secure=request.url.scheme == "https",
85+
httponly=True
86+
)
87+
7188
return response

src/frontend/src/auth/AuthModal.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react";
22
import ReactDOM from "react-dom";
33
import { capture } from "../utils/posthog";
44
import { Mail } from "lucide-react";
5+
import { queryClient } from "../api/queryClient";
56
import "../styles/AuthModal.scss";
67

78
interface AuthModalProps {
@@ -16,11 +17,22 @@ const AuthModal: React.FC<AuthModalProps> = ({
1617
useEffect(() => {
1718
setIsMounted(true);
1819
capture("auth_modal_shown");
19-
// Prevent scrolling when modal is open
20-
document.body.style.overflow = "hidden";
20+
}, []);
2121

22+
useEffect(() => {
23+
const checkLocalStorage = () => {
24+
const authCompleted = localStorage.getItem('auth_completed');
25+
if (authCompleted) {
26+
localStorage.removeItem('auth_completed');
27+
queryClient.invalidateQueries({ queryKey: ['auth'] });
28+
clearInterval(intervalId);
29+
}
30+
};
31+
32+
const intervalId = setInterval(checkLocalStorage, 500);
33+
2234
return () => {
23-
document.body.style.overflow = "auto";
35+
clearInterval(intervalId);
2436
};
2537
}, []);
2638

@@ -62,7 +74,11 @@ const AuthModal: React.FC<AuthModalProps> = ({
6274
<button
6375
className="auth-modal-button auth-modal-button-primary"
6476
onClick={() => {
65-
window.location.href = "/auth/login?kc_idp_hint=google";
77+
window.open(
78+
"/auth/login?kc_idp_hint=google&popup=1",
79+
"authPopup",
80+
"width=500,height=700,noopener,noreferrer"
81+
);
6682
}}
6783
>
6884
<svg
@@ -96,7 +112,11 @@ const AuthModal: React.FC<AuthModalProps> = ({
96112
<button
97113
className="auth-modal-button auth-modal-button-outline"
98114
onClick={() => {
99-
window.location.href = "/auth/login?kc_idp_hint=github";
115+
window.open(
116+
"/auth/login?kc_idp_hint=github&popup=1",
117+
"authPopup",
118+
"width=500,height=700,noopener,noreferrer"
119+
);
100120
}}
101121
>
102122
<svg

src/frontend/src/ui/MainMenu.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { LogOut, SquarePlus, LayoutDashboard, SquareCode, Eye, Coffee, Grid2x2,
77
import { capture } from '../utils/posthog';
88
import { ExcalidrawElementFactory, PlacementMode } from '../lib/ExcalidrawElementFactory';
99
import { useUserProfile } from "../api/hooks";
10+
import { queryClient } from "../api/queryClient";
1011

1112
interface MainMenuConfigProps {
1213
MainMenu: typeof MainMenuType;
@@ -171,9 +172,27 @@ export const MainMenuConfig: React.FC<MainMenuConfigProps> = ({
171172

172173
<MainMenu.Item
173174
icon={<LogOut />}
174-
onClick={() => {
175+
onClick={async () => {
175176
capture('logout_clicked');
176-
window.location.href = "/auth/logout";
177+
178+
try {
179+
// Call the logout endpoint but don't follow the redirect
180+
await fetch('/auth/logout', {
181+
method: 'GET',
182+
credentials: 'include'
183+
});
184+
185+
// Clear the session_id cookie client-side
186+
document.cookie = "session_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
187+
188+
// Invalidate auth query to show the AuthModal
189+
queryClient.invalidateQueries({ queryKey: ['auth'] });
190+
queryClient.invalidateQueries({ queryKey: ['userProfile'] });
191+
192+
console.log("Logged out successfully");
193+
} catch (error) {
194+
console.error("Logout failed:", error);
195+
}
177196
}}
178197
>
179198
Logout

0 commit comments

Comments
 (0)