[0.9.28] - 2025-09-28
All checks were successful
Deploy on push / deploy (push) Successful in 2m46s

### 🍪 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:
2025-09-28 13:06:03 +03:00
parent fb98a1c6c8
commit 752e2dcbdc
9 changed files with 255 additions and 223 deletions

View File

@@ -2,14 +2,21 @@
## 📚 Обзор
Модульная система аутентификации с **httpOnly cookies**, JWT токенами, Redis-сессиями, OAuth интеграцией и RBAC авторизацией.
Модульная система аутентификации с JWT токенами, Redis-сессиями, OAuth интеграцией и RBAC авторизацией.
### 🎯 **Единый подход с httpOnly cookies для ВСЕХ типов авторизации:**
### 🎯 **Гибридный подход авторизации:**
-**OAuth** (Google/GitHub/Yandex/VK) → httpOnly cookie
-**Email/Password** → httpOnly cookie
**Основной сайт (стандартный подход):**
-**OAuth** (Google/GitHub/Yandex/VK) → Bearer токен в URL → localStorage
-**Email/Password** → Bearer токен в response → localStorage
-**GraphQL запросы**`Authorization: Bearer <token>`
-**Cross-origin совместимость** → работает везде
**Админка (максимальная безопасность):**
-**Email/Password** → httpOnly cookie (только для /panel)
-**GraphQL запросы**`credentials: 'include'`
-**Максимальная безопасность** → защита от XSS/CSRF
-**Защита от XSS/CSRF** → httpOnly + SameSite cookies
-**OAuth отключен** → только email/password для админов
## 🚀 Быстрый старт
@@ -31,8 +38,24 @@ if payload:
### Для фронтенда
**Основной сайт (Bearer токены):**
```typescript
// Все запросы используют httpOnly cookies
// Токен из localStorage
const token = localStorage.getItem('access_token');
const response = await fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}` // ✅ Bearer токен из localStorage
},
body: JSON.stringify({ query, variables })
});
```
**Админка (httpOnly cookies):**
```typescript
// Cookies отправляются автоматически
const response = await fetch('/graphql', {
method: 'POST',
credentials: 'include', // ✅ КРИТИЧНО: отправляет httpOnly cookies
@@ -75,12 +98,16 @@ oauth_refresh:{user_id}:{provider} # Refresh токен
- **[Security System](../security.md)** - Управление паролями и email
- **[Redis Schema](../redis-schema.md)** - Схема данных и кеширование
## 🔄 OAuth Flow (обновленный 2025)
## 🔄 OAuth Flow (правильный 2025)
### 1. 🚀 Инициация OAuth
```typescript
// Пользователь нажимает "Войти через Google"
const handleOAuthLogin = (provider: string) => {
// Сохраняем текущую страницу для возврата
localStorage.setItem('oauth_return_url', window.location.pathname);
// Редиректим на OAuth endpoint
window.location.href = `/oauth/${provider}/login`;
};
```
@@ -91,21 +118,48 @@ const handleOAuthLogin = (provider: string) => {
# 1. Обменивает code на access_token
# 2. Получает профиль пользователя
# 3. Создает JWT сессию
# 4. Устанавливает httpOnly cookie
# 5. Редиректит на фронтенд БЕЗ токена в URL
# 4. Проверяет тип приложения:
# - Основной сайт: редиректит с токеном в URL
# - Админка: устанавливает httpOnly cookie
```
### 3. 🌐 Фронтенд финализация
**Основной сайт:**
```typescript
// Проверяем URL на ошибки
// Читаем токен из URL
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get('access_token');
const error = urlParams.get('error');
if (error) {
console.error('OAuth error:', error);
navigate('/login');
} else if (token) {
// Сохраняем токен в localStorage
localStorage.setItem('access_token', token);
// Очищаем URL от токена
window.history.replaceState({}, '', window.location.pathname);
// Возвращаемся на сохраненную страницу
const returnUrl = localStorage.getItem('oauth_return_url') || '/';
localStorage.removeItem('oauth_return_url');
navigate(returnUrl);
}
```
**Админка:**
```typescript
// httpOnly cookie уже установлен
const error = urlParams.get('error');
if (error) {
// Обработка ошибок OAuth
console.error('OAuth error:', error);
navigate('/panel/login');
} else {
// Успех! httpOnly cookie уже установлен
await auth.checkSession(); // Загружает из cookie
navigate('/dashboard');
// Проверяем сессию (cookie отправится автоматически)
await auth.checkSession();
navigate('/panel');
}
```
@@ -225,9 +279,16 @@ if health["status"] == "healthy":
## 🎯 Результат архитектуры 2025
После внедрения httpOnly cookies:
-**OAuth**: Google/GitHub → httpOnly cookie → GraphQL запросы
-**Email/Password**: Login form → httpOnly cookie → GraphQL запросы
-**Единая архитектура**: Все через cookies + `credentials: 'include'`
-**Максимальная безопасность**: Защита от XSS и CSRF для всех типов авторизации
-**Простота**: Браузер автоматически управляет токенами
**Гибридный подход - лучшее из двух миров:**
**Основной сайт (стандартный подход):**
-**OAuth**: Google/GitHub → Bearer токен в URL → localStorage → GraphQL запросы
-**Email/Password**: Login form → Bearer токен в response → localStorage → GraphQL запросы
-**Cross-origin совместимость**: Работает везде, включая мобильные приложения
-**Простота интеграции**: Стандартный Bearer токен подход
**Админка (максимальная безопасность):**
-**OAuth отключен**: Только email/password для админов
-**Email/Password**: Login form → httpOnly cookie → GraphQL запросы
-**Максимальная безопасность**: Защита от XSS и CSRF
- ✅ **Автоматическое управление**: Браузер сам отправляет cookies

View File

@@ -2,9 +2,11 @@
## 🎯 Обзор
Система OAuth интеграции с **httpOnly cookies** для максимальной безопасности. Поддержка популярных провайдеров с единым подходом к аутентификации.
Система OAuth интеграции с **Bearer токенами** для основного сайта. Поддержка популярных провайдеров с cross-origin совместимостью.
### 🔄 **Архитектура 2025: httpOnly cookies для всех**
**Важно:** OAuth доступен только для основного сайта. Админка использует только email/password аутентификацию.
### 🔄 **Архитектура: стандартный подход**
```mermaid
sequenceDiagram
@@ -13,17 +15,23 @@ sequenceDiagram
participant B as Backend
participant P as OAuth Provider
U->>F: Click "Login with Google"
F->>B: GET /oauth/google/login
U->>F: Click "Login with Provider"
F->>B: GET /oauth/{provider}/login
B->>P: Redirect to Provider
P->>U: Show authorization page
U->>P: Grant permission
P->>B: GET /oauth/google/callback?code=xxx
P->>B: GET /oauth/{provider}/callback?code={code}
B->>P: Exchange code for token
P->>B: Return access token + user data
B->>B: Create JWT session
B->>F: Redirect + Set httpOnly cookie
F->>U: User logged in (cookie automatic)
B->>B: Create/update user + JWT session
B->>F: Redirect with token in URL
Note over B,F: URL: /?access_token=JWT_TOKEN
F->>F: Save token to localStorage
F->>F: Clear token from URL
F->>U: User logged in
Note over F,B: All subsequent requests
F->>B: GraphQL with Authorization: Bearer
```
## 🚀 Поддерживаемые провайдеры
@@ -82,49 +90,33 @@ const handleOAuthLogin = (provider: string) => {
### 3. 🌐 Фронтенд финализация
```typescript
// OAuth callback route (/oauth/callback или аналогичный)
// OAuth callback route
export default function OAuthCallback() {
const navigate = useNavigate();
const auth = useAuth();
onMount(async () => {
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get('access_token');
const error = urlParams.get('error');
if (error) {
// ❌ Ошибка OAuth
console.error('OAuth error:', error);
navigate('/login?error=' + error);
} else if (token) {
// ✅ Успех! Сохраняем токен в localStorage
localStorage.setItem('access_token', token);
switch (error) {
case 'access_denied':
alert('Доступ отклонен провайдером');
break;
case 'oauth_state_expired':
alert('Сессия OAuth истекла. Попробуйте еще раз.');
break;
default:
alert('Ошибка авторизации. Попробуйте еще раз.');
}
// Очищаем URL от токена
window.history.replaceState({}, '', window.location.pathname);
navigate('/login');
// Возвращаемся на сохраненную страницу
const returnUrl = localStorage.getItem('oauth_return_url') || '/';
localStorage.removeItem('oauth_return_url');
navigate(returnUrl);
} else {
// ✅ Успех! httpOnly cookie уже установлен
try {
// Проверяем сессию (cookie отправится автоматически)
await auth.checkSession();
if (auth.isAuthenticated()) {
// Возвращаемся на сохраненную страницу
const returnUrl = localStorage.getItem('oauth_return_url') || '/';
localStorage.removeItem('oauth_return_url');
navigate(returnUrl);
} else {
throw new Error('Session validation failed');
}
} catch (error) {
console.error('Failed to validate session:', error);
navigate('/login?error=session_failed');
}
navigate('/login?error=no_token');
}
});
@@ -137,15 +129,19 @@ export default function OAuthCallback() {
}
```
### 4. 🍪 Единая аутентификация через httpOnly cookie
### 4. 🔑 Использование Bearer токенов
```typescript
// GraphQL клиент использует httpOnly cookie
// GraphQL клиент использует Bearer токены из localStorage
const graphqlRequest = async (query: string, variables?: any) => {
const token = localStorage.getItem('access_token');
const response = await fetch('/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include', // ✅ КРИТИЧНО: отправляет httpOnly cookie
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}` // ✅ Bearer токен из localStorage
},
body: JSON.stringify({ query, variables })
});