from datetime import datetime, timezone from fastapi import HTTPException, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from backend.core.security import create_access_token, verify_password from backend.modules.audit.models import AuditAction, AuditLog, AuditResourceType from backend.modules.users.models import User async def authenticate_user(session: AsyncSession, username: str, password: str) -> User: user = await session.scalar( select(User).where(User.username == username).options(selectinload(User.role), selectinload(User.customer)) ) if not user or not verify_password(password, user.password_hash): raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials") if not user.is_active: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User disabled") user.last_login_at = datetime.now(timezone.utc) await session.commit() await session.refresh(user) return user async def create_login_audit(session: AsyncSession, user: User, request) -> None: audit = AuditLog( user_id=user.id, customer_id=user.customer_id, action=AuditAction.LOGIN, resource_type=AuditResourceType.USER, resource_id=user.id, description="User login success", ip_address=request.client.host if request and request.client else None, user_agent=request.headers.get("User-Agent") if request else None, ) session.add(audit) await session.commit() def build_access_token(user: User) -> str: payload = {"sub": str(user.id), "role": user.role.name if user.role else "", "customer_id": user.customer_id} return create_access_token(payload)