Refactor and enhance OAuth2.0 implementation; update models and routes

- Refactored AdminSummaryData and AdminSummaryResponse classes for better clarity.
- Added OAUTH type to SettingsType enum.
- Cleaned up imports in webdav.py.
- Updated admin router to improve summary data retrieval and response handling.
- Enhanced file management routes with better condition handling and user storage updates.
- Improved group management routes by optimizing data retrieval.
- Refined task management routes for better condition handling.
- Updated user management routes to streamline access token retrieval.
- Implemented a new captcha verification structure with abstract base class.
- Removed deprecated env.md file and replaced with a new structured version.
- Introduced a unified OAuth2.0 client base class for GitHub and QQ integrations.
- Enhanced password management with improved hashing strategies.
- Added detailed comments and documentation throughout the codebase for clarity.
This commit is contained in:
2026-01-12 18:07:44 +08:00
parent 61ddc96f17
commit d2c914cff8
29 changed files with 814 additions and 4609 deletions

View File

@@ -13,7 +13,7 @@ oauth2_scheme = OAuth2PasswordBearer(
refreshUrl="/api/v1/user/session/refresh",
)
SECRET_KEY = ''
SECRET_KEY: str = ''
async def load_secret_key() -> None:
@@ -26,10 +26,10 @@ async def load_secret_key() -> None:
global SECRET_KEY
async for session in get_session():
setting = await Setting.get(
setting: Setting = await Setting.get(
session,
(Setting.type == "auth") & (Setting.name == "secret_key")
)
) # type: ignore
if setting:
SECRET_KEY = setting.value
@@ -40,7 +40,14 @@ def build_token_payload(
algorithm: str,
expires_delta: timedelta | None = None,
) -> tuple[str, datetime]:
"""构建令牌"""
"""
构建令牌。
:param data: 需要放进 JWT Payload 的字段
:param is_refresh: 是否为刷新令牌
:param algorithm: JWT 签名算法
:param expires_delta: 过期时间
"""
to_encode = data.copy()
@@ -61,8 +68,11 @@ def build_token_payload(
# 访问令牌
def create_access_token(data: dict, expires_delta: timedelta | None = None,
algorithm: str = "HS256") -> AccessTokenBase:
def create_access_token(
data: dict,
expires_delta: timedelta | None = None,
algorithm: str = "HS256"
) -> AccessTokenBase:
"""
生成访问令牌,默认有效期 3 小时。
@@ -73,7 +83,12 @@ def create_access_token(data: dict, expires_delta: timedelta | None = None,
:return: 包含密钥本身和过期时间的 `AccessTokenBase`
"""
access_token, expire_at = build_token_payload(data, False, algorithm, expires_delta)
access_token, expire_at = build_token_payload(
data,
False,
algorithm,
expires_delta
)
return AccessTokenBase(
access_token=access_token,
access_expires=expire_at,
@@ -81,8 +96,11 @@ def create_access_token(data: dict, expires_delta: timedelta | None = None,
# 刷新令牌
def create_refresh_token(data: dict, expires_delta: timedelta | None = None,
algorithm: str = "HS256") -> RefreshTokenBase:
def create_refresh_token(
data: dict,
expires_delta: timedelta | None = None,
algorithm: str = "HS256"
) -> RefreshTokenBase:
"""
生成刷新令牌,默认有效期 30 天。
@@ -93,7 +111,12 @@ def create_refresh_token(data: dict, expires_delta: timedelta | None = None,
:return: 包含密钥本身和过期时间的 `RefreshTokenBase`
"""
refresh_token, expire_at = build_token_payload(data, True, algorithm, expires_delta)
refresh_token, expire_at = build_token_payload(
data,
True,
algorithm,
expires_delta
)
return RefreshTokenBase(
refresh_token=refresh_token,
refresh_expires=expire_at,

View File

@@ -13,7 +13,7 @@ license_info = {"name": "GPLv3", "url": "https://opensource.org/license/gpl-3.0"
BackendVersion = "0.0.1"
"""后端版本"""
IsPro = False
IsPro: bool = False
mode: str = os.getenv('MODE', 'master')
"""运行模式"""

View File

@@ -1,4 +1,5 @@
import secrets
from typing import Literal
from loguru import logger
from argon2 import PasswordHasher
@@ -11,7 +12,23 @@ from pydantic import BaseModel, Field
from utils.JWT import SECRET_KEY
from utils.conf import appmeta
_ph = PasswordHasher()
# FIRST RECOMMENDED option per RFC 9106.
_ph_lowmem = PasswordHasher(
salt_len=16,
hash_len=32,
time_cost=3,
memory_cost=65536, # 64 MiB
parallelism=4,
)
# SECOND RECOMMENDED option per RFC 9106.
_ph_highmem = PasswordHasher(
salt_len=16,
hash_len=32,
time_cost=1,
memory_cost=2097152, # 2 GiB
parallelism=4,
)
class PasswordStatus(StrEnum):
"""密码校验状态枚举"""
@@ -48,7 +65,8 @@ class Password:
@staticmethod
def generate(
length: int = 8
length: int = 8,
url_safe: bool = False
) -> str:
"""
生成指定长度的随机密码。
@@ -58,7 +76,16 @@ class Password:
:return: 随机密码
:rtype: str
"""
return secrets.token_hex(length)
if url_safe:
return secrets.token_urlsafe(length)
else:
return secrets.token_hex(length)
@staticmethod
def generate_hex(
length: int = 8
) -> bytes:
return secrets.token_bytes(length)
@staticmethod
def hash(
@@ -72,7 +99,7 @@ class Password:
:param password: 需要哈希的原始密码
:return: Argon2 哈希字符串
"""
return _ph.hash(password)
return _ph_lowmem.hash(password)
@staticmethod
def verify(
@@ -87,21 +114,16 @@ class Password:
:return: 如果密码匹配返回 True, 否则返回 False
"""
try:
# verify 函数会自动解析 stored_password 中的盐和参数
_ph.verify(hash, password)
_ph_lowmem.verify(hash, password)
# 检查哈希参数是否已过时。如果返回True
# 意味着你应该使用新的参数重新哈希密码并更新存储。
# 这是一个很好的实践,可以随着时间推移增强安全性。
if _ph.check_needs_rehash(hash):
# 检查哈希参数是否已过时
if _ph_lowmem.check_needs_rehash(hash):
logger.warning("密码哈希参数已过时,建议重新哈希并更新。")
return PasswordStatus.EXPIRED
return PasswordStatus.VALID
except VerifyMismatchError:
# 这是预期的异常,当密码不匹配时触发。
return PasswordStatus.INVALID
# 其他异常(如哈希格式错误)应该传播,让调用方感知系统问题
@staticmethod
async def generate_totp(