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

14 KiB
Raw Blame History

Безопасность проекта — объяснение для чайников

Этот документ объясняет простым языком, что и зачем было сделано для защиты API.


Оглавление

  1. Что такое «небезопасный» API и почему это плохо
  2. Что было сделано — краткий список
  3. Аутентификация — кто ты такой?
  4. JWT-токены — цифровой пропуск
  5. Хеширование паролей
  6. Защита маршрутов (роутов)
  7. Admin Key — ключ администратора
  8. Защита документации /docs
  9. CORS — кто может обращаться к API из браузера
  10. Переменные окружения — секреты не в коде
  11. Как всё настроить — пошаговая инструкция
  12. Как использовать API — примеры

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:

{
  "username": "ivan",
  "password": ойпароль123",
  "first_name": "Иван",
  "last_name": "Иванов"
}

ВходPOST /auth/login (формат form-data):

username=ivan
password=мойпароль123

Ответ:

{
  "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

Открой терминал и запусти:

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_... на реальные значения:

environment:
  - SECRET_KEY=abcdef1234...  # результат из шага 1
  - ADMIN_KEY=supersecret456  # результат из шага 1
  - DOCS_USERNAME=admin
  - DOCS_PASSWORD=мойпарольдокументации

Шаг 4. Запусти проект

docker-compose up --build

Шаг 5. Проверь

Открой в браузере http://localhost:8000/docs — должно появиться окошко с запросом пароля.


12. Примеры использования

Зарегистрироваться

curl -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"username":"ivan","password":"пароль123","first_name":"Иван","last_name":"Иванов"}'

Войти и получить токен

curl -X POST http://localhost:8000/auth/login \
  -d "username=ivan&password=пароль123"

Использовать токен в запросе

TOKEN="eyJhbGci..."  # твой токен из предыдущего шага

curl -X GET http://localhost:8000/users/me \
  -H "Authorization: Bearer $TOKEN"

Создать группу (нужен токен)

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 при регистрации