Files
pipu-music/docs/auth-and-roles.md
T
mrleo1nidandClaude Opus 5.5 9eb1c25147 docs: add product idea, design prototypes and development documentation
- IDEA.md: product vision from the user's perspective (input for design)
- Architecture, ADRs, data model, REST API overview
- Auth and roles, library storage (NFS, path regexes, scanner, search),
  playback, recommendation system ("My Wave"), track upload queue
- Web, Android (offline, self-update), i18n, design system
- Testing strategy, Gitea Actions CI/CD, deployment, conventions, roadmap
- Clickable web/Android design prototypes and wave button variants
- .claude/launch.json to serve the prototypes locally

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 23:21:31 +03:00

7.2 KiB
Raw Permalink Blame History

Аутентификация, роли, первый запуск

Роли

Роль Назначение
admin Владелец системы. Может быть несколько, но минимум один всегда (последнего админа нельзя понизить / удалить / заблокировать).
moderator Заместитель администратора: управление обычными пользователями.
user Слушатель.

Матрица прав

Действие user moderator admin
Слушать, поиск, коллекция, волна ✅ ✅ ✅
Сменить свой пароль, язык, сессии ✅ ✅ ✅
Открыть админку — ✅ ✅
Видеть список пользователей — ✅ ✅
Создать пользователя с ролью user — ✅ ✅
Сбросить пароль / заблокировать user — ✅ ✅
Создать moderator / admin, менять роли — — ✅
Сбросить пароль / заблокировать moderator — — ✅
Удалить пользователя — — ✅
Хранилища, сканирование, переиндексация — — ✅
Статус библиотеки (только просмотр) — ✅ ✅
Предлагать треки (загрузка в очередь), видеть свои предложения — ✅ ✅
Рассматривать очередь, одобрять / отклонять, править метаданные — — ✅
Лимит очереди, целевое хранилище, шаблон раскладки — — ✅
Статус рекомендательной системы, аудит — — ✅

Реализация: в Go — политики в internal/auth/policy.go вида CanManageUser(actor, target, action) bool, проверяются в сервисном слое (не только в middleware), покрываются табличными тестами по этой матрице. Все админские действия пишутся в audit_log.

Первый запуск (bootstrap)

  1. При старте api проверяет app_settings['bootstrap.completed']. Если нет и в users нет ни одного админа — создаёт пользователя:
    • username = BOOTSTRAP_ADMIN_USERNAME (по умолчанию admin);
    • пароль = BOOTSTRAP_ADMIN_PASSWORD из env, а если не задан — генерируется случайный (20 символов) и печатается в лог один раз;
    • must_change_credentials = true.
  2. GET /server-info возвращает bootstrap_required: true — клиент может показать подсказку на экране входа.
  3. После входа с must_change_credentials = true любые эндпоинты, кроме /me, /me/bootstrap-credentials, /auth/logout, отвечают 403 { code: "auth.credentials_change_required" }. Клиент показывает экран «Смените логин и пароль администратора».
  4. POST /me/bootstrap-credentials — новый логин (может остаться admin, но пароль обязан отличаться), новый пароль (≥ 12 символов для админа) → флаг снимается, bootstrap.completed = true, все прочие токены отзываются, выдаётся новая пара.

Аналогично для must_change_password (после сброса пароля админом / модератором): разрешён только POST /me/password (без current_password, если вход был по временному паролю).

Пароли

  • argon2id: m=64 MiB, t=3, p=2, соль 16 байт; параметры в хэше (PHC), при изменении — rehash при входе.
  • Политика: ≥ 8 символов для user, ≥ 12 для moderator/admin; не совпадает с логином; проверка по встроенному списку 10k самых частых паролей.
  • Временный пароль при создании пользователя и при сбросе генерирует сервер: 12 символов, показывается один раз; у пользователя ставится must_change_password = true.
  • Защита от перебора: после 5 неудачных попыток — locked_until = now() + 5 мин (растёт экспоненциально до 1 ч), плюс rate limit по IP в Redis. Ответ всегда одинаковый (auth.invalid_credentials) — не раскрываем, существует ли логин. Время ответа выравнивается (хэш с фиктивным паролем для несуществующих).

Токены

Access Refresh
Формат JWT, EdDSA (Ed25519) 32 случайных байта, base64url; в БД — SHA-256
Срок 15 мин 30 дней скользящий (каждая ротация продлевает), абсолютный максимум 180 дней
Claims sub, role, sid (family_id), ver (token_version пользователя), exp, iat —
Веб в памяти (JS) cookie pipu_rt: HttpOnly; Secure; SameSite=Strict; Path=/api/v1/auth
Android в памяти expo-secure-store
  • Ротация: каждый /auth/refresh помечает старый токен used_at и выдаёт новый в той же family_id. Повторное предъявление уже использованного токена ⇒ компрометация ⇒ отзыв всей семьи.
  • Мгновенный отзыв (блокировка, смена роли, смена пароля): у пользователя инкрементируется token_version (в Redis-кэше), middleware сравнивает с ver в access-токене.
  • Ключи подписи: JWT_PRIVATE_KEY из env / файла; поддержка kid для ротации ключей.
  • CSRF: refresh-cookie SameSite=Strict и путь только /api/v1/auth; остальные запросы — Bearer-заголовок, поэтому CSRF к ним неприменим.

Клиентская логика

  • Один общий модуль в packages/core/auth: хранит access в памяти, при 401 auth.token_expired делает единственный параллельный refresh (single-flight) и повторяет запросы.
  • Роутинг-гарды: must_change_credentials → /setup/admin; must_change_password → /setup/password; админские разделы — по role.