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