import type { BaseResponse, GoogleAuthorizeResponse, GoogleLoginRequest, GoogleLoginSuccessResponse, EmailConflictData, OAuthState } from '@/app/types/google-oauth'; import { setUserProperties } from '@/utils/analytics'; import { javaUrl, baseUrl, googleClientId, getGoogleRedirectUri } from '@/lib/env'; // API配置 - 直接使用导入的配置变量 // 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(`${javaUrl}/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)); // 设置详细的GA用户属性 if (user && user.id) { setUserProperties(user.id, { // 基础用户信息 user_id: user.id, email: user.email, username: user.username, // 认证信息 auth_type: user.authType || 'LOCAL', is_active: user.isActive || 1, // 用户状态 user_status: user.isActive === 1 ? 'active' : 'inactive', // 登录信息 last_login: user.lastLogin || new Date().toISOString(), // 会话信息 session_id: `${user.id}_${Date.now()}` }); } }; /** * 清除认证数据 */ 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相关函数 /** * 初始化Google GSI SDK */ export const initializeGoogleGSI = (): Promise => { return new Promise((resolve, reject) => { // 检查是否已经加载 if (window.google?.accounts?.id) { resolve(); return; } // 动态加载Google GSI脚本 const script = document.createElement('script'); script.src = 'https://accounts.google.com/gsi/client'; script.async = true; script.defer = true; script.onload = () => { // 等待Google API完全加载 const checkGoogleReady = () => { if (window.google?.accounts?.id) { resolve(); } else { setTimeout(checkGoogleReady, 100); } }; checkGoogleReady(); }; script.onerror = () => { reject(new Error('Failed to load Google GSI SDK')); }; document.head.appendChild(script); }); }; /** * Medium风格: 页面直接跳转到Google登录 (推荐) * 参考Medium的实现方式,使用页面跳转 * @param inviteCode 邀请码(可选) */ export const signInWithGoogle = async (inviteCode?: string): Promise => { try { console.log('开始Google登录流程,使用环境变量配置...'); // 从 sessionStorage 获取邀请码(如果没有传入的话) let finalInviteCode = inviteCode; if (!finalInviteCode) { try { const sessionInviteCode = sessionStorage.getItem("inviteCode"); if (sessionInviteCode) { finalInviteCode = sessionInviteCode; console.log('从 sessionStorage 获取到邀请码:', finalInviteCode); } } catch (e) { console.warn('无法从 sessionStorage 获取邀请码:', e); } } // 从统一配置获取配置 const redirectUri = getGoogleRedirectUri(); // 生成随机nonce用于安全验证 const nonce = Array.from(crypto.getRandomValues(new Uint8Array(32))) .map(b => b.toString(16).padStart(2, '0')).join(''); // 构建state参数(包含邀请码等信息) const stateData = { inviteCode: finalInviteCode || '', timestamp: Date.now(), origin: window.location.pathname + window.location.search, nonce: nonce }; console.log('使用的配置:', { clientId: googleClientId, javaBaseUrl: javaUrl, redirectUri }); // 详细的调试日志 console.log('🔍 Google OAuth 调试信息:'); console.log(' - 当前域名:', window.location.hostname); console.log(' - 当前协议:', window.location.protocol); console.log(' - 当前端口:', window.location.port); console.log(' - 完整 origin:', window.location.origin); console.log(' - 环境变量 NEXT_PUBLIC_GOOGLE_REDIRECT_URI:', redirectUri); console.log(' - 最终使用的 redirect_uri:', redirectUri); console.log(' - Google Client ID:', googleClientId); // 构建Google OAuth2授权URL const authParams = new URLSearchParams({ access_type: 'online', client_id: googleClientId, nonce: nonce, redirect_uri: redirectUri, response_type: 'code', // 使用授权码模式 scope: 'email openid profile', state: JSON.stringify(stateData), prompt: 'select_account' // 总是显示账号选择 }); const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${authParams.toString()}`; console.log('跳转到Google授权页面:', authUrl); console.log('🔍 调试信息 - 授权URL中的redirect_uri:', authParams.get('redirect_uri')); console.log('🔍 调试信息 - 当前页面域名:', window.location.origin); console.log('📤 发送给 Google 的完整授权 URL:'); console.log(authUrl); console.log('📋 URL 参数解析:'); authParams.forEach((value, key) => { if (key === 'state') { console.log(` - ${key}: ${JSON.stringify(JSON.parse(value), null, 2)}`); } else { console.log(` - ${key}: ${value}`); } }); // 保存state到sessionStorage用于验证 sessionStorage.setItem('google_oauth_state', JSON.stringify({ nonce: nonce, timestamp: Date.now(), inviteCode: finalInviteCode || '' })); // 直接在当前页面跳转到Google window.location.href = authUrl; } catch (error) { console.error('Google登录跳转失败:', error); throw error; } }; /** * Google ID Token 登录 * @param idToken Google ID Token * @param action 操作类型:login | register | auto * @param inviteCode 邀请码(可选) */ export const loginWithGoogleToken = async (idToken: string, action: 'login' | 'register' | 'auto' = 'auto', inviteCode?: string) => { try { const requestData: GoogleLoginRequest = { idToken, action, inviteCode }; const response = await fetch(`${javaUrl}/api/auth/google/login`, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify(requestData), }); const data: BaseResponse = await response.json(); if (response.status === 409) { // 邮箱冲突处理 const conflictData = data as unknown as BaseResponse; 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(`${javaUrl}/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(`${javaUrl}/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} 用户信息对象 */ export const getUserProfile = async (): Promise => { // 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(`${baseUrl}/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} 最新的用户信息 */ export const refreshUserProfile = async (): Promise => { 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} 注册结果 * @throws {Error} 注册失败时抛出异常 */ export const registerUser = async ({ userName, password, email, inviteCode, }: { userName: string; password: string; email: string; inviteCode?: string; }): Promise => { try { const response = await fetch(`${baseUrl}/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 ({ password, email, invite_code, }: { password: string; email: string; invite_code?: string; }): Promise => { try { const response = await fetch(`${baseUrl}/api/user_fission/register_with_invite`, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ 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} 发送验证链接结果 */ export const sendVerificationLink = async (email: string) => { try { const response = await fetch(`${javaUrl}/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; } };