- 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>
7.2 KiB
7.2 KiB
Аутентификация, роли, первый запуск
Роли
| Роль | Назначение |
|---|---|
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)
- При старте
apiпроверяетapp_settings['bootstrap.completed']. Если нет и вusersнет ни одного админа — создаёт пользователя:username = BOOTSTRAP_ADMIN_USERNAME(по умолчаниюadmin);- пароль =
BOOTSTRAP_ADMIN_PASSWORDиз env, а если не задан — генерируется случайный (20 символов) и печатается в лог один раз; must_change_credentials = true.
GET /server-infoвозвращаетbootstrap_required: true— клиент может показать подсказку на экране входа.- После входа с
must_change_credentials = trueлюбые эндпоинты, кроме/me,/me/bootstrap-credentials,/auth/logout, отвечают403 { code: "auth.credentials_change_required" }. Клиент показывает экран «Смените логин и пароль администратора». 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.