Files
core/panel/context/auth.tsx
Untone f2398d3592
All checks were successful
Deploy on push / deploy (push) Successful in 3m2s
protected-route-fix
2025-09-29 15:54:22 +03:00

209 lines
6.5 KiB
TypeScript
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 { Component, createContext, createSignal, JSX, onMount, useContext } from 'solid-js'
import { AuthSuccess } from '~/graphql/generated/graphql'
import { query } from '../graphql'
import { ADMIN_LOGIN_MUTATION, ADMIN_LOGOUT_MUTATION } from '../graphql/mutations'
import {
AUTH_TOKEN_KEY,
CSRF_TOKEN_KEY,
checkAuthStatus,
clearAuthTokens,
getAuthTokenFromCookie,
getCsrfTokenFromCookie,
saveAuthToken
} from '../utils/auth'
/**
* Модуль авторизации
* @module auth
*/
/**
* Интерфейс для учетных данных
*/
export interface Credentials {
email: string
password: string
}
/**
* Интерфейс для результата авторизации
*/
export interface LoginResult {
success: boolean
token?: string
error?: string
}
// Экспортируем утилитарные функции для обратной совместимости
export {
AUTH_TOKEN_KEY,
CSRF_TOKEN_KEY,
getAuthTokenFromCookie,
getCsrfTokenFromCookie,
checkAuthStatus,
clearAuthTokens,
saveAuthToken
}
interface AuthContextType {
isAuthenticated: () => boolean
isReady: () => boolean
login: (username: string, password: string) => Promise<void>
logout: () => Promise<void>
}
const AuthContext = createContext<AuthContextType>({
isAuthenticated: () => false,
isReady: () => false,
login: async () => {},
logout: async () => {}
})
export const useAuth = () => useContext(AuthContext)
interface AuthProviderProps {
children: JSX.Element
}
export const AuthProvider: Component<AuthProviderProps> = (props) => {
console.log('[AuthProvider] Initializing...')
const [isAuthenticated, setIsAuthenticated] = createSignal(checkAuthStatus())
const [isReady, setIsReady] = createSignal(false)
console.log(
`[AuthProvider] Initial auth state: ${isAuthenticated() ? 'authenticated' : 'not authenticated'}`
)
// Инициализация авторизации при монтировании
onMount(async () => {
console.log('[AuthProvider] Performing auth initialization...')
// 🍪 Для httpOnly cookies проверяем авторизацию через GraphQL запрос
try {
console.log('[AuthProvider] Checking authentication via 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 {
me {
id
name
email
}
}
`
)
// Делаем тестовый запрос для проверки авторизации с таймаутом
const result = (await Promise.race([authPromise, timeoutPromise])) as {
me: { id: string; name: string; email: string } | null
}
if (result?.me?.id) {
console.log('[AuthProvider] User authenticated via httpOnly cookie:', result.me.id)
setIsAuthenticated(true)
} else {
console.log('[AuthProvider] No authenticated user found')
setIsAuthenticated(false)
}
} catch (error) {
console.log('[AuthProvider] Authentication check failed:', error)
setIsAuthenticated(false)
} finally {
// Всегда устанавливаем ready в true, даже при ошибке
console.log('[AuthProvider] Auth initialization complete, ready for requests')
setIsReady(true)
}
})
const login = async (username: string, password: string) => {
console.log('[AuthProvider] Attempting login...')
try {
const result = await query<{
login: { success: boolean; token?: string }
}>(`${location.origin}/graphql`, ADMIN_LOGIN_MUTATION, {
email: username,
password
})
if (result?.login?.success) {
console.log('[AuthProvider] Login successful')
// Backend автоматически установил session_token cookie при успешном login
console.log('[AuthProvider] Token saved in httpOnly cookie by backend')
setIsAuthenticated(true)
// Убираем window.location.href - пусть роутер сам обрабатывает навигацию
} else {
console.error('[AuthProvider] Login failed')
throw new Error('Неверные учетные данные')
}
} catch (error) {
console.error('[AuthProvider] Login error:', error)
throw error
}
}
const logout = async () => {
console.log('[AuthProvider] Attempting logout...')
try {
// Сначала очищаем токены на клиенте
clearAuthTokens()
setIsAuthenticated(false)
// Затем делаем запрос на сервер
const result = await query<{ logout: { success: boolean; message?: string } }>(
`${location.origin}/graphql`,
ADMIN_LOGOUT_MUTATION
)
console.log('[AuthProvider] Logout response:', result)
if (result?.logout?.success) {
console.log('[AuthProvider] Logout successful:', result.logout.message)
window.location.href = '/login'
} else {
console.warn('[AuthProvider] Logout was not successful:', result?.logout?.message)
// Все равно редиректим на страницу входа
window.location.href = '/login'
}
} catch (error) {
console.error('[AuthProvider] Logout error:', error)
// При любой ошибке редиректим на страницу входа
window.location.href = '/login'
}
}
const value: AuthContextType = {
isAuthenticated,
isReady,
login,
logout
}
console.log('[AuthProvider] Rendering provider with context')
return <AuthContext.Provider value={value}>{props.children}</AuthContext.Provider>
}
// Export the logout function for direct use
export const logout = async () => {
console.log('[Auth] Executing standalone logout...')
try {
const result = await query<{ logout: AuthSuccess }>(`${location.origin}/graphql`, ADMIN_LOGOUT_MUTATION)
console.log('[Auth] Standalone logout result:', result)
if (result?.logout?.success) {
clearAuthTokens()
return true
}
return false
} catch (error) {
console.error('[Auth] Standalone logout error:', error)
// Даже при ошибке очищаем токены
clearAuthTokens()
throw error
}
}