test fix authorization
This commit is contained in:
@ -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<boolean | null>(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 <FullScreenLoader message="Загрузка..." />;
|
||||
return <FullScreenLoader message="Проверка авторизации..." />;
|
||||
}
|
||||
|
||||
return isAuthenticated ? children : <Navigate to="/login" replace />;
|
||||
@ -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 (
|
||||
<Box sx={{ width: '100vw', height: '100vh', position: 'relative', overflow: 'hidden' }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{/* ФОН — НЕ масштабируется */}
|
||||
<Box sx={{ position: 'fixed', inset: 0, zIndex: 0, pointerEvents: 'none', }}>
|
||||
<Box
|
||||
sx={{ position: 'fixed', inset: 0, zIndex: 0, pointerEvents: 'none' }}
|
||||
>
|
||||
<MinecraftBackground />
|
||||
</Box>
|
||||
|
||||
{/* UI — масштабируется */}
|
||||
<Box
|
||||
<Box
|
||||
id="app-scroll"
|
||||
sx={{
|
||||
position: 'relative',
|
||||
@ -344,7 +398,7 @@ const AppLayout = () => {
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</Routes>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@ -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<AuthSession> {
|
||||
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<boolean> {
|
||||
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<AuthSession | null> {
|
||||
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<MeResponse> {
|
||||
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<DailyDaysResponse> {
|
||||
export async function fetchDailyClaimDays(
|
||||
limit = 120,
|
||||
): Promise<DailyDaysResponse> {
|
||||
const { accessToken, clientToken } = getAuthTokens();
|
||||
|
||||
if (!accessToken || !clientToken) {
|
||||
@ -695,14 +787,20 @@ export async function fetchDailyQuestsStatus(): Promise<DailyQuestsStatusRespons
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
export async function claimDailyQuest(questKey: string): Promise<DailyQuestClaimResponse> {
|
||||
export async function claimDailyQuest(
|
||||
questKey: string,
|
||||
): Promise<DailyQuestClaimResponse> {
|
||||
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' },
|
||||
|
||||
@ -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<boolean> => {
|
||||
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<AuthSession | null> => {
|
||||
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<AuthSession | null> => {
|
||||
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<boolean> => {
|
||||
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<AuthSession | null> => {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@ -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) => {
|
||||
<AuthForm
|
||||
config={config}
|
||||
handleInputChange={handleInputChange}
|
||||
onLogin={authorization}
|
||||
onLogin={handleLogin}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user