import uuid from datetime import UTC, datetime, timedelta from fastapi import Response from sqlalchemy import delete from sqlalchemy.ext.asyncio import AsyncSession from app.config import get_settings from app.models import AuthSession, User COOKIE_NAME = "pablan_session" async def create_auth_session(db: AsyncSession, user: User) -> AuthSession: settings = get_settings() session = AuthSession( user_id=user.id, expires_at=datetime.now(UTC) + timedelta(days=settings.auth_session_ttl_days), ) db.add(session) await db.flush() return session async def get_valid_session( db: AsyncSession, session_id: uuid.UUID ) -> AuthSession | None: session = await db.get(AuthSession, session_id) if session is None or session.expires_at <= datetime.now(UTC): return None return session async def revoke_user_sessions( db: AsyncSession, user_id: uuid.UUID, *, keep_session_id: uuid.UUID | None = None ) -> None: """Log a user out everywhere — the session-revocation primitive. `keep_session_id` spares the caller's own session, which is what a self-service password change wants: every other device is logged out, the one you are typing on is not. """ statement = delete(AuthSession).where(AuthSession.user_id == user_id) if keep_session_id is not None: statement = statement.where(AuthSession.id != keep_session_id) await db.execute(statement) def set_session_cookie(response: Response, session: AuthSession) -> None: settings = get_settings() response.set_cookie( COOKIE_NAME, str(session.id), max_age=settings.auth_session_ttl_days * 24 * 60 * 60, httponly=True, secure=settings.cookie_secure, samesite="lax", ) def clear_session_cookie(response: Response) -> None: settings = get_settings() response.delete_cookie( COOKIE_NAME, httponly=True, secure=settings.cookie_secure, samesite="lax", )