import uuid from datetime import datetime, timedelta, timezone import jwt from fastapi import Cookie, Depends, HTTPException, status from pwdlib import PasswordHash from sqlalchemy import select from sqlalchemy.orm import Session from .config import settings from .database import get_db from .models import UserAccount COOKIE_NAME = "urmi_session" ADMIN_COOKIE_NAME = "urmi_admin_session" password_hash = PasswordHash.recommended() def normalize_email(email: str) -> str: return email.strip().casefold() def create_token(user_id: uuid.UUID, purpose: str = "session", minutes: int | None = None) -> str: now = datetime.now(timezone.utc) return jwt.encode( { "sub": str(user_id), "purpose": purpose, "iat": now, "exp": now + timedelta(minutes=minutes or settings.access_token_minutes), }, settings.auth_secret, algorithm="HS256", ) def decode_token(token: str, purpose: str = "session") -> dict: try: payload = jwt.decode(token, settings.auth_secret, algorithms=["HS256"]) if payload.get("purpose") != purpose: raise ValueError("invalid token purpose") return payload except (jwt.PyJWTError, ValueError) as exc: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid session") from exc def current_user( urmi_session: str | None = Cookie(default=None, alias=COOKIE_NAME), db: Session = Depends(get_db), ) -> UserAccount: if not urmi_session: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication required") payload = decode_token(urmi_session) try: user_id = uuid.UUID(payload["sub"]) except (KeyError, ValueError) as exc: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid session") from exc user = db.scalar(select(UserAccount).where(UserAccount.id == user_id)) if not user or user.status != "active": raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Account unavailable") return user def create_admin_token(username: str) -> str: now = datetime.now(timezone.utc) return jwt.encode( { "sub": username, "purpose": "admin_session", "iat": now, "exp": now + timedelta(minutes=settings.access_token_minutes), }, settings.auth_secret, algorithm="HS256", ) def current_admin(urmi_admin_session: str | None = Cookie(default=None, alias=ADMIN_COOKIE_NAME)) -> str: if not urmi_admin_session: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Admin authentication required") payload = decode_token(urmi_admin_session, purpose="admin_session") username = payload.get("sub") if username != settings.admin_username: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid admin session") return username