WIP-CHECKPOINT[auth-refactor]: 固化工程师崩溃前部分成果 + 同树其他未提交WIP(仅源码,不含密钥/二进制)-- 待重激活工程师续作
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 阶段5 自动化 依赖注入
|
||||
# =============================================================================
|
||||
# 说明:提供自动化接口的认证/鉴权依赖:
|
||||
# 1. get_current_employee_id — 从 Bearer Token 解析 H5 员工身份
|
||||
# (Redis key 与 app/api/ws.py 的 employee:token:{token} 保持一致)
|
||||
# 2. get_automation_agent — 复用坐席认证(转人工接管/审批用)
|
||||
# 3. get_scenario_config — 加载并校验场景配置是否启用
|
||||
# 高危配置写接口复用 app.dependencies.require_high_risk_otp(管理端 OTP)。
|
||||
# =============================================================================
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import Depends
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.constants import AutomationErrorCode
|
||||
from app.database import get_db
|
||||
from app.dependencies import get_redis
|
||||
from app.models.automation import ScenarioConfig
|
||||
from app.services.cache_service import cache_service
|
||||
from app.utils.response import AppException, ERR_UNAUTHORIZED
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Bearer 认证方案(auto_error=False:缺失时由我们自行返回 1002)
|
||||
_security = HTTPBearer(auto_error=False)
|
||||
|
||||
|
||||
async def get_current_employee_id(
|
||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(_security),
|
||||
) -> str:
|
||||
"""从 Bearer Token 解析 H5 员工身份。
|
||||
|
||||
与 app/api/ws.py 中 H5 WebSocket 的鉴权保持一致:
|
||||
Redis key = employee:token:{token} → employee_id。
|
||||
|
||||
Returns:
|
||||
str: 员工企微 UserID
|
||||
|
||||
Raises:
|
||||
AppException(1002): 令牌缺失或无效
|
||||
"""
|
||||
token = credentials.credentials if credentials else ""
|
||||
if not token:
|
||||
raise AppException(ERR_UNAUTHORIZED.code, "缺少认证令牌")
|
||||
|
||||
# 优先从共享 Redis(cache_service),降级到独立连接
|
||||
employee_id = None
|
||||
try:
|
||||
employee_id = await cache_service.get(f"employee:token:{token}")
|
||||
except Exception as e:
|
||||
logger.warning(f"employee token 校验 Redis 异常: {e}")
|
||||
if not employee_id:
|
||||
redis_client = await get_redis()
|
||||
if redis_client is not None:
|
||||
try:
|
||||
employee_id = await redis_client.get(f"employee:token:{token}")
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.warning(f"employee token 校验失败: {e}")
|
||||
|
||||
if not employee_id:
|
||||
raise AppException(ERR_UNAUTHORIZED.code, "员工令牌无效或已过期")
|
||||
return employee_id
|
||||
|
||||
|
||||
async def get_automation_agent(agent=Depends(lambda: None)) -> str: # pragma: no cover
|
||||
"""占位:坐席认证由 app.api.agents.get_current_agent 直接提供。
|
||||
|
||||
本函数保留以便路由层统一引用;实际坐席端点应使用
|
||||
`from app.api.agents import get_current_agent`。
|
||||
"""
|
||||
return agent
|
||||
|
||||
|
||||
async def get_scenario_config(
|
||||
scenario_key: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> ScenarioConfig:
|
||||
"""加载场景配置,并校验是否启用。
|
||||
|
||||
Raises:
|
||||
AppException(4001): 场景未配置或已禁用
|
||||
"""
|
||||
stmt = select(ScenarioConfig).where(ScenarioConfig.scenario_key == scenario_key)
|
||||
result = await db.execute(stmt)
|
||||
config = result.scalar_one_or_none()
|
||||
if config is None or not config.enabled:
|
||||
raise AppException(
|
||||
AutomationErrorCode.SCENARIO_NOT_FOUND,
|
||||
f"场景未启用或未配置: {scenario_key}",
|
||||
)
|
||||
return config
|
||||
Reference in New Issue
Block a user