feat(rbac): role-based access control via X-Authenticated-User middleware (PR A — backend + Caddy) #585

Merged
Light1YT merged 1 commit from feat/rbac-pra-backend-caddy into main 2026-05-26 06:18:41 +00:00
Owner

Summary

Backend-side enforcement RBAC поверх Caddy basic_auth — defense-in-depth для 12-юзеров на gendsgn.ru. Решает запрос Лёхи на ограничение видимости админ-страниц по ролям + критичное требование «человек без ролей вообще ничего не видит».

Что включено:

  • auth/roles.yaml — SSOT для 2 ролей (admin, pilot) + 12 user→role mappings
  • app/core/auth.py (main + tradein-mvp mirror) — YAML loader + get_role/get_user_scope/is_path_allowed (fnmatch globs)
  • GET /api/v1/me (×2) — фронт читает scope для nav-фильтра в PR B
  • rbac_guard middleware (×2):
    • non-public path без X-Authenticated-User → 401
    • unknown user → 403 на ВСЁ (decided 2026-05-25: «человек без ролей ничего не видит»)
    • /api/v1/admin/* — только role=admin, иначе 403
  • Caddyfileheader_up X-Authenticated-User {http.auth.user.id} в 6 reverse_proxy блоках (gendsgn.ru main + trade-in + git.gendsgn.ru)
  • docker-compose.prod.yml (×2) — bind-mount auth/roles.yaml:/app/auth/roles.yaml:ro

Frontend nav-filter + 403 fullscreen — отдельный PR B (после merge этого).

Test plan

  • 20 unit + integration tests в обоих стэках (pytest)
  • ruff clean (E F I B UP N RUF ASYNC)
  • Caddyfile validate OK (caddy validate)
  • code-reviewer LGTM (4 nits, не блокирующие — follow-up)
  • Post-deploy: curl https://gendsgn.ru/api/v1/me без auth → 401; с unknown user → 403; с kopylov → 200+role=pilot; с admin → 200+role=admin
  • qa-tester smoke: pilot на /admin/* → 403, admin → 200; ничего не сломано на gendsgn.ru main app + tradein

Изменения в репо

  • auth/roles.yaml (new) — SSOT
  • backend/app/{core/auth.py,api/v1/me.py,main.py,tests/test_rbac.py} — RBAC ядро + 20 тестов
  • tradein-mvp/backend/app/{core/auth.py,api/v1/me.py,main.py,tests/test_rbac.py} — MIRROR
  • Caddyfile, docker-compose.prod.yml (×2), backend/pyproject.toml (+pyyaml)

Follow-ups (code-reviewer nits)

  • Eager-load _load_roles_config() в lifespan (fail-fast если YAML сломан)
  • Refactor: вынести _ADMIN_API_RE / _PUBLIC_PATHS в app.core.auth (сейчас 4 копии)
  • Caddy snippet caddy/auth-header.snippet для header_up (6 копий)
## Summary Backend-side enforcement RBAC поверх Caddy basic_auth — defense-in-depth для 12-юзеров на gendsgn.ru. Решает запрос Лёхи на ограничение видимости админ-страниц по ролям + критичное требование «человек без ролей вообще ничего не видит». **Что включено:** - `auth/roles.yaml` — SSOT для 2 ролей (admin, pilot) + 12 user→role mappings - `app/core/auth.py` (main + tradein-mvp mirror) — YAML loader + `get_role`/`get_user_scope`/`is_path_allowed` (fnmatch globs) - `GET /api/v1/me` (×2) — фронт читает scope для nav-фильтра в PR B - `rbac_guard` middleware (×2): * non-public path без `X-Authenticated-User` → 401 * unknown user → 403 на ВСЁ (decided 2026-05-25: «человек без ролей ничего не видит») * `/api/v1/admin/*` — только role=admin, иначе 403 - `Caddyfile` — `header_up X-Authenticated-User {http.auth.user.id}` в 6 `reverse_proxy` блоках (gendsgn.ru main + trade-in + git.gendsgn.ru) - `docker-compose.prod.yml` (×2) — bind-mount `auth/roles.yaml:/app/auth/roles.yaml:ro` **Frontend nav-filter + 403 fullscreen — отдельный PR B** (после merge этого). ## Test plan - [x] 20 unit + integration tests в обоих стэках (pytest) - [x] ruff clean (`E F I B UP N RUF ASYNC`) - [x] Caddyfile validate OK (`caddy validate`) - [x] code-reviewer LGTM (4 nits, не блокирующие — follow-up) - [ ] Post-deploy: `curl https://gendsgn.ru/api/v1/me` без auth → 401; с unknown user → 403; с `kopylov` → 200+role=pilot; с `admin` → 200+role=admin - [ ] qa-tester smoke: pilot на `/admin/*` → 403, admin → 200; ничего не сломано на gendsgn.ru main app + tradein ## Изменения в репо - `auth/roles.yaml` (new) — SSOT - `backend/app/{core/auth.py,api/v1/me.py,main.py,tests/test_rbac.py}` — RBAC ядро + 20 тестов - `tradein-mvp/backend/app/{core/auth.py,api/v1/me.py,main.py,tests/test_rbac.py}` — MIRROR - `Caddyfile`, `docker-compose.prod.yml` (×2), `backend/pyproject.toml` (+pyyaml) ## Follow-ups (code-reviewer nits) - Eager-load `_load_roles_config()` в lifespan (fail-fast если YAML сломан) - Refactor: вынести `_ADMIN_API_RE` / `_PUBLIC_PATHS` в `app.core.auth` (сейчас 4 копии) - Caddy snippet `caddy/auth-header.snippet` для `header_up` (6 копий)
Light1YT added 1 commit 2026-05-26 06:15:57 +00:00
Backend-side enforcement поверх Caddy basic_auth — defense-in-depth для
12-юзеров на gendsgn.ru (admin + kopylov + 10 пилотных слотов).

- `auth/roles.yaml` — single source of truth: 2 роли (admin, pilot)
  + 12 user→role mappings. Bind-mounted в обоих backend контейнерах.
- `app/core/auth.py` (main + tradein-mvp mirror) — YAML loader через
  `pyyaml`, `get_role`/`get_user_scope`/`is_path_allowed` с fnmatch globs.
  Custom `_glob_to_regex` чтобы `/**` ≠ `/*` (matches multi-segment paths).
- `GET /api/v1/me` (main + tradein) — фронт читает scope при login,
  фильтрует nav по `allowed_paths`/`deny_paths`.
- `rbac_guard` middleware (main + tradein):
  * любой non-public path требует `X-Authenticated-User` → иначе 401
  * юзер должен быть в roles.yaml → иначе 403 («человек без ролей
    вообще ничего не видит» — decided 2026-05-25)
  * /api/v1/admin/* — только role=admin, иначе 403
- `Caddyfile` — `header_up X-Authenticated-User {http.auth.user.id}` в
  6 reverse_proxy блоках (gendsgn.ru main + trade-in + git.gendsgn.ru).
- Tests: 20 unit + integration в обоих стэках. Покрытие:
  no-header / unknown user / pilot / admin × admin-path / non-admin path.
  `_build_test_app()` копирует middleware inline, чтобы обойти heavy
  imports (weasyprint dlopen падает на macOS dev).

Public paths (/health, /docs, /redoc, /openapi.json) пропускаются —
X-Authenticated-User там просто не приходит из Caddy.

Frontend nav-filter + 403 fullscreen — отдельный PR B.
Light1YT merged commit 88cdfd6adb into main 2026-05-26 06:18:41 +00:00
Light1YT deleted branch feat/rbac-pra-backend-caddy 2026-05-26 06:18:41 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#585
No description provided.