151 lines
9.8 KiB
Markdown
151 lines
9.8 KiB
Markdown
# 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": <user.id>}`,
|
||
подписанный секретным ключом (`ENV.SECRET_STRING`, алгоритм `ENV.ALGORITHM`).
|
||
4. Клиент передаёт токен в заголовке `Authorization: Bearer <token>` в каждом
|
||
последующем запросе.
|
||
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 |