2026-09-02 15:53:30 +03:00

234 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { CircularProgress } from '@mui/material';
import Keycloak from 'keycloak-js';
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
import { AuthApi } from '../../api/auth';
import { UsersApi } from '../../api/users';
import { getAuthToken, isTokenExpired, saveAuthTokens } from '../../api/utils/token';
import { Config } from '../../conf/config';
const AuthContext = createContext(null);
const LoadingScreen = () => (
<div className='flex items-center justify-center min-h-screen'>
<CircularProgress />
</div>
);
const LocalAuthProvider = ({ children }) => {
const [loading, setLoading] = useState(true);
const [user, setUser] = useState(null);
const [isAuthenticated, setIsAuthenticated] = useState(() => {
const token = getAuthToken();
return !!token && !isTokenExpired();
});
const loadUser = useCallback(async () => {
if (!isAuthenticated) {
setUser(null);
return;
}
try {
const userData = await UsersApi.me();
setUser(userData);
} catch (error) {
console.error('Ошибка загрузки пользователя:', error);
if (error.response?.status === 401) {
setIsAuthenticated(false);
setUser(null);
} else {
setUser(null);
}
}
}, [isAuthenticated]);
const login = async (username, password) => {
setLoading(true);
try {
const data = await AuthApi.login(username, password);
saveAuthTokens(data);
setIsAuthenticated(true);
try {
const userData = await UsersApi.me();
setUser(userData);
} catch (userError) {
console.error('Ошибка загрузки пользователя:', userError);
setUser(null);
}
} catch (error) {
setIsAuthenticated(false);
setUser(null);
console.error('Ошибка авторизации:', error);
throw new Error(error?.response?.data?.detail || 'Неверный логин или пароль', { cause: error });
} finally {
setLoading(false);
}
};
const logout = async () => {
try {
AuthApi.logout();
setIsAuthenticated(false);
setUser(null);
} catch (error) {
console.error('Ошибка выхода:', error);
}
};
useEffect(() => {
setLoading(false);
}, []);
useEffect(() => {
loadUser();
}, [loadUser]);
useEffect(() => {
const handleAuthChange = (_event) => {
const token = getAuthToken();
const newIsAuthenticated = !!token && !isTokenExpired();
setIsAuthenticated(newIsAuthenticated);
};
const handleAuthExpired = () => {
setIsAuthenticated(false);
setUser(null);
};
window.addEventListener('auth:changed', handleAuthChange);
window.addEventListener('auth:expired', handleAuthExpired);
return () => {
window.removeEventListener('auth:changed', handleAuthChange);
window.removeEventListener('auth:expired', handleAuthExpired);
};
}, []);
return (
<AuthContext.Provider value={{ isAuthenticated, user, login, logout, loadUser, loading }}>
{loading ? <LoadingScreen /> : children}
</AuthContext.Provider>
);
};
const KeycloakAuthProvider = ({ children }) => {
const [keycloak] = useState(
() =>
new Keycloak({
url: Config.KEYCLOAK_URL,
realm: Config.REALM,
clientId: Config.CLIENT_ID,
}),
);
const [loading, setLoading] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [user, setUser] = useState(null);
const saveKeycloakToken = useCallback(() => {
if (!keycloak.token) return;
localStorage.setItem('access_token', keycloak.token);
localStorage.removeItem('refresh_token');
window.dispatchEvent(new CustomEvent('auth:changed', { detail: { loggedIn: true } }));
}, [keycloak]);
const loadUser = useCallback(async () => {
try {
const userData = await UsersApi.me();
setUser(userData);
} catch (error) {
console.error('Ошибка загрузки пользователя:', error);
setUser(null);
}
}, []);
const login = useCallback(() => keycloak.login(), [keycloak]);
const logout = useCallback(async () => {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
setIsAuthenticated(false);
setUser(null);
await keycloak.logout({ redirectUri: window.location.origin });
}, [keycloak]);
useEffect(() => {
let active = true;
const initialize = async () => {
try {
const authenticated = await keycloak.init({
onLoad: 'login-required',
checkLoginIframe: false,
pkceMethod: 'S256',
});
if (!active) return;
setIsAuthenticated(authenticated);
if (authenticated) {
saveKeycloakToken();
await loadUser();
}
} catch (error) {
console.error('Ошибка инициализации Keycloak:', error);
if (active) {
setIsAuthenticated(false);
setUser(null);
}
} finally {
if (active) setLoading(false);
}
};
keycloak.onAuthRefreshSuccess = saveKeycloakToken;
keycloak.onTokenExpired = async () => {
try {
await keycloak.updateToken(30);
saveKeycloakToken();
} catch (error) {
console.error('Не удалось обновить токен Keycloak:', error);
await login();
}
};
keycloak.onAuthLogout = () => {
localStorage.removeItem('access_token');
setIsAuthenticated(false);
setUser(null);
};
initialize();
return () => {
active = false;
keycloak.onAuthRefreshSuccess = undefined;
keycloak.onTokenExpired = undefined;
keycloak.onAuthLogout = undefined;
};
}, [keycloak, loadUser, login, saveKeycloakToken]);
return (
<AuthContext.Provider value={{ isAuthenticated, user, login, logout, loadUser, loading }}>
{loading ? <LoadingScreen /> : children}
</AuthContext.Provider>
);
};
export const AuthProvider = ({ children }) => {
if (Config.IS_PRODUCTION) {
return <KeycloakAuthProvider>{children}</KeycloakAuthProvider>;
}
return <LocalAuthProvider>{children}</LocalAuthProvider>;
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};