Refactor auth and unify error handling in routers

Renamed AuthRequired/AdminRequired to auth_required/admin_required and updated all references. Replaced direct HTTPException usage with utils.http_exceptions for consistent error handling. Updated router endpoints to use new auth dependency and standardized not implemented responses. Cleaned up unused theme fields in SiteConfigResponse and improved site config endpoint. Minor type and import cleanups across routers and middleware.
This commit is contained in:
2025-12-25 19:08:46 +08:00
parent 5835b4c626
commit abd85e2290
24 changed files with 347 additions and 391 deletions

View File

@@ -1 +1,2 @@
from .password.pwd import Password, PasswordStatus
from .password.pwd import Password, PasswordStatus
from .http import http_exceptions

View File

@@ -1,20 +1,6 @@
from typing import Any, NoReturn
from fastapi import HTTPException
from starlette.status import (
HTTP_400_BAD_REQUEST,
HTTP_401_UNAUTHORIZED,
HTTP_402_PAYMENT_REQUIRED,
HTTP_403_FORBIDDEN,
HTTP_404_NOT_FOUND,
HTTP_409_CONFLICT,
HTTP_429_TOO_MANY_REQUESTS,
HTTP_500_INTERNAL_SERVER_ERROR,
HTTP_501_NOT_IMPLEMENTED,
HTTP_503_SERVICE_UNAVAILABLE,
HTTP_504_GATEWAY_TIMEOUT,
)
from fastapi import HTTPException, status
# --- 400 ---
@@ -24,50 +10,54 @@ def ensure_request_param(to_check: Any, detail: str) -> None:
This function returns None if the check passes.
"""
if not to_check:
raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail=detail)
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=detail)
def raise_bad_request(detail: str = '') -> NoReturn:
"""Raises an HTTP 400 Bad Request exception."""
raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail=detail)
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=detail)
def raise_unauthorized(detail: str) -> NoReturn:
"""Raises an HTTP 401 Unauthorized exception."""
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail=detail)
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=detail)
def raise_insufficient_quota(detail: str = "积分不足,请充值") -> NoReturn:
"""Raises an HTTP 402 Payment Required exception."""
raise HTTPException(status_code=HTTP_402_PAYMENT_REQUIRED, detail=detail)
raise HTTPException(status_code=status.HTTP_402_PAYMENT_REQUIRED, detail=detail)
def raise_forbidden(detail: str) -> NoReturn:
"""Raises an HTTP 403 Forbidden exception."""
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail=detail)
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=detail)
def raise_not_found(detail: str) -> NoReturn:
"""Raises an HTTP 404 Not Found exception."""
raise HTTPException(status_code=HTTP_404_NOT_FOUND, detail=detail)
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=detail)
def raise_conflict(detail: str) -> NoReturn:
"""Raises an HTTP 409 Conflict exception."""
raise HTTPException(status_code=HTTP_409_CONFLICT, detail=detail)
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=detail)
def raise_precondition_required(detail: str) -> NoReturn:
"""Raises an HTTP 428 Precondition required exception."""
raise HTTPException(status_code=status.HTTP_428_PRECONDITION_REQUIRED, detail=detail)
def raise_too_many_requests(detail: str) -> NoReturn:
"""Raises an HTTP 429 Too Many Requests exception."""
raise HTTPException(status_code=HTTP_429_TOO_MANY_REQUESTS, detail=detail)
raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail=detail)
# --- 500 ---
def raise_internal_error(detail: str = "服务器出现故障,请稍后再试或联系管理员") -> NoReturn:
"""Raises an HTTP 500 Internal Server Error exception."""
raise HTTPException(status_code=HTTP_500_INTERNAL_SERVER_ERROR, detail=detail)
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=detail)
def raise_not_implemented(detail: str = "尚未支持这种方法") -> NoReturn:
"""Raises an HTTP 501 Not Implemented exception."""
raise HTTPException(status_code=HTTP_501_NOT_IMPLEMENTED, detail=detail)
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=detail)
def raise_service_unavailable(detail: str) -> NoReturn:
"""Raises an HTTP 503 Service Unavailable exception."""
raise HTTPException(status_code=HTTP_503_SERVICE_UNAVAILABLE, detail=detail)
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=detail)
def raise_gateway_timeout(detail: str) -> NoReturn:
"""Raises an HTTP 504 Gateway Timeout exception."""
raise HTTPException(status_code=HTTP_504_GATEWAY_TIMEOUT, detail=detail)
raise HTTPException(status_code=status.HTTP_504_GATEWAY_TIMEOUT, detail=detail)

View File

@@ -1,4 +1,5 @@
import secrets
from loguru import logger
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
@@ -104,10 +105,11 @@ class Password:
@staticmethod
async def generate_totp(
username: str
*args, **kwargs
) -> TwoFactorResponse:
"""
生成 TOTP 密钥和对应的 URI用于两步验证。
所有的参数将会给到 `pyotp.totp.TOTP`
:return: 包含 TOTP 密钥和 URI 的元组
"""
@@ -121,8 +123,7 @@ class Password:
salt="2fa-setup-salt"
)
otp_uri = pyotp.totp.TOTP(secret).provisioning_uri(
name=username,
otp_uri = pyotp.totp.TOTP(secret, *args, **kwargs).provisioning_uri(
issuer_name=appmeta.APP_NAME
)
@@ -134,17 +135,21 @@ class Password:
@staticmethod
def verify_totp(
secret: str,
code: str
code: int,
*args, **kwargs
) -> PasswordStatus:
"""
验证 TOTP 验证码。
:param secret: TOTP 密钥Base32 编码)
:param code: 用户输入的 6 位验证码
:param args: 传入 `totp.verify` 的参数
:param kwargs: 传入 `totp.verify` 的参数
:return: 验证是否成功
"""
totp = pyotp.TOTP(secret)
if totp.verify(code):
if totp.verify(otp=str(code), *args, **kwargs):
return PasswordStatus.VALID
else:
return PasswordStatus.INVALID