Newer
Older
bugtrail / server / app / deps.py
import uuid
from datetime import UTC, datetime

from fastapi import Depends, HTTPException, Request, status
from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession

from .db import get_session
from .models import Project, Session, User
from .security import SESSION_COOKIE_NAME, hash_token


def _extract_credential(request: Request) -> str | None:
    auth = request.headers.get("Authorization", "")
    if auth.startswith("Bearer "):
        return auth.removeprefix("Bearer ").strip()
    return request.cookies.get(SESSION_COOKIE_NAME)


async def get_current_user(
    request: Request, session: AsyncSession = Depends(get_session)
) -> User | None:
    token = _extract_credential(request)
    if not token:
        return None
    result = await session.execute(
        select(Session).where(Session.token_hash == hash_token(token))
    )
    sess = result.scalar_one_or_none()
    if sess is None:
        return None
    if sess.expires_at <= datetime.now(UTC):
        await session.delete(sess)
        await session.commit()
        return None
    await session.execute(
        update(Session).where(Session.id == sess.id).values(last_used_at=datetime.now(UTC))
    )
    await session.commit()
    return sess.user


async def require_user(user: User | None = Depends(get_current_user)) -> User:
    if user is None:
        raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Authentication required")
    return user


async def get_project_for_owner(
    project_id: uuid.UUID, user: User = Depends(require_user), session: AsyncSession = Depends(get_session)
) -> Project:
    project = await session.get(Project, project_id)
    if project is None or project.owner_user_id != user.id:
        raise HTTPException(status.HTTP_404_NOT_FOUND, "Project not found")
    return project