# Auth & Authorization Service Backend-приложение на **FastAPI + SQLAlchemy**, реализующее собственную систему аутентификации и авторизации без использования готовых auth-механизмов фреймворка. ## Стек - **FastAPI** — веб-фреймворк, роутинг, dependency injection - **SQLAlchemy (ORM)** — доступ к БД, `sessionmaker`, декларативные модели - **python-jose (jwt)** — создание и валидация JWT-токенов - **bcrypt / passlib** — хеширование паролей - **Pydantic v2** — схемы валидации запросов/ответов - **Alembic** - Миграции бд ## Аутентификация Аутентификация построена на **JWT-токенах** (Bearer), без серверных сессий. 1. Клиент отправляет `POST /protected/token` с `email` (как `username`) и `password` в формате `OAuth2PasswordRequestForm` (form-data). 2. Сервер ищет пользователя по email, сверяет пароль через bcrypt (`hash_check(plain, hashed)`). 3. Если пара email/пароль верна — выпускается JWT с payload `{"sub": }`, подписанный секретным ключом (`ENV.SECRET_STRING`, алгоритм `ENV.ALGORITHM`). 4. Клиент передаёт токен в заголовке `Authorization: Bearer ` в каждом последующем запросе. 5. На защищённых эндпоинтах зависимость `get_current_user`: - декодирует токен (`jwt.decode`), - достаёт `sub` (id пользователя), - загружает пользователя из БД вместе с его правами, - при любой ошибке (невалидный/просроченный токен, юзер не найден) — **401 Unauthorized**. > logout в чистой JWT-схеме требует либо короткого TTL токена + refresh-токена, > либо чёрного списка отозванных токенов (таблица `revoked_tokens`). > Также можно просто затирать токен на фронтенде при разлогине, как самый простой вариант. ## Модель авторизации Вместо классической схемы `roles / business_elements / access_roles_rules` (с раздельными `read/create/update/delete` × `_all`) выбрана более простая **flat-модель прав доступа**, которая проще для демонстрации, но покрывает те же принципы: пользователь получает набор строковых прав (permissions), и каждый эндпоинт объявляет, какие права требуются для его вызова. ### Таблицы БД **users** | поле | тип | описание | |-------------------|-------------|---------------------------------------------------------------------------| | id | int, PK | идентификатор пользователя | | name | str(64) | имя | | last_name | str(64) | фамилия | | middle_name | str(64) | отчество | | email | str(255) | email, unique | | status | bool | активен ли аккаунт (используется вместо `is_active` для мягкого удаления) | | hashed_password | str(255) | bcrypt-хеш пароля | **permissions** | поле | тип | описание | |-------------|-----------|------------------------------------------------------------------------| | id | int, PK | идентификатор права | | permission | str(64) | код права: `admin`, `can_view`, `can_create`, `can_edit`, `can_delete` | **user_permission** (association table, many-to-many) | поле | тип | описание | |------------------|-----|--------------------------| | user_id | FK | → users.id | | permission_id | FK | → permissions.id | Один пользователь может иметь несколько прав одновременно (например, `can_view` + `can_edit`); право `admin` даёт полный доступ и разрешает выдавать/забирать любые права, включая `admin`, другим пользователям. ### Правила проверки доступа Каждый метод бизнес-логики (`CrudActions`) явно объявляет множество допустимых прав и проверяет его перед выполнением действия: | Действие | Требуемое право | |----------------------------|---------------------------------| | Просмотр пользователя (по email/id) | `can_view` или `admin` | | Создание пользователя | `can_create` или `admin` | | Изменение пользователя | `can_edit` или `admin` | | Удаление пользователя | `can_delete` или `admin` | Дополнительно действует правило **защиты от эскалации привилегий**: пользователь без права `admin` не может назначить право `admin` ни себе, ни другому пользователю при создании/редактировании аккаунта. ### Обработка ошибок доступа - **401 Unauthorized** — если запрос не удаётся сопоставить с валидным залогиненным пользователем (нет токена, токен просрочен/невалиден, юзер из токена не существует). - **403 Forbidden** — пользователь определён, но у него нет нужного права на запрошенное действие/ресурс (в т.ч. попытка эскалации прав, попытка занять чужой email). - **404 Not Found** — запрошенный ресурс (пользователь, право) не существует. ## API | Метод | Путь | Требуемое право | Описание | |--------|------------------------------------|-------------------------|------------------------------------| | POST | `/protected/token` | — | Логин, выдача JWT | | POST | `/protected/logout` | — | Логин, выдача JWT | | GET | `/protected/get_user_by_email` | `can_view` / `admin` | Получить пользователя по email | | GET | `/protected/get_user_by_id` | `can_view` / `admin` | Получить пользователя по id | | POST | `/protected/create_user` | `can_create` / `admin` | Создать пользователя | | PATCH | `/protected/update_user` | `can_edit` / `admin` | Обновить свой профиль или (для admin/can_edit) чужой через `target_id` | | DELETE | `/protected/delete_user` | `can_delete` / `admin` | Удалить (деактивировать) пользователя | ## Тестовые данные Для демонстрации системы в БД должны быть заведены минимум: Пароли d@d.d, d1@d.d, d2@d.d 12345678 - 1 пользователь с правом `admin` (полный доступ) - 1 пользователь с правом `can_view` (только просмотр) - 1 пользователь без прав (для проверки 403) ## Структура проекта ``` src/ db/ # ActionsDB — доступ к БД (SQLAlchemy sessions, запросы) model/ database_model/ # SQLAlchemy-модели (User, Permissions) user/ # Pydantic-схемы (UserOut, UserCreate, UserUpdate, PermissionIn/Out) env_read/ # Чтение env через pydantic settings service/ crud_actions/ # CrudActions — бизнес-логика + проверки прав доступа JWT/ # JWT (кодирование/декодирование токена), Hash (bcrypt) auth/ # Создание и проверка jwt токена errors/ # централизованные HTTP-ошибки (401/403/404) web/ # роутеры FastAPI (эндпоинты) migrations/ # миграции бд tests/ # тесты (Пусто, не создавались) ``` ## Запуск ```bash poetry make run ``` Переименовать env