PMFlow/backend/app/dependencies/auth.py

89 lines
2.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
鉴权依赖注入
JWT 验证、当前用户、角色检查。
"""
from enum import Enum
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from ..database import get_db
from ..dependencies.tenant import get_tenant_id
from ..models import User, UserStatus # noqa: E402
from ..services.auth import AuthService, has_any_role
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login")
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db),
) -> User:
"""从 JWT 解析并返回当前认证用户"""
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
username = AuthService.verify_token(token)
if username is None:
raise credentials_exception
result = await db.execute(select(User).where(User.username == username))
user = result.scalar_one_or_none()
if user is None:
raise credentials_exception
return user
async def get_current_active_user(
current_user: User = Depends(get_current_user),
) -> User:
"""当前活跃用户(排除 AWAY / OFFLINE)"""
if (not getattr(current_user, "is_active", True)) or current_user.status != UserStatus.ACTIVE:
raise HTTPException(status_code=403, detail="User is not active")
return current_user
def require_roles(*roles: str):
"""依赖工厂:要求用户具备指定角色之一"""
async def role_checker(
current_user: User = Depends(get_current_user),
) -> User:
user_role = (
current_user.role.value
if isinstance(current_user.role, Enum)
else current_user.role
)
if not has_any_role(user_role, list(roles)):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"Access denied. Required roles: {', '.join(roles)}",
)
return current_user
return role_checker
async def get_current_tenant_id(
current_user: User = Depends(get_current_user),
header_tenant_id: int = Depends(get_tenant_id),
) -> int:
"""兼容旧接口:优先取 JWT 中 tenant_id,没有则回落 Header。"""
try:
tenant_id = int(getattr(current_user, "tenant_id", 0) or 0)
if tenant_id > 0:
return tenant_id
except (TypeError, ValueError):
pass
return int(header_tenant_id or 1)