forked from 77media/video-flow
594 lines
14 KiB
TypeScript
594 lines
14 KiB
TypeScript
|
||
import type {
|
||
BaseResponse,
|
||
GoogleAuthorizeResponse,
|
||
GoogleLoginRequest,
|
||
GoogleLoginSuccessResponse,
|
||
EmailConflictData,
|
||
OAuthState
|
||
} from '@/app/types/google-oauth';
|
||
|
||
// API配置
|
||
//const JAVA_BASE_URL = 'http://192.168.120.36:8080';
|
||
const JAVA_BASE_URL = process.env.NEXT_PUBLIC_JAVA_URL || 'https://77.app.java.auth.qikongjian.com';
|
||
// Token存储键
|
||
const TOKEN_KEY = 'token';
|
||
const USER_KEY = 'currentUser';
|
||
|
||
/**
|
||
* 注册用户响应数据
|
||
*/
|
||
type RegisterUserData = {
|
||
user_id: string;
|
||
email: string;
|
||
name: string;
|
||
invite_code: string;
|
||
};
|
||
|
||
/**
|
||
* 注册用户API响应
|
||
*/
|
||
type RegisterUserResponse = {
|
||
code: number;
|
||
message: string;
|
||
data: RegisterUserData;
|
||
successful: boolean;
|
||
};
|
||
|
||
/**
|
||
* 登录用户
|
||
*/
|
||
export const loginUser = async (email: string, password: string) => {
|
||
try {
|
||
const response = await fetch(`${JAVA_BASE_URL}/api/user/login`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Accept': 'application/json',
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
username: email,
|
||
password: password,
|
||
}),
|
||
});
|
||
|
||
const data = await response.json();
|
||
if(!data.success){
|
||
throw new Error(data.message)
|
||
}
|
||
// 保存token到本地存储
|
||
setToken(data.token);
|
||
|
||
const user = await getUserProfile();
|
||
|
||
return user;
|
||
} catch (error) {
|
||
console.error('Login failed:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 获取token
|
||
*/
|
||
export const getToken = (): string | null => {
|
||
if (typeof window === 'undefined') return null;
|
||
return localStorage.getItem(TOKEN_KEY);
|
||
};
|
||
|
||
/**
|
||
* 设置token
|
||
*/
|
||
export const setToken = (token: string) => {
|
||
if (typeof window === 'undefined') return;
|
||
localStorage.setItem(TOKEN_KEY, token);
|
||
};
|
||
|
||
/**
|
||
* 获取当前用户
|
||
*/
|
||
export const getCurrentUser = () => {
|
||
if (typeof window === 'undefined') return null;
|
||
|
||
const userJson = localStorage.getItem(USER_KEY);
|
||
if (!userJson) return null;
|
||
|
||
try {
|
||
return JSON.parse(userJson);
|
||
} catch (error) {
|
||
console.error('Failed to parse user data from storage', error);
|
||
return null;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 设置用户信息
|
||
*/
|
||
export const setUser = (user: any) => {
|
||
if (typeof window === 'undefined') return;
|
||
localStorage.setItem(USER_KEY, JSON.stringify(user));
|
||
};
|
||
|
||
/**
|
||
* 清除认证数据
|
||
*/
|
||
export const clearAuthData = () => {
|
||
if (typeof window === 'undefined') return;
|
||
localStorage.removeItem(TOKEN_KEY);
|
||
localStorage.removeItem(USER_KEY);
|
||
};
|
||
|
||
/**
|
||
* 用户登出
|
||
*/
|
||
export const logoutUser = () => {
|
||
clearAuthData();
|
||
window.location.href = '/login';
|
||
};
|
||
|
||
/**
|
||
* 检查是否已登录
|
||
*/
|
||
export const isAuthenticated = (): boolean => {
|
||
return !!getToken();
|
||
};
|
||
|
||
/**
|
||
* 创建带有token的请求头
|
||
*/
|
||
export const getAuthHeaders = () => {
|
||
const token = getToken();
|
||
return {
|
||
'Accept': 'application/json',
|
||
'Content-Type': 'application/json',
|
||
...(token && { 'X-EASE-ADMIN-TOKEN': token }),
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 带有自动token处理的fetch封装
|
||
*/
|
||
export const authFetch = async (url: string, options: RequestInit = {}) => {
|
||
const token = getToken();
|
||
|
||
if (!token) {
|
||
throw new Error('No token available');
|
||
}
|
||
|
||
const headers = {
|
||
'Accept': 'application/json',
|
||
'Content-Type': 'application/json',
|
||
'X-EASE-ADMIN-TOKEN': token,
|
||
...options.headers,
|
||
};
|
||
|
||
const response = await fetch(url, {
|
||
...options,
|
||
headers,
|
||
});
|
||
|
||
// 检查是否token过期
|
||
if (response.status === 401) {
|
||
const data = await response.json();
|
||
if (data.code === '401') {
|
||
// Token过期,清除本地数据并重定向到登录页
|
||
clearAuthData();
|
||
window.location.href = '/login';
|
||
throw new Error('Token expired');
|
||
}
|
||
}
|
||
|
||
return response;
|
||
};
|
||
|
||
// Google OAuth相关函数
|
||
|
||
/**
|
||
* Initiates Google OAuth authentication flow using backend API
|
||
*/
|
||
export const signInWithGoogle = async () => {
|
||
try {
|
||
// 获取授权URL
|
||
const response = await fetch(`${JAVA_BASE_URL}/api/auth/google/authorize`, {
|
||
method: 'GET',
|
||
headers: {
|
||
'Accept': 'application/json',
|
||
'Content-Type': 'application/json',
|
||
},
|
||
});
|
||
|
||
const data: BaseResponse<GoogleAuthorizeResponse> = await response.json();
|
||
|
||
if (data.success && data.data) {
|
||
// 保存state参数用于CSRF防护
|
||
const oauthState: OAuthState = {
|
||
state: data.data.state,
|
||
timestamp: Date.now(),
|
||
redirectUrl: window.location.pathname
|
||
};
|
||
sessionStorage.setItem('google_oauth_state', JSON.stringify(oauthState));
|
||
|
||
// 重定向到Google授权页面
|
||
window.location.href = data.data.authUrl;
|
||
} else {
|
||
throw new Error(data.message || 'Failed to get authorization URL');
|
||
}
|
||
} catch (error) {
|
||
console.error('Google OAuth initialization failed:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Google ID Token 登录
|
||
* @param idToken Google ID Token
|
||
* @param action 操作类型:login | register | auto
|
||
*/
|
||
export const loginWithGoogleToken = async (idToken: string, action: 'login' | 'register' | 'auto' = 'auto') => {
|
||
try {
|
||
const requestData: GoogleLoginRequest = {
|
||
idToken,
|
||
action
|
||
};
|
||
|
||
const response = await fetch(`${JAVA_BASE_URL}/api/auth/google/login`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Accept': 'application/json',
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify(requestData),
|
||
});
|
||
|
||
const data: BaseResponse<GoogleLoginSuccessResponse> = await response.json();
|
||
|
||
if (response.status === 409) {
|
||
// 邮箱冲突处理
|
||
const conflictData = data as unknown as BaseResponse<EmailConflictData>;
|
||
throw {
|
||
type: 'EMAIL_CONFLICT',
|
||
status: 409,
|
||
data: conflictData.data,
|
||
message: data.message || 'Email already exists'
|
||
};
|
||
}
|
||
|
||
if (!data.success || !data.data) {
|
||
throw new Error(data.message || 'Google login failed');
|
||
}
|
||
|
||
// 保存token和用户信息
|
||
setToken(data.data.token);
|
||
setUser(data.data.userInfo);
|
||
|
||
return data.data.userInfo;
|
||
} catch (error) {
|
||
console.error('Google token login failed:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 绑定Google账户到当前用户
|
||
* @param bindToken 绑定令牌
|
||
* @param idToken Google ID Token (可选)
|
||
*/
|
||
export const bindGoogleAccount = async (bindToken: string, idToken?: string) => {
|
||
try {
|
||
const token = getToken();
|
||
if (!token) {
|
||
throw new Error('User not authenticated');
|
||
}
|
||
|
||
const requestData = {
|
||
bindToken,
|
||
idToken,
|
||
confirm: true
|
||
};
|
||
|
||
const response = await fetch(`${JAVA_BASE_URL}/api/auth/google/bind`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Accept': 'application/json',
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${token}`,
|
||
},
|
||
body: JSON.stringify(requestData),
|
||
});
|
||
|
||
const data: BaseResponse<{ token: string; message: string }> = await response.json();
|
||
|
||
if (!data.success || !data.data) {
|
||
throw new Error(data.message || 'Account binding failed');
|
||
}
|
||
|
||
// 更新token
|
||
setToken(data.data.token);
|
||
|
||
return data.data;
|
||
} catch (error) {
|
||
console.error('Google account binding failed:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 获取Google账户绑定状态
|
||
*/
|
||
export const getGoogleBindStatus = async () => {
|
||
try {
|
||
const token = getToken();
|
||
if (!token) {
|
||
throw new Error('User not authenticated');
|
||
}
|
||
|
||
const response = await fetch(`${JAVA_BASE_URL}/api/auth/google/status`, {
|
||
method: 'GET',
|
||
headers: {
|
||
'Accept': 'application/json',
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${token}`,
|
||
},
|
||
});
|
||
|
||
const data: BaseResponse<{ isBound: boolean; userId: string }> = await response.json();
|
||
|
||
if (!data.success) {
|
||
throw new Error(data.message || 'Failed to get bind status');
|
||
}
|
||
|
||
return data.data;
|
||
} catch (error) {
|
||
console.error('Get Google bind status failed:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Generates and stores a state parameter for OAuth to prevent CSRF attacks
|
||
*/
|
||
export const generateOAuthState = () => {
|
||
if (typeof window === 'undefined') return '';
|
||
|
||
// Generate a random string for state
|
||
const state = Math.random().toString(36).substring(2, 15);
|
||
|
||
// Store the state in session storage to validate later
|
||
sessionStorage.setItem('oauthState', state);
|
||
|
||
return state;
|
||
};
|
||
|
||
/**
|
||
* Validates the state parameter returned from OAuth to prevent CSRF attacks
|
||
*/
|
||
export const validateOAuthState = (state: string): boolean => {
|
||
if (typeof window === 'undefined') return false;
|
||
|
||
const storedState = sessionStorage.getItem('oauthState');
|
||
|
||
// Clean up the stored state regardless of validity
|
||
sessionStorage.removeItem('oauthState');
|
||
|
||
// Validate that the returned state matches what we stored
|
||
return state === storedState;
|
||
};
|
||
|
||
/**
|
||
* 获取用户信息
|
||
* @returns {Promise<any>} 用户信息对象
|
||
*/
|
||
export const getUserProfile = async (): Promise<any> => {
|
||
// const t = {
|
||
// id: 'fcb6768b6f49449387e6617f75baabc0',
|
||
// userId: 'fcb6768b6f49449387e6617f75baabc0',
|
||
// username: 'gxy',
|
||
// name: 'gxy',
|
||
// email: 'moviflow66@test.com',
|
||
// role: 'USER',
|
||
// isActive: 1,
|
||
// authType: 'LOCAL',
|
||
// lastLogin: new Date(),
|
||
// }
|
||
// setUser(t);
|
||
// return t;
|
||
try {
|
||
const token = getToken();
|
||
if (!token) {
|
||
throw new Error('No token available');
|
||
}
|
||
|
||
const response = await fetch(`${JAVA_BASE_URL}/auth/profile`, {
|
||
method: 'GET',
|
||
headers: {
|
||
'Accept': 'application/json',
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${token}`,
|
||
},
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(response.status.toString());
|
||
}
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.code === 0 && data.successful) {
|
||
// 更新本地存储的用户信息
|
||
const userInfo = {
|
||
id: data.data.user_id,
|
||
userId: data.data.user_id,
|
||
username: data.data.username,
|
||
name: data.data.name,
|
||
email: data.data.email,
|
||
role: data.data.role,
|
||
isActive: data.data.is_active,
|
||
authType: data.data.auth_type,
|
||
lastLogin: data.data.last_login,
|
||
};
|
||
|
||
setUser(userInfo);
|
||
return userInfo;
|
||
} else {
|
||
throw new Error(data.message || 'Failed to get user profile');
|
||
}
|
||
} catch (error) {
|
||
console.error('Get user profile failed:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 刷新用户信息
|
||
* @returns {Promise<any>} 最新的用户信息
|
||
*/
|
||
export const refreshUserProfile = async (): Promise<any> => {
|
||
try {
|
||
const profile = await getUserProfile();
|
||
return profile;
|
||
} catch (error) {
|
||
console.error('Refresh user profile failed:', error);
|
||
// 如果刷新失败,返回本地存储的用户信息
|
||
return getCurrentUser();
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 检查用户权限
|
||
* @param {string} requiredRole 需要的角色权限
|
||
* @returns {boolean} 是否有权限
|
||
*/
|
||
export const checkUserPermission = (requiredRole: string): boolean => {
|
||
const user = getCurrentUser();
|
||
if (!user || !user.role) {
|
||
return false;
|
||
}
|
||
|
||
// 角色权限等级
|
||
const roleHierarchy = {
|
||
'ADMIN': 3,
|
||
'MODERATOR': 2,
|
||
'USER': 1,
|
||
};
|
||
|
||
const userRoleLevel = roleHierarchy[user.role as keyof typeof roleHierarchy] || 0;
|
||
const requiredRoleLevel = roleHierarchy[requiredRole as keyof typeof roleHierarchy] || 0;
|
||
|
||
return userRoleLevel >= requiredRoleLevel;
|
||
};
|
||
|
||
/**
|
||
* 检查用户是否激活
|
||
* @returns {boolean} 用户是否激活
|
||
*/
|
||
export const isUserActive = (): boolean => {
|
||
const user = getCurrentUser();
|
||
return user?.isActive === 1;
|
||
};
|
||
/**
|
||
* 用户注册
|
||
* @param {Object} params - 注册参数
|
||
* @param {string} params.userName - 用户名
|
||
* @param {string} params.password - 密码
|
||
* @param {string} params.email - 邮箱
|
||
* @param {string} [params.inviteCode] - 邀请码(可选)
|
||
* @returns {Promise<any>} 注册结果
|
||
* @throws {Error} 注册失败时抛出异常
|
||
*/
|
||
export const registerUser = async ({
|
||
userName,
|
||
password,
|
||
email,
|
||
inviteCode,
|
||
}: {
|
||
userName: string;
|
||
password: string;
|
||
email: string;
|
||
inviteCode?: string;
|
||
}): Promise<any> => {
|
||
try {
|
||
const response = await fetch(`${JAVA_BASE_URL}/api/user/register`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Accept': 'application/json',
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
userName,
|
||
password,
|
||
email,
|
||
inviteCode,
|
||
}),
|
||
});
|
||
|
||
const data = await response.json();
|
||
console.log('data', data)
|
||
if(!data.success){
|
||
throw new Error(data.message||data.msg)
|
||
}
|
||
return data as {
|
||
success: boolean;
|
||
};
|
||
} catch (error) {
|
||
console.error('Register failed:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
export const registerUserWithInvite = async ({
|
||
name,
|
||
password,
|
||
email,
|
||
invite_code,
|
||
}: {
|
||
name: string;
|
||
password: string;
|
||
email: string;
|
||
invite_code?: string;
|
||
}): Promise<RegisterUserResponse> => {
|
||
try {
|
||
const response = await fetch(`${JAVA_BASE_URL}/api/user_fission/register_with_invite`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Accept': 'application/json',
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
name,
|
||
password,
|
||
email,
|
||
invite_code,
|
||
}),
|
||
});
|
||
|
||
const data = await response.json();
|
||
if(!data.successful){
|
||
throw new Error(data.message || '注册失败');
|
||
}
|
||
return data as RegisterUserResponse;
|
||
} catch (error) {
|
||
console.error('Register with invite failed:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 发送验证链接
|
||
* @param {string} email - 邮箱
|
||
* @returns {Promise<any>} 发送验证链接结果
|
||
*/
|
||
export const sendVerificationLink = async (email: string) => {
|
||
try {
|
||
const response = await fetch(`${JAVA_BASE_URL}/api/user/sendVerificationLink?email=${email}`);
|
||
const data = await response.json();
|
||
if(!data.success){
|
||
throw new Error(data.message||data.msg)
|
||
}
|
||
return data as {
|
||
success: boolean;
|
||
};
|
||
} catch (error) {
|
||
console.error('Send verification link failed:', error);
|
||
throw error;
|
||
}
|
||
}; |