diff --git a/package.json b/package.json index f8891c2..8b21de9 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "easymde": "^2.19.0", "i18next": "^24.2.2", "js-cookie": "^3.0.5", + "jwt-decode": "^4.0.0", "react": "^18.0.0", "react-dom": "^18.0.0", "react-hook-form": "^7.30.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a0ed24f..4fe1e28 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,9 @@ importers: js-cookie: specifier: ^3.0.5 version: 3.0.5 + jwt-decode: + specifier: ^4.0.0 + version: 4.0.0 react: specifier: ^18.0.0 version: 18.3.1 @@ -2067,6 +2070,10 @@ packages: jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jwt-decode@4.0.0: + resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} + engines: {node: '>=18'} + kbar@0.1.0-beta.45: resolution: {integrity: sha512-kXvjthqPLoWZXlxLJPrFKioskNdQv1O3Ukg5mqq2ExK3Ix1qvYT3W/ACDRIv/e/CHxPWZoTriB4oFbQ6UCSX5g==} peerDependencies: @@ -5408,6 +5415,8 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jwt-decode@4.0.0: {} + kbar@0.1.0-beta.45(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) diff --git a/src/App.tsx b/src/App.tsx index ab7cc23..f306722 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,7 +4,7 @@ import {RefineKbar, RefineKbarProvider} from '@refinedev/kbar' import {ErrorComponent, useNotificationProvider, RefineSnackbarProvider, ThemedLayoutV2} from '@refinedev/mui' -import dataProvider from '@refinedev/simple-rest' +import {customDataProvider} from './providers/data' import CssBaseline from '@mui/material/CssBaseline' import GlobalStyles from '@mui/material/GlobalStyles' import {BrowserRouter, Route, Routes, Outlet} from 'react-router' @@ -29,7 +29,6 @@ import {RouteList, RouteCreate, RouteEdit, RouteShow} from './pages/route' import {CountryIcon, CityIcon, CarrierIcon, MediaIcon, ArticleIcon, SightIcon, StationIcon, VehicleIcon, RouteIcon} from './components/ui/Icons' import SidebarTitle from './components/ui/SidebarTitle' -import {BACKEND_URL} from './lib/constants' function App() { return ( @@ -41,7 +40,7 @@ function App() { { try { - if (email === MOCK_USER.email && password === MOCK_USER.password) { - const mockResponse = { - token: 'mock-jwt-token', - user: MOCK_USER, - } - - localStorage.setItem(TOKEN_KEY, mockResponse.token) - localStorage.setItem('user', JSON.stringify(mockResponse.user)) - - return { - success: true, - redirectTo: '/', - } - } - - // If not mock user, try real API - const response = await axios.post(`${API_URL}/auth/login`, { + const response = await axios.post(`${BACKEND_URL}/auth/login`, { email, password, }) - if (response.data.token) { - localStorage.setItem(TOKEN_KEY, response.data.token) - localStorage.setItem('user', JSON.stringify(response.data.user)) + const {token, user} = response.data + + if (token) { + localStorage.setItem(TOKEN_KEY, token) + localStorage.setItem('user', JSON.stringify(user)) return { success: true, @@ -57,16 +55,18 @@ export const authProvider: AuthProvider = { throw new AuthError('Неверный email или пароль') } catch (error) { + const errorMessage = (error as AxiosError)?.response?.data?.message || 'Неверный email или пароль' + return { success: false, - error: new AuthError('Неверный email или пароль'), + error: new AuthError(errorMessage), } } }, logout: async () => { try { await axios.post( - `${API_URL}/auth/logout`, + `${BACKEND_URL}/auth/logout`, {}, { headers: { @@ -87,8 +87,6 @@ export const authProvider: AuthProvider = { }, check: async () => { const token = localStorage.getItem(TOKEN_KEY) - const user = localStorage.getItem('user') - if (!token) { return { authenticated: false, @@ -96,22 +94,16 @@ export const authProvider: AuthProvider = { } } - // If using mock token, skip API verification - if (token === 'mock-jwt-token' && user) { - return { - authenticated: true, - } - } - - // For real tokens, verify with API try { - const response = await axios.get(`${API_URL}/auth/me`, { + const response = await axios.get(`${BACKEND_URL}/auth/me`, { headers: { Authorization: `Bearer ${token}`, }, }) if (response.status === 200) { + // Обновляем информацию о пользователе + localStorage.setItem('user', JSON.stringify(response.data)) return { authenticated: true, } @@ -129,7 +121,6 @@ export const authProvider: AuthProvider = { return { authenticated: false, redirectTo: '/login', - error: new AuthError('Пожалуйста, войдите в систему'), } }, getPermissions: async () => { @@ -137,25 +128,31 @@ export const authProvider: AuthProvider = { if (!token) return null try { - const response = await axios.get(`${API_URL}/auth/permissions`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }) - return response.data.permissions - } catch (error) { - return null + const decoded = jwtDecode(token) + return decoded.is_admin ? ['admin'] : ['user'] + } catch { + return ['user'] } }, getIdentity: async () => { + const token = localStorage.getItem(TOKEN_KEY) const user = localStorage.getItem('user') - if (user) { - return JSON.parse(user) + + if (!token || !user) return null + + try { + const decoded = jwtDecode(token) + const userData = JSON.parse(user) + + return { + ...userData, + is_admin: decoded.is_admin, // всегда используем значение из токена + } + } catch { + return null } - return null }, onError: async (error) => { - console.error('Ошибка:', error) const status = (error as AxiosError)?.response?.status if (status === 401 || status === 403) { localStorage.removeItem(TOKEN_KEY) diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index 5b95356..ca9b75f 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -6,12 +6,14 @@ export const Login = () => { } />} - formProps={{ - defaultValues: { - email: 'test@wn.ru', - password: 'testwn', - }, - }} + // formProps={ + // { + // // defaultValues: { + // // email: 'test@wn.ru', + // // password: 'testwn', + // // }, + // } + // } /> ) } diff --git a/src/providers/data.ts b/src/providers/data.ts new file mode 100644 index 0000000..a1da391 --- /dev/null +++ b/src/providers/data.ts @@ -0,0 +1,18 @@ +import dataProvider from '@refinedev/simple-rest' +import axios from 'axios' +import {BACKEND_URL} from '../lib/constants' +import {TOKEN_KEY} from '../authProvider' + +const axiosInstance = axios.create() + +axiosInstance.interceptors.request.use((config) => { + const token = localStorage.getItem(TOKEN_KEY) + + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + + return config +}) + +export const customDataProvider = dataProvider(BACKEND_URL, axiosInstance)