Files
api-copp/SECURITY.md
2026-03-23 21:28:10 +05:00

307 lines
14 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.
# Безопасность проекта — объяснение для чайников
Этот документ объясняет простым языком, **что и зачем** было сделано для защиты API.
---
## Оглавление
1. [Что такое «небезопасный» API и почему это плохо](#1-что-такое-небезопасный-api)
2. [Что было сделано — краткий список](#2-что-было-сделано)
3. [Аутентификация — кто ты такой?](#3-аутентификация)
4. [JWT-токены — цифровой пропуск](#4-jwt-токены)
5. [Хеширование паролей](#5-хеширование-паролей)
6. [Защита маршрутов (роутов)](#6-защита-маршрутов)
7. [Admin Key — ключ администратора](#7-admin-key)
8. [Защита документации `/docs`](#8-защита-документации-docs)
9. [CORS — кто может обращаться к API из браузера](#9-cors)
10. [Переменные окружения — секреты не в коде](#10-переменные-окружения)
11. [Как всё настроить — пошаговая инструкция](#11-как-настроить)
12. [Как использовать API — примеры](#12-примеры-использования)
---
## 1. Что такое «небезопасный» API
Представь, что у тебя есть склад. **Небезопасный API** — это когда дверь склада открыта для всех: любой может зайти, взять что хочет или сломать что хочет.
Конкретно, без защиты:
- любой человек в интернете мог удалить любого пользователя
- любой мог посмотреть чужие данные
- пароли хранились бы в открытом виде — украли базу = украли все пароли
- документация (Swagger) была видна всем, давая полную карту API для взломщика
---
## 2. Что было сделано
| Что | Зачем |
|-----|-------|
| JWT-токены | Пользователь должен войти, чтобы делать что-то важное |
| Хеширование паролей (bcrypt) | Даже если украдут БД — пароли не читаемы |
| Проверка владельца при PUT/DELETE | Ты можешь менять только свои данные |
| Admin Key | Опасные операции (инициализация БД) — только для администратора |
| HTTP Basic Auth на `/docs` | Документация API скрыта за паролем |
| CORS | Браузерные запросы принимаются только с разрешённых сайтов |
| Секреты в переменных окружения | Пароли и ключи не хранятся в коде |
---
## 3. Аутентификация
**Аутентификация** = подтверждение того, кто ты есть.
Как это работает в нашем API:
```
1. Ты отправляешь логин и пароль на POST /auth/login
2. Сервер проверяет: правильный ли пароль?
3. Если да — сервер выдаёт тебе "токен" (длинную строку-пропуск)
4. При каждом следующем запросе ты добавляешь этот токен в заголовок
5. Сервер смотрит на токен и понимает, кто ты
```
Аналогия: как билет в кино. Купил один раз — показываешь при входе, не нужно снова платить.
**Регистрация**`POST /auth/register`:
```json
{
"username": "ivan",
"password": ойпароль123",
"first_name": "Иван",
"last_name": "Иванов"
}
```
**Вход**`POST /auth/login` (формат form-data):
```
username=ivan
password=мойпароль123
```
Ответ:
```json
{
"access_token": "eyJhbGci...(длинная строка)...",
"token_type": "bearer"
}
```
---
## 4. JWT-токены
**JWT** (JSON Web Token) — это как паспорт, только цифровой.
Токен состоит из трёх частей, разделённых точкой:
```
eyJhbGciOiJIUzI1NiJ9 . eyJzdWIiOiIxMjMifQ . SflKxwRJSMeKKF2QT4
заголовок данные подпись
```
- **Заголовок**: тип токена и алгоритм шифрования
- **Данные (payload)**: ID пользователя, время истечения токена
- **Подпись**: криптографическая подпись — без секретного ключа сервера её нельзя подделать
Аналогия: токен — это как печать на паспорте. Печать нельзя нарисовать самому (без настоящей печати), поэтому её нельзя подделать.
**Как использовать токен в запросах:**
В заголовке HTTP:
```
Authorization: Bearer eyJhbGci...(твой токен)...
```
**Срок жизни токена** задаётся переменной `ACCESS_TOKEN_EXPIRE_MINUTES` (по умолчанию 60 минут).
---
## 5. Хеширование паролей
Пароли **никогда** не хранятся в открытом виде. Вместо этого хранится их хеш.
```
Пароль: "мойпароль123"
↓ bcrypt
Хеш: "$2b$12$eImiTXuWVxfM37uY4JANjQ..."
```
**Почему это безопасно:**
- Хеш нельзя "расшифровать" обратно в пароль
- Даже если база данных будет украдена — злоумышленник увидит только хеши
- При проверке он не расшифровывает, а снова хеширует введённый пароль и сравнивает хеши
Алгоритм **bcrypt** специально медленный — это делает перебор паролей (brute force) очень долгим.
---
## 6. Защита маршрутов
Некоторые действия требуют авторизации, другие открыты для всех.
| Маршрут | Открыт? | Объяснение |
|---------|---------|-----------|
| `POST /auth/register` | ✅ Да | Регистрация открыта всем |
| `POST /auth/login` | ✅ Да | Вход открыт всем |
| `GET /groups/` | ✅ Да | Просмотр групп — публичное |
| `POST /groups/` | 🔒 Нет | Создать группу — нужен токен |
| `PUT /users/{id}` | 🔒 Нет | Изменить профиль — только свой |
| `DELETE /users/{id}` | 🔒 Нет | Удалить — только свой аккаунт |
| `POST /init_database` | 🔒 Admin | Только с Admin Key |
**Проверка владельца**: при попытке изменить или удалить чужие данные сервер вернёт ошибку `403 Forbidden`.
---
## 7. Admin Key
Некоторые операции очень опасны (например, инициализация базы данных). Для них нужен специальный ключ администратора.
**Как использовать:**
```
Заголовок запроса: X-Admin-Key: твой_секретный_admin_ключ
```
Если `ADMIN_KEY` не задан в переменных окружения — такие операции полностью отключены (сервер вернёт `503`).
---
## 8. Защита документации `/docs`
Swagger UI (`/docs`) — это интерактивная документация API. Она показывает все маршруты, параметры, позволяет делать запросы прямо из браузера. Это очень удобно для разработчика, но и очень удобно для взломщика.
**Решение:** HTTP Basic Auth — браузер сам показывает окошко с запросом пароля.
```
Открываешь http://твой-сервер/docs
Браузер показывает окошко: "Введите логин и пароль"
Вводишь DOCS_USERNAME и DOCS_PASSWORD
Видишь документацию
```
То же самое работает для `/redoc` и `/openapi.json`.
---
## 9. CORS
**CORS** (Cross-Origin Resource Sharing) — механизм браузера, который контролирует, с каких сайтов можно делать запросы к API.
Аналогия: CORS — это как охранник на входе, который проверяет твой бейдж (адрес сайта). Если бейдж не из списка — не пропустит.
Разрешённые источники в нашем проекте:
```
http://localhost:3000
http://127.0.0.1:3000
```
Это значит, что только фронтенд, запущенный локально на порту 3000, может обращаться к API из браузера.
> ⚠️ **Важно для production**: если у тебя есть публичный домен, добавь его в список `allow_origins` в `main.py`.
---
## 10. Переменные окружения
**Секреты никогда не должны быть в коде.** Если код попадёт на GitHub — все пароли станут публичными.
Все секреты хранятся в переменных окружения (в `docker-compose.yml` или в `.env` файле).
| Переменная | Что это | Как создать |
|-----------|---------|------------|
| `SECRET_KEY` | Ключ для подписи JWT-токенов | `python -c "import secrets; print(secrets.token_hex(32))"` |
| `ADMIN_KEY` | Пароль для admin-операций | Придумай длинную случайную строку |
| `DOCS_USERNAME` | Логин для доступа к `/docs` | Например: `admin` |
| `DOCS_PASSWORD` | Пароль для доступа к `/docs` | Придумай надёжный пароль |
| `ACCESS_TOKEN_EXPIRE_MINUTES` | Сколько минут живёт токен | По умолчанию 60 |
---
## 11. Как настроить
### Шаг 1. Сгенерируй SECRET_KEY
Открой терминал и запусти:
```bash
python -c "import secrets; print(secrets.token_hex(32))"
```
Скопируй результат — это твой `SECRET_KEY`.
Сделай то же самое для `ADMIN_KEY`.
### Шаг 2. Задай пароль для документации
Придумай любой логин и пароль для `DOCS_USERNAME` и `DOCS_PASSWORD`.
### Шаг 3. Обнови docker-compose.yml
Найди блок `environment` в `docker-compose.yml` и замени все `REPLACE_ME_...` на реальные значения:
```yaml
environment:
- SECRET_KEY=abcdef1234... # результат из шага 1
- ADMIN_KEY=supersecret456 # результат из шага 1
- DOCS_USERNAME=admin
- DOCS_PASSWORD=мойпарольдокументации
```
### Шаг 4. Запусти проект
```bash
docker-compose up --build
```
### Шаг 5. Проверь
Открой в браузере `http://localhost:8000/docs` — должно появиться окошко с запросом пароля.
---
## 12. Примеры использования
### Зарегистрироваться
```bash
curl -X POST http://localhost:8000/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"ivan","password":"пароль123","first_name":"Иван","last_name":"Иванов"}'
```
### Войти и получить токен
```bash
curl -X POST http://localhost:8000/auth/login \
-d "username=ivan&password=пароль123"
```
### Использовать токен в запросе
```bash
TOKEN="eyJhbGci..." # твой токен из предыдущего шага
curl -X GET http://localhost:8000/users/me \
-H "Authorization: Bearer $TOKEN"
```
### Создать группу (нужен токен)
```bash
curl -X POST http://localhost:8000/groups/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name_group":"Группа 1","user_id":"uuid-пользователя"}'
```
### Открыть документацию в браузере
Просто зайди на `http://localhost:8000/docs` — браузер спросит логин/пароль.
---
## Что ещё можно улучшить (на будущее)
- **Rate limiting** — ограничение количества запросов с одного IP (защита от брутфорса)
- **Refresh tokens** — долгоживущие токены для обновления access token без повторного входа
- **HTTPS** — шифрование соединения (критично в production, настраивается на уровне nginx/proxy)
- **Логирование** — запись подозрительных действий (много неудачных попыток входа)
- **Email-верификация** — подтверждение email при регистрации