312 lines
7.8 KiB
TypeScript
312 lines
7.8 KiB
TypeScript
/**
|
|
* Python CLI 认证服务
|
|
* 通过Rust调用Python CLI实现用户认证功能
|
|
*/
|
|
|
|
import { invoke } from '@tauri-apps/api/core';
|
|
|
|
export interface LoginCredentials {
|
|
email?: string;
|
|
username?: string;
|
|
password: string;
|
|
}
|
|
|
|
export interface RegisterCredentials {
|
|
email: string;
|
|
username: string;
|
|
password: string;
|
|
displayName?: string;
|
|
}
|
|
|
|
export interface UserProfile {
|
|
id: string;
|
|
username: string;
|
|
displayName: string;
|
|
email?: string;
|
|
avatarUrl?: string;
|
|
createTime: string;
|
|
updateTime: string;
|
|
lastLogin?: string;
|
|
isActive: boolean;
|
|
}
|
|
|
|
export interface AuthResponse {
|
|
success: boolean;
|
|
message: string;
|
|
data?: any;
|
|
output?: string;
|
|
error?: string;
|
|
}
|
|
|
|
export interface PythonCliAuthResponse {
|
|
success: boolean;
|
|
message: string;
|
|
data?: {
|
|
user?: UserProfile;
|
|
token?: string;
|
|
expires_at?: string;
|
|
};
|
|
output?: string;
|
|
error?: string;
|
|
}
|
|
|
|
class PythonCliAuth {
|
|
private token: string | null = null;
|
|
private user: UserProfile | null = null;
|
|
|
|
constructor() {
|
|
// 从localStorage恢复认证状态
|
|
this.loadStoredAuth();
|
|
}
|
|
|
|
/**
|
|
* 用户登录
|
|
*/
|
|
async login(credentials: LoginCredentials): Promise<UserProfile> {
|
|
try {
|
|
const username_or_email = credentials.email || credentials.username;
|
|
if (!username_or_email) {
|
|
throw new Error('Email or username is required');
|
|
}
|
|
|
|
const response: PythonCliAuthResponse = await invoke('python_cli_auth_login', {
|
|
request: {
|
|
username_or_email,
|
|
password: credentials.password,
|
|
json_output: true
|
|
}
|
|
});
|
|
|
|
console.log({ response })
|
|
|
|
if (!response.success) {
|
|
throw new Error(response.error || response.message || 'Login failed');
|
|
}
|
|
|
|
// 解析输出中的用户信息和token
|
|
if (response.success && response.output) {
|
|
// 尝试从输出中解析JSON
|
|
const jsonMatch = response.output.match(/\{[\s\S]*\}/);
|
|
if (jsonMatch) {
|
|
try {
|
|
const authData = JSON.parse(jsonMatch[0]);
|
|
if (authData.success && authData.data?.user && authData.data?.token) {
|
|
this.token = authData.data.token;
|
|
this.user = {
|
|
id: authData.data.user.id,
|
|
username: authData.data.user.username,
|
|
displayName: authData.data.user.display_name,
|
|
email: authData.data.user.email,
|
|
avatarUrl: authData.data.user.avatar_url,
|
|
createTime: authData.data.user.created_at,
|
|
updateTime: authData.data.user.updated_at,
|
|
lastLogin: authData.data.user.last_login,
|
|
isActive: authData.data.user.is_active
|
|
};
|
|
|
|
// 保存到localStorage
|
|
this.saveAuthToStorage();
|
|
|
|
return this.user;
|
|
}
|
|
} catch (parseError) {
|
|
console.error('Failed to parse auth response JSON:', parseError);
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new Error(response.error || response.message || 'Login failed');
|
|
} catch (error) {
|
|
console.error('Login failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 用户注册
|
|
*/
|
|
async register(credentials: RegisterCredentials): Promise<UserProfile> {
|
|
try {
|
|
const response: PythonCliAuthResponse = await invoke('python_cli_auth_register', {
|
|
request: {
|
|
username: credentials.username,
|
|
email: credentials.email,
|
|
password: credentials.password,
|
|
display_name: credentials.displayName,
|
|
json_output: true
|
|
}
|
|
});
|
|
console.log({ response })
|
|
if (!response.success) {
|
|
throw new Error(response.error || response.message || 'Registration failed');
|
|
}
|
|
|
|
// 解析输出中的用户信息和token
|
|
if (response.success && response.output) {
|
|
// 尝试从输出中解析JSON
|
|
const jsonMatch = response.output.match(/\{[\s\S]*\}/);
|
|
if (jsonMatch) {
|
|
try {
|
|
const authData = JSON.parse(jsonMatch[0]);
|
|
if (authData.success && authData.data?.user && authData.data?.token) {
|
|
this.token = authData.data.token;
|
|
this.user = {
|
|
id: authData.data.user.id,
|
|
username: authData.data.user.username,
|
|
displayName: authData.data.user.display_name,
|
|
email: authData.data.user.email,
|
|
avatarUrl: authData.data.user.avatar_url,
|
|
createTime: authData.data.user.created_at,
|
|
updateTime: authData.data.user.updated_at,
|
|
lastLogin: authData.data.user.last_login,
|
|
isActive: authData.data.user.is_active
|
|
};
|
|
|
|
// 保存到localStorage
|
|
this.saveAuthToStorage();
|
|
|
|
return this.user;
|
|
}
|
|
} catch (parseError) {
|
|
console.error('Failed to parse auth response JSON:', parseError);
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new Error(response.error || response.message || 'Registration failed');
|
|
} catch (error) {
|
|
console.error('Registration failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 验证Token
|
|
*/
|
|
async verifyToken(token?: string): Promise<boolean> {
|
|
try {
|
|
const tokenToVerify = token || this.token;
|
|
if (!tokenToVerify) {
|
|
return false;
|
|
}
|
|
|
|
const response: PythonCliAuthResponse = await invoke('python_cli_auth_verify', {
|
|
request: {
|
|
token: tokenToVerify,
|
|
verbose: true
|
|
}
|
|
});
|
|
|
|
return response.success;
|
|
} catch (error) {
|
|
console.error('Token verification failed:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 用户登出
|
|
*/
|
|
async logout(): Promise<void> {
|
|
this.token = null;
|
|
this.user = null;
|
|
this.clearStoredAuth();
|
|
}
|
|
|
|
/**
|
|
* 获取当前用户信息
|
|
*/
|
|
async getCurrentUser(): Promise<UserProfile | null> {
|
|
if (!this.token) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
// 验证当前token是否有效
|
|
const isValid = await this.verifyToken();
|
|
if (!isValid) {
|
|
this.logout();
|
|
return null;
|
|
}
|
|
|
|
return this.user;
|
|
} catch (error) {
|
|
console.error('Failed to get current user:', error);
|
|
this.logout();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 更新用户资料
|
|
*/
|
|
async updateProfile(updates: Partial<Pick<UserProfile, 'displayName' | 'avatarUrl'>>): Promise<void> {
|
|
// TODO: 实现用户资料更新
|
|
console.log('Update profile:', updates);
|
|
throw new Error('Profile update not implemented yet');
|
|
}
|
|
|
|
/**
|
|
* 检查是否已认证
|
|
*/
|
|
isAuthenticated(): boolean {
|
|
return this.token !== null && this.user !== null;
|
|
}
|
|
|
|
/**
|
|
* 获取当前token
|
|
*/
|
|
getToken(): string | null {
|
|
return this.token;
|
|
}
|
|
|
|
/**
|
|
* 获取当前用户
|
|
*/
|
|
getUser(): UserProfile | null {
|
|
return this.user;
|
|
}
|
|
|
|
/**
|
|
* 保存认证信息到localStorage
|
|
*/
|
|
private saveAuthToStorage(): void {
|
|
if (this.token && this.user) {
|
|
localStorage.setItem('auth_token', this.token);
|
|
localStorage.setItem('auth_user', JSON.stringify(this.user));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 从localStorage加载认证信息
|
|
*/
|
|
private loadStoredAuth(): void {
|
|
try {
|
|
const token = localStorage.getItem('auth_token');
|
|
const userStr = localStorage.getItem('auth_user');
|
|
|
|
if (token && userStr) {
|
|
this.token = token;
|
|
this.user = JSON.parse(userStr);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load stored auth:', error);
|
|
this.clearStoredAuth();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 清除存储的认证信息
|
|
*/
|
|
private clearStoredAuth(): void {
|
|
localStorage.removeItem('auth_token');
|
|
localStorage.removeItem('auth_user');
|
|
}
|
|
}
|
|
|
|
// 创建全局实例
|
|
export const pythonCliAuth = new PythonCliAuth();
|
|
|
|
// 导出默认实例
|
|
export default pythonCliAuth;
|