feat: 实现 2FA (TOTP) 的设置、验证和禁用流程
This commit is contained in:
@@ -2,10 +2,11 @@ import { defineStore } from 'pinia';
|
||||
import axios from 'axios';
|
||||
import router from '../router'; // 引入 router 用于重定向
|
||||
|
||||
// 用户信息接口 (不含敏感信息)
|
||||
// 扩展的用户信息接口,包含 2FA 状态
|
||||
interface UserInfo {
|
||||
id: number;
|
||||
username: string;
|
||||
isTwoFactorEnabled?: boolean; // 后端 /status 接口会返回这个
|
||||
}
|
||||
|
||||
// Auth Store State 接口
|
||||
@@ -14,6 +15,7 @@ interface AuthState {
|
||||
user: UserInfo | null;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
loginRequires2FA: boolean; // 新增状态:标记登录是否需要 2FA
|
||||
}
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
@@ -22,6 +24,7 @@ export const useAuthStore = defineStore('auth', {
|
||||
user: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
loginRequires2FA: false, // 初始为不需要
|
||||
}),
|
||||
getters: {
|
||||
// 可以添加一些 getter,例如获取用户名
|
||||
@@ -32,32 +35,74 @@ export const useAuthStore = defineStore('auth', {
|
||||
async login(credentials: { username: string; password: string }) {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
this.loginRequires2FA = false; // 重置 2FA 状态
|
||||
try {
|
||||
const response = await axios.post<{ message: string; user: UserInfo }>('/api/v1/auth/login', credentials);
|
||||
// 登录成功
|
||||
this.isAuthenticated = true;
|
||||
this.user = response.data.user;
|
||||
console.log('登录成功:', this.user);
|
||||
// 登录成功后重定向到连接管理页面 (或仪表盘)
|
||||
await router.push({ name: 'Connections' }); // 使用 await 确保导航完成
|
||||
return true;
|
||||
// 后端可能返回 user 或 requiresTwoFactor
|
||||
const response = await axios.post<{ message: string; user?: UserInfo; requiresTwoFactor?: boolean }>('/api/v1/auth/login', credentials);
|
||||
|
||||
if (response.data.requiresTwoFactor) {
|
||||
// 需要 2FA 验证
|
||||
console.log('登录需要 2FA 验证');
|
||||
this.loginRequires2FA = true;
|
||||
// 不设置 isAuthenticated 和 user,等待 2FA 验证
|
||||
return { requiresTwoFactor: true }; // 返回特殊状态给调用者
|
||||
} else if (response.data.user) {
|
||||
// 登录成功 (无 2FA)
|
||||
this.isAuthenticated = true;
|
||||
this.user = response.data.user;
|
||||
console.log('登录成功 (无 2FA):', this.user);
|
||||
await router.push({ name: 'Connections' });
|
||||
return { success: true };
|
||||
} else {
|
||||
// 不应该发生,但作为防御性编程
|
||||
throw new Error('登录响应无效');
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('登录失败:', err);
|
||||
this.isAuthenticated = false;
|
||||
this.user = null;
|
||||
this.loginRequires2FA = false;
|
||||
this.error = err.response?.data?.message || err.message || '登录时发生未知错误。';
|
||||
return false;
|
||||
return { success: false, error: this.error };
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 登出 Action (占位符)
|
||||
async logout() {
|
||||
// 登录时的 2FA 验证 Action
|
||||
async verifyLogin2FA(token: string) {
|
||||
if (!this.loginRequires2FA) {
|
||||
throw new Error('当前登录流程不需要 2FA 验证。');
|
||||
}
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
try {
|
||||
// TODO: 调用后端的登出 API (如果需要)
|
||||
const response = await axios.post<{ message: string; user: UserInfo }>('/api/v1/auth/login/2fa', { token });
|
||||
// 2FA 验证成功
|
||||
this.isAuthenticated = true;
|
||||
this.user = response.data.user;
|
||||
this.loginRequires2FA = false; // 重置状态
|
||||
console.log('2FA 验证成功,登录完成:', this.user);
|
||||
await router.push({ name: 'Connections' });
|
||||
return { success: true };
|
||||
} catch (err: any) {
|
||||
console.error('2FA 验证失败:', err);
|
||||
// 不清除 isAuthenticated 或 user,因为用户可能只是输错了验证码
|
||||
this.error = err.response?.data?.message || err.message || '2FA 验证时发生未知错误。';
|
||||
return { success: false, error: this.error };
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// 登出 Action
|
||||
async logout() {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
this.loginRequires2FA = false; // 重置 2FA 状态
|
||||
try {
|
||||
// TODO: 调用后端的登出 API
|
||||
// await axios.post('/api/v1/auth/logout');
|
||||
|
||||
// 清除本地状态
|
||||
@@ -101,7 +146,34 @@ export const useAuthStore = defineStore('auth', {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// 新增:检查并更新认证状态 Action
|
||||
async checkAuthStatus() {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const response = await axios.get<{ isAuthenticated: boolean; user: UserInfo }>('/api/v1/auth/status');
|
||||
if (response.data.isAuthenticated && response.data.user) {
|
||||
this.isAuthenticated = true;
|
||||
this.user = response.data.user; // 更新用户信息,包含 isTwoFactorEnabled
|
||||
this.loginRequires2FA = false; // 确保重置
|
||||
console.log('认证状态已更新:', this.user);
|
||||
} else {
|
||||
this.isAuthenticated = false;
|
||||
this.user = null;
|
||||
this.loginRequires2FA = false;
|
||||
}
|
||||
} catch (error: any) {
|
||||
// 如果获取状态失败 (例如 session 过期),则认为未认证
|
||||
console.warn('检查认证状态失败:', error.response?.data?.message || error.message);
|
||||
this.isAuthenticated = false;
|
||||
this.user = null;
|
||||
this.loginRequires2FA = false;
|
||||
// 可选:如果不是 401 错误,可以记录更详细的日志
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 修改密码 Action
|
||||
async changePassword(currentPassword: string, newPassword: string) {
|
||||
if (!this.isAuthenticated) {
|
||||
|
||||
Reference in New Issue
Block a user