BIG_UPDATE deleted vps, added s3 infrastructure.
This commit is contained in:
236
QR-AUTH-SECURITY.md
Normal file
236
QR-AUTH-SECURITY.md
Normal file
@@ -0,0 +1,236 @@
|
||||
# QR-аутентификация — Безопасность
|
||||
|
||||
## Обзор
|
||||
|
||||
QR-аутентификация реализована по модели **OAuth2-подобного flow**, аналогично Google/Яндекс/Telegram Login.
|
||||
|
||||
---
|
||||
|
||||
## Архитектура безопасности
|
||||
|
||||
### ✅ Правильный flow (текущая реализация)
|
||||
|
||||
```
|
||||
1. ПК (неавторизованный)
|
||||
↓
|
||||
POST /api/qr-auth/generate
|
||||
← Получает уникальный code (без привязки к пользователю)
|
||||
↓
|
||||
Показывает QR: https://ospab.host/qr-login?code=XXX
|
||||
↓
|
||||
Polling: GET /api/qr-auth/status/:code каждые 2 секунды
|
||||
|
||||
2. Телефон (пользователь УЖЕ авторизован)
|
||||
↓
|
||||
Сканирует QR → открывается /qr-login?code=XXX
|
||||
↓
|
||||
POST /api/qr-auth/scanning (с Bearer token)
|
||||
→ Backend обновляет статус QR на "scanning"
|
||||
← ПК видит "Ожидание подтверждения на телефоне..."
|
||||
↓
|
||||
GET /api/auth/me (с Bearer token)
|
||||
← Получает данные ТЕКУЩЕГО пользователя телефона
|
||||
↓
|
||||
Показывает экран подтверждения:
|
||||
"Войти на новом устройстве как [Ваше имя]?"
|
||||
↓
|
||||
Пользователь нажимает "Подтвердить"
|
||||
↓
|
||||
POST /api/qr-auth/confirm + Bearer token + code
|
||||
→ Backend привязывает userId к QR-запросу
|
||||
→ Обновляет статус на "confirmed"
|
||||
|
||||
3. ПК (polling получает confirmed)
|
||||
↓
|
||||
Получает JWT токен ЭТОГО пользователя
|
||||
↓
|
||||
Вызывает login(token) → обновляет AuthContext
|
||||
↓
|
||||
Редирект на /dashboard
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Защита от уязвимостей
|
||||
|
||||
### 🔒 1. Анонимный QR-код
|
||||
|
||||
- ✅ QR создаётся **БЕЗ** привязки к пользователю
|
||||
- ✅ `userId` присваивается **только после подтверждения**
|
||||
- ❌ Невозможно "угадать" чей токен получит ПК
|
||||
|
||||
### 🔒 2. Требование авторизации на телефоне
|
||||
|
||||
- ✅ `/api/qr-auth/scanning` требует `authMiddleware`
|
||||
- ✅ `/api/qr-auth/confirm` требует `authMiddleware`
|
||||
- ❌ Неавторизованный пользователь НЕ может подтвердить вход
|
||||
|
||||
### 🔒 3. Экран подтверждения
|
||||
|
||||
```tsx
|
||||
// Телефон показывает:
|
||||
<div>
|
||||
<p>Войти на новом устройстве как:</p>
|
||||
<p className="text-xl font-bold">{userData.username}</p>
|
||||
<p className="text-sm text-gray-500">{userData.email}</p>
|
||||
</div>
|
||||
|
||||
<button onClick={handleConfirm}>Подтвердить</button>
|
||||
<button onClick={handleCancel}>Отмена</button>
|
||||
```
|
||||
|
||||
- ✅ Пользователь **видит** от чьего имени происходит вход
|
||||
- ✅ Может **отказаться**, если это не он
|
||||
|
||||
### 🔒 4. Время жизни QR-кода
|
||||
|
||||
```typescript
|
||||
const QR_EXPIRATION_SECONDS = 60; // 60 секунд
|
||||
```
|
||||
|
||||
- ✅ QR истекает через 60 секунд
|
||||
- ✅ После использования (confirmed/rejected) — удаляется
|
||||
- ✅ Cleanup устаревших кодов каждые 24 часа
|
||||
|
||||
### 🔒 5. Статусы и переходы
|
||||
|
||||
```
|
||||
pending → scanning → confirmed/rejected/expired
|
||||
↓ ↓ ↓
|
||||
Создан Открыт Финальный статус
|
||||
```
|
||||
|
||||
- ✅ `pending` → `scanning`: пользователь открыл страницу
|
||||
- ✅ `scanning` → `confirmed`: подтвердил вход
|
||||
- ✅ `scanning` → `rejected`: отклонил вход
|
||||
- ✅ `pending/scanning` → `expired`: истёк таймаут
|
||||
|
||||
### 🔒 6. Polling на ПК
|
||||
|
||||
```typescript
|
||||
// Каждые 2 секунды:
|
||||
GET /api/qr-auth/status/:code
|
||||
|
||||
// Ответы:
|
||||
{ status: 'pending' } // Ещё не сканировали
|
||||
{ status: 'scanning' } // Пользователь открыл страницу подтверждения
|
||||
{ status: 'confirmed', token: 'JWT', user: {...} } // Подтвердили
|
||||
{ status: 'rejected' } // Отклонили
|
||||
{ status: 'expired' } // Истёк
|
||||
```
|
||||
|
||||
- ✅ ПК **не генерирует токен** сам
|
||||
- ✅ ПК **получает токен** только после подтверждения с телефона
|
||||
- ✅ Токен содержит `userId` пользователя с телефона
|
||||
|
||||
---
|
||||
|
||||
## Защита от атак
|
||||
|
||||
### ❌ Атака: Перехват QR-кода
|
||||
|
||||
**Сценарий:** Злоумышленник фотографирует QR с чужого экрана
|
||||
|
||||
**Защита:**
|
||||
- ✅ QR живёт 60 секунд
|
||||
- ✅ Требуется авторизация на телефоне атакующего
|
||||
- ✅ Экран подтверждения показывает имя/email входящего пользователя
|
||||
- ✅ Жертва видит что в её аккаунт пытаются войти
|
||||
|
||||
### ❌ Атака: MITM (Man-in-the-Middle)
|
||||
|
||||
**Сценарий:** Злоумышленник перехватывает сетевой трафик
|
||||
|
||||
**Защита:**
|
||||
- ✅ Все запросы через HTTPS (`https://ospab.host:5000`)
|
||||
- ✅ JWT токены передаются в `Authorization: Bearer`
|
||||
- ✅ Токены хранятся в `localStorage` (HttpOnly cookie было бы лучше, но требует серверный рендеринг)
|
||||
|
||||
### ❌ Атака: Replay Attack
|
||||
|
||||
**Сценарий:** Злоумышленник повторно отправляет перехваченный запрос
|
||||
|
||||
**Защита:**
|
||||
- ✅ QR-код одноразовый (удаляется после confirm/reject)
|
||||
- ✅ `status !== 'pending' && status !== 'scanning'` → ошибка
|
||||
- ✅ JWT токены имеют `expiresIn: '24h'`
|
||||
|
||||
### ❌ Атака: Session Fixation
|
||||
|
||||
**Сценарий:** Злоумышленник пытается навязать свой QR-код
|
||||
|
||||
**Защита:**
|
||||
- ✅ ПК генерирует QR **локально** через `/api/qr-auth/generate`
|
||||
- ✅ Невозможно "навязать" чужой QR (каждый code уникален)
|
||||
- ✅ Backend не принимает "предустановленные" коды
|
||||
|
||||
---
|
||||
|
||||
## Сравнение с другими методами
|
||||
|
||||
| Метод | Безопасность | Удобство | Скорость |
|
||||
|------------------------|--------------|----------|----------|
|
||||
| **QR-аутентификация** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
| Логин + пароль | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
|
||||
| Email magic link | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐ |
|
||||
| SMS OTP | ⭐⭐⭐ | ⭐⭐ | ⭐⭐ |
|
||||
| OAuth (Google/Yandex) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
|
||||
|
||||
---
|
||||
|
||||
## Рекомендации по улучшению (будущее)
|
||||
|
||||
### 1. Rate Limiting
|
||||
```typescript
|
||||
// Ограничить количество попыток генерации QR с одного IP
|
||||
// Пример: максимум 10 QR в минуту
|
||||
```
|
||||
|
||||
### 2. Device Fingerprinting
|
||||
```typescript
|
||||
// При создании QR запоминать fingerprint ПК
|
||||
// При polling проверять что запросы идут с того же устройства
|
||||
```
|
||||
|
||||
### 3. Geolocation Check
|
||||
```typescript
|
||||
// Если расстояние между IP адресами ПК и телефона > 1000 км → предупреждение
|
||||
// "Попытка входа из другой страны. Подтвердите что это вы"
|
||||
```
|
||||
|
||||
### 4. WebSocket вместо Polling
|
||||
```typescript
|
||||
// Вместо GET /status/:code каждые 2 секунды
|
||||
// Использовать WebSocket для реального времени
|
||||
```
|
||||
|
||||
### 5. Push Notifications
|
||||
```typescript
|
||||
// Отправлять пуш на телефон: "Вход на новом устройстве. Подтвердите?"
|
||||
// Не требует открывать браузер
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Заключение
|
||||
|
||||
Текущая реализация QR-аутентификации **безопасна** и соответствует индустриальным стандартам (Google, Яндекс, Telegram).
|
||||
|
||||
**Ключевые принципы:**
|
||||
1. ✅ Анонимный QR без привязки к пользователю
|
||||
2. ✅ Требование авторизации на подтверждающем устройстве
|
||||
3. ✅ Явный экран подтверждения с информацией о пользователе
|
||||
4. ✅ Короткое время жизни кодов (60 сек)
|
||||
5. ✅ Одноразовое использование
|
||||
6. ✅ HTTPS + JWT токены
|
||||
|
||||
**Защищает от:**
|
||||
- ❌ Перехвата QR
|
||||
- ❌ MITM атак
|
||||
- ❌ Replay атак
|
||||
- ❌ Session Fixation
|
||||
- ❌ Несанкционированного доступа
|
||||
|
||||
---
|
||||
|
||||
_Документ обновлён: 10 ноября 2025 г._
|
||||
Reference in New Issue
Block a user