fix: 认证审查修复(角色依赖连锁409门/停用租户token取舍注记/公共面整理)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+31
-21
@@ -97,9 +97,10 @@ def verify_password(password: str, password_hash: str, salt: str) -> bool:
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def _dummy_credentials() -> tuple[str, str]:
|
||||
def dummy_credentials() -> tuple[str, str]:
|
||||
"""固定哑凭据 (password_hash, salt)——登录查无用户时跑一次同代价
|
||||
verify,抹平"用户不存在秒回/密码错 50ms"的时序枚举面。"""
|
||||
verify,抹平"用户不存在秒回/密码错 50ms"的时序枚举面。公共面:
|
||||
auth_routes 的防枚举时序等化依赖它。"""
|
||||
return hash_password("", salt=b"\x00" * 16)
|
||||
|
||||
|
||||
@@ -237,8 +238,11 @@ class UserContext:
|
||||
must_change_password: bool
|
||||
|
||||
|
||||
def _bearer_token(request: Request) -> str | None:
|
||||
"""Authorization: Bearer <token> → token;非该形态返回 None。"""
|
||||
def bearer_token(request: Request) -> str | None:
|
||||
"""Authorization: Bearer <token> → token;非该形态返回 None。
|
||||
|
||||
公共面:get_current_user 依赖与 auth_routes 的 logout/change-password
|
||||
(依赖验过后重读头取原串)共用同一解析,不各写一份漂移。"""
|
||||
header = request.headers.get("authorization", "")
|
||||
if not header.startswith("Bearer "):
|
||||
return None
|
||||
@@ -250,7 +254,7 @@ async def get_current_user(request: Request) -> UserContext:
|
||||
|
||||
缺头/坏格式/未知/过期一律 401(静态文案,不细分原因——token 本身
|
||||
就是凭据,细化文案只会泄露信息)。"""
|
||||
token = _bearer_token(request)
|
||||
token = bearer_token(request)
|
||||
record = token_store.validate(token) if token else None
|
||||
if record is None:
|
||||
raise HTTPException(status_code=401, detail="未认证或凭证已失效")
|
||||
@@ -264,22 +268,6 @@ async def get_current_user(request: Request) -> UserContext:
|
||||
CurrentUser = Annotated[UserContext, Depends(get_current_user)]
|
||||
|
||||
|
||||
def require_super(user: CurrentUser) -> UserContext:
|
||||
"""非 super → 403(端点级覆盖在 Task 5;本任务依赖直测)。"""
|
||||
if user.role != "super":
|
||||
raise HTTPException(status_code=403, detail="需要平台超管权限")
|
||||
return user
|
||||
|
||||
|
||||
def require_tenant(slug: str):
|
||||
"""依赖工厂: super 或指定租户的管理员放行,否则 403。"""
|
||||
def _dep(user: CurrentUser) -> UserContext:
|
||||
if user.role == "super" or user.tenant_slug == slug:
|
||||
return user
|
||||
raise HTTPException(status_code=403, detail="无权访问该租户资源")
|
||||
return _dep
|
||||
|
||||
|
||||
def enforce_password_changed(user: CurrentUser) -> UserContext:
|
||||
"""must_change_password=True → 409:除 login/change-password 外的受保护
|
||||
端点都要过本依赖(引导/重置出的初始密码不得只当摆设——spec 强制
|
||||
@@ -293,3 +281,25 @@ def enforce_password_changed(user: CurrentUser) -> UserContext:
|
||||
# `user: platform_user`(= get_current_user + enforce_password_changed,
|
||||
# 链式 Depends 自动先认证后查标志)。
|
||||
platform_user = Annotated[UserContext, Depends(enforce_password_changed)]
|
||||
|
||||
|
||||
def require_super(user: platform_user) -> UserContext:
|
||||
"""非 super → 403(端点级覆盖在 Task 5;本任务依赖直测)。
|
||||
|
||||
参数走 platform_user(认证+已改密连锁):Task 5 路由挂本依赖时
|
||||
must_change 409 门自动生效,不可能静默漏挂。"""
|
||||
if user.role != "super":
|
||||
raise HTTPException(status_code=403, detail="需要平台超管权限")
|
||||
return user
|
||||
|
||||
|
||||
def require_tenant(slug: str):
|
||||
"""依赖工厂: super 或指定租户的管理员放行,否则 403。
|
||||
|
||||
参数同 require_super 走 platform_user——409 门连锁,理由见其
|
||||
docstring。"""
|
||||
def _dep(user: platform_user) -> UserContext:
|
||||
if user.role == "super" or user.tenant_slug == slug:
|
||||
return user
|
||||
raise HTTPException(status_code=403, detail="无权访问该租户资源")
|
||||
return _dep
|
||||
|
||||
Reference in New Issue
Block a user