feat: OTP首次绑定 + 三端登录修复 + 管理端权限修复 (2026-07-08)
OTP首次绑定: - 新增统一 OTP 路由 /auth/otp-* (otp.py + router.py) - 坐席端 OTP 绑定面板 (OtpBindPanel.vue) - 管理端 OTP 管理列表 (MfaManage.vue) - agent_login 签发半认证 token 支持首次绑定流程 三端登录修复: - 坐席/管理端去掉'返回扫码登录'按钮 - 管理端改为二维码始终可见+轮询扫码状态 - 员工端 /itdesk/ 改为 alias 直接服务 H5 (不再301重定向) - docker-compose 添加 h5 volume 挂载 管理端权限修复: - 扫码登录改用 get_user_roles() 替代写死 roles=['agent'] - get_user_roles() 增加 agents.role 回退 - 新增 GET /admin/roles/user-roles 端点 - 角色管理页加载用户角色分配数据 文档更新: - OTP PRD + 系统设计文档 - 故障排查手册 v1.1 (新增6案例) - nginx 生产基准配置
This commit is contained in:
@@ -267,13 +267,15 @@ async def agent_login(
|
||||
await db.flush()
|
||||
logger.info(f"坐席登录: user_id={body.user_id}, name={body.name}")
|
||||
|
||||
# 2. MFA 二次验证(已绑定 MFA 的坐席/管理员)
|
||||
# 决策3(三端认证重构 AUTH-04):移除「企微已登录+角色→免密直接进入」分支,
|
||||
# 2. MFA 二次验证(三端认证重构 AUTH-04/AUTH-05)
|
||||
# 决策3(AUTH-04):移除「企微已登录+角色→免密直接进入」分支,
|
||||
# 所有登录方式(扫码/账密/企微验证)均需 OTP 验证,统一安全水位。
|
||||
# 执行MFA验证
|
||||
# 决策4(AUTH-05):区分两种 OTP 状态——
|
||||
# - mfa_enabled=True → 已绑定,需验证 OTP 动态码
|
||||
# - mfa_enabled=False → 未绑定,引导首次绑定流程
|
||||
if agent.mfa_enabled:
|
||||
# 已绑定 OTP → 要求验证或校验码
|
||||
if not body.otp_code:
|
||||
# 需要 OTP 验证,返回 require_otp 标记(必须包含role字段,否则前端校验会失败)
|
||||
return success_response(data={
|
||||
"require_otp": True,
|
||||
"message": "请输入OTP动态码",
|
||||
@@ -281,10 +283,34 @@ async def agent_login(
|
||||
"name": agent.name,
|
||||
"role": agent.role, # 必须包含role字段,供前端校验权限
|
||||
})
|
||||
else:
|
||||
# 验证 OTP 码(决策3:复用 MFAService 统一校验逻辑)
|
||||
if not MFAService.verify_code(agent.mfa_secret, body.otp_code, valid_window=1):
|
||||
raise AppException(1006, "OTP验证码错误,请重新输入")
|
||||
# 验证 OTP 码(复用 MFAService 统一校验逻辑)
|
||||
if not MFAService.verify_code(agent.mfa_secret, body.otp_code, valid_window=1):
|
||||
raise AppException(1006, "OTP验证码错误,请重新输入")
|
||||
else:
|
||||
# 未绑定 OTP → 引导首次绑定(AUTH-05)
|
||||
# BUG-001 修复: 签发半认证 token,使前端可以调用 otp-bind / otp-verify
|
||||
# 这些端点需要 Bearer token(get_current_user 认证),否则流程完全阻断
|
||||
from app.services.token_service import TokenService
|
||||
from app.dependencies import get_redis
|
||||
|
||||
redis_client = await get_redis()
|
||||
token_service = TokenService(redis_client)
|
||||
bind_token = await token_service.create_token(
|
||||
employee_id=agent.user_id,
|
||||
name=agent.name,
|
||||
roles=["agent"],
|
||||
avatar=avatar,
|
||||
login_source="agent_pending_otp",
|
||||
)
|
||||
|
||||
return success_response(data={
|
||||
"require_otp_bind": True,
|
||||
"message": "首次登录请先绑定OTP二次验证",
|
||||
"user_id": agent.user_id,
|
||||
"name": agent.name,
|
||||
"role": agent.role,
|
||||
"token": bind_token,
|
||||
})
|
||||
|
||||
# 3. 生成随机 token(使用统一格式)
|
||||
from app.services.token_service import TokenService
|
||||
|
||||
Reference in New Issue
Block a user