"""FlowDeck — Auth routes: login, callback, logout.""" from __future__ import annotations import logging import secrets from fastapi import APIRouter, Request, Query from fastapi.responses import RedirectResponse, HTMLResponse from app.auth.session import SessionManager from app.auth.oauth import gitea_oauth from app.config import settings logger = logging.getLogger(__name__) router = APIRouter(tags=["auth"], prefix="/auth") @router.get("/login") async def login(request: Request): """Redirect to Gitea OAuth2 authorize page.""" if not gitea_oauth.enabled: # Fallback: use global token, create a fake session from app.db import get_conn with get_conn() as conn: user = conn.execute("SELECT * FROM users WHERE login='admin'").fetchone() if not user: conn.execute("INSERT INTO users (login, full_name, email, avatar_url) VALUES ('admin', 'Admin', '', '')") conn.commit() user = conn.execute("SELECT * FROM users WHERE login='admin'").fetchone() user_data = dict(user) session = SessionManager.create_session(user_data) response = RedirectResponse(url="/", status_code=302) response.set_cookie("flowdeck_session", session, httponly=True, max_age=86400 * 7, samesite="lax") return response state = secrets.token_hex(32) request.session["oauth_state"] = state auth_url = gitea_oauth.get_authorize_url(state) return RedirectResponse(url=auth_url, status_code=302) @router.get("/callback") async def callback( request: Request, code: str = Query(...), state: str = Query(...), ): """Handle OAuth2 callback from Gitea.""" # Validate state expected_state = request.session.get("oauth_state", "") if not expected_state or state != expected_state: return HTMLResponse("

Invalid state

", status_code=400) # Exchange code for token token_data = await gitea_oauth.exchange_code(code) if not token_data: return HTMLResponse("

Token exchange failed

", status_code=400) access_token = token_data.get("access_token") if not access_token: return HTMLResponse("

No access token

", status_code=400) # Get user info user = await gitea_oauth.get_user(access_token) if not user: return HTMLResponse("

Failed to get user

", status_code=400) # Store user in DB from app.db import get_conn with get_conn() as conn: conn.execute( """INSERT INTO users (login, full_name, email, avatar_url) VALUES (?, ?, ?, ?) ON CONFLICT(login) DO UPDATE SET full_name=excluded.full_name, email=excluded.email, avatar_url=excluded.avatar_url""", (user["login"], user.get("full_name", ""), user.get("email", ""), user.get("avatar_url", "")), ) conn.commit() # Store token SessionManager.store_token(user["id"], access_token) # Also store user in local DB with get_conn() as conn: db_user = conn.execute("SELECT * FROM users WHERE login=?", (user["login"],)).fetchone() user_data = dict(db_user) if db_user else user # Create session session = SessionManager.create_session(user_data) response = RedirectResponse(url="/", status_code=302) response.set_cookie("flowdeck_session", session, httponly=True, max_age=86400 * 7, samesite="lax") return response @router.get("/logout") async def logout(): """Clear session and redirect to dashboard.""" response = RedirectResponse(url="/", status_code=302) response.delete_cookie("flowdeck_session") return response @router.get("/user") async def current_user(request: Request): """Return current user info as JSON.""" from app.auth.session import get_current_user as gcu user = await gcu(request) if not user: return {"authenticated": False} return {"authenticated": True, "user": user}