From dd424c4e10b37a2baca76404f22c4a1019bbe214 Mon Sep 17 00:00:00 2001 From: Alex TYRODE Date: Thu, 24 Apr 2025 05:24:55 +0000 Subject: [PATCH 1/5] feat: add static file serving for static assets in backend --- src/backend/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/main.py b/src/backend/main.py index a9d2f1d..c54ea76 100644 --- a/src/backend/main.py +++ b/src/backend/main.py @@ -44,6 +44,7 @@ async def lifespan(_: FastAPI): ) app.mount("/assets", StaticFiles(directory=ASSETS_DIR), name="assets") +app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") @app.get("/") async def read_root(request: Request, auth: Optional[SessionData] = Depends(optional_auth)): From 2f8819ea5c92df1066685458eaf7f23ddaf2ae36 Mon Sep 17 00:00:00 2001 From: Alex TYRODE Date: Thu, 24 Apr 2025 05:25:04 +0000 Subject: [PATCH 2/5] feat: add authentication completion popup HTML file --- src/frontend/public/auth/popup-close.html | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/frontend/public/auth/popup-close.html diff --git a/src/frontend/public/auth/popup-close.html b/src/frontend/public/auth/popup-close.html new file mode 100644 index 0000000..ef2b783 --- /dev/null +++ b/src/frontend/public/auth/popup-close.html @@ -0,0 +1,34 @@ + + + + + Authentication Complete + + + +
Authentication complete! You may close this window.
+ + + From 399a82ae97e7454e3c80cd3eee5de7539734cb28 Mon Sep 17 00:00:00 2001 From: Alex TYRODE Date: Thu, 24 Apr 2025 05:25:44 +0000 Subject: [PATCH 3/5] fix: prevent workspace state polling when unauthenticated --- src/frontend/src/pad/controls/StateIndicator.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/frontend/src/pad/controls/StateIndicator.tsx b/src/frontend/src/pad/controls/StateIndicator.tsx index 52e0a29..b840ed2 100644 --- a/src/frontend/src/pad/controls/StateIndicator.tsx +++ b/src/frontend/src/pad/controls/StateIndicator.tsx @@ -4,9 +4,13 @@ import '../styles/index.scss'; export const StateIndicator: React.FC = () => { const { data: isAuthenticated, isLoading: isAuthLoading } = useAuthCheck(); + + // Only fetch workspace state if authenticated const { data: workspaceState, isLoading: isWorkspaceLoading } = useWorkspaceState({ queryKey: ['workspaceState'], enabled: isAuthenticated === true && !isAuthLoading, + // Explicitly set refetchInterval to false when not authenticated + refetchInterval: isAuthenticated === true ? undefined : false, }); const getStateClassName = () => { From 42a34502a2b5169da4bb25763bc7489f1fdddaa1 Mon Sep 17 00:00:00 2001 From: Alex TYRODE Date: Thu, 24 Apr 2025 05:26:55 +0000 Subject: [PATCH 4/5] refactor: adjust workspace state polling and retry behavior based on authentication --- src/frontend/src/api/hooks.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/api/hooks.ts b/src/frontend/src/api/hooks.ts index b8562cd..91373d1 100644 --- a/src/frontend/src/api/hooks.ts +++ b/src/frontend/src/api/hooks.ts @@ -132,10 +132,16 @@ export function useUserProfile(options?: UseQueryOptions) { } export function useWorkspaceState(options?: UseQueryOptions) { + // Get the current auth state from the query cache + const authState = queryClient.getQueryData(['auth']); + return useQuery({ queryKey: ['workspaceState'], queryFn: api.getWorkspaceState, - refetchInterval: 5000, // Poll every 5 seconds + // Only poll if authenticated + refetchInterval: authState === true ? 5000 : false, // Poll every 5 seconds if authenticated, otherwise don't poll + // Don't retry on error if not authenticated + retry: authState === true ? 1 : false, ...options, }); } From 3baff1841f88383cf59f2ad7d37dc11b113824f7 Mon Sep 17 00:00:00 2001 From: Alex TYRODE Date: Thu, 24 Apr 2025 05:29:45 +0000 Subject: [PATCH 5/5] 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. --- src/backend/routers/auth.py | 37 +++++++++++++++++++++-------- src/frontend/src/auth/AuthModal.tsx | 30 +++++++++++++++++++---- src/frontend/src/ui/MainMenu.tsx | 23 ++++++++++++++++-- 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/src/backend/routers/auth.py b/src/backend/routers/auth.py index 0d013bc..25194fd 100644 --- a/src/backend/routers/auth.py +++ b/src/backend/routers/auth.py @@ -2,9 +2,10 @@ import jwt import httpx from fastapi import APIRouter, Request, HTTPException, Depends -from fastapi.responses import RedirectResponse +from fastapi.responses import RedirectResponse, FileResponse +import os -from config import get_auth_url, get_token_url, OIDC_CONFIG, sessions +from config import get_auth_url, get_token_url, OIDC_CONFIG, sessions, STATIC_DIR from dependencies import SessionData, require_auth from coder import CoderAPI @@ -12,18 +13,21 @@ coder_api = CoderAPI() @auth_router.get("/login") -async def login(request: Request, kc_idp_hint: str = None): +async def login(request: Request, kc_idp_hint: str = None, popup: str = None): session_id = secrets.token_urlsafe(32) auth_url = get_auth_url() + state = "popup" if popup == "1" else "default" if kc_idp_hint: auth_url = f"{auth_url}&kc_idp_hint={kc_idp_hint}" + # Add state param to OIDC URL + auth_url = f"{auth_url}&state={state}" response = RedirectResponse(auth_url) response.set_cookie('session_id', session_id) return response @auth_router.get("/callback") -async def callback(request: Request, code: str): +async def callback(request: Request, code: str, state: str = "default"): session_id = request.cookies.get('session_id') if not session_id: raise HTTPException(status_code=400, detail="No session") @@ -53,19 +57,32 @@ async def callback(request: Request, code: str): user_info ) coder_api.ensure_workspace_exists(user_data['username']) - - except Exception as e: print(f"Error in user/workspace setup: {str(e)}") # Continue with login even if Coder API fails - - return RedirectResponse('/') + + if state == "popup": + return FileResponse(os.path.join(STATIC_DIR, "auth/popup-close.html")) + else: + return RedirectResponse('/') @auth_router.get("/logout") async def logout(request: Request): session_id = request.cookies.get('session_id') if session_id in sessions: del sessions[session_id] - response = RedirectResponse('/') - response.delete_cookie('session_id') + + # Create a response that doesn't redirect but still clears the cookie + from fastapi.responses import JSONResponse + response = JSONResponse({"status": "success", "message": "Logged out successfully"}) + + # Clear the session_id cookie with all necessary parameters + response.delete_cookie( + key="session_id", + path="/", + domain=None, # Use None to match the current domain + secure=request.url.scheme == "https", + httponly=True + ) + return response diff --git a/src/frontend/src/auth/AuthModal.tsx b/src/frontend/src/auth/AuthModal.tsx index 7dc4273..088bdb8 100644 --- a/src/frontend/src/auth/AuthModal.tsx +++ b/src/frontend/src/auth/AuthModal.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react"; import ReactDOM from "react-dom"; import { capture } from "../utils/posthog"; import { Mail } from "lucide-react"; +import { queryClient } from "../api/queryClient"; import "../styles/AuthModal.scss"; interface AuthModalProps { @@ -16,11 +17,22 @@ const AuthModal: React.FC = ({ useEffect(() => { setIsMounted(true); capture("auth_modal_shown"); - // Prevent scrolling when modal is open - document.body.style.overflow = "hidden"; + }, []); + useEffect(() => { + const checkLocalStorage = () => { + const authCompleted = localStorage.getItem('auth_completed'); + if (authCompleted) { + localStorage.removeItem('auth_completed'); + queryClient.invalidateQueries({ queryKey: ['auth'] }); + clearInterval(intervalId); + } + }; + + const intervalId = setInterval(checkLocalStorage, 500); + return () => { - document.body.style.overflow = "auto"; + clearInterval(intervalId); }; }, []); @@ -62,7 +74,11 @@ const AuthModal: React.FC = ({