- Created a new SVG file for login devices with detailed paths and styles. - Added a new SVG file for the passkey icon with multiple vector paths and fill attributes.
142 lines
3.6 KiB
JavaScript
142 lines
3.6 KiB
JavaScript
import { createContext, useState, useContext, useEffect } from 'react';
|
||
import { useNavigate } from 'react-router-dom';
|
||
import axios from 'axios';
|
||
import { getApiBaseUrl } from '../utils/apiBaseUrl';
|
||
|
||
const AuthContext = createContext(null);
|
||
|
||
const API_BASE_URL = getApiBaseUrl();
|
||
|
||
export function AuthProvider({ children }) {
|
||
const [user, setUser] = useState(null);
|
||
const [loading, setLoading] = useState(true);
|
||
const navigate = useNavigate();
|
||
|
||
useEffect(() => {
|
||
checkAuth();
|
||
}, []);
|
||
|
||
const checkAuth = async () => {
|
||
const token = localStorage.getItem('accessToken');
|
||
const refreshToken = localStorage.getItem('refreshToken');
|
||
|
||
if (!token || !refreshToken) {
|
||
setLoading(false);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await axios.get(`${API_BASE_URL}/auth/me`, {
|
||
headers: { Authorization: `Bearer ${token}` }
|
||
});
|
||
setUser(response.data);
|
||
} catch (error) {
|
||
// Try to refresh token
|
||
try {
|
||
const refreshResponse = await axios.post(`${API_BASE_URL}/auth/refresh`, {
|
||
refreshToken
|
||
});
|
||
localStorage.setItem('accessToken', refreshResponse.data.accessToken);
|
||
|
||
// Retry getting user
|
||
const userResponse = await axios.get(`${API_BASE_URL}/auth/me`, {
|
||
headers: { Authorization: `Bearer ${refreshResponse.data.accessToken}` }
|
||
});
|
||
setUser(userResponse.data);
|
||
} catch (refreshError) {
|
||
// Refresh failed, logout
|
||
logout();
|
||
}
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const login = async (username, password) => {
|
||
try {
|
||
const response = await axios.post(`${API_BASE_URL}/auth/login`, {
|
||
username,
|
||
password
|
||
});
|
||
|
||
completeLogin(response.data);
|
||
|
||
return { success: true };
|
||
} catch (error) {
|
||
console.error('Login error:', error);
|
||
return {
|
||
success: false,
|
||
error: error.response?.data?.error || 'Login failed'
|
||
};
|
||
}
|
||
};
|
||
|
||
const loginWithPasskey = async (credential) => {
|
||
try {
|
||
const response = await axios.post(
|
||
`${API_BASE_URL}/auth/passkeys/authentication/verify`,
|
||
{ response: credential }
|
||
);
|
||
|
||
completeLogin(response.data);
|
||
return { success: true };
|
||
} catch (error) {
|
||
console.error('Passkey login error:', error);
|
||
return {
|
||
success: false,
|
||
error: error.response?.data?.error || 'Не удалось выполнить вход по passkey'
|
||
};
|
||
}
|
||
};
|
||
|
||
const completeLogin = ({ user, accessToken, refreshToken }) => {
|
||
localStorage.setItem('accessToken', accessToken);
|
||
localStorage.setItem('refreshToken', refreshToken);
|
||
|
||
setUser(user);
|
||
navigate('/');
|
||
};
|
||
|
||
const logout = async () => {
|
||
const refreshToken = localStorage.getItem('refreshToken');
|
||
|
||
if (refreshToken) {
|
||
try {
|
||
await axios.post(`${API_BASE_URL}/auth/logout`, { refreshToken });
|
||
} catch (error) {
|
||
console.error('Logout error:', error);
|
||
}
|
||
}
|
||
|
||
localStorage.removeItem('accessToken');
|
||
localStorage.removeItem('refreshToken');
|
||
setUser(null);
|
||
navigate('/login');
|
||
};
|
||
|
||
const getAccessToken = () => {
|
||
return localStorage.getItem('accessToken');
|
||
};
|
||
|
||
const value = {
|
||
user,
|
||
loading,
|
||
login,
|
||
loginWithPasskey,
|
||
logout,
|
||
getAccessToken,
|
||
isAuthenticated: !!user,
|
||
isAdmin: user?.role === 'admin'
|
||
};
|
||
|
||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||
}
|
||
|
||
export function useAuth() {
|
||
const context = useContext(AuthContext);
|
||
if (!context) {
|
||
throw new Error('useAuth must be used within AuthProvider');
|
||
}
|
||
return context;
|
||
}
|