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

14 KiB
Raw Blame History

API для разработчиков: Управление сессиями

Этот файл описывает методы, которые используются уже после успешной авторизации пользователя в сессию.

Здесь два метода:

  • ListSessions — получить список активных сессий пользователя;
  • CloseActiveSession — закрыть одну из активных сессий.

Дополнительно в этом же слое управления сессиями появился сценарий pairing через доверенную уже авторизованную сессию пользователя:

  • GetTrustedDeviceLoginSettings
  • UpsertTrustedDeviceLoginSettings
  • ListTrustedDeviceLoginRequests
  • ApproveTrustedDeviceLogin
  • RejectTrustedDeviceLogin
  • CancelTrustedDeviceLogin

Анонимное новое устройство работает с двумя связанными операциями:

  • StartTrustedDeviceLogin
  • GetTrustedDeviceLoginStatus

Логика раздела такая:

  • сначала пользователь проходит SessionLogin;
  • после этого сервер считает соединение авторизованным;
  • уже в этом состоянии клиент может читать список сессий и управлять ими.

То есть это не этап создания или входа в сессию, а этап последующего контроля уже существующих активных сессий.

1. ListSessions

Доступно только после успешного SessionLogin.

Запрос

{
  "op": "ListSessions",
  "requestId": "list-001",
  "payload": {
  }
}

Успешный ответ

{
  "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 — строка платформы, как её прислал клиент;
  • onlineOnThisServertrue, если эта сессия сейчас держит живое WebSocket-подключение именно к данному серверу;
  • clientInfoFromClient — краткая строка клиента;
  • clientInfoFromRequest — строка, собранная сервером из запроса;
  • geo — страна/город или fallback-строка;
  • lastAuthenticatedAtMs — время последней успешной авторизации этой сессии.

2. CloseActiveSession

Доступно только после успешного SessionLogin.

Запрос

{
  "op": "CloseActiveSession",
  "requestId": "close-001",
  "payload": {
    "sessionId": "sess_7c5e5c4b"
  }
}

Успешный ответ

{
  "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. Пример ошибки

{
  "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

Доступно для любой уже авторизованной доверенной сессии пользователя.

Запрос

{
  "op": "GetTrustedDeviceLoginSettings",
  "requestId": "trusted-login-get-001",
  "payload": {
  }
}

Успешный ответ

{
  "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

Доступно для любой уже авторизованной доверенной сессии пользователя.

Запрос

{
  "op": "UpsertTrustedDeviceLoginSettings",
  "requestId": "esp-set-001",
  "payload": {
    "enabled": true,
    "passwordHash": "sha256$0123abcd..."
  }
}

Если вход через доверенное устройство должен работать без доп. пароля, клиент включает его с пустым passwordHash.

Если enabled = false, сервер автоматически удаляет пароль и запрещает вход через другое устройство.

Формат непустого passwordHash:

sha256$<hex( SHA-256("shine-pairing|" + lower(login.trim()) + "|" + password) )>

Успешный ответ

{
  "op": "UpsertTrustedDeviceLoginSettings",
  "requestId": "esp-set-001",
  "status": 200,
  "ok": true,
  "payload": {
    "enabled": true,
    "hasPassword": true
  }
}

Ошибки

  • 463 / PAIRING_REQUIRES_AUTH_SESSION — операция вызвана без уже авторизованной доверенной сессии пользователя.

5.3. StartTrustedDeviceLogin

Эта операция доступна без уже существующей пользовательской сессии.

Запрос

{
  "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 секундам.

Успешный ответ

{
  "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 заявки в этот список больше не попадают.

Успешный ответ

{
  "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

Доступно для любой уже авторизованной доверенной сессии пользователя.

Запрос

{
  "op": "ApproveTrustedDeviceLogin",
  "requestId": "esp-approve-001",
  "payload": {
    "pairingId": "base64url",
    "encryptedPayload": "BASE64_OR_OTHER_OPAQUE_PAYLOAD"
  }
}

Успешный ответ

{
  "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

Операция для нового устройства.

Запрос

{
  "op": "GetTrustedDeviceLoginStatus",
  "requestId": "esp-status-001",
  "payload": {
    "pairingId": "base64url"
  }
}

Успешный ответ после approve

{
  "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.

Запрос

{
  "op": "CancelTrustedDeviceLogin",
  "requestId": "esp-cancel-001",
  "payload": {
    "pairingId": "base64url",
    "requesterSessionKey": "ed25519/BASE64_PUBLIC_KEY"
  }
}

Успешный ответ

{
  "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