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>
This commit is contained in:
1 parent
36e99fe960
commit
9eb1c25147
30 files changed
+7538
No files matched your search
@@ -0,0 +1,88 @@
|
||||
# Аутентификация, роли, первый запуск
|
||||
|
||||
## Роли
|
||||
|
||||
| Роль | Назначение |
|
||||
|---|---|
|
||||
| `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`.
|
||||
Reference in new issue
Block a user