From 70ec57d6fb6939b9f1a6759b23ba02137147b55d Mon Sep 17 00:00:00 2001 From: DIKER0K Date: Tue, 16 Dec 2025 17:09:51 +0500 Subject: [PATCH] test fix authorization --- src/renderer/App.tsx | 92 +++++++++++++++++----- src/renderer/api.ts | 104 ++++++++++++++++++++++++- src/renderer/hooks/useAuth.ts | 143 ++++++++++++++++++---------------- src/renderer/pages/Login.tsx | 83 +++++++------------- 4 files changed, 277 insertions(+), 145 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index a215023..98022e5 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -27,6 +27,7 @@ import DailyQuests from './pages/DailyQuests'; import Settings from './pages/Settings'; import Inventory from './pages/Inventory'; import { TrayBridge } from './utils/TrayBridge'; +import { API_BASE_URL } from './api'; const AuthCheck = ({ children }: { children: ReactNode }) => { const [isAuthenticated, setIsAuthenticated] = useState(null); @@ -35,18 +36,53 @@ const AuthCheck = ({ children }: { children: ReactNode }) => { const checkAuth = async () => { try { const savedConfig = localStorage.getItem('launcher_config'); - if (savedConfig) { - const config = JSON.parse(savedConfig); - if (config.accessToken) { - // Можно добавить дополнительную проверку токена - const isValid = await validateToken( - config.accessToken, - config.clientToken, - ); - setIsAuthenticated(isValid); - return; - } + if (!savedConfig) { + setIsAuthenticated(false); + return; } + + const config = JSON.parse(savedConfig); + + if (config.accessToken && config.clientToken) { + // 1. Проверяем валидность токена через ваш API + const response = await fetch(`${API_BASE_URL}/auth/validate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + accessToken: config.accessToken, + clientToken: config.clientToken, + }), + }); + + const isValid = response.ok; + + // 2. ДОПОЛНИТЕЛЬНО: проверяем доступ к API через /auth/me + if (isValid) { + try { + const meResponse = await fetch( + `${API_BASE_URL}/auth/me?${new URLSearchParams({ + accessToken: config.accessToken, + clientToken: config.clientToken, + })}`, + ); + + if (!meResponse.ok) { + // Токен валиден для Yggdrasil, но нет доступа к API + console.warn('Токен валиден, но нет доступа к API лаунчера'); + setIsAuthenticated(false); + return; + } + } catch (error) { + console.error('Ошибка проверки доступа к API:', error); + setIsAuthenticated(false); + return; + } + } + + setIsAuthenticated(isValid); + return; + } + setIsAuthenticated(false); } catch (error) { console.error('Ошибка проверки авторизации:', error); @@ -95,7 +131,7 @@ const AuthCheck = ({ children }: { children: ReactNode }) => { }; if (isAuthenticated === null) { - return ; + return ; } return isAuthenticated ? children : ; @@ -144,8 +180,14 @@ const AppLayout = () => { const settings = JSON.parse(raw); - document.body.classList.toggle('reduce-motion', Boolean(settings.reduceMotion)); - document.body.classList.toggle('no-blur', settings.blurEffects === false); + document.body.classList.toggle( + 'reduce-motion', + Boolean(settings.reduceMotion), + ); + document.body.classList.toggle( + 'no-blur', + settings.blurEffects === false, + ); const ui = document.getElementById('app-ui'); if (ui && typeof settings.uiScale === 'number') { @@ -186,7 +228,10 @@ const AppLayout = () => { return () => { window.removeEventListener('settings-updated', applySettings); - window.removeEventListener('settings-updated', pushLauncherSettingsToMain); + window.removeEventListener( + 'settings-updated', + pushLauncherSettingsToMain, + ); }; }, []); @@ -215,14 +260,23 @@ const AppLayout = () => { }, []); return ( - + {/* ФОН — НЕ масштабируется */} - + {/* UI — масштабируется */} - { } /> - + diff --git a/src/renderer/api.ts b/src/renderer/api.ts index 2a42935..d027946 100644 --- a/src/renderer/api.ts +++ b/src/renderer/api.ts @@ -1,5 +1,95 @@ export const API_BASE_URL = 'https://minecraft.api.popa-popa.ru'; +// АВТОРИЗАЦИЯ \\ + +export interface AuthSession { + accessToken: string; + clientToken: string; + selectedProfile: { + id: string; // UUID пользователя + name: string; // Имя игрока + }; + user?: { + id: string; + properties?: Array<{ + name: string; + value: string; + }>; + }; +} + +export async function authenticate( + username: string, + password: string, +): Promise { + const clientToken = Math.random().toString(36).substring(2); + + const response = await fetch(`${API_BASE_URL}/auth/authenticate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + username, + password, + clientToken, + requestUser: true, + }), + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Ошибка авторизации: ${errorText}`); + } + + return await response.json(); +} + +// Валидация токена +export async function validateToken( + accessToken: string, + clientToken: string, +): Promise { + const response = await fetch(`${API_BASE_URL}/auth/validate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ accessToken, clientToken }), + }); + + return response.ok; +} + +// Обновление токена +export async function refreshToken( + accessToken: string, + clientToken: string, +): Promise { + const response = await fetch(`${API_BASE_URL}/auth/refresh`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ accessToken, clientToken }), + }); + + if (!response.ok) return null; + return await response.json(); +} + +// Получение информации о себе (проверяет, что токен валидный И есть доступ к API) +export async function getMe(): Promise { + const { accessToken, clientToken } = getAuthTokens(); + + if (!accessToken || !clientToken) { + throw new Error('Нет токенов авторизации'); + } + + const params = new URLSearchParams({ accessToken, clientToken }); + const response = await fetch(`${API_BASE_URL}/auth/me?${params.toString()}`); + + if (!response.ok) { + throw new Error('Не удалось получить данные пользователя'); + } + + return await response.json(); +} + export interface Player { uuid: string; username: string; @@ -604,7 +694,9 @@ export interface DailyDaysResponse { count: number; } -export async function fetchDailyClaimDays(limit = 120): Promise { +export async function fetchDailyClaimDays( + limit = 120, +): Promise { const { accessToken, clientToken } = getAuthTokens(); if (!accessToken || !clientToken) { @@ -695,14 +787,20 @@ export async function fetchDailyQuestsStatus(): Promise { +export async function claimDailyQuest( + questKey: string, +): Promise { const { accessToken, clientToken } = getAuthTokens(); if (!accessToken || !clientToken) { throw new Error('Нет токенов авторизации'); } - const params = new URLSearchParams({ accessToken, clientToken, quest_key: questKey }); + const params = new URLSearchParams({ + accessToken, + clientToken, + quest_key: questKey, + }); const response = await fetch( `${API_BASE_URL}/users/daily-quests/claim?${params.toString()}`, { method: 'POST' }, diff --git a/src/renderer/hooks/useAuth.ts b/src/renderer/hooks/useAuth.ts index 503b50f..8a69a90 100644 --- a/src/renderer/hooks/useAuth.ts +++ b/src/renderer/hooks/useAuth.ts @@ -1,83 +1,45 @@ import { useState } from 'react'; - -interface AuthSession { - accessToken: string; - clientToken: string; - selectedProfile: { - id: string; - name: string; - }; -} +import { + authenticate, + validateToken, + refreshToken, + type AuthSession, +} from '../api'; export default function useAuth() { - const [status, setStatus] = useState('idle'); + const [status, setStatus] = useState< + 'idle' | 'validating' | 'refreshing' | 'authenticating' | 'error' + >('idle'); - // Проверка валидности токена - const validateSession = async (accessToken: string): Promise => { - try { - setStatus('validating'); - const response = await window.electron.ipcRenderer.invoke( - 'validate-token', - accessToken, - ); - setStatus('idle'); - return response.valid; - } catch (error) { - console.error('Ошибка при валидации токена:', error); - setStatus('error'); - return false; - } - }; - - // Обновление токена - const refreshSession = async ( - accessToken: string, - clientToken: string, - ): Promise => { - try { - setStatus('refreshing'); - const response = await window.electron.ipcRenderer.invoke( - 'refresh-token', - { accessToken, clientToken }, - ); - setStatus('idle'); - return response; - } catch (error) { - console.error('Ошибка при обновлении токена:', error); - setStatus('error'); - return null; - } - }; - - // Аутентификация в Ely.by - const authenticateWithElyBy = async ( + // Аутентификация (HTTP напрямую, без IPC!) + const authenticateUser = async ( username: string, password: string, - saveConfigFunc: Function, + saveConfigFunc: (config: any) => void, ): Promise => { try { setStatus('authenticating'); - const response = await window.electron.ipcRenderer.invoke( - 'authenticate', - { username, password }, - ); - if (response && response.accessToken) { - // Правильно сохраняем данные в конфигурации - saveConfigFunc({ - username: response.selectedProfile.name, // Имя игрока как строка - uuid: response.selectedProfile.id, - accessToken: response.accessToken, - clientToken: response.clientToken, - memory: 4096, // Сохраняем значение по умолчанию или из предыдущей конфигурации - }); + // Прямой HTTP-запрос к вашему серверу + const session = await authenticate(username, password); - setStatus('authenticated'); - return response; - } + // Сохраняем в конфигурацию + saveConfigFunc({ + username: session.selectedProfile.name, + uuid: session.selectedProfile.id, + accessToken: session.accessToken, + clientToken: session.clientToken, + memory: 4096, + }); - setStatus('error'); - return null; + // Уведомляем Electron (для запуска Minecraft) + await window.electron.ipcRenderer.invoke('auth-changed', { + isAuthed: true, + minecraftSession: session, // Передаём всю сессию + }); + + setStatus('authenticating'); + return session; } catch (error) { console.error('Ошибка при аутентификации:', error); setStatus('error'); @@ -85,10 +47,53 @@ export default function useAuth() { } }; + // Валидация токена (HTTP напрямую) + const validateSession = async (accessToken: string): Promise => { + try { + setStatus('validating'); + + // Получаем clientToken из localStorage + const savedConfig = localStorage.getItem('launcher_config'); + if (!savedConfig) return false; + + const config = JSON.parse(savedConfig); + + // Прямой HTTP-запрос на валидацию + const isValid = await validateToken(accessToken, config.clientToken); + + setStatus('idle'); + return isValid; + } catch (error) { + console.error('Ошибка при валидации токена:', error); + setStatus('error'); + return false; + } + }; + + // Обновление токена (HTTP напрямую) + const refreshSession = async ( + accessToken: string, + clientToken: string, + ): Promise => { + try { + setStatus('refreshing'); + + // Прямой HTTP-запрос на обновление + const session = await refreshToken(accessToken, clientToken); + + setStatus('idle'); + return session; + } catch (error) { + console.error('Ошибка при обновлении токена:', error); + setStatus('error'); + return null; + } + }; + return { status, + authenticateUser, validateSession, refreshSession, - authenticateWithElyBy, }; } diff --git a/src/renderer/pages/Login.tsx b/src/renderer/pages/Login.tsx index a576be4..d58725a 100644 --- a/src/renderer/pages/Login.tsx +++ b/src/renderer/pages/Login.tsx @@ -15,91 +15,66 @@ interface LoginProps { const Login = ({ onLoginSuccess }: LoginProps) => { const navigate = useNavigate(); const { config, setConfig, saveConfig, handleInputChange } = useConfig(); - const { status, validateSession, refreshSession, authenticateWithElyBy } = - useAuth(); + const auth = useAuth(); const [loading, setLoading] = useState(false); - const authorization = async () => { - console.log('Начинаем процесс авторизации...'); - + const handleLogin = async () => { if (!config.username.trim()) { - console.log('Ошибка: не указан никнейм'); alert('Введите никнейм!'); return; } + if (!config.password) { + alert('Введите пароль!'); + return; + } + setLoading(true); try { - // Проверяем, есть ли сохранённый токен + // Проверяем существующий токен if (config.accessToken && config.clientToken) { - console.log('Проверка валидности существующего токена...'); - const isValid = await validateSession(config.accessToken); + const isValid = await auth.validateSession(config.accessToken); if (!isValid) { - console.log('Токен недействителен, пытаемся обновить...'); - const refreshedSession = await refreshSession( + // Пробуем обновить + const refreshed = await auth.refreshSession( config.accessToken, config.clientToken, ); - if (!refreshedSession) { - console.log( - 'Не удалось обновить токен, требуется новая авторизация', + if (!refreshed) { + // Нужна новая авторизация + const session = await auth.authenticateUser( + config.username, + config.password, + saveConfig, ); - // Очищаем недействительные токены - saveConfig({ - accessToken: '', - clientToken: '', - }); - // Пытаемся выполнить новую авторизацию - if (config.password) { - const newSession = await authenticateWithElyBy( - config.username, - config.password, - saveConfig, - ); - if (!newSession) { - console.log('Авторизация не удалась'); - return; - } - } else { - console.log('Требуется ввод пароля для новой авторизации'); - return; - } + if (!session) throw new Error('Авторизация не удалась'); } - } else { - console.log('Токен действителен'); } } else { - console.log('Токен отсутствует, выполняем авторизацию...'); - // Проверяем наличие пароля - if (!config.password) { - console.log('Ошибка: не указан пароль'); - alert('Введите пароль!'); - return; - } - - const session = await authenticateWithElyBy( + // Новая авторизация + const session = await auth.authenticateUser( config.username, config.password, saveConfig, ); - if (!session) { - console.log('Авторизация не удалась'); - return; - } + + if (!session) throw new Error('Авторизация не удалась'); } - console.log('Авторизация успешно завершена'); - + // Успех if (onLoginSuccess) { onLoginSuccess(config.username); } - await window.electron.ipcRenderer.invoke('auth-changed', { isAuthed: true }); + navigate('/'); } catch (error: any) { - console.log(`ОШИБКА при авторизации: ${error.message}`); + console.error('Ошибка авторизации:', error); + alert(`Ошибка: ${error.message}`); + + // Очищаем невалидные токены saveConfig({ accessToken: '', clientToken: '', @@ -119,7 +94,7 @@ const Login = ({ onLoginSuccess }: LoginProps) => { )}