from typing import Optional from fastapi import Depends, HTTPException, status, Header from fastapi.security import OAuth2PasswordBearer from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from app.core.database import get_db from app.core.security import decode_access_token, hash_token from app.models.models import User, Client, ClientCredential oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login", auto_error=False) async def get_current_user( token: Optional[str] = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db) ) -> User: """Authenticates web UI users via JWT Bearer token.""" if not token: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication required", headers={"WWW-Authenticate": "Bearer"} ) payload = decode_access_token(token) if not payload or "sub" not in payload: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired authentication token", headers={"WWW-Authenticate": "Bearer"} ) user_id = int(payload["sub"]) result = await db.execute(select(User).where(User.id == user_id, User.is_active == True)) user = result.scalar_one_or_none() if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found or deactivated" ) return user async def require_admin(user: User = Depends(get_current_user)) -> User: """Ensures current user has ADMIN role.""" if user.role != "ADMIN": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Admin privilege required" ) return user async def get_current_client( x_device_id: Optional[str] = Header(None, alias="X-Device-Id"), x_device_token: Optional[str] = Header(None, alias="X-Device-Token"), db: AsyncSession = Depends(get_db) ) -> Client: """Authenticates Windows Agent devices via individual device ID and secret token.""" if not x_device_id or not x_device_token: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Device authentication headers (X-Device-Id, X-Device-Token) required" ) token_hash = hash_token(x_device_token) result = await db.execute( select(ClientCredential) .where( ClientCredential.device_id == x_device_id, ClientCredential.token_hash == token_hash, ClientCredential.is_revoked == False ) ) credential = result.scalar_one_or_none() if not credential: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or revoked device credentials" ) client_res = await db.execute(select(Client).where(Client.id == credential.client_id, Client.is_active == True)) client = client_res.scalar_one_or_none() if not client: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Client device not found or inactive" ) return client