"""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}