feat(auth): 完成 AUTH-01~04 后端实现 - IP白名单中间件+mfa.py删除+agents.py重构+conftest修复
This commit is contained in:
@@ -405,7 +405,7 @@ async def refresh_token(
|
||||
)
|
||||
|
||||
logger.info(f"Token 刷新成功: employee_id={user_info.get('employee_id')}")
|
||||
return success_response(data={"token": token, "expires_in": TOKEN_TTL_SECONDS})
|
||||
return success_response(data={"token": token, "expires_in": TOKEN_TTL_SECONDS})
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
@@ -526,176 +526,7 @@ async def refresh_token_alias(
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# POST /api/auth_wecom/jsdk-login — 企微 JS-SDK 免认证登录 (v1.8 新增)
|
||||
# 注意:/api/auth_wecom/jsdk-login 接口已按决策4删除
|
||||
# 原功能为"企微免密登录",已按 PRD 要求移除
|
||||
# --------------------------------------------------------------------------
|
||||
# 流程:
|
||||
# 1. 前端通过 wx.agentConfig 获取企微用户 userid
|
||||
# 2. 前端调用本接口,传入 userid
|
||||
# 3. 后端验证 userid 是否是坐席
|
||||
# 4. 生成 token,返回给前端
|
||||
# --------------------------------------------------------------------------
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class WecomJsdkLoginRequest(BaseModel):
|
||||
"""企微 JS-SDK 免认证登录请求"""
|
||||
userid: str = Field(..., description="企微用户 ID(从 wx.agentConfig 获取)")
|
||||
login_source: str = Field(default="wecom_jsdk", description="登录来源标识")
|
||||
|
||||
|
||||
@router.post("/jsdk-login")
|
||||
async def wecom_jsdk_login(
|
||||
body: WecomJsdkLoginRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
redis_client = Depends(get_redis),
|
||||
):
|
||||
"""企微 JS-SDK 免认证登录。
|
||||
|
||||
前端通过企微 JS-SDK (wx.agentConfig) 获取当前用户 ID,
|
||||
然后调用本接口进行免认证登录。
|
||||
|
||||
流程:
|
||||
1. 调用 /wecom/check-role 接口验证 userid 是否是坐席
|
||||
2. 查找或创建坐席记录
|
||||
3. 生成 token,存入 Redis
|
||||
4. 返回 token 和坐席信息
|
||||
|
||||
Args:
|
||||
body: 包含企微 userid
|
||||
db: 数据库会话
|
||||
redis_client: Redis 客户端
|
||||
|
||||
Returns:
|
||||
登录成功:{ code: 0, data: { token, employee_id, name, roles } }
|
||||
"""
|
||||
try:
|
||||
# 1. 验证用户是否具有坐席或管理员角色
|
||||
wecom_service = WecomService(redis_client)
|
||||
userid = body.userid
|
||||
|
||||
# 从数据库查询用户角色(支持坐席和管理员)
|
||||
from app.services.role_mapping_service import RoleMappingService
|
||||
role_service = RoleMappingService(db)
|
||||
user_roles = await role_service.get_user_roles(userid)
|
||||
|
||||
# 检查是否具有坐席或管理员角色
|
||||
has_agent_role = "agent" in user_roles
|
||||
has_admin_role = "admin" in user_roles
|
||||
|
||||
if not has_agent_role and not has_admin_role:
|
||||
# 无坐席或管理员角色,尝试从企微标签检测(兼容旧逻辑)
|
||||
tag_id = getattr(settings, "wecom_agent_tag_id", None)
|
||||
if tag_id:
|
||||
try:
|
||||
access_token = await wecom_service.get_access_token()
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/tag/get?access_token={access_token}&tagid={tag_id}"
|
||||
import httpx
|
||||
async with httpx.AsyncClient(timeout=5.0) as client:
|
||||
resp = await client.get(url)
|
||||
result = resp.json()
|
||||
|
||||
if result.get("errcode", 0) == 0:
|
||||
user_list = result.get("userlist", [])
|
||||
user_ids = [
|
||||
u if isinstance(u, str) else u.get("userid", "")
|
||||
for u in user_list
|
||||
]
|
||||
if userid in user_ids:
|
||||
has_agent_role = True
|
||||
logger.info(f"企微 JS-SDK 免认证: userid={userid} 通过企微标签验证为坐席")
|
||||
except Exception as e:
|
||||
logger.warning(f"企微标签检测失败: {e}")
|
||||
|
||||
# 如果既没有坐席也没有管理员角色,拒绝登录
|
||||
if not has_agent_role and not has_admin_role:
|
||||
logger.warning(f"企微 JS-SDK 免认证失败: userid={userid} 没有坐席或管理员角色")
|
||||
raise AppException(403, "您没有坐席或管理员权限,无法使用此方式登录")
|
||||
|
||||
# 确定用户角色
|
||||
role_names = []
|
||||
if has_admin_role:
|
||||
role_names.append("admin")
|
||||
if has_agent_role:
|
||||
role_names.append("agent")
|
||||
|
||||
logger.info(f"企微 JS-SDK 免认证: userid={userid}, roles={role_names}")
|
||||
|
||||
# 3. 获取用户详细信息
|
||||
try:
|
||||
user_info = await wecom_service.get_user_info(userid)
|
||||
user_name = user_info.get("name", userid)
|
||||
# 同步头像到 employee 表 + 清缓存(要求 A;不阻塞登录)
|
||||
try:
|
||||
from app.services.avatar_service import sync_employee_avatar
|
||||
await sync_employee_avatar(db, redis_client, userid, user_info.get("avatar", ""))
|
||||
except Exception as av_err:
|
||||
logger.warning(f"JS-SDK 同步头像失败(不阻塞): userid={userid}, error={av_err}")
|
||||
except Exception as e:
|
||||
logger.warning(f"获取企微用户信息失败: {e}")
|
||||
user_name = userid
|
||||
|
||||
# 4. 查找或创建坐席记录
|
||||
from app.models.agent import Agent
|
||||
|
||||
stmt = select(Agent).where(Agent.user_id == userid)
|
||||
result = await db.execute(stmt)
|
||||
agent = result.scalars().first()
|
||||
|
||||
if not agent:
|
||||
# 首次登录,创建坐席记录
|
||||
agent = Agent(
|
||||
user_id=userid,
|
||||
name=user_name,
|
||||
status="online",
|
||||
current_load=0,
|
||||
max_load=5,
|
||||
)
|
||||
db.add(agent)
|
||||
await db.flush()
|
||||
logger.info(f"企微 JS-SDK 免认证创建坐席: user_id={userid}, name={user_name}")
|
||||
else:
|
||||
# 更新坐席状态
|
||||
agent.name = user_name
|
||||
agent.status = "online"
|
||||
agent.updated_at = datetime.now()
|
||||
db.add(agent)
|
||||
await db.flush()
|
||||
logger.info(f"企微 JS-SDK 免认证登录: user_id={userid}, name={user_name}")
|
||||
|
||||
# 5. 生成 token
|
||||
token = secrets.token_urlsafe(32)
|
||||
|
||||
# 6. 存储 token 到 Redis
|
||||
token_key = f"agent:token:{token}"
|
||||
await redis_client.setex(token_key, TOKEN_TTL_SECONDS, userid)
|
||||
|
||||
# 7. 记录审计日志
|
||||
await record_audit_log(
|
||||
db=db,
|
||||
employee_id=userid,
|
||||
action="wecom_jsdk_login",
|
||||
resource="auth",
|
||||
resource_id=userid,
|
||||
details={
|
||||
"name": user_name,
|
||||
"roles": role_names,
|
||||
"login_method": "wecom_jsdk",
|
||||
},
|
||||
result="success",
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
logger.info(f"企微 JS-SDK 免认证登录成功: user_id={userid}, roles={role_names}")
|
||||
|
||||
return success_response(data={
|
||||
"token": token,
|
||||
"employee_id": userid,
|
||||
"name": user_name,
|
||||
"roles": role_names,
|
||||
})
|
||||
|
||||
except AppException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"企微 JS-SDK 免认证登录异常: {e}", exc_info=True)
|
||||
raise AppException(500, f"免认证登录失败: {str(e)}")
|
||||
|
||||
Reference in New Issue
Block a user