Files
video-gen/video-gen-api/app/dependencies.py
T

157 lines
4.9 KiB
Python

from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.base import async_session
from app.models.user import User
from app.services.auth import decode_access_token, user_must_set_password
from app.services.credit.query_service import attach_credit_snapshot, get_available_credits
from app.services.system_config_cache import get_system_config_value
security = HTTPBearer(auto_error=False)
async def get_db():
async with async_session() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
finally:
await session.close()
async def get_current_user_allow_password_pending(
credentials: HTTPAuthorizationCredentials | None = Depends(security),
db: AsyncSession = Depends(get_db),
) -> User:
if not credentials:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="未登录",
)
payload = decode_access_token(credentials.credentials)
if not payload:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="登录已过期",
)
user_id = payload.get("sub")
token_version = payload.get("ver", 0)
device_type = payload.get("dev", "pc")
# Skip captcha tokens
if user_id and user_id.startswith("captcha:"):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无效的凭证",
)
result = await db.execute(select(User).where(User.id == user_id).limit(1))
user = result.scalar_one_or_none()
if not user or not user.is_active:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="账号不存在或已禁用",
)
# 单设备登录校验 — 根据全局开关 + 用户级覆盖决定是否启用
override = getattr(user, "single_device_login_override", None)
if override is True:
enabled = True
elif override is False:
enabled = False
else:
# 跟随全局设置
config_val = await get_system_config_value(db, "single_device_login_enabled")
enabled = config_val is not None and config_val.lower() in ("true", "1", "yes")
if enabled:
# 按设备类型比对对应版本号
if device_type == "mobile":
current_version = user.mobile_token_version
else:
current_version = user.pc_token_version
if token_version != current_version:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="账号已在其他设备登录,请重新登录",
)
attach_credit_snapshot(user, await get_available_credits(db, user.id))
return user
async def get_current_user(
current_user: User = Depends(get_current_user_allow_password_pending),
) -> User:
if user_must_set_password(current_user):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail={
"code": "PASSWORD_REQUIRED",
"message": "请先设置登录密码",
},
)
return current_user
async def get_optional_current_user(
credentials: HTTPAuthorizationCredentials | None = Depends(security),
db: AsyncSession = Depends(get_db),
) -> User | None:
if not credentials:
return None
user_id = decode_access_token(credentials.credentials)
if not user_id:
return None
if user_id.startswith("captcha:"):
return None
result = await db.execute(select(User).where(User.id == user_id).limit(1))
user = result.scalar_one_or_none()
if not user or not user.is_active:
return None
if user_must_set_password(user):
return None
attach_credit_snapshot(user, await get_available_credits(db, user.id))
return user
async def get_admin_user(
current_user: User = Depends(get_current_user_allow_password_pending),
) -> User:
"""验证后台用户权限。
- user_type="admin" 的后台用户即可通过(含非管理员子账号)
- 前端通过 allowed_menus 控制非管理员子账号的菜单可见性
- 非后台用户: 403 拒绝
"""
if current_user.user_type != "admin":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="需要后台用户权限",
)
return current_user
async def get_backend_user(
current_user: User = Depends(get_current_user_allow_password_pending),
) -> User:
"""与 get_admin_user 等价: 验证 user_type="admin" 的后台用户。"""
if current_user.user_type != "admin":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="需要后台用户权限",
)
return current_user