gendesign/auth/roles.yaml
Light1YT 3651c85f9b feat(rbac): enforce role-based access via X-Authenticated-User middleware
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.
2026-05-26 11:14:47 +05:00

53 lines
1.4 KiB
YAML

# RBAC roles + user → role mapping.
#
# Single source of truth for both main backend (backend/app/core/auth.py) and
# tradein backend (tradein-mvp/backend/app/core/auth.py). Mounted into both
# containers at /app/auth/roles.yaml via docker-compose bind-mount.
#
# Glob semantics (fnmatch-based, see app.core.auth.is_path_allowed):
# - "/**" → matches any path (admin scope).
# - "/foo/**" → matches /foo, /foo/, /foo/bar, /foo/bar/baz/...
# - "/foo" → matches exactly /foo.
#
# A path is allowed iff (1) it matches one of `paths` AND (2) it does NOT match
# any pattern in `deny`. `deny` overrides `paths` (deny-by-default within
# match scope).
#
# Username list MUST match exactly the 12 entries in caddy/users.caddy.snippet
# — Caddy basic_auth sets X-Authenticated-User from {http.auth.user.id}, and
# unknown users hit /me with 403 ("user not in roles config").
#
# Last updated: 2026-05-26 (PR A — RBAC).
roles:
admin:
paths:
- "/**"
deny: []
pilot:
paths:
- "/"
- "/analytics/**"
- "/site-finder/**"
- "/trade-in/**"
- "/concept/**"
- "/api/v1/**"
- "/trade-in/api/v1/**"
deny:
- "/admin/**"
- "/api/v1/admin/**"
- "/trade-in/api/v1/admin/**"
users:
admin: admin
kopylov: pilot
user1: pilot
user2: pilot
user3: pilot
user4: pilot
user5: pilot
user6: pilot
user7: pilot
user8: pilot
user9: pilot
user10: pilot