Files
voidea/docs/adr/003-oauth-schema.md

243 lines
7.2 KiB
Markdown
Raw Permalink 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.
# ADR-003: OAuth схема авторизации
**Статус:** принято
**Дата:** 2026-05-10
---
## Контекст
Проект VoIdea поддерживает несколько способов авторизации:
- Email + пароль
- OAuth провайдеры (Яндекс, Google, Apple)
Необходимо определить правила привязки провайдеров к аккаунтам.
---
## Решение
**Один пользователь = один провайдер**
> Нельзя привязать Google к аккаунту, зарегистрированному через Яндекс.
---
## Правила
### Основные
1. **При регистрации через OAuth** — аккаунт привязан к этому провайдеру навсегда
2. **При регистрации через email** — можно использовать только email + пароль
3. **Нельзя добавить второй провайдер** — даже если email совпадает
### Примеры
| Действие | Результат |
|----------|-----------|
| Регистрация через Яндекс → Вход через Google | ❌ Ошибка: создай новый аккаунт |
| Регистрация через Google → Вход через Яндекс | ❌ Ошибка: создай новый аккаунт |
| Регистрация через email → Вход через Яндекс | ❌ Ошибка: это разные аккаунты |
| Регистрация через Яндекс → Повторный вход через Яндекс | ✅ Работает |
---
## Обоснование
### Почему один провайдер
1. **Безопасность**
- Меньше точек входа для атак
- Сложнее украсть аккаунт
- Чёткая атрибуция действий
2. **Простота реализации**
- Не нужно merge аккаунтов
- Не нужно решать конфликты данных
- Понятная модель данных
3. **Privacy**
- Данные не смешиваются между провайдерами
- Пользователь понимает что использует
4. **Яндекс vs Google**
- Разные экосистемы
- Разные данные пользователя
- Разная политика безопасности
---
## Структура данных
### Users table
```sql
CREATE TABLE users (
id UUID PRIMARY KEY,
-- Идентификация
email VARCHAR(255) UNIQUE, -- NULL если OAuth без email
password_hash VARCHAR(255), -- NULL если OAuth-only
-- OAuth (только один провайдер)
oauth_provider VARCHAR(20), -- yandex|google|apple|null
oauth_id VARCHAR(255), -- ID в системе провайдера
-- Метаданные
email_verified BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
-- Constraints
CONSTRAINT users_oauth_xor_email CHECK (
-- OAuth с email
(oauth_provider IS NOT NULL AND email IS NOT NULL) OR
-- Email-only
(oauth_provider IS NULL AND email IS NOT NULL AND password_hash IS NOT NULL)
)
);
CREATE UNIQUE INDEX uq_users_oauth
ON users(oauth_provider, oauth_id)
WHERE oauth_provider IS NOT NULL;
```
---
## OAuth Flow
### Пример: Яндекс OAuth
```python
async def yandex_oauth_callback(code: str, db: AsyncSession):
# 1. Получаем access_token
token_data = await yandex_api.get_token(code)
# 2. Получаем данные пользователя
user_data = await yandex_api.get_user_info(token_data.access_token)
# 3. Проверяем/создаём аккаунт
user = await db.execute(
select(User).where(
User.oauth_provider == 'yandex',
User.oauth_id == user_data.id
)
)
if not user:
# Новый пользователь
user = User(
email=user_data.email,
oauth_provider='yandex',
oauth_id=user_data.id
)
db.add(user)
await db.commit()
# 4. Создаём JWT session
return create_jwt_session(user.id)
```
---
## Google OAuth
Аналогично Яндексу, с заменой endpoint-ов.
### Различия
| Параметр | Яндекс | Google |
|----------|--------|--------|
| OAuth endpoint | oauth.yandex.ru | oauth2.googleapis.com |
| User info | login.yandex.ru | www.googleapis.com/oauth2/v2/userinfo |
| Scope | login:email, profile | email, profile |
---
## Apple OAuth (отложено)
Apple будет реализован ближе к коммерческой версии.
### Требования
- App Store Developer Account ($99/год)
- Private Key для подписи (в Keychain)
- Тот же принцип: один пользователь = один провайдер
---
## Защита от привязки чужого аккаунта
### Проблема
Злоумышленник может попытаться привязать Google к чужому email.
### Решение
1. **Email verification**
- OAuth возвращает verified email
- Привязка только verified email
2. **Separate tables**
- OAuth и email разделены логически
- Разные flows для входа
3. **Audit logging**
- Все попытки OAuth логируются
- Подозрительная активность → SecurityAgent
---
## Последствия
### Положительные
- Простая модель данных
- Безопасность выше
- Понятно для пользователя
- Легко реализовать
### Отрицательные
- Пользователь не может "добавить" Google к существующему аккаунту
- При потере доступа к провайдеру — сложнее восстановить
- Нельзя merge аккаунты
### Workaround для пользователя
При потере доступа к OAuth провайдеру:
1. Обращение в поддержку
2. Подтверждение личности
3. Смена email (если нужно)
4. Сброс пароля на email
---
## Конфигурация
```bash
# .env
OAUTH_YANDEX_ID=your_yandex_client_id
OAUTH_YANDEX_SECRET=your_yandex_secret
OAUTH_YANDEX_REDIRECT_URI=http://localhost:8020/auth/yandex/callback
OAUTH_GOOGLE_ID=your_google_client_id
OAUTH_GOOGLE_SECRET=your_google_secret
OAUTH_GOOGLE_REDIRECT_URI=http://localhost:8020/auth/google/callback
# Apple - зарезервировано для будущего
# OAUTH_APPLE_ID=
# OAUTH_APPLE_TEAM_ID=
```
---
## Ответственный
**Decision maker:** Owner
**Review date:** При добавлении Apple OAuth
---
*Создано: 2026-05-10*
*См. также: docs/backlog/oauth-schema-note.md*