feat: embed permission claims in JWT and add captcha verification

- Add GroupClaims model for JWT permission snapshots
- Add JWTPayload model for typed JWT decoding
- Refactor auth middleware: jwt_required (no DB) -> admin_required (no DB) -> auth_required (DB)
- Add UserBanStore for instant ban enforcement via Redis + memory fallback
- Fix status check bug: StrEnum is always truthy, use explicit != ACTIVE
- Shorten access_token expiry from 3h to 1h
- Add CaptchaScene enum and verify_captcha_if_needed service
- Add require_captcha dependency injection factory
- Add CLA document and new default settings
- Update all tests for new JWT API

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-10 19:07:00 +08:00
parent 209cb24ab4
commit a99091ea7a
20 changed files with 766 additions and 244 deletions

View File

@@ -99,7 +99,7 @@ class LoginRequest(SQLModelBase):
captcha: str | None = None
"""验证码"""
two_fa_code: int | None = Field(min_length=6, max_length=6)
two_fa_code: int | None = Field(default=None, min_length=6, max_length=6)
"""两步验证代码"""
@@ -151,6 +151,22 @@ class WebAuthnInfo(SQLModelBase):
transports: list[str]
"""支持的传输方式"""
class JWTPayload(SQLModelBase):
"""JWT 访问令牌解析后的 claims"""
sub: UUID
"""用户 ID"""
jti: UUID
"""令牌唯一标识符"""
status: UserStatus
"""用户状态"""
group: "GroupClaims"
"""用户组权限快照"""
class AccessTokenBase(BaseModel):
"""访问令牌响应 DTO"""
@@ -370,10 +386,11 @@ class UserAdminDetailResponse(UserPublic):
# 前向引用导入
from .group import GroupResponse # noqa: E402
from .group import GroupClaims, GroupResponse # noqa: E402
from .user_authn import AuthnResponse # noqa: E402
# 更新前向引用
JWTPayload.model_rebuild()
UserResponse.model_rebuild()
UserSettingResponse.model_rebuild()