This commit is contained in:
@@ -292,14 +292,53 @@ class AuthMiddleware:
|
|||||||
if not token:
|
if not token:
|
||||||
cookies = headers.get("cookie", "")
|
cookies = headers.get("cookie", "")
|
||||||
logger.debug(f"[middleware] Проверяем cookies: {cookies[:100]}...")
|
logger.debug(f"[middleware] Проверяем cookies: {cookies[:100]}...")
|
||||||
|
logger.debug(f"[middleware] Ищем cookie с именем: '{SESSION_COOKIE_NAME}'")
|
||||||
|
|
||||||
|
# 🔍 Дополнительная диагностика для отладки
|
||||||
|
if not cookies:
|
||||||
|
logger.warning("[middleware] 🚨 ПРОБЛЕМА: Cookie заголовок полностью отсутствует!")
|
||||||
|
logger.warning(f"[middleware] 🔍 Все заголовки: {list(headers.keys())}")
|
||||||
|
# Проверяем, есть ли активные сессии для этого пользователя
|
||||||
|
try:
|
||||||
|
session_keys = await redis_adapter.keys("session:*")
|
||||||
|
if session_keys:
|
||||||
|
logger.warning(
|
||||||
|
f"[middleware] 🔍 В Redis найдено {len(session_keys)} активных сессий, но cookie не передается!"
|
||||||
|
)
|
||||||
|
# Показываем первые 3 сессии для диагностики
|
||||||
|
for session_key in session_keys[:3]:
|
||||||
|
try:
|
||||||
|
session_data = await redis_adapter.hgetall(session_key)
|
||||||
|
if session_data:
|
||||||
|
user_id = (
|
||||||
|
session_key.decode("utf-8").split(":")[1]
|
||||||
|
if isinstance(session_key, bytes)
|
||||||
|
else session_key.split(":")[1]
|
||||||
|
)
|
||||||
|
logger.warning(
|
||||||
|
f"[middleware] 🔍 Активная сессия для user_id={user_id}: {session_key}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"[middleware] Ошибка чтения сессии {session_key}: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"[middleware] Ошибка проверки сессий: {e}")
|
||||||
|
|
||||||
cookie_items = cookies.split(";")
|
cookie_items = cookies.split(";")
|
||||||
|
found_cookies = []
|
||||||
for item in cookie_items:
|
for item in cookie_items:
|
||||||
if "=" in item:
|
if "=" in item:
|
||||||
name, value = item.split("=", 1)
|
name, value = item.split("=", 1)
|
||||||
if name.strip() == SESSION_COOKIE_NAME:
|
cookie_name = name.strip()
|
||||||
|
found_cookies.append(cookie_name)
|
||||||
|
if cookie_name == SESSION_COOKIE_NAME:
|
||||||
token = value.strip()
|
token = value.strip()
|
||||||
logger.debug(f"[middleware] Токен получен из cookie {SESSION_COOKIE_NAME}: {len(token)}")
|
logger.debug(
|
||||||
|
f"[middleware] ✅ Токен получен из cookie {SESSION_COOKIE_NAME}: {len(token)} символов"
|
||||||
|
)
|
||||||
break
|
break
|
||||||
|
logger.debug(f"[middleware] Найденные cookies: {found_cookies}")
|
||||||
|
if not token:
|
||||||
|
logger.debug(f"[middleware] ❌ Cookie '{SESSION_COOKIE_NAME}' не найден среди: {found_cookies}")
|
||||||
|
|
||||||
if token:
|
if token:
|
||||||
logger.debug(f"[middleware] Токен найден: {len(token)} символов")
|
logger.debug(f"[middleware] Токен найден: {len(token)} символов")
|
||||||
|
|||||||
@@ -528,15 +528,32 @@ async def oauth_callback(request: Any) -> JSONResponse | RedirectResponse:
|
|||||||
response = RedirectResponse(url=final_redirect_url)
|
response = RedirectResponse(url=final_redirect_url)
|
||||||
|
|
||||||
# 🍪 Устанавливаем httpOnly cookie для безопасности
|
# 🍪 Устанавливаем httpOnly cookie для безопасности
|
||||||
|
# 💋 Исправляем domain для testing.discours.io - не используем wildcard domain
|
||||||
|
cookie_domain = None # Убираем wildcard domain для корректной работы
|
||||||
|
cookie_samesite = SESSION_COOKIE_SAMESITE
|
||||||
|
|
||||||
|
if "discours.io" in parsed_redirect.netloc and "testing.discours.io" not in parsed_redirect.netloc:
|
||||||
|
cookie_domain = ".discours.io" # Только для основного домена
|
||||||
|
elif "testing.discours.io" in parsed_redirect.netloc:
|
||||||
|
# 💋 Для testing.discours.io используем None domain и SameSite=Lax для лучшей совместимости
|
||||||
|
# SameSite=None требует Secure=True, что может не работать если HTTPS_ENABLED неправильно настроен
|
||||||
|
cookie_domain = None
|
||||||
|
cookie_samesite = "lax" # Более безопасный вариант для same-site запросов
|
||||||
|
|
||||||
|
# 💋 Принудительно включаем Secure для testing.discours.io (всегда HTTPS)
|
||||||
|
cookie_secure = SESSION_COOKIE_SECURE
|
||||||
|
if "testing.discours.io" in parsed_redirect.netloc:
|
||||||
|
cookie_secure = True # testing.discours.io всегда использует HTTPS
|
||||||
|
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
SESSION_COOKIE_NAME,
|
SESSION_COOKIE_NAME,
|
||||||
session_token,
|
session_token,
|
||||||
httponly=SESSION_COOKIE_HTTPONLY,
|
httponly=SESSION_COOKIE_HTTPONLY,
|
||||||
secure=SESSION_COOKIE_SECURE,
|
secure=cookie_secure,
|
||||||
samesite=SESSION_COOKIE_SAMESITE,
|
samesite=cookie_samesite,
|
||||||
max_age=SESSION_COOKIE_MAX_AGE,
|
max_age=SESSION_COOKIE_MAX_AGE,
|
||||||
path="/", # Важно: устанавливаем path="/" для доступности cookie во всех путях
|
path="/", # Важно: устанавливаем path="/" для доступности cookie во всех путях
|
||||||
domain=".discours.io" if "discours.io" in parsed_redirect.netloc else None, # Поддержка поддоменов
|
domain=cookie_domain, # Поддержка поддоменов только для основного домена
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"OAuth успешно завершен для {provider}, user_id={author.id}")
|
logger.info(f"OAuth успешно завершен для {provider}, user_id={author.id}")
|
||||||
@@ -853,20 +870,44 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon
|
|||||||
response = RedirectResponse(url=final_redirect_url, status_code=307)
|
response = RedirectResponse(url=final_redirect_url, status_code=307)
|
||||||
|
|
||||||
# 🍪 Устанавливаем httpOnly cookie для безопасности
|
# 🍪 Устанавливаем httpOnly cookie для безопасности
|
||||||
cookie_domain = ".discours.io" if "discours.io" in parsed_redirect.netloc else None
|
# 💋 Исправляем domain для testing.discours.io - не используем wildcard domain
|
||||||
|
cookie_domain = None # Убираем wildcard domain для корректной работы
|
||||||
|
cookie_samesite = SESSION_COOKIE_SAMESITE
|
||||||
|
|
||||||
|
if "discours.io" in parsed_redirect.netloc and "testing.discours.io" not in parsed_redirect.netloc:
|
||||||
|
cookie_domain = ".discours.io" # Только для основного домена
|
||||||
|
elif "testing.discours.io" in parsed_redirect.netloc:
|
||||||
|
# 💋 Для testing.discours.io используем None domain и SameSite=Lax для лучшей совместимости
|
||||||
|
# SameSite=None требует Secure=True, что может не работать если HTTPS_ENABLED неправильно настроен
|
||||||
|
cookie_domain = None
|
||||||
|
cookie_samesite = "lax" # Более безопасный вариант для same-site запросов
|
||||||
|
|
||||||
|
# 💋 Принудительно включаем Secure для testing.discours.io (всегда HTTPS)
|
||||||
|
cookie_secure = SESSION_COOKIE_SECURE
|
||||||
|
if "testing.discours.io" in parsed_redirect.netloc:
|
||||||
|
cookie_secure = True # testing.discours.io всегда использует HTTPS
|
||||||
|
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
SESSION_COOKIE_NAME,
|
SESSION_COOKIE_NAME,
|
||||||
session_token,
|
session_token,
|
||||||
httponly=SESSION_COOKIE_HTTPONLY,
|
httponly=SESSION_COOKIE_HTTPONLY,
|
||||||
secure=SESSION_COOKIE_SECURE,
|
secure=cookie_secure,
|
||||||
samesite=SESSION_COOKIE_SAMESITE,
|
samesite=cookie_samesite,
|
||||||
max_age=SESSION_COOKIE_MAX_AGE,
|
max_age=SESSION_COOKIE_MAX_AGE,
|
||||||
path="/", # Важно: устанавливаем path="/" для доступности cookie во всех путях
|
path="/", # Важно: устанавливаем path="/" для доступности cookie во всех путях
|
||||||
domain=cookie_domain, # Поддержка поддоменов
|
domain=cookie_domain, # Поддержка поддоменов только для основного домена
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"🍪 Cookie установлен: name={SESSION_COOKIE_NAME}, domain={cookie_domain}, secure={SESSION_COOKIE_SECURE}"
|
f"🍪 Cookie установлен: name={SESSION_COOKIE_NAME}, domain={cookie_domain}, secure={cookie_secure}, samesite={cookie_samesite}"
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"🔍 Cookie debug: redirect_netloc={parsed_redirect.netloc}, is_testing={('testing.discours.io' in parsed_redirect.netloc)}"
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"🔍 Session token preview: {session_token[:30]}..."
|
||||||
|
if len(session_token) > 30
|
||||||
|
else f"🔍 Session token: {session_token}"
|
||||||
)
|
)
|
||||||
logger.info(f"🔗 Final redirect: {final_redirect_url}")
|
logger.info(f"🔗 Final redirect: {final_redirect_url}")
|
||||||
logger.info(f"✅ OAuth успешно завершен для {provider}, user_id={author.id}")
|
logger.info(f"✅ OAuth успешно завершен для {provider}, user_id={author.id}")
|
||||||
|
|||||||
@@ -27,22 +27,19 @@ function getRequestHeaders(): Record<string, string> {
|
|||||||
|
|
||||||
// Проверяем наличие токена в localStorage
|
// Проверяем наличие токена в localStorage
|
||||||
const localToken = localStorage.getItem(AUTH_TOKEN_KEY)
|
const localToken = localStorage.getItem(AUTH_TOKEN_KEY)
|
||||||
|
|
||||||
|
// Используем только токен из localStorage (если есть)
|
||||||
|
const token = localToken
|
||||||
|
|
||||||
// Проверяем наличие токена в cookie
|
// Если есть токен в localStorage, добавляем его в заголовок Authorization с префиксом Bearer
|
||||||
const cookieToken = getAuthTokenFromCookie()
|
|
||||||
|
|
||||||
// Используем токен из localStorage или cookie
|
|
||||||
const token = localToken || cookieToken
|
|
||||||
|
|
||||||
// Если есть токен, добавляем его в заголовок Authorization с префиксом Bearer
|
|
||||||
if (token && token.length > 10) {
|
if (token && token.length > 10) {
|
||||||
headers['Authorization'] = `Bearer ${token}`
|
headers['Authorization'] = `Bearer ${token}`
|
||||||
console.debug('Отправка запроса с токеном авторизации')
|
console.debug('Отправка запроса с токеном авторизации из localStorage')
|
||||||
console.debug(`[Frontend] Authorization header: Bearer ${token.substring(0, 20)}...`)
|
console.debug(`[Frontend] Authorization header: Bearer ${token.substring(0, 20)}...`)
|
||||||
} else {
|
} else {
|
||||||
console.warn('[Frontend] Токен не найден или слишком короткий')
|
console.debug('[Frontend] Токен в localStorage не найден, полагаемся на httpOnly cookie')
|
||||||
console.debug(`[Frontend] Local token: ${localToken ? 'present' : 'missing'}`)
|
console.debug(`[Frontend] Local token: ${localToken ? 'present' : 'missing'}`)
|
||||||
console.debug(`[Frontend] Cookie token: ${cookieToken ? 'present' : 'missing'}`)
|
// httpOnly cookie будет автоматически отправлен браузером благодаря credentials: 'include'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем CSRF-токен, если он есть
|
// Добавляем CSRF-токен, если он есть
|
||||||
|
|||||||
@@ -82,24 +82,22 @@ export function saveAuthToken(token: string): void {
|
|||||||
export function checkAuthStatus(): boolean {
|
export function checkAuthStatus(): boolean {
|
||||||
console.log('[Auth] Checking authentication status...')
|
console.log('[Auth] Checking authentication status...')
|
||||||
|
|
||||||
// Проверяем наличие cookie auth_token
|
// 💋 НЕ проверяем httpOnly cookie через JavaScript - он недоступен!
|
||||||
const cookieToken = getAuthTokenFromCookie()
|
// httpOnly cookie автоматически отправляется браузером, но недоступен для чтения
|
||||||
const hasCookie = !!cookieToken && cookieToken.length > 10
|
|
||||||
|
|
||||||
// Проверяем наличие токена в localStorage
|
// Проверяем наличие токена в localStorage
|
||||||
const localToken = localStorage.getItem(AUTH_TOKEN_KEY)
|
const localToken = localStorage.getItem(AUTH_TOKEN_KEY)
|
||||||
const hasLocalToken = !!localToken && localToken.length > 10
|
const hasLocalToken = !!localToken && localToken.length > 10
|
||||||
|
|
||||||
const isAuth = hasCookie || hasLocalToken
|
// 💋 Для httpOnly cookie полагаемся на backend проверку
|
||||||
console.log(`[Auth] Cookie token: ${hasCookie ? 'present' : 'missing'}`)
|
// Если нет токена в localStorage, считаем что пользователь может быть авторизован через httpOnly cookie
|
||||||
|
// Окончательная проверка произойдет при первом GraphQL запросе
|
||||||
|
const isAuth = hasLocalToken
|
||||||
|
|
||||||
console.log(`[Auth] Local token: ${hasLocalToken ? 'present' : 'missing'}`)
|
console.log(`[Auth] Local token: ${hasLocalToken ? 'present' : 'missing'}`)
|
||||||
console.log(`[Auth] Authentication status: ${isAuth ? 'authenticated' : 'not authenticated'}`)
|
console.log(`[Auth] Authentication status: ${isAuth ? 'authenticated via localStorage' : 'unknown (may be authenticated via httpOnly cookie)'}`)
|
||||||
|
|
||||||
// Дополнительное логирование для диагностики
|
// Дополнительное логирование для диагностики
|
||||||
if (cookieToken) {
|
|
||||||
console.log(`[Auth] Cookie token length: ${cookieToken.length}`)
|
|
||||||
console.log(`[Auth] Cookie token preview: ${cookieToken.substring(0, 20)}...`)
|
|
||||||
}
|
|
||||||
if (localToken) {
|
if (localToken) {
|
||||||
console.log(`[Auth] Local token length: ${localToken.length}`)
|
console.log(`[Auth] Local token length: ${localToken.length}`)
|
||||||
console.log(`[Auth] Local token preview: ${localToken.substring(0, 20)}...`)
|
console.log(`[Auth] Local token preview: ${localToken.substring(0, 20)}...`)
|
||||||
|
|||||||
Reference in New Issue
Block a user