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:
@@ -150,6 +150,7 @@ async def scan_qrcode(
|
||||
state: Optional[str] = Query(None, description="扫码登录票据(企微 OAuth state 标准参数名)"),
|
||||
code: Optional[str] = Query(None, description="企微 OAuth 授权码"),
|
||||
redis_client: aioredis.Redis = Depends(dep_redis),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""处理企微 OAuth2 扫码回调。
|
||||
|
||||
@@ -186,12 +187,61 @@ async def scan_qrcode(
|
||||
service = _get_qrcode_service(redis_client)
|
||||
result = await service.process_scan(ticket=final_ticket, code=final_code)
|
||||
|
||||
# ==========================================================================
|
||||
# 扫码后自动确认(auto-confirm)
|
||||
# ==========================================================================
|
||||
# 原设计:requester 需已登录坐席调 /confirm 来授权新登录
|
||||
# 问题:首次登录时电脑端无人登录,没有合法 current_user 可调 confirm
|
||||
# 修正:扫码即确认,直接为扫码的企微用户签发 token
|
||||
# ==========================================================================
|
||||
from app.services.token_service import TokenService
|
||||
from app.services.role_mapping_service import RoleMappingService
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
token_service = TokenService(redis_client)
|
||||
|
||||
# 获取用户的真实角色(而非写死 agent)
|
||||
role_service = RoleMappingService(db)
|
||||
user_roles = await role_service.get_user_roles(result["employee_id"])
|
||||
logger.info(
|
||||
f"扫码登录角色: employee_id={result['employee_id']}, "
|
||||
f"roles={user_roles}"
|
||||
)
|
||||
|
||||
auto_token = await token_service.create_token(
|
||||
employee_id=result["employee_id"],
|
||||
name=result["name"],
|
||||
roles=user_roles,
|
||||
avatar=result.get("avatar", ""),
|
||||
login_source="qrcode_scan",
|
||||
)
|
||||
|
||||
confirm_payload = {
|
||||
"token": auto_token,
|
||||
"confirmed_at": datetime.now().isoformat(),
|
||||
"roles": user_roles,
|
||||
"employee_id": result["employee_id"],
|
||||
"name": result["name"],
|
||||
}
|
||||
CONFIRM_TTL = 60
|
||||
await redis_client.setex(
|
||||
f"qrcode:confirm:{final_ticket}",
|
||||
CONFIRM_TTL,
|
||||
json.dumps(confirm_payload, ensure_ascii=False),
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"扫码自动确认: ticket={final_ticket[:8]}..., "
|
||||
f"employee_id={result['employee_id']}, name={result['name']}"
|
||||
)
|
||||
|
||||
# GET 请求(企微 OAuth 回调)→ 重定向到前端选择页
|
||||
# 因为企微 OAuth 流程不在这个端点完成最终登录,只标记 scanned,
|
||||
# 等用户在坐席端点 confirm 后才能拿到 token。
|
||||
# 但企微 WebView 期望看到跳转后的页面,所以这里给个提示页。
|
||||
from fastapi.responses import HTMLResponse
|
||||
if code is not None and ticket is not None and body is None:
|
||||
if final_code is not None and final_ticket is not None and body is None:
|
||||
# GET 模式:渲染一个 "扫码成功" 的 HTML 提示页 + 引导用户到登录页
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
Reference in New Issue
Block a user