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)):
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/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.
+
+
+
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,
});
}
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 = ({