diff --git a/backend/app/api/agents.py b/backend/app/api/agents.py
index bc1e52e..14d5e10 100644
--- a/backend/app/api/agents.py
+++ b/backend/app/api/agents.py
@@ -291,20 +291,14 @@ async def agent_login(
# BUG-001 修复: 签发半认证 token,使前端可以调用 otp-bind / otp-verify
# 这些端点需要 Bearer token(get_current_user 认证),否则流程完全阻断
from app.services.token_service import TokenService
- from app.services.role_mapping_service import RoleMappingService
from app.dependencies import get_redis
redis_client = await get_redis()
token_service = TokenService(redis_client)
- # BUGFIX: 从 UserRole 表查询真实角色,而非硬编码 ["agent"]
- role_service = RoleMappingService(db)
- roles = await role_service.get_user_roles(agent.user_id)
- if not roles:
- roles = ["agent"] # 无角色时默认 fallback
bind_token = await token_service.create_token(
employee_id=agent.user_id,
name=agent.name,
- roles=roles,
+ roles=["agent"],
avatar=avatar,
login_source="agent_pending_otp",
)
diff --git a/backend/app/api/auth_qrcode.py b/backend/app/api/auth_qrcode.py
index 1c1813f..adc1c10 100644
--- a/backend/app/api/auth_qrcode.py
+++ b/backend/app/api/auth_qrcode.py
@@ -236,46 +236,59 @@ async def scan_qrcode(
f"employee_id={result['employee_id']}, name={result['name']}"
)
- # GET 请求(企微 OAuth 回调)→ 已自动确认,显示成功页 + 自动关闭
+ # GET 请求(企微 OAuth 回调)→ 重定向到前端选择页
+ # 因为企微 OAuth 流程不在这个端点完成最终登录,只标记 scanned,
+ # 等用户在坐席端点 confirm 后才能拿到 token。
+ # 但企微 WebView 期望看到跳转后的页面,所以这里给个提示页。
from fastapi.responses import HTMLResponse
if final_code is not None and final_ticket is not None and body is None:
- user_name = result.get('name', '')
+ # GET 模式:渲染一个 "扫码成功" 的 HTML 提示页 + 引导用户到登录页
html = f"""
-登录成功 - IT智能服务台
+扫码成功 - IT智能服务台
-
-
"""
return HTMLResponse(content=html, status_code=200)
diff --git a/frontend-admin/src/api/mfa.ts b/frontend-admin/src/api/mfa.ts
index ef49994..eeadebd 100644
--- a/frontend-admin/src/api/mfa.ts
+++ b/frontend-admin/src/api/mfa.ts
@@ -23,42 +23,11 @@
// =============================================================================
import apiClient from './index'
-import type { AxiosResponse } from 'axios'
// --------------------------------------------------------------------------
// TypeScript 类型定义
// --------------------------------------------------------------------------
-/** POST /auth/otp-bind 响应(用户绑定用) */
-export interface OtpBindData {
- /** TOTP 共享密钥(base32) */
- secret: string
- /** otpauth:// URI */
- otpauth_url: string
- /** 二维码 PNG base64(不含 data: 前缀) */
- qr_code_base64: string
-}
-
-/** POST /auth/otp-verify 请求体 */
-export interface OtpVerifyRequest {
- /** 6 位 OTP 动态码 */
- otp_code: string
-}
-
-/** POST /auth/otp-verify 响应 */
-export interface OtpVerifyData {
- /** 验证是否通过 */
- verified: boolean
- /** 登录 token(首次绑定成功后返回) */
- token?: string
- /** 用户 ID */
- user_id?: string
- /** 用户姓名 */
- name?: string
- /** 用户角色 */
- role?: string
-}
-
/** 单个用户的 OTP 状态条目 */
export interface MfaUserStatus {
/** 员工 ID(企微 userid) */
@@ -137,32 +106,3 @@ export async function resetMfa(employeeId: string): Promise {
)
return response
}
-
-// --------------------------------------------------------------------------
-// 用户端 OTP 绑定/验证函数(供登录绑定面板使用)
-// --------------------------------------------------------------------------
-
-/**
- * 绑定 OTP — 生成 secret + 二维码
- * 用户首次登录时调用,获取 TOTP 密钥和二维码
- *
- * @returns OTP 绑定信息(secret + otpauth_url + base64 PNG)
- */
-export async function bindOtp(): Promise {
- // 拦截器已返回 inner data,直接返回
- return await apiClient.post('/auth/otp-bind')
-}
-
-/**
- * 验证 OTP 并完成绑定
- * 用户扫码后输入 6 位验证码,验证通过后完成绑定
- * 如果是登录流程中的首次绑定,返回 token 等登录信息
- *
- * @param otpCode - 6 位 OTP 动态码
- * @returns 验证结果(verified + 可选的登录 token)
- */
-export async function verifyOtp(otpCode: string): Promise {
- const body: OtpVerifyRequest = { otp_code: otpCode }
- // 拦截器已返回 inner data,直接返回
- return await apiClient.post('/auth/otp-verify', body)
-}
diff --git a/frontend-admin/src/views/Login.vue b/frontend-admin/src/views/Login.vue
index 7c2ecf7..07b61d4 100644
--- a/frontend-admin/src/views/Login.vue
+++ b/frontend-admin/src/views/Login.vue
@@ -130,15 +130,6 @@ IT智能服务台 — 管理员登录页 (v1.2, 2026-07-06)
-
-
-
(null)
-
/** 错误信息 */
const errorMsg = ref('')
@@ -344,9 +328,6 @@ async function checkWecomClient(): Promise {
/**
* 企微免密登录
- * @deprecated 已废弃 - AUTH-04 任务移除免密分支,后端 /api/auth_wecom/jsdk-login 接口已删除
- * @see docs/system_design.md 第347行 AUTH-04 验收标准
- * 此函数仅作占位,暂未删除以保持代码可追溯性
*/
async function handleWecomQuickLogin(): Promise {
wecomQuickLoading.value = true
@@ -455,34 +436,6 @@ function stopPolling(): void {
}
}
-/**
- * OTP 绑定成功回调
- * @see docs/system_design.md AUTH-10 验收标准
- */
-function onBindSuccess(token: string, userId: string, name: string, role: string): void {
- // 保存 token
- localStorage.setItem('admin_token', token)
- localStorage.setItem('admin_user_id', userId)
-
- // 更新 store
- adminStore.token = token
- adminStore.adminUserId = userId
-
- ElMessage.success('OTP 绑定成功,已登录')
- router.push('/')
-}
-
-/**
- * OTP 绑定取消回调
- * 清除绑定状态,返回登录表单
- */
-function onBindCancel(): void {
- requireOtpBind.value = false
- otpBindUser.value = null
- // 登出以清除半认证 token
- adminStore.logout()
-}
-
/**
* 处理登录
*/
@@ -501,7 +454,6 @@ async function handleLogin(): Promise {
)
} catch (error: unknown) {
// 检查是否需要 OTP 验证
- // @see docs/system_design.md AUTH-10 验收标准
if (error instanceof Error && error.message === 'require_otp') {
requireOtp.value = true
loginForm.otpCode = ''
@@ -509,20 +461,6 @@ async function handleLogin(): Promise {
return
}
- // 检查是否需要 OTP 首次绑定
- if (error instanceof Error && error.message === 'require_otp_bind') {
- // 从 adminStore 获取用户信息(登录时已保存)
- requireOtpBind.value = true
- otpBindUser.value = {
- user_id: adminStore.adminUserId || loginForm.userId,
- name: adminStore.adminInfo?.name || '',
- role: adminStore.adminInfo?.role || 'admin',
- }
- // 隐藏登录表单
- showPasswordPanel.value = false
- return
- }
-
const errMsg = error instanceof Error ? error.message : '登录失败,请重试'
errorMsg.value = errMsg
}