fix authProvider
for added custom
auth
This commit is contained in:
parent
da3a162adb
commit
35e4718630
@ -22,6 +22,7 @@
|
|||||||
"easymde": "^2.19.0",
|
"easymde": "^2.19.0",
|
||||||
"i18next": "^24.2.2",
|
"i18next": "^24.2.2",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
|
"jwt-decode": "^4.0.0",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^18.0.0",
|
||||||
"react-hook-form": "^7.30.0",
|
"react-hook-form": "^7.30.0",
|
||||||
|
@ -62,6 +62,9 @@ importers:
|
|||||||
js-cookie:
|
js-cookie:
|
||||||
specifier: ^3.0.5
|
specifier: ^3.0.5
|
||||||
version: 3.0.5
|
version: 3.0.5
|
||||||
|
jwt-decode:
|
||||||
|
specifier: ^4.0.0
|
||||||
|
version: 4.0.0
|
||||||
react:
|
react:
|
||||||
specifier: ^18.0.0
|
specifier: ^18.0.0
|
||||||
version: 18.3.1
|
version: 18.3.1
|
||||||
@ -2067,6 +2070,10 @@ packages:
|
|||||||
jsonfile@6.1.0:
|
jsonfile@6.1.0:
|
||||||
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
|
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:
|
kbar@0.1.0-beta.45:
|
||||||
resolution: {integrity: sha512-kXvjthqPLoWZXlxLJPrFKioskNdQv1O3Ukg5mqq2ExK3Ix1qvYT3W/ACDRIv/e/CHxPWZoTriB4oFbQ6UCSX5g==}
|
resolution: {integrity: sha512-kXvjthqPLoWZXlxLJPrFKioskNdQv1O3Ukg5mqq2ExK3Ix1qvYT3W/ACDRIv/e/CHxPWZoTriB4oFbQ6UCSX5g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -5408,6 +5415,8 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs: 4.2.11
|
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):
|
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:
|
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)
|
'@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)
|
||||||
|
@ -4,7 +4,7 @@ import {RefineKbar, RefineKbarProvider} from '@refinedev/kbar'
|
|||||||
|
|
||||||
import {ErrorComponent, useNotificationProvider, RefineSnackbarProvider, ThemedLayoutV2} from '@refinedev/mui'
|
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 CssBaseline from '@mui/material/CssBaseline'
|
||||||
import GlobalStyles from '@mui/material/GlobalStyles'
|
import GlobalStyles from '@mui/material/GlobalStyles'
|
||||||
import {BrowserRouter, Route, Routes, Outlet} from 'react-router'
|
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 {CountryIcon, CityIcon, CarrierIcon, MediaIcon, ArticleIcon, SightIcon, StationIcon, VehicleIcon, RouteIcon} from './components/ui/Icons'
|
||||||
import SidebarTitle from './components/ui/SidebarTitle'
|
import SidebarTitle from './components/ui/SidebarTitle'
|
||||||
import {BACKEND_URL} from './lib/constants'
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@ -41,7 +40,7 @@ function App() {
|
|||||||
<RefineSnackbarProvider>
|
<RefineSnackbarProvider>
|
||||||
<DevtoolsProvider>
|
<DevtoolsProvider>
|
||||||
<Refine
|
<Refine
|
||||||
dataProvider={dataProvider(BACKEND_URL)}
|
dataProvider={customDataProvider}
|
||||||
notificationProvider={useNotificationProvider}
|
notificationProvider={useNotificationProvider}
|
||||||
routerProvider={routerBindings}
|
routerProvider={routerBindings}
|
||||||
authProvider={authProvider}
|
authProvider={authProvider}
|
||||||
|
@ -1,11 +1,24 @@
|
|||||||
import type {AuthProvider} from '@refinedev/core'
|
import type {AuthProvider} from '@refinedev/core'
|
||||||
import axios, {AxiosError} from 'axios'
|
import axios, {AxiosError} from 'axios'
|
||||||
|
import {BACKEND_URL} from './lib/constants'
|
||||||
// import {BACKEND_URL} from './lib/constants'
|
import {jwtDecode} from 'jwt-decode'
|
||||||
const API_URL = 'https://wn.krbl.ru'
|
|
||||||
|
|
||||||
export const TOKEN_KEY = 'refine-auth'
|
export const TOKEN_KEY = 'refine-auth'
|
||||||
|
|
||||||
|
interface AuthResponse {
|
||||||
|
token: string
|
||||||
|
user: {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
email: string
|
||||||
|
is_admin: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ErrorResponse {
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
class AuthError extends Error {
|
class AuthError extends Error {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
super(message)
|
super(message)
|
||||||
@ -13,41 +26,26 @@ class AuthError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const MOCK_USER = {
|
interface JWTPayload {
|
||||||
email: 'test@wn.ru',
|
user_id: number
|
||||||
password: 'testwn',
|
email: string
|
||||||
name: 'Константин Иванов',
|
is_admin: boolean
|
||||||
avatar: 'https://i.pravatar.cc/300',
|
exp: number
|
||||||
// roles: ['admin'],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const authProvider: AuthProvider = {
|
export const authProvider: AuthProvider = {
|
||||||
login: async ({email, password}) => {
|
login: async ({email, password}) => {
|
||||||
try {
|
try {
|
||||||
if (email === MOCK_USER.email && password === MOCK_USER.password) {
|
const response = await axios.post<AuthResponse>(`${BACKEND_URL}/auth/login`, {
|
||||||
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`, {
|
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.data.token) {
|
const {token, user} = response.data
|
||||||
localStorage.setItem(TOKEN_KEY, response.data.token)
|
|
||||||
localStorage.setItem('user', JSON.stringify(response.data.user))
|
if (token) {
|
||||||
|
localStorage.setItem(TOKEN_KEY, token)
|
||||||
|
localStorage.setItem('user', JSON.stringify(user))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@ -57,16 +55,18 @@ export const authProvider: AuthProvider = {
|
|||||||
|
|
||||||
throw new AuthError('Неверный email или пароль')
|
throw new AuthError('Неверный email или пароль')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
const errorMessage = (error as AxiosError<ErrorResponse>)?.response?.data?.message || 'Неверный email или пароль'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: new AuthError('Неверный email или пароль'),
|
error: new AuthError(errorMessage),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
logout: async () => {
|
logout: async () => {
|
||||||
try {
|
try {
|
||||||
await axios.post(
|
await axios.post(
|
||||||
`${API_URL}/auth/logout`,
|
`${BACKEND_URL}/auth/logout`,
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
@ -87,8 +87,6 @@ export const authProvider: AuthProvider = {
|
|||||||
},
|
},
|
||||||
check: async () => {
|
check: async () => {
|
||||||
const token = localStorage.getItem(TOKEN_KEY)
|
const token = localStorage.getItem(TOKEN_KEY)
|
||||||
const user = localStorage.getItem('user')
|
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return {
|
return {
|
||||||
authenticated: false,
|
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 {
|
try {
|
||||||
const response = await axios.get(`${API_URL}/auth/me`, {
|
const response = await axios.get(`${BACKEND_URL}/auth/me`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
|
// Обновляем информацию о пользователе
|
||||||
|
localStorage.setItem('user', JSON.stringify(response.data))
|
||||||
return {
|
return {
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
}
|
}
|
||||||
@ -129,7 +121,6 @@ export const authProvider: AuthProvider = {
|
|||||||
return {
|
return {
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
redirectTo: '/login',
|
redirectTo: '/login',
|
||||||
error: new AuthError('Пожалуйста, войдите в систему'),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getPermissions: async () => {
|
getPermissions: async () => {
|
||||||
@ -137,25 +128,31 @@ export const authProvider: AuthProvider = {
|
|||||||
if (!token) return null
|
if (!token) return null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${API_URL}/auth/permissions`, {
|
const decoded = jwtDecode<JWTPayload>(token)
|
||||||
headers: {
|
return decoded.is_admin ? ['admin'] : ['user']
|
||||||
Authorization: `Bearer ${token}`,
|
} catch {
|
||||||
},
|
return ['user']
|
||||||
})
|
|
||||||
return response.data.permissions
|
|
||||||
} catch (error) {
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getIdentity: async () => {
|
getIdentity: async () => {
|
||||||
|
const token = localStorage.getItem(TOKEN_KEY)
|
||||||
const user = localStorage.getItem('user')
|
const user = localStorage.getItem('user')
|
||||||
if (user) {
|
|
||||||
return JSON.parse(user)
|
if (!token || !user) return null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded = jwtDecode<JWTPayload>(token)
|
||||||
|
const userData = JSON.parse(user)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...userData,
|
||||||
|
is_admin: decoded.is_admin, // всегда используем значение из токена
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
},
|
},
|
||||||
onError: async (error) => {
|
onError: async (error) => {
|
||||||
console.error('Ошибка:', error)
|
|
||||||
const status = (error as AxiosError)?.response?.status
|
const status = (error as AxiosError)?.response?.status
|
||||||
if (status === 401 || status === 403) {
|
if (status === 401 || status === 403) {
|
||||||
localStorage.removeItem(TOKEN_KEY)
|
localStorage.removeItem(TOKEN_KEY)
|
||||||
|
@ -6,12 +6,14 @@ export const Login = () => {
|
|||||||
<AuthPage
|
<AuthPage
|
||||||
type="login"
|
type="login"
|
||||||
title={<ThemedTitleV2 collapsed={false} text="Белые Ночи" icon={<ProjectIcon style={{color: '#7f6b58'}} />} />}
|
title={<ThemedTitleV2 collapsed={false} text="Белые Ночи" icon={<ProjectIcon style={{color: '#7f6b58'}} />} />}
|
||||||
formProps={{
|
// formProps={
|
||||||
defaultValues: {
|
// {
|
||||||
email: 'test@wn.ru',
|
// // defaultValues: {
|
||||||
password: 'testwn',
|
// // email: 'test@wn.ru',
|
||||||
},
|
// // password: 'testwn',
|
||||||
}}
|
// // },
|
||||||
|
// }
|
||||||
|
// }
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
18
src/providers/data.ts
Normal file
18
src/providers/data.ts
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user