### 🍪 CRITICAL Cross-Origin Auth - **🔧 SESSION_COOKIE_DOMAIN**: Добавлена поддержка поддоменов `.discours.io` для cross-origin cookies - **🌐 Cross-Origin SSE**: Исправлена работа Server-Sent Events с httpOnly cookies между поддоменами - **🔐 Unified Auth**: Унифицированы настройки cookies для OAuth, login, refresh, logout операций - **📝 MyPy Compliance**: Исправлена типизация `SESSION_COOKIE_SAMESITE` с использованием `cast()` ### 🛠️ Technical Changes - **settings.py**: Добавлен `SESSION_COOKIE_DOMAIN` с типобезопасной настройкой SameSite - **auth/oauth.py**: Обновлены все `set_cookie` вызовы с `domain` параметром - **auth/middleware.py**: Добавлена поддержка `SESSION_COOKIE_DOMAIN` в logout операциях - **resolvers/auth.py**: Унифицированы cookie настройки в login/refresh/logout resolvers - **auth/__init__.py**: Обновлены cookie операции с domain поддержкой ### 📚 Documentation - **docs/auth/sse-httponly-integration.md**: Новая документация по SSE + httpOnly cookies интеграции - **docs/auth/architecture.md**: Обновлены диаграммы для unified httpOnly cookie архитектуры ### 🎯 Impact - ✅ **GraphQL API** (`v3.discours.io`) теперь работает с httpOnly cookies cross-origin - ✅ **SSE сервер** (`connect.discours.io`) работает с теми же cookies - ✅ **Безопасность**: httpOnly cookies защищают от XSS атак - ✅ **UX**: Автоматическая аутентификация без управления токенами в JavaScript
This commit is contained in:
@@ -76,17 +76,33 @@ export const AuthProvider: Component<AuthProviderProps> = (props) => {
|
||||
// Инициализация авторизации при монтировании
|
||||
onMount(async () => {
|
||||
console.log('[AuthProvider] Performing auth initialization...')
|
||||
console.log('[AuthProvider] Checking localStorage token:', !!localStorage.getItem(AUTH_TOKEN_KEY))
|
||||
console.log('[AuthProvider] Checking cookie token:', !!getAuthTokenFromCookie())
|
||||
console.log('[AuthProvider] Checking CSRF token:', !!getCsrfTokenFromCookie())
|
||||
|
||||
// Небольшая задержка для завершения других инициализаций
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
|
||||
// Проверяем текущее состояние авторизации
|
||||
const authStatus = checkAuthStatus()
|
||||
console.log('[AuthProvider] Final auth status after check:', authStatus)
|
||||
setIsAuthenticated(authStatus)
|
||||
|
||||
// 🍪 Для httpOnly cookies проверяем авторизацию через GraphQL запрос
|
||||
try {
|
||||
console.log('[AuthProvider] Checking authentication via GraphQL...')
|
||||
|
||||
// Делаем тестовый запрос для проверки авторизации
|
||||
const result = await query<{ me: { id: string } | null }>(`${location.origin}/graphql`, `
|
||||
query CheckAuth {
|
||||
me {
|
||||
id
|
||||
name
|
||||
email
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
console.log('[AuthProvider] Auth initialization complete, ready for requests')
|
||||
setIsReady(true)
|
||||
@@ -104,9 +120,8 @@ export const AuthProvider: Component<AuthProviderProps> = (props) => {
|
||||
|
||||
if (result?.login?.success) {
|
||||
console.log('[AuthProvider] Login successful')
|
||||
if (result.login.token) {
|
||||
saveAuthToken(result.login.token)
|
||||
}
|
||||
// Backend автоматически установил session_token cookie при успешном login
|
||||
console.log('[AuthProvider] Token saved in httpOnly cookie by backend')
|
||||
setIsAuthenticated(true)
|
||||
// Убираем window.location.href - пусть роутер сам обрабатывает навигацию
|
||||
} else {
|
||||
|
||||
@@ -2,7 +2,6 @@ export const ADMIN_LOGIN_MUTATION = `
|
||||
mutation AdminLogin($email: String!, $password: String!) {
|
||||
login(email: $email, password: $password) {
|
||||
success
|
||||
token
|
||||
author {
|
||||
id
|
||||
name
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
*/
|
||||
|
||||
// Экспортируем константы для использования в других модулях
|
||||
export const AUTH_TOKEN_KEY = 'auth_token'
|
||||
export const AUTH_TOKEN_KEY = 'auth_token' // localStorage fallback
|
||||
export const SESSION_COOKIE_NAME = 'session_token' // ✅ httpOnly cookie от backend
|
||||
export const CSRF_TOKEN_KEY = 'csrf_token'
|
||||
|
||||
/**
|
||||
@@ -76,34 +77,28 @@ export function saveAuthToken(token: string): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, авторизован ли пользователь
|
||||
* @returns Статус авторизации
|
||||
* Проверяет, авторизован ли пользователь через httpOnly cookie
|
||||
* @returns Статус авторизации (всегда true для httpOnly - проверка на backend)
|
||||
*/
|
||||
export function checkAuthStatus(): boolean {
|
||||
console.log('[Auth] Checking authentication status...')
|
||||
|
||||
// 💋 НЕ проверяем httpOnly cookie через JavaScript - он недоступен!
|
||||
// httpOnly cookie автоматически отправляется браузером, но недоступен для чтения
|
||||
// 🍪 Админка использует httpOnly cookies - токен недоступен JavaScript!
|
||||
// Браузер автоматически отправляет session_token cookie с каждым запросом
|
||||
// Окончательная проверка авторизации происходит на backend через GraphQL
|
||||
|
||||
// Проверяем наличие токена в localStorage
|
||||
// Проверяем localStorage только как fallback для старых сессий
|
||||
const localToken = localStorage.getItem(AUTH_TOKEN_KEY)
|
||||
const hasLocalToken = !!localToken && localToken.length > 10
|
||||
|
||||
// 💋 Для httpOnly cookie полагаемся на backend проверку
|
||||
// Если нет токена в localStorage, считаем что пользователь может быть авторизован через httpOnly cookie
|
||||
// Окончательная проверка произойдет при первом GraphQL запросе
|
||||
const isAuth = hasLocalToken
|
||||
|
||||
console.log(`[Auth] Local token: ${hasLocalToken ? 'present' : 'missing'}`)
|
||||
console.log(
|
||||
`[Auth] Authentication status: ${isAuth ? 'authenticated via localStorage' : 'unknown (may be authenticated via httpOnly cookie)'}`
|
||||
)
|
||||
|
||||
// Дополнительное логирование для диагностики
|
||||
if (localToken) {
|
||||
console.log(`[Auth] Local token length: ${localToken.length}`)
|
||||
console.log(`[Auth] Local token preview: ${localToken.substring(0, 20)}...`)
|
||||
if (hasLocalToken) {
|
||||
console.log('[Auth] Found legacy token in localStorage - will be migrated to httpOnly cookie')
|
||||
}
|
||||
|
||||
return isAuth
|
||||
// ✅ Для httpOnly cookie всегда возвращаем true
|
||||
// Реальная проверка авторизации произойдет при первом GraphQL запросе
|
||||
// Если cookie недействителен, backend вернет ошибку авторизации
|
||||
console.log('[Auth] Using httpOnly cookie authentication - status will be verified by backend')
|
||||
|
||||
return true // ✅ Полагаемся на httpOnly cookie + backend проверку
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user