Files
onever_drive/backend/app/api/deps.py
T

88 lines
3.1 KiB
Python

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