This commit is contained in:
@@ -76,13 +76,19 @@ export const AuthProvider: Component<AuthProviderProps> = (props) => {
|
|||||||
// Инициализация авторизации при монтировании
|
// Инициализация авторизации при монтировании
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
console.log('[AuthProvider] Performing auth initialization...')
|
console.log('[AuthProvider] Performing auth initialization...')
|
||||||
|
|
||||||
// 🍪 Для httpOnly cookies проверяем авторизацию через GraphQL запрос
|
// 🍪 Для httpOnly cookies проверяем авторизацию через GraphQL запрос
|
||||||
try {
|
try {
|
||||||
console.log('[AuthProvider] Checking authentication via GraphQL...')
|
console.log('[AuthProvider] Checking authentication via GraphQL...')
|
||||||
|
|
||||||
// Делаем тестовый запрос для проверки авторизации
|
// Добавляем таймаут для запроса
|
||||||
const result = await query<{ me: { id: string } | null }>(`${location.origin}/graphql`, `
|
const timeoutPromise = new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('Auth check timeout')), 10000)
|
||||||
|
)
|
||||||
|
|
||||||
|
const authPromise = query<{ me: { id: string } | null }>(
|
||||||
|
`${location.origin}/graphql`,
|
||||||
|
`
|
||||||
query CheckAuth {
|
query CheckAuth {
|
||||||
me {
|
me {
|
||||||
id
|
id
|
||||||
@@ -90,8 +96,14 @@ export const AuthProvider: Component<AuthProviderProps> = (props) => {
|
|||||||
email
|
email
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`)
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Делаем тестовый запрос для проверки авторизации с таймаутом
|
||||||
|
const result = (await Promise.race([authPromise, timeoutPromise])) as {
|
||||||
|
me: { id: string; name: string; email: string } | null
|
||||||
|
}
|
||||||
|
|
||||||
if (result?.me?.id) {
|
if (result?.me?.id) {
|
||||||
console.log('[AuthProvider] User authenticated via httpOnly cookie:', result.me.id)
|
console.log('[AuthProvider] User authenticated via httpOnly cookie:', result.me.id)
|
||||||
setIsAuthenticated(true)
|
setIsAuthenticated(true)
|
||||||
@@ -102,10 +114,11 @@ export const AuthProvider: Component<AuthProviderProps> = (props) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('[AuthProvider] Authentication check failed:', error)
|
console.log('[AuthProvider] Authentication check failed:', error)
|
||||||
setIsAuthenticated(false)
|
setIsAuthenticated(false)
|
||||||
|
} finally {
|
||||||
|
// Всегда устанавливаем ready в true, даже при ошибке
|
||||||
|
console.log('[AuthProvider] Auth initialization complete, ready for requests')
|
||||||
|
setIsReady(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[AuthProvider] Auth initialization complete, ready for requests')
|
|
||||||
setIsReady(true)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const login = async (username: string, password: string) => {
|
const login = async (username: string, password: string) => {
|
||||||
|
|||||||
@@ -177,6 +177,81 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Auth Error Screen */
|
||||||
|
.auth-error-screen {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-error-content {
|
||||||
|
text-align: center;
|
||||||
|
max-width: 500px;
|
||||||
|
padding: 2rem;
|
||||||
|
background-color: var(--card-background);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-error-content h2 {
|
||||||
|
color: var(--danger-color);
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-error-content p {
|
||||||
|
color: var(--text-color-light);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-error-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-error-actions .btn {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: none;
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-error-actions .btn-primary {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-error-actions .btn-primary:hover {
|
||||||
|
background-color: var(--primary-color-dark);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-error-actions .btn-secondary {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--text-color-light);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-error-actions .btn-secondary:hover {
|
||||||
|
background-color: var(--hover-color);
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
.error-message {
|
.error-message {
|
||||||
background-color: var(--danger-light);
|
background-color: var(--danger-light);
|
||||||
border-left: 4px solid var(--danger-color);
|
border-left: 4px solid var(--danger-color);
|
||||||
|
|||||||
@@ -29,9 +29,19 @@ export const ProtectedRoute = () => {
|
|||||||
<Show
|
<Show
|
||||||
when={auth.isAuthenticated()}
|
when={auth.isAuthenticated()}
|
||||||
fallback={
|
fallback={
|
||||||
<div class="loading-screen">
|
<div class="auth-error-screen">
|
||||||
<div class="loading-spinner" />
|
<div class="auth-error-content">
|
||||||
<div>Перенаправление на страницу входа...</div>
|
<h2>Доступ запрещен</h2>
|
||||||
|
<p>У вас нет прав доступа к админ-панели или ваша сессия истекла.</p>
|
||||||
|
<div class="auth-error-actions">
|
||||||
|
<button class="btn btn-primary" onClick={() => (window.location.href = '/login')}>
|
||||||
|
Войти в аккаунт
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" onClick={() => auth.logout()}>
|
||||||
|
Выйти из текущего аккаунта
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Экспортируем константы для использования в других модулях
|
// Экспортируем константы для использования в других модулях
|
||||||
export const AUTH_TOKEN_KEY = 'auth_token' // localStorage fallback
|
export const AUTH_TOKEN_KEY = 'auth_token' // localStorage fallback
|
||||||
export const SESSION_COOKIE_NAME = 'session_token' // ✅ httpOnly cookie от backend
|
export const SESSION_COOKIE_NAME = 'session_token' // ✅ httpOnly cookie от backend
|
||||||
export const CSRF_TOKEN_KEY = 'csrf_token'
|
export const CSRF_TOKEN_KEY = 'csrf_token'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,6 +99,6 @@ export function checkAuthStatus(): boolean {
|
|||||||
// Реальная проверка авторизации произойдет при первом GraphQL запросе
|
// Реальная проверка авторизации произойдет при первом GraphQL запросе
|
||||||
// Если cookie недействителен, backend вернет ошибку авторизации
|
// Если cookie недействителен, backend вернет ошибку авторизации
|
||||||
console.log('[Auth] Using httpOnly cookie authentication - status will be verified by backend')
|
console.log('[Auth] Using httpOnly cookie authentication - status will be verified by backend')
|
||||||
|
|
||||||
return true // ✅ Полагаемся на httpOnly cookie + backend проверку
|
return true // ✅ Полагаемся на httpOnly cookie + backend проверку
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user