Files
core/panel/context/auth.tsx
Untone 504152981b
All checks were successful
Deploy on push / deploy (push) Successful in 3m3s
admin-auth
2025-09-29 16:08:58 +03:00

225 lines
7.2 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...')
// Начинаем с false чтобы избежать мерцания, реальная проверка будет в onMount
const [isAuthenticated, setIsAuthenticated] = createSignal(false)
const [isReady, setIsReady] = createSignal(false)
// Флаг для предотвращения повторных инициализаций
let isInitializing = false
console.log('[AuthProvider] Initial auth state: not authenticated (will check via GraphQL)')
// Инициализация авторизации при монтировании
onMount(async () => {
// Защита от повторных вызовов
if (isInitializing) {
console.log('[AuthProvider] Already initializing, skipping...')
return
}
isInitializing = true
console.log('[AuthProvider] Performing auth initialization...')
// 🍪 Для httpOnly cookies проверяем авторизацию через GraphQL запрос
try {
console.log('[AuthProvider] Checking authentication via GraphQL...')
// Добавляем таймаут для запроса (5 секунд для лучшего UX)
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Auth check timeout')), 5000)
)
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)
isInitializing = false
}
})
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...')
// Предотвращаем повторные инициализации во время logout
isInitializing = true
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'
} finally {
isInitializing = false
}
}
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
}
}