from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from sqlalchemy.orm import Session from app.api.deps import CurrentUser from app.core.security import create_access_token, create_refresh_token, decode_token, verify_password from app.db.session import get_db from app.models.domain import User from app.schemas.domain import LoginRequest, TokenPair, UserRead from app.services.audit import write_audit router = APIRouter(prefix="/auth", tags=["auth"]) @router.post("/login", response_model=TokenPair) def login(payload: LoginRequest, db: Session = Depends(get_db)) -> TokenPair: email = payload.email.strip().lower() user = db.scalar(select(User).where(User.email == email)) if not user or not verify_password(payload.password, user.password_hash): if user: user.failed_login_attempts += 1 db.commit() raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials") permissions = sorted({permission for role in user.roles for permission in role.permissions}) user.failed_login_attempts = 0 db.commit() write_audit(db, action="login", object_type="user", object_id=user.id, user_id=user.id) return TokenPair( access_token=create_access_token(user.id, permissions), refresh_token=create_refresh_token(user.id), ) @router.post("/refresh", response_model=TokenPair) def refresh(payload: dict[str, str], db: Session = Depends(get_db)) -> TokenPair: token = payload.get("refresh_token") if not token: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Missing refresh token") try: claims = decode_token(token) except Exception as exc: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token") from exc if claims.get("typ") != "refresh": raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token type") user = db.scalar(select(User).where(User.id == claims.get("sub"), User.is_active.is_(True))) if not user: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Inactive or missing user") permissions = sorted({permission for role in user.roles for permission in role.permissions}) return TokenPair( access_token=create_access_token(user.id, permissions), refresh_token=create_refresh_token(user.id), ) @router.get("/me", response_model=UserRead) def me(user: CurrentUser) -> User: return user @router.post("/logout") def logout(user: CurrentUser, db: Session = Depends(get_db)) -> dict[str, str]: write_audit(db, action="logout", object_type="user", object_id=user.id, user_id=user.id) return {"status": "ok", "at": datetime.utcnow().isoformat()}