brain / src /services /api /routes /auth.py
Leon4gr45's picture
Deploy complete BrainAPI source d15a5889e878
2f1e7ff verified
Raw History Blame Contribute Delete
2.43 kB
"""TLS console login; browsers receive an opaque cookie, never the input token."""
import os
from fastapi import APIRouter, HTTPException, Request, Response
from pydantic import BaseModel, SecretStr, Field
from src.services.api.constants.auth import SessionResponse, LogoutResponse
from src.services.auth import AuthError, get_auth_service
from src.services.console_sessions import SESSION_COOKIE, get_console_sessions
auth_router = APIRouter(prefix="/auth", tags=["auth"])
class SessionLogin(BaseModel):
token: SecretStr = Field(min_length=1, max_length=4096)
def check_origin(request):
origin = request.headers.get("origin")
allowed = (os.getenv("PUBLIC_BASE_URL") or str(request.base_url)).rstrip("/")
if origin and origin != allowed:
raise HTTPException(403, "CSRF_ORIGIN_INVALID")
@auth_router.post("/session", response_model=SessionResponse)
async def login(body: SessionLogin, request: Request, response: Response):
check_origin(request)
if request.url.scheme != "https" and os.getenv("ENV", "production") != "development":
raise HTTPException(400, "HTTPS_REQUIRED")
try:
principal = get_auth_service().authenticate_token(body.token.get_secret_value())
except AuthError as error:
raise HTTPException(error.status, error.code) from None
sessions = get_console_sessions()
# Discard a prior session on login to prevent session fixation/leaks.
sessions.revoke(request.cookies.get(SESSION_COOKIE))
cookie, csrf, ttl = sessions.create(principal)
response.set_cookie(SESSION_COOKIE, cookie, max_age=ttl, httponly=True,
secure=True, samesite="strict", path="/")
response.headers["Cache-Control"] = "no-store"
return {**principal.public(), "csrf_token": csrf}
@auth_router.get("/me", response_model=SessionResponse)
async def me(request: Request, response: Response):
response.headers["Cache-Control"] = "no-store"
return {**request.state.principal.public(), "csrf_token": getattr(request.state, "csrf_token", None)}
@auth_router.post("/logout", response_model=LogoutResponse)
async def logout(request: Request, response: Response):
get_console_sessions().revoke(request.cookies.get(SESSION_COOKIE))
response.delete_cookie(SESSION_COOKIE, secure=True, httponly=True, samesite="strict", path="/")
response.headers["Cache-Control"] = "no-store"
return {"authenticated": False}