SHiNE-server/Dev_Docs/API/03_Session_Management_API.md

486 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# API для разработчиков: Управление сессиями
Этот файл описывает методы, которые используются уже после успешной авторизации пользователя в сессию.
Здесь два метода:
- `ListSessions` — получить список активных сессий пользователя;
- `CloseActiveSession` — закрыть одну из активных сессий.
Дополнительно в этом же слое управления сессиями появился сценарий pairing через доверенную уже авторизованную сессию пользователя:
- `GetTrustedDeviceLoginSettings`
- `UpsertTrustedDeviceLoginSettings`
- `ListTrustedDeviceLoginRequests`
- `ApproveTrustedDeviceLogin`
- `RejectTrustedDeviceLogin`
- `CancelTrustedDeviceLogin`
Анонимное новое устройство работает с двумя связанными операциями:
- `StartTrustedDeviceLogin`
- `GetTrustedDeviceLoginStatus`
Логика раздела такая:
- сначала пользователь проходит `SessionLogin`;
- после этого сервер считает соединение авторизованным;
- уже в этом состоянии клиент может читать список сессий и управлять ими.
То есть это не этап создания или входа в сессию, а этап последующего контроля уже существующих активных сессий.
## 1. `ListSessions`
Доступно только после успешного `SessionLogin`.
### Запрос
```json
{
"op": "ListSessions",
"requestId": "list-001",
"payload": {
}
}
```
### Успешный ответ
```json
{
"op": "ListSessions",
"requestId": "list-001",
"status": 200,
"ok": true,
"payload": {
"sessions": [
{
"sessionId": "sess_7c5e5c4b",
"sessionType": 1,
"clientPlatform": "Web",
"onlineOnThisServer": true,
"clientInfoFromClient": "Android 15; Pixel 9",
"clientInfoFromRequest": "UA=Java-http-client/17.0.18; remote=127.0.0.1",
"geo": "RU/Moscow",
"lastAuthenticatedAtMs": 1774600010500
}
]
}
}
```
### Специфические коды ошибок `ListSessions`
- `422 / NOT_AUTHENTICATED` — запрос доступен только после успешного `SessionLogin`.
- `501 / DB_ERROR_LIST_SESSIONS` — ошибка БД при чтении списка активных сессий.
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
### Поля одной сессии в `ListSessions`
- `sessionId` — идентификатор активной сессии;
- `sessionType` — числовой код типа сессии:
- `1` — клиент;
- `50` — кошелёк;
- `100` — homeserver;
- `clientPlatform` — строка платформы, как её прислал клиент;
- `onlineOnThisServer``true`, если эта сессия сейчас держит живое WebSocket-подключение именно к данному серверу;
- `clientInfoFromClient` — краткая строка клиента;
- `clientInfoFromRequest` — строка, собранная сервером из запроса;
- `geo` — страна/город или fallback-строка;
- `lastAuthenticatedAtMs` — время последней успешной авторизации этой сессии.
---
## 2. `CloseActiveSession`
Доступно только после успешного `SessionLogin`.
### Запрос
```json
{
"op": "CloseActiveSession",
"requestId": "close-001",
"payload": {
"sessionId": "sess_7c5e5c4b"
}
}
```
### Успешный ответ
```json
{
"op": "CloseActiveSession",
"requestId": "close-001",
"status": 200,
"ok": true,
"payload": {
}
}
```
### Специфические коды ошибок `CloseActiveSession`
- `422 / NOT_AUTHENTICATED` — запрос доступен только после успешного `SessionLogin`.
- `400 / NO_SESSION_TO_CLOSE` — сервер не смог определить, какую сессию нужно закрыть.
- `501 / DB_ERROR` — ошибка БД при поиске сессии или её удалении.
- `422 / SESSION_NOT_FOUND` — целевая сессия не найдена.
- `422 / SESSION_OF_ANOTHER_USER` — нельзя закрывать сессию другого пользователя.
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
---
## 3. Пример ошибки
```json
{
"op": "CloseActiveSession",
"requestId": "close-001",
"status": 403,
"ok": false,
"error": "NOT_AUTHENTICATED",
"message": "Операция доступна только для авторизованных пользователей",
"payload": {
}
}
```
## 4. Формат `sessionId`
Текущее серверное значение `sessionId` генерируется как:
- случайные **32 байта** (`SecureRandom`),
- кодирование в **стандартный Base64 RFC 4648** (алфавит `A-Z a-z 0-9 + /`),
- **без padding** `=`.
Практически это строка длиной около **43 символов** (для 32 байт без `=`).
Пример реального формата:
```
K9v3nQ4u8jYk0a2p7cD4mLx1zR0sT5wV6bN8eH3fQ1M
```
Важно: это **не человеко-читаемое имя**, а непрозрачный идентификатор.
Нужно передавать его как есть, без нормализации регистра и без URL-экранирования внутри JSON.
---
## 5. TrustedDeviceLogin через доверенную сессию
Этот блок относится к сценарию добавления новой сессии через доверенное устройство пользователя.
### 5.1. `GetTrustedDeviceLoginSettings`
Доступно для любой уже авторизованной доверенной сессии пользователя.
### Запрос
```json
{
"op": "GetTrustedDeviceLoginSettings",
"requestId": "trusted-login-get-001",
"payload": {
}
}
```
### Успешный ответ
```json
{
"op": "GetTrustedDeviceLoginSettings",
"requestId": "trusted-login-get-001",
"status": 200,
"ok": true,
"payload": {
"enabled": true,
"hasPassword": false
}
}
```
Если отдельной записи настроек на сервере ещё нет, сервер считает состояние по умолчанию таким:
- `enabled = true`
- `hasPassword = false`
### Ошибки
- `463 / PAIRING_REQUIRES_AUTH_SESSION` — операция вызвана без уже авторизованной доверенной сессии пользователя.
### 5.2. `UpsertTrustedDeviceLoginSettings`
Доступно для любой уже авторизованной доверенной сессии пользователя.
### Запрос
```json
{
"op": "UpsertTrustedDeviceLoginSettings",
"requestId": "esp-set-001",
"payload": {
"enabled": true,
"passwordHash": "sha256$0123abcd..."
}
}
```
Если вход через доверенное устройство должен работать **без доп. пароля**, клиент включает его с пустым `passwordHash`.
Если `enabled = false`, сервер автоматически удаляет пароль и запрещает вход через другое устройство.
Формат непустого `passwordHash`:
```text
sha256$<hex( SHA-256("shine-pairing|" + lower(login.trim()) + "|" + password) )>
```
### Успешный ответ
```json
{
"op": "UpsertTrustedDeviceLoginSettings",
"requestId": "esp-set-001",
"status": 200,
"ok": true,
"payload": {
"enabled": true,
"hasPassword": true
}
}
```
### Ошибки
- `463 / PAIRING_REQUIRES_AUTH_SESSION` — операция вызвана без уже авторизованной доверенной сессии пользователя.
### 5.3. `StartTrustedDeviceLogin`
Эта операция доступна без уже существующей пользовательской сессии.
### Запрос
```json
{
"op": "StartTrustedDeviceLogin",
"requestId": "esp-start-001",
"payload": {
"login": "alice",
"passwordHash": "sha256$0123abcd...",
"requesterSessionKey": "ed25519/BASE64_PUBLIC_KEY",
"requesterSessionType": 1,
"requesterClientPlatform": "Android",
"payloadType": 1
}
}
```
Если на доверённом устройстве вход включён **без доп. пароля**, новое устройство может отправить пустой `passwordHash`.
Поле `trustedSessionOnline` показывает, что у пользователя сейчас есть хотя бы одна онлайн доверенная сессия, способная принять pairing-заявку.
Поле `shortCode` теперь содержит `10` цифр. В UI его рекомендуется показывать как `5` пар, например: `49 20 70 91 23`.
TTL заявки фиксирован на сервере и сейчас всегда равен `300` секундам.
### Успешный ответ
```json
{
"op": "StartTrustedDeviceLogin",
"requestId": "esp-start-001",
"status": 200,
"ok": true,
"payload": {
"pairingId": "base64url",
"state": "created",
"shortCode": "4920709123",
"fingerprintB58": "ASvYDPQidnAroKzQjtCjTuEQE8ckktV5nmmhYRhDzGaA",
"expiresAtMs": 1781441990538,
"trustedSessionOnline": true
}
}
```
### Ошибки
- `400 / EMPTY_LOGIN`
- `400 / EMPTY_REQUESTER_SESSION_KEY`
- `400 / BAD_REQUESTER_SESSION_KEY`
- `400 / BAD_SESSION_TYPE`
- `400 / BAD_PAYLOAD_TYPE`
- `422 / PAIRING_NOT_AVAILABLE`
- `422 / PAIRING_PASSWORD_INVALID` — pairing-пароль не подходит. Та же ошибка возвращается и если новое устройство ввело пароль, а у пользователя режим pairing включён без пароля.
- `422 / PAIRING_NO_TRUSTED_SESSION_ONLINE` — сейчас нет ни одной онлайн доверённой сессии пользователя, поэтому код не создаётся.
- `429 / PAIRING_RATE_LIMITED`
### 5.4. `ListTrustedDeviceLoginRequests`
Доступно для любой уже авторизованной доверенной сессии пользователя.
Возвращает только реально активные pending-заявки со `state = created`. Уже `approved` и `rejected` заявки в этот список больше не попадают.
### Успешный ответ
```json
{
"op": "ListTrustedDeviceLoginRequests",
"requestId": "esp-list-001",
"status": 200,
"ok": true,
"payload": {
"requests": [
{
"pairingId": "base64url",
"state": "created",
"requesterSessionKey": "ed25519/BASE64_PUBLIC_KEY",
"requesterSessionType": 1,
"requesterClientPlatform": "Android",
"payloadType": 1,
"shortCode": "4920709123",
"fingerprintB58": "ASvYDPQidnAroKzQjtCjTuEQE8ckktV5nmmhYRhDzGaA",
"createdAtMs": 1781441810538,
"expiresAtMs": 1781441990538,
"deliveredToHomeserver": true
}
]
}
}
```
### Ошибки
- `463 / PAIRING_REQUIRES_AUTH_SESSION`
### 5.5. `ApproveTrustedDeviceLogin`
Доступно для любой уже авторизованной доверенной сессии пользователя.
### Запрос
```json
{
"op": "ApproveTrustedDeviceLogin",
"requestId": "esp-approve-001",
"payload": {
"pairingId": "base64url",
"encryptedPayload": "BASE64_OR_OTHER_OPAQUE_PAYLOAD"
}
}
```
### Успешный ответ
```json
{
"op": "ApproveTrustedDeviceLogin",
"requestId": "esp-approve-001",
"status": 200,
"ok": true,
"payload": {
"pairingId": "base64url",
"state": "approved"
}
}
```
### Ошибки
- `400 / EMPTY_PAIRING_ID`
- `400 / EMPTY_ENCRYPTED_PAYLOAD`
- `404 / PAIRING_NOT_FOUND`
- `422 / PAIRING_OF_ANOTHER_USER`
- `422 / PAIRING_NOT_PENDING`
- `422 / PAIRING_EXPIRED`
- `463 / PAIRING_REQUIRES_AUTH_SESSION`
### 5.6. `RejectTrustedDeviceLogin`
Доступно для любой уже авторизованной доверенной сессии пользователя. Похоже на approve, но переводит заявку в `state=rejected`.
### 5.7. `GetTrustedDeviceLoginStatus`
Операция для нового устройства.
### Запрос
```json
{
"op": "GetTrustedDeviceLoginStatus",
"requestId": "esp-status-001",
"payload": {
"pairingId": "base64url"
}
}
```
### Успешный ответ после approve
```json
{
"op": "GetTrustedDeviceLoginStatus",
"requestId": "esp-status-001",
"status": 200,
"ok": true,
"payload": {
"pairingId": "base64url",
"state": "approved",
"shortCode": "4920709123",
"fingerprintB58": "ASvYDPQidnAroKzQjtCjTuEQE8ckktV5nmmhYRhDzGaA",
"payloadType": 1,
"encryptedPayload": "AQIDBA==",
"expiresAtMs": 1781441990538
}
}
```
### Возможные `state`
- `created`
- `approved`
- `rejected`
- `canceled`
- `expired`
### 5.8. `CancelTrustedDeviceLogin`
Операция для нового устройства, которое уже создало pairing-заявку и хочет принудительно снять ожидание до истечения TTL.
### Запрос
```json
{
"op": "CancelTrustedDeviceLogin",
"requestId": "esp-cancel-001",
"payload": {
"pairingId": "base64url",
"requesterSessionKey": "ed25519/BASE64_PUBLIC_KEY"
}
}
```
### Успешный ответ
```json
{
"op": "CancelTrustedDeviceLogin",
"requestId": "esp-cancel-001",
"status": 200,
"ok": true,
"payload": {
"pairingId": "base64url",
"state": "canceled"
}
}
```
### Ошибки
- `400 / EMPTY_PAIRING_ID`
- `400 / EMPTY_REQUESTER_SESSION_KEY`
- `400 / BAD_REQUESTER_SESSION_KEY`
- `404 / PAIRING_NOT_FOUND`
- `422 / PAIRING_OF_ANOTHER_REQUESTER`
- `422 / PAIRING_NOT_PENDING`