docs(spec): sessions API, device fields, profile sessions UI. Refs EventHub/EventHubBack#74

This commit is contained in:
2026-08-16 18:30:33 +03:00
parent 4dd741ba48
commit 9b4499e884
2 changed files with 37 additions and 17 deletions
+25 -11
View File
@@ -260,7 +260,10 @@ Legacy: прямой `POST /v1/calendars/:id/specialists` **не использ
- `calendar_share` (`read` | `write` | `admin`): соредактор personal / заместитель commercial;
invite/accept/revoke + grants; personal **не** в search/discovery. Не путать с follow,
specialist_invite и платной subscription.
- `client_type=mobile` — хвост, когда появится нативный клиент.
- Управление своими сессиями + `client_type`/`device_name`**сделано** (Back#74):
`GET/DELETE /v1/sessions`, `POST /v1/sessions/revoke-others`; login принимает
`client_type` (`web`|`mobile`) и `device_name`, сохраняет `User-Agent`.
UI списка — Front#72. Kick-on-login / нативный клиент — по-прежнему future.
- **Не в фазе 2:** оплата услуги клиентом (B2C) — вне горизонта продукта.
### 2.1.3. Share / приглашения (соредактор и заместитель)
@@ -574,11 +577,18 @@ src/
`/invites?token=`, `/reset-password?token=` (бренд CalenTIQ).
- `POST /v1/reset-password``{token, password}` (пароль ≥ 8 символов); успех → новый hash,
токен удалён, refresh-сессии user отозваны; `404`/`410`/`400`/`403`.
- `POST /v1/login` — вход.
- `POST /v1/refresh` — обновление токена.
- `POST /v1/login` — вход; опционально `client_type` (`web`|`mobile`, default `web`),
`device_name` (max 120); `User-Agent` пишется в сессию. Ответ:
`{ token, refresh_token, session_id, user }`. Неверный `client_type``400`.
- `POST /v1/refresh` — обновление токена; ответ `{ token, refresh_token, session_id }`.
- `POST /v1/logout``{ refresh_token }` → отзыв текущей `auth_session`; после этого
refresh той же сессии → `401`. Access JWT до истечения TTL не отзывается.
Невалидный refresh → `401`; отсутствие поля → `400`. Клиент чистит storage даже при ошибке сети.
- `GET /v1/sessions` — список активных своих user-сессий (Bearer):
`session_id`, `client_type`, `device_name`, `user_agent`, `created_at`, `updated_at`, `expires_at`.
- `DELETE /v1/sessions/:id` — отзыв своей сессии; чужая/нет → `404`.
- `POST /v1/sessions/revoke-others` — Bearer + `{ refresh_token }`: оставить сессию
из refresh, отозвать остальные; чужой subject → `403`; невалидный refresh → `401`.
- `GET /v1/user/me` — профиль пользователя.
- `PATCH /v1/user/me` — частичное обновление своего профиля: `language` (`ru`|`en`),
`nickname`, `timezone`, `phone`, `avatar_url`, `preferences`; смена пароля —
@@ -721,43 +731,47 @@ src/
| `current_jti` | Актуальный jti refresh JWT |
| `expires_at` | Срок жизни сессии (30 дней) |
| `revoked` | Флаг отзыва |
| `device_name` | Человекочитаемая метка устройства (с login; может быть пустой) |
| `user_agent` | `User-Agent` с login (может быть пустой) |
| `created_at` / `updated_at` | Создание / последняя ротация или отзыв |
**Refresh JWT (admin)** — claims:
- `typ=refresh`, `aud=admin`, `sub=<admin_id>`
- `sid=<session_id>`, `fid=<family_id>`, `jti=<current_jti>`
- `client=admin`, `exp`, `iat`
**Refresh JWT (user, фаза 2)** — claims:
**Refresh JWT (user)** — claims:
- `typ=refresh`, `aud=user`, `sub=<user_id>`
- `sid`, `fid`, `jti` — как у admin
- `client=web` (фаза 3: `mobile`), `exp`, `iat`
- `client=web` \| `mobile` (из сессии; refresh не меняет device/UA)
- Подпись: `JWT_SECRET` (user JWK), отдельно от admin
**Поток login admin** (`POST /v1/admin/login`):
1. Проверка email/password.
2. Создание записи `auth_session`.
3. Ответ: `{ token, refresh_token, user }`.
3. Ответ: `{ token, refresh_token, session_id, user }`.
**Поток refresh admin** (`POST /v1/admin/refresh`):
1. Верификация подписи и срока refresh JWT.
2. Сверка `jti` из JWT с `current_jti` в Mnesia.
3. При совпадении — ротация: новый `jti`, новая пара токенов.
3. При совпадении — ротация: новый `jti`, новая пара токенов + `session_id`.
4. При несовпадении (reuse) — `revoke_family`, ответ 401.
**Поток login user** (`POST /v1/login`):
1. Проверка email/password.
2. Создание записи `auth_session` (`subject_type=user`, `client_type=web`).
3. Ответ: `{ token, refresh_token, user }`.
2. Создание `auth_session` (`subject_type=user`, `client_type`, `device_name`, `user_agent`).
3. Ответ: `{ token, refresh_token, session_id, user }`.
**Поток refresh user** (`POST /v1/refresh`):
1. Верификация refresh JWT (`aud=user`).
2. Сверка `jti` с `current_jti` в Mnesia, ротация при успехе.
2. Сверка `jti` с `current_jti` в Mnesia, ротация при успехе; `client` берётся из сессии.
3. Reuse — `revoke_family`, 401.
**Фазы внедрения:**
- Фаза 1 (#23): admin API — реализовано.
- Фаза 2 (#26): user login + `/v1/refresh` — реализовано.
- Фаза 3: client web + mobile (явный `client_type`, тот же контракт).
- Фаза 3 (Back#74): явный `client_type` web|mobile, device meta, list/revoke sessions — реализовано.
Kick-on-login / concurrent policy по `client_type` — не сделано.
### Legacy (не используется login/refresh user)
- Таблица `session` (`ram_copies`) и opaque refresh в `core_session` — оставлены в кодовой базе, hot path user API переведён на `auth_session`.