diff --git a/be/app/routers/auth.py b/be/app/routers/auth.py
index ffb7b2c..a1a1a46 100644
--- a/be/app/routers/auth.py
+++ b/be/app/routers/auth.py
@@ -3,6 +3,7 @@
from fastapi.responses import RedirectResponse
from authlib.integrations.starlette_client import OAuth
from starlette.config import Config as AuthlibConfig
+from fastapi.responses import JSONResponse
from app import config
from app.models import TokenRequest
@@ -33,6 +34,10 @@ async def login(request: Request):
@router.post("/api/auth/token")
def get_token(request: Request, token_req: TokenRequest):
+ """
+ Exchanges the SSO code for an Access Token, sets the token in a secure
+ HTTP-only cookie, and returns minimal user details.
+ """
data = {
"grant_type": "authorization_code",
"code": token_req.code,
@@ -41,17 +46,70 @@ def get_token(request: Request, token_req: TokenRequest):
"redirect_uri": config.REDIRECT_URI,
}
response = requests.post(config.TOKEN_URL, data=data)
+
if response.status_code != 200:
raise HTTPException(status_code=400, detail="Failed to exchange code for token")
token_data = response.json()
- user_info_resp = requests.get(f"{config.OPENID_PROVIDER_URL}/userinfo",
- headers={'Authorization': f'Bearer {token_data["access_token"]}'})
- user_email = user_info_resp.json()['email']
- request.session['user'] = user_email
- return user_email
+ access_token = token_data["access_token"]
+
+ user_info_resp = requests.get(
+ f"{config.OPENID_PROVIDER_URL}/userinfo",
+ headers={'Authorization': f'Bearer {access_token}'}
+ )
+ user_data = user_info_resp.json()
+
+ user_return_data = {
+ "id": user_data.get('sub', user_data.get('email', 'unknown')),
+ "email": user_data.get('email', 'unknown'),
+ }
+
+ response_to_client = JSONResponse(content=user_return_data)
-@router.get("/api/logout")
+ request.session['user'] = user_data.get('email')
+ request.session['access_token'] = access_token
+
+ return response_to_client
+
+@router.get("/api/auth/session")
+def check_session(request: Request):
+ # 1. Get the token from the cookie sent by the browser
+ access_token = request.session.get("access_token")
+
+ if not access_token:
+ # If no cookie exists, return 401 Unauthorized
+ raise HTTPException(status_code=401, detail="No session token found")
+
+ # 2. Use the token to get the user info/validate it against the SSO provider
+ # NOTE: Your BE may need to check the token's expiration itself before calling the SSO provider
+ user_info_resp = requests.get(
+ f"{config.OPENID_PROVIDER_URL}/userinfo",
+ headers={'Authorization': f'Bearer {access_token}'}
+ )
+
+ if user_info_resp.status_code != 200:
+ # If the SSO provider says the token is invalid/expired
+ raise HTTPException(status_code=401, detail="Token validation failed or expired")
+
+ # 3. Token is valid. Return the user data to the frontend.
+ user_data = user_info_resp.json()
+ return {
+ "id": user_data.get('sub', user_data.get('email', 'unknown')),
+ "email": user_data.get('email', 'unknown'),
+ }
+
+@router.post("/api/logout")
def logout(request: Request):
+ """
+ Clears the server-side session.
+ """
+ request.session.pop("user", None)
+ request.session.pop("access_token", None)
+ return JSONResponse(content={"message": "Logged out successfully"})
+
+@router.get("/api/logout")
+def logout_get(request: Request):
+ """Fallback GET logout endpoint (original behavior)"""
request.session.pop("user", None)
+ request.session.pop("access_token", None)
return RedirectResponse(config.REDIRECT_URI)
\ No newline at end of file
diff --git a/be/tests/routes/test_api_auth.py b/be/tests/routes/test_api_auth.py
index acc622f..4aafcae 100644
--- a/be/tests/routes/test_api_auth.py
+++ b/be/tests/routes/test_api_auth.py
@@ -52,7 +52,7 @@ def test_get_token_success(mock_requests_get, mock_requests_post, client: TestCl
response = client.post("/api/auth/token", json={"code": "test-code"})
assert response.status_code == 200
- assert response.json() == "user@example.com"
+ assert response.json() == {'email': 'user@example.com', 'id': 'user@example.com'}
# Check that the session was set
assert client.cookies.get("session") is not None
@@ -67,7 +67,7 @@ def test_get_token_failure(mock_requests_post, client: TestClient):
def test_logout(client: TestClient):
"""Test that the logout endpoint returns a redirect."""
# We test the direct outcome (a redirect response) rather than inspect session state
- response = client.get("/api/logout", follow_redirects=False)
+ response = client.post("/api/logout", follow_redirects=False)
# Check for a redirect status code (307 is used by FastAPI for temporary redirects)
- assert response.status_code == 307
+ assert client.cookies.get("session") is None
diff --git a/fe/src/lib/stores.js b/fe/src/lib/stores.js
index c4152ca..9f63005 100644
--- a/fe/src/lib/stores.js
+++ b/fe/src/lib/stores.js
@@ -4,5 +4,7 @@ export const selectedNamespce = writable(null);
export const selectedTable = writable(null);
export const sample_limit = writable(100);
+export const user = writable(null);
+
export const healthEnabled = writable(false);
export const HEALTH_DISABLED_MESSAGE = 'Feature is disabled. Please contact your app administrator or read the documentation to enable DB connection.';
\ No newline at end of file
diff --git a/fe/src/routes/+layout.svelte b/fe/src/routes/+layout.svelte
index 1358cd7..adf750c 100644
--- a/fe/src/routes/+layout.svelte
+++ b/fe/src/routes/+layout.svelte
@@ -1,136 +1,179 @@
-
-
-
- {#if extra_header_links.length > 0}
+
+
+
+ {#if extra_header_links.length > 0}
{#each extra_header_links as link}
-
-
-
- {#if AUTH_ENABLED}
-
-
- {/if}
- {#if extra_links.length > 0}
+
+
+
+
+ {#if AUTH_ENABLED}
+
+
+ {/if}
+ {#if extra_links.length > 0}
{#each extra_links as link}
@@ -165,16 +208,16 @@
{/if}
-
+
\ No newline at end of file