readme add
This commit is contained in:
@@ -1,151 +1,154 @@
|
|||||||
# Auth & Authorization Service
|
# Auth & Authorization Service
|
||||||
|
|
||||||
Backend-приложение на **FastAPI + SQLAlchemy**, реализующее собственную систему
|
Backend application built with **FastAPI + SQLAlchemy**, implementing a custom
|
||||||
аутентификации и авторизации без использования готовых auth-механизмов фреймворка.
|
authentication and authorization system without using the framework's built-in
|
||||||
|
auth mechanisms.
|
||||||
|
|
||||||
## Стек
|
## Stack
|
||||||
|
|
||||||
- **FastAPI** — веб-фреймворк, роутинг, dependency injection
|
- **FastAPI** — web framework, routing, dependency injection
|
||||||
- **SQLAlchemy (ORM)** — доступ к БД, `sessionmaker`, декларативные модели
|
- **SQLAlchemy (ORM)** — database access, `sessionmaker`, declarative models
|
||||||
- **python-jose (jwt)** — создание и валидация JWT-токенов
|
- **python-jose (jwt)** — creating and validating JWT tokens
|
||||||
- **bcrypt / passlib** — хеширование паролей
|
- **bcrypt / passlib** — password hashing
|
||||||
- **Pydantic v2** — схемы валидации запросов/ответов
|
- **Pydantic v2** — request/response validation schemas
|
||||||
- **Alembic** - Миграции бд
|
- **Alembic** — database migrations
|
||||||
|
|
||||||
|
|
||||||
## Аутентификация
|
## Authentication
|
||||||
|
|
||||||
Аутентификация построена на **JWT-токенах** (Bearer), без серверных сессий.
|
Authentication is built on **JWT tokens** (Bearer), without server-side sessions.
|
||||||
|
|
||||||
1. Клиент отправляет `POST /protected/token` с `email` (как `username`) и `password`
|
1. The client sends `POST /protected/token` with `email` (as `username`) and
|
||||||
в формате `OAuth2PasswordRequestForm` (form-data).
|
`password` in `OAuth2PasswordRequestForm` format (form-data).
|
||||||
2. Сервер ищет пользователя по email, сверяет пароль через bcrypt
|
2. The server looks up the user by email and verifies the password via bcrypt
|
||||||
(`hash_check(plain, hashed)`).
|
(`hash_check(plain, hashed)`).
|
||||||
3. Если пара email/пароль верна — выпускается JWT с payload `{"sub": <user.id>}`,
|
3. If the email/password pair is correct — a JWT is issued with payload
|
||||||
подписанный секретным ключом (`ENV.SECRET_STRING`, алгоритм `ENV.ALGORITHM`).
|
`{"sub": <user.id>}`, signed with a secret key (`ENV.SECRET_STRING`,
|
||||||
4. Клиент передаёт токен в заголовке `Authorization: Bearer <token>` в каждом
|
algorithm `ENV.ALGORITHM`).
|
||||||
последующем запросе.
|
4. The client passes the token in the `Authorization: Bearer <token>` header
|
||||||
5. На защищённых эндпоинтах зависимость `get_current_user`:
|
with every subsequent request.
|
||||||
- декодирует токен (`jwt.decode`),
|
5. On protected endpoints, the `get_current_user` dependency:
|
||||||
- достаёт `sub` (id пользователя),
|
- decodes the token (`jwt.decode`),
|
||||||
- загружает пользователя из БД вместе с его правами,
|
- extracts `sub` (user id),
|
||||||
- при любой ошибке (невалидный/просроченный токен, юзер не найден) —
|
- loads the user from the DB along with their permissions,
|
||||||
|
- on any error (invalid/expired token, user not found from the token) —
|
||||||
**401 Unauthorized**.
|
**401 Unauthorized**.
|
||||||
|
|
||||||
> logout в чистой JWT-схеме требует либо короткого TTL токена + refresh-токена,
|
> Logout in a pure JWT scheme requires either a short token TTL + a refresh
|
||||||
> либо чёрного списка отозванных токенов (таблица `revoked_tokens`).
|
> token, or a blacklist of revoked tokens (a `revoked_tokens` table).
|
||||||
> Также можно просто затирать токен на фронтенде при разлогине, как самый простой вариант.
|
> Alternatively, the token can simply be cleared on the frontend at logout, as
|
||||||
|
> the simplest option.
|
||||||
|
|
||||||
## Модель авторизации
|
## Authorization Model
|
||||||
|
|
||||||
Вместо классической схемы `roles / business_elements / access_roles_rules`
|
Instead of the classic `roles / business_elements / access_roles_rules` scheme
|
||||||
(с раздельными `read/create/update/delete` × `_all`) выбрана более простая
|
(with separate `read/create/update/delete` × `_all`), a simpler **flat
|
||||||
**flat-модель прав доступа**, которая проще для демонстрации, но покрывает те же
|
permission model** was chosen, which is easier to demonstrate but covers the
|
||||||
принципы: пользователь получает набор строковых прав (permissions), и каждый
|
same principles: a user is granted a set of string permissions, and each
|
||||||
эндпоинт объявляет, какие права требуются для его вызова.
|
endpoint declares which permissions are required to call it.
|
||||||
|
|
||||||
### Таблицы БД
|
### Database Tables
|
||||||
|
|
||||||
**users**
|
**users**
|
||||||
|
|
||||||
| поле | тип | описание |
|
| field | type | description |
|
||||||
|-------------------|-------------|---------------------------------------------------------------------------|
|
|-------------------|-------------|-----------------------------------------------------------------------------|
|
||||||
| id | int, PK | идентификатор пользователя |
|
| id | int, PK | user identifier |
|
||||||
| name | str(64) | имя |
|
| name | str(64) | first name |
|
||||||
| last_name | str(64) | фамилия |
|
| last_name | str(64) | last name |
|
||||||
| middle_name | str(64) | отчество |
|
| middle_name | str(64) | middle name |
|
||||||
| email | str(255) | email, unique |
|
| email | str(255) | email, unique |
|
||||||
| status | bool | активен ли аккаунт (используется вместо `is_active` для мягкого удаления) |
|
| status | bool | whether the account is active (used instead of `is_active` for soft delete) |
|
||||||
| hashed_password | str(255) | bcrypt-хеш пароля |
|
| hashed_password | str(255) | bcrypt password hash |
|
||||||
|
|
||||||
**permissions**
|
**permissions**
|
||||||
|
|
||||||
| поле | тип | описание |
|
| field | type | description |
|
||||||
|-------------|-----------|------------------------------------------------------------------------|
|
|-------------|-----------|--------------------------------------------------------------------------|
|
||||||
| id | int, PK | идентификатор права |
|
| id | int, PK | permission identifier |
|
||||||
| permission | str(64) | код права: `admin`, `can_view`, `can_create`, `can_edit`, `can_delete` |
|
| permission | str(64) | permission code: `admin`, `can_view`, `can_create`, `can_edit`, `can_delete` |
|
||||||
|
|
||||||
**user_permission** (association table, many-to-many)
|
**user_permission** (association table, many-to-many)
|
||||||
|
|
||||||
| поле | тип | описание |
|
| field | type | description |
|
||||||
|------------------|-----|--------------------------|
|
|------------------|------|---------------------------|
|
||||||
| user_id | FK | → users.id |
|
| user_id | FK | → users.id |
|
||||||
| permission_id | FK | → permissions.id |
|
| permission_id | FK | → permissions.id |
|
||||||
|
|
||||||
Один пользователь может иметь несколько прав одновременно (например,
|
A single user can hold several permissions at once (e.g. `can_view` +
|
||||||
`can_view` + `can_edit`); право `admin` даёт полный доступ и разрешает
|
`can_edit`); the `admin` permission grants full access and allows
|
||||||
выдавать/забирать любые права, включая `admin`, другим пользователям.
|
granting/revoking any permissions, including `admin`, to/from other users.
|
||||||
|
|
||||||
### Правила проверки доступа
|
### Access Check Rules
|
||||||
|
|
||||||
Каждый метод бизнес-логики (`CrudActions`) явно объявляет множество
|
Every business-logic method (`CrudActions`) explicitly declares the set of
|
||||||
допустимых прав и проверяет его перед выполнением действия:
|
permitted permissions and checks it before performing the action:
|
||||||
|
|
||||||
| Действие | Требуемое право |
|
| Action | Required permission |
|
||||||
|----------------------------|---------------------------------|
|
|------------------------------|----------------------------------|
|
||||||
| Просмотр пользователя (по email/id) | `can_view` или `admin` |
|
| View user (by email/id) | `can_view` or `admin` |
|
||||||
| Создание пользователя | `can_create` или `admin` |
|
| Create user | `can_create` or `admin` |
|
||||||
| Изменение пользователя | `can_edit` или `admin` |
|
| Update user | `can_edit` or `admin` |
|
||||||
| Удаление пользователя | `can_delete` или `admin` |
|
| Delete user | `can_delete` or `admin` |
|
||||||
|
|
||||||
Дополнительно действует правило **защиты от эскалации привилегий**: пользователь
|
Additionally, a **privilege escalation protection** rule applies: a user
|
||||||
без права `admin` не может назначить право `admin` ни себе, ни другому
|
without the `admin` permission cannot grant the `admin` permission to
|
||||||
пользователю при создании/редактировании аккаунта.
|
themselves or any other user when creating/editing an account.
|
||||||
|
|
||||||
### Обработка ошибок доступа
|
### Access Error Handling
|
||||||
|
|
||||||
- **401 Unauthorized** — если запрос не удаётся сопоставить с валидным
|
- **401 Unauthorized** — if the request cannot be matched to a valid logged-in
|
||||||
залогиненным пользователем (нет токена, токен просрочен/невалиден, юзер
|
user (no token, expired/invalid token, user from the token does not exist).
|
||||||
из токена не существует).
|
- **403 Forbidden** — the user is identified, but lacks the required
|
||||||
- **403 Forbidden** — пользователь определён, но у него нет нужного права
|
permission for the requested action/resource (including privilege
|
||||||
на запрошенное действие/ресурс (в т.ч. попытка эскалации прав, попытка
|
escalation attempts, attempts to claim someone else's email).
|
||||||
занять чужой email).
|
- **404 Not Found** — the requested resource (user, permission) does not
|
||||||
- **404 Not Found** — запрошенный ресурс (пользователь, право) не существует.
|
exist.
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
| Метод | Путь | Требуемое право | Описание |
|
| Method | Path | Required permission | Description |
|
||||||
|--------|------------------------------------|-------------------------|------------------------------------|
|
|--------|--------------------------------------|--------------------------|----------------------------------------|
|
||||||
| POST | `/protected/token` | — | Логин, выдача JWT |
|
| POST | `/protected/token` | — | Login, JWT issuance |
|
||||||
| POST | `/protected/logout` | — | Логин, выдача JWT |
|
| POST | `/protected/logout` | — | Login, JWT issuance |
|
||||||
| GET | `/protected/get_user_by_email` | `can_view` / `admin` | Получить пользователя по email |
|
| GET | `/protected/get_user_by_email` | `can_view` / `admin` | Get user by email |
|
||||||
| GET | `/protected/get_user_by_id` | `can_view` / `admin` | Получить пользователя по id |
|
| GET | `/protected/get_user_by_id` | `can_view` / `admin` | Get user by id |
|
||||||
| POST | `/protected/create_user` | `can_create` / `admin` | Создать пользователя |
|
| POST | `/protected/create_user` | `can_create` / `admin` | Create user |
|
||||||
| PATCH | `/protected/update_user` | `can_edit` / `admin` | Обновить свой профиль или (для admin/can_edit) чужой через `target_id` |
|
| PATCH | `/protected/update_user` | `can_edit` / `admin` | Update own profile, or (for admin/can_edit) someone else's via `target_id` |
|
||||||
| DELETE | `/protected/delete_user` | `can_delete` / `admin` | Удалить (деактивировать) пользователя |
|
| DELETE | `/protected/delete_user` | `can_delete` / `admin` | Delete (deactivate) user |
|
||||||
|
|
||||||
## Тестовые данные
|
## Test Data
|
||||||
|
|
||||||
Для демонстрации системы в БД должны быть заведены минимум:
|
For demonstrating the system, the DB must contain at least:
|
||||||
Пароли d@d.d, d1@d.d, d2@d.d 12345678
|
Passwords d@d.d, d1@d.d, d2@d.d 12345678
|
||||||
- 1 пользователь с правом `admin` (полный доступ)
|
- 1 user with the `admin` permission (full access)
|
||||||
- 1 пользователь с правом `can_view` (только просмотр)
|
- 1 user with the `can_view` permission (view only)
|
||||||
- 1 пользователь без прав (для проверки 403)
|
- 1 user with no permissions (for testing 403)
|
||||||
|
|
||||||
|
|
||||||
## Структура проекта
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
db/ # ActionsDB — доступ к БД (SQLAlchemy sessions, запросы)
|
db/ # ActionsDB — DB access (SQLAlchemy sessions, queries)
|
||||||
model/
|
model/
|
||||||
database_model/ # SQLAlchemy-модели (User, Permissions)
|
database_model/ # SQLAlchemy models (User, Permissions)
|
||||||
user/ # Pydantic-схемы (UserOut, UserCreate, UserUpdate, PermissionIn/Out)
|
user/ # Pydantic schemas (UserOut, UserCreate, UserUpdate, PermissionIn/Out)
|
||||||
env_read/ # Чтение env через pydantic settings
|
env_read/ # Reading env via pydantic settings
|
||||||
service/
|
service/
|
||||||
crud_actions/ # CrudActions — бизнес-логика + проверки прав доступа
|
crud_actions/ # CrudActions — business logic + access permission checks
|
||||||
JWT/ # JWT (кодирование/декодирование токена), Hash (bcrypt)
|
JWT/ # JWT (token encoding/decoding), Hash (bcrypt)
|
||||||
auth/ # Создание и проверка jwt токена
|
auth/ # JWT token creation and verification
|
||||||
errors/ # централизованные HTTP-ошибки (401/403/404)
|
errors/ # centralized HTTP errors (401/403/404)
|
||||||
web/ # роутеры FastAPI (эндпоинты)
|
web/ # FastAPI routers (endpoints)
|
||||||
migrations/ # миграции бд
|
migrations/ # DB migrations
|
||||||
tests/ # тесты (Пусто, не создавались)
|
tests/ # tests (empty, not yet created)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Запуск
|
## Running
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
poetry
|
poetry
|
||||||
make run
|
make run
|
||||||
```
|
```
|
||||||
Переименовать env
|
Rename env
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
from pydantic import BaseModel, EmailStr, Field
|
from pydantic import BaseModel, EmailStr, Field
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Base(BaseModel):
|
class Base(BaseModel):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user