Files
group_xinghuo_jinrong/app/api/auth_adapter.py
T
zhanghongyu_0626 6d780d45c3 feat(auth): Enhance JWT handling and error management in authentication flow
- Updated the login endpoint to utilize shared JWT issuer/audience settings, improving consistency across modules.
- Introduced error handling for unknown accounts during token issuance, raising an UnauthorizedError when necessary.
- Enhanced traceability by adding trace and request IDs to responses, ensuring better tracking of requests.
- Refactored exception handling in middleware to properly bubble up application-specific errors, preventing them from being swallowed.
- Added new utility functions for generating trace headers to improve debugging capabilities.

This update strengthens the authentication process and enhances error visibility, contributing to a more robust and maintainable codebase.
2026-09-08 19:55:08 +08:00

105 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""宿主 AuthContext → 模块 AuthContext 适配器(S2 接缝,AL-09 接线用)。
状态:**AL-09 已接线(JWT 统一 + 适配器可用)**。
- `/api/auth/login` 与模块 API 共用 `settings.jwt_issuer` / `jwt_audience` 签发的 token;
- chat / risk 一律 `deps.get_auth_context` 验签;
- 若某宿主路由仍产出 `schemas.AuthContext`,经 `module_auth_from_host()` 转换后进入模块 Service。
设计取舍:
- **不 import 宿主模块**(main 的 `app/model/schemas.py` 等),改用鸭子类型读取字段,
保证本文件在当前分支(宿主文件尚不存在)也能被导入与单测;
- 只做字段映射与语义补齐,不做任何权限判定(判定仍在 `deps.py` 的矩阵内);
- 权限原样透传,另提供 `perm_matches` 兼容宿主的 `前缀:*` 通配语义,
避免模块侧 `has_permission` 精确匹配漏判通配权限。
字段映射(宿主 → 模块):
sub → actor_id (字段名差异,模块全量代码依赖 actor_id)
trace_id → contextvar 绑定 (模块不存字段,走 utils.trace.current_trace)
agent_type → 不入模,仅返回 (模块按 agent_type 单独传参做准入判定)
roles/permissions/tenant_id/jti/customer_id → 原样透传
"""
from __future__ import annotations
from typing import Any
from app.api.deps import AuthContext
from app.utils.trace import set_trace
__all__ = ["from_host_auth", "perm_matches", "resolve_agent_type", "module_auth_from_host", "HostAuthAdapterError"]
class HostAuthAdapterError(ValueError):
"""宿主上下文字段缺失且无法安全兜底时抛出(fail-closed,不静默降级)。"""
def perm_matches(perm: str, permissions: list[str] | tuple[str, ...]) -> bool:
"""权限匹配:精确命中,或命中宿主的 `前缀:*` 通配写法。
例:`risk:alert:read` 在权限集含 `risk:*` 时返回 True。
"""
if not perm:
return False
if perm in permissions:
return True
prefix = perm.split(":", 1)[0]
return any(p == f"{prefix}:*" or p.endswith(":*") and p.startswith(f"{prefix}:") for p in permissions)
def from_host_auth(
host_ctx: Any,
*,
agent_type: str | None = None,
bind_trace: bool = True,
) -> AuthContext:
"""把宿主 AuthContext 转成模块 AuthContext。
Args:
host_ctx: 宿主的 `AuthContext`(含 sub/roles/token_type/... 的任意对象)。
agent_type: 显式指定请求目标 agent;缺省时取宿主的 `agent_type` 字段。
bind_trace: 是否把宿主的 `trace_id` 绑定到模块 trace contextvar。
Raises:
HostAuthAdapterError: 连主体标识(sub/actor_id)都取不到时抛出。
"""
actor_id = getattr(host_ctx, "sub", None) or getattr(host_ctx, "actor_id", None)
if not actor_id:
# fail-closed:拿不到主体就拒绝,不匿名放行
raise HostAuthAdapterError("宿主 AuthContext 缺少主体标识(sub/actor_id),拒绝放行")
roles = list(getattr(host_ctx, "roles", None) or [])
permissions = list(getattr(host_ctx, "permissions", None) or [])
token_type = getattr(host_ctx, "token_type", None) or "staff"
customer_id = getattr(host_ctx, "customer_id", None)
# customer 类 token 若未显式带 customer_id,回退为主体自身
if customer_id is None and token_type == "customer":
customer_id = actor_id
if bind_trace:
trace_id = getattr(host_ctx, "trace_id", None)
if trace_id:
set_trace(str(trace_id))
return AuthContext(
actor_id=str(actor_id),
roles=roles,
customer_id=customer_id,
token_type=str(token_type),
permissions=permissions,
tenant_id=getattr(host_ctx, "tenant_id", None),
jti=getattr(host_ctx, "jti", None),
)
def resolve_agent_type(host_ctx: Any, default: str = "risk") -> str:
"""取请求目标 agent 类型;宿主无该字段时回退 default。"""
value = getattr(host_ctx, "agent_type", None)
return str(value) if value else default
def module_auth_from_host(host_ctx: Any, *, agent_type: str | None = None) -> AuthContext:
"""S2 接缝接线入口:宿主 AuthContext → 模块 AuthContext(含 trace 绑定)。"""
resolved = agent_type or resolve_agent_type(host_ctx)
return from_host_auth(host_ctx, agent_type=resolved, bind_trace=True)