sequenceDiagram participant User as 坐席 participant LoginVue as Login.vue participant AgentStore as agentStore participant OtpBind as OtpBindPanel.vue participant API as apiClient participant Backend as FastAPI Backend participant Redis as Redis User->>LoginVue: 输入账号密码 → 点击登录 LoginVue->>AgentStore: login(userId, password, undefined) AgentStore->>API: POST /api/agents/login {user_id, password} API->>Backend: agent_login() Note over Backend: agent.mfa_enabled == False Backend-->>API: { require_otp_bind: true, user_id, name, role } API-->>AgentStore: { require_otp_bind: true, ... } AgentStore-->>LoginVue: return { require_otp_bind: true } Note over LoginVue: 隐藏账密表单
显示 OtpBindPanel LoginVue->>OtpBind: requireOtpBind = true OtpBind->>API: POST /api/auth/otp-bind API->>Backend: bind_otp() Note over Backend: 生成 secret + QR Backend-->>API: { secret, otpauth_url, qr_code_base64 } API-->>OtpBind: { secret, otpauth_url, qr_code_base64 } Note over OtpBind: 渲染二维码 + secret 明文 User->>User: 用 Authenticator 扫码(或手动输入 secret) User->>OtpBind: 输入 6 位验证码 → 点击"验证并完成绑定" OtpBind->>API: POST /api/auth/otp-verify { otp_code } API->>Backend: verify_otp() Note over Backend: mfa_enabled=False (绑定场景)
校验 TOTP → 通过
设置 mfa_enabled=True
设置 mfa_bound_at=now Backend->>Redis: mark_verified(employee_id, TTL=1800) Backend->>Backend: 签发 JWT token Backend-->>API: { verified: true, token, user_id, name, role } API-->>OtpBind: { verified: true, token, ... } OtpBind-->>LoginVue: emit('bind-success', { token, user_id, name }) LoginVue->>AgentStore: 保存 token → 设置 agentInfo LoginVue->>LoginVue: router.push('/workspace')