Доделал API функции для авторификации и работы с сессиями сервер и документ для разработчиков по

Авторификациии и серверам

Всё работает
This commit is contained in:
AidarKC
2026-03-27 22:06:19 +03:00
parent 51de9779e3
commit 1aabcf4d80
23 changed files with 1117 additions and 574 deletions
+10 -4
View File
@@ -4,11 +4,17 @@
## Список документов
0. **API/01_Auth_and_Sessions_API.md**
API-глава для разработчиков: транспортный JSON-конверт, форматы запросов/ответов, создание и вход в сессию, `session_key`, `storagePwd`, подписи и совместимость версий.
0. **API/00_Common_API_Format.md**
Общий формат JSON-запросов и JSON-ответов по всему API: `op`, `requestId`, `status`, `ok`, `payload`, единые правила успеха и ошибок.
0. **API/02_User_Registration_API.md**
Временная глава API по регистрации пользователя: текущая заглушка `AddUser`, ограничения схемы и пометка о будущем переходе на полноценную регистрацию через Solana.
0. **API/01_User_Registration_API.md**
Временная глава API по регистрации пользователя: `AddUser` и временный `GetUser`, с пометкой о будущем переходе проверки identity напрямую через Solana.
0. **API/02_Authentication_API.md**
Глава API по авторизации: `AuthChallenge`, `CreateAuthSession`, `SessionChallenge`, `SessionLogin`, подписи, `deviceKey`, `sessionKey`.
0. **API/03_Session_Management_API.md**
Глава API по управлению сессиями: `ListSessions` и `CloseActiveSession`.
1. **01_Connection_and_Sessions.md**
Процесс подключения к WebSocket, авторизация (двухшаговая), создание сессии, вход в существующую сессию, просмотр и закрытие сессий.
+130
View File
@@ -0,0 +1,130 @@
# API для разработчиков: Общий формат запросов и ответов
Этот файл описывает не конкретные операции, а общий wire-контракт всего API сервера.
Здесь зафиксировано:
- как выглядит любой запрос;
- как выглядит любой успешный ответ;
- как выглядит любой ответ с ошибкой;
- какие поля являются обязательными для всех операций;
- как клиент должен интерпретировать `status`, `ok` и `payload`.
Логика простая: сначала клиент и сервер договариваются о едином формате конверта, и только потом в остальных документах уже описываются конкретные методы и их поля.
## 1. Общий формат запроса
Все запросы по WebSocket используют один и тот же JSON-конверт:
```json
{
"op": "OperationName",
"requestId": "req-001",
"payload": {
}
}
```
### Поля
- `op` — имя операции.
- `requestId` — клиентский идентификатор запроса.
- `payload` — объект параметров операции.
---
## 2. Общий формат успешного ответа
```json
{
"op": "OperationName",
"requestId": "req-001",
"status": 200,
"ok": true,
"payload": {
}
}
```
---
## 3. Общий формат ответа с ошибкой
```json
{
"op": "OperationName",
"requestId": "req-001",
"status": 400,
"ok": false,
"error": "BAD_REQUEST",
"message": "Human readable description",
"payload": {
}
}
```
---
## 4. Обязательные правила
- Сервер возвращает `op` в каждом ответе.
- Сервер возвращает `requestId` в каждом ответе без изменений.
- Сервер возвращает `status` в каждом ответе.
- Сервер возвращает `ok` в каждом ответе.
- Сервер всегда возвращает `payload` как объект.
- Даже при отсутствии данных сервер возвращает `payload: {}`.
- `ok` находится на верхнем уровне ответа, а не внутри `payload`.
---
## 5. Правило интерпретации
Источник истины — `status`.
- если `status` в диапазоне `200..299`, то ответ успешный и `ok` должен быть `true`;
- если `status` вне диапазона `200..299`, то ответ ошибочный и `ok` должен быть `false`.
Запрещённые состояния:
- `status = 200` и `ok = false`;
- `status = 400` и `ok = true`.
---
## 6. Общие правила формата
- Все строки подписи и challenge собираются в UTF-8.
- Временные метки передаются как Unix time в миллисекундах.
- Бинарные поля передаются строками Base64.
- При ошибке `error` — это машинный код причины.
- При ошибке `message` — человекочитаемое описание причины.
---
## 7. Общие коды ошибок
Ниже перечислены коды ошибок, которые не привязаны к одной конкретной операции и могут встречаться в разных местах API.
- `400 / EMPTY_JSON` — клиент отправил пустое или полностью отсутствующее JSON-сообщение.
- `400 / NO_OP` — в корневом объекте не передано поле `op`.
- `400 / UNKNOWN_OP` — сервер не знает такую операцию.
- `400 / NO_PAYLOAD` — в корневом объекте отсутствует `payload`.
- `400 / BAD_PAYLOAD``payload` передан, но это не JSON-объект.
- `400 / BAD_REQUEST_FORMAT` — JSON-конверт формально валиден, но поля операции не удалось распарсить в ожидаемый формат.
- `500 / INTERNAL_HANDLER_ERROR` — в handler конкретной операции случилась непредвиденная серверная ошибка.
- `500 / INTERNAL_ERROR` — произошла внутренняя ошибка на уровне общего JSON-процессора или другого серверного слоя.
Общее правило для dev/test этапа:
- `message` в таких ошибках должен быть коротким, но полезным;
- по возможности сервер добавляет тип исключения и краткую деталь причины;
- это сделано для упрощения интеграционных тестов и отладки;
- позже для production этот уровень детализации может быть уменьшен.
---
## 8. Короткое резюме
- Запросы всегда идут как `op + requestId + payload`.
- Ответы всегда идут как `op + requestId + status + ok + payload`.
- Ошибки всегда возвращают `ok: false`, `error`, `message`, `payload: {}`.
-507
View File
@@ -1,507 +0,0 @@
# API для разработчиков: Авторизация и сессии
## Статус документа
Это **первая глава API-спецификации для клиентов**.
Документ фиксирует:
- единый JSON-формат запросов и ответов по WebSocket;
- роли `device key`, `session_key` и `storagePwd`;
- целевой формат подписываемых строк для авторизации;
- совместимость между текущей реализацией сервера и предлагаемым расширением.
---
## 1. Транспортный конверт
Все клиентские вызовы идут через WebSocket в общем JSON-конверте:
```json
{
"op": "OperationName",
"requestId": "req-001",
"payload": {
}
}
```
### Поля запроса
- `op` — имя операции.
- `requestId` — уникальный идентификатор запроса на стороне клиента.
- `payload` — объект с параметрами операции.
### Базовый формат ответа
Успешный ответ:
```json
{
"requestId": "req-001",
"status": 200,
"payload": {
}
}
```
Ответ с ошибкой:
```json
{
"requestId": "req-001",
"status": 400,
"error": "BAD_REQUEST",
"message": "Human readable description"
}
```
### Общие правила
- Все строки подписи и challenge собираются в UTF-8.
- Временные метки передаются в `timeMs` как Unix time в миллисекундах.
- Бинарные поля передаются как Base64-строки.
- `requestId` должен возвращаться сервером без изменений.
---
## 2. Роли ключей и секретов
### `device key`
Постоянный ключ устройства или аккаунта, которым клиент подтверждает право создать новую сессию.
Используется для:
- `CreateAuthSession`
### `session_key`
Клиент **сам создаёт** отдельный ключ сессии и передаёт на сервер только публичную часть.
Этот ключ используется для:
- `SessionLogin`
- последующих перевходов в уже созданную сессию
В API клиент передаёт `sessionKey` целиком одной строкой, и сервер хранит `active_sessions.session_key` тоже целиком одной строкой.
### `storagePwd`
`storagePwd` тоже **генерируется и передаётся клиентом** при создании сессии.
Сервер:
- сохраняет это значение в составе активной сессии;
- возвращает его клиенту после успешного `SessionLogin`.
Это нужно, чтобы клиент мог восстановить доступ к локально/серверно зашифрованному хранилищу сессии.
---
## 3. Формат `session_key` с префиксом алгоритма
Чтобы поддерживать разные аппаратные и программные типы ключей, `session_key` рекомендуется хранить и передавать не как "просто base64", а как строку с явным префиксом алгоритма:
```text
<algorithm>/<public-key-data>
```
Примеры:
```text
ed25519/MCowBQYDK2VwAyEA2I7...
secp256r1/BBD9LVa8gk9...
rsa2048/MIIBIjANBgkqh...
```
### Зачем это нужно
- у разных устройств разный набор аппаратно поддерживаемых ключей;
- серверу проще понимать, какой верификатор использовать;
- формат можно расширять без миграции всей таблицы сессий.
### Рекомендация по полю API
Во внешнем API лучше использовать поле:
```json
{
"sessionKey": "ed25519/BASE64_PUBLIC_KEY"
}
```
Если сервер внутри пока хранит старое поле `sessionPubKeyB64`, допускается переходный слой, который:
- принимает `sessionKey`;
- разбирает префикс алгоритма;
- сохраняет алгоритм и публичный ключ раздельно либо в одном поле.
---
## 4. Поток авторизации
Поддерживаются два базовых сценария:
1. Создание новой сессии.
2. Вход в существующую сессию.
---
## 5. Создание новой сессии
### Шаг 1. `AuthChallenge`
Клиент запрашивает nonce для логина.
Запрос:
```json
{
"op": "AuthChallenge",
"requestId": "auth-001",
"payload": {
"login": "alice"
}
}
```
Успешный ответ:
```json
{
"requestId": "auth-001",
"status": 200,
"payload": {
"login": "alice",
"authNonce": "8f2f0f71-0b1c-4ab2-8f5d-0bc5d6f6aa11",
"expiresInMs": 30000
}
}
```
Назначение:
- сервер убеждается, что пользователь существует;
- сервер связывает `authNonce` с текущим WebSocket-соединением;
- nonce одноразовый и живёт ограниченное время.
### Шаг 2. `CreateAuthSession`
Клиент:
- генерирует новый `session_key`;
- генерирует или выбирает `storagePwd`;
- подписывает строку создания сессии своим `device key`.
#### Целевой формат запроса
```json
{
"op": "CreateAuthSession",
"requestId": "create-001",
"payload": {
"login": "alice",
"sessionKey": "ed25519/BASE64_PUBLIC_KEY",
"storagePwd": "BASE64_OR_APP_SPECIFIC_SECRET",
"timeMs": 1774600000123,
"authNonce": "8f2f0f71-0b1c-4ab2-8f5d-0bc5d6f6aa11",
"deviceKey": "BASE64_DEVICE_PUBLIC_KEY",
"signatureB64": "BASE64_SIGNATURE_BY_DEVICE_KEY",
"clientInfo": "Android 15; Pixel 9"
}
}
```
#### Целевая строка для подписи
Рекомендуемый формат:
```text
AUTH_CREATE_SESSION:{login}:{sessionKey}:{storagePwd}:{timeMs}:{authNonce}
```
Пример:
```text
AUTH_CREATE_SESSION:alice:ed25519/BASE64_PUBLIC_KEY:BASE64_OR_APP_SPECIFIC_SECRET:1774600000123:8f2f0f71-0b1c-4ab2-8f5d-0bc5d6f6aa11
```
### Почему `sessionKey` и `storagePwd` нужно включить в подпись
- сервер получает криптографическое подтверждение того, какие именно значения утвердил клиент;
- снижается риск подмены `session_key` между клиентом и сервером;
- `storagePwd` становится частью подтверждённого набора параметров создания сессии.
### Дополнительная проверка `deviceKey`
Перед проверкой подписи сервер должен:
1. загрузить актуальный `device_key` пользователя;
2. сравнить его со значением `payload.deviceKey`;
3. только после совпадения ключей проверять подпись.
Если ключ не совпадает, сервер должен возвращать ошибку о том, что ключ не соответствует актуальной версии.
На будущее:
- для сценария обновления `device_key` желательно добавить дополнительную проверку актуального ключа через Solana;
- если и после этого ключ не подтверждается, сервер всё равно должен возвращать ошибку о несовпадении актуального ключа.
### Успешный ответ
```json
{
"requestId": "create-001",
"status": 200,
"payload": {
"sessionId": "sess_7c5e5c4b",
"sessionKey": "ed25519/BASE64_PUBLIC_KEY",
"createdAtMs": 1774600000201
}
}
```
---
## 6. Вход в существующую сессию
### Шаг 1. `SessionChallenge`
Запрос:
```json
{
"op": "SessionChallenge",
"requestId": "sch-001",
"payload": {
"sessionId": "sess_7c5e5c4b"
}
}
```
Успешный ответ:
```json
{
"requestId": "sch-001",
"status": 200,
"payload": {
"sessionId": "sess_7c5e5c4b",
"nonce": "0e5bb0f4-c7d8-4efb-b44d-bf31a6126c66",
"expiresInMs": 30000,
"sessionKeyAlgorithm": "ed25519"
}
}
```
### Шаг 2. `SessionLogin`
Клиент подписывает challenge приватной частью соответствующего `session_key`.
Запрос:
```json
{
"op": "SessionLogin",
"requestId": "slogin-001",
"payload": {
"sessionId": "sess_7c5e5c4b",
"sessionKey": "ed25519/BASE64_PUBLIC_KEY",
"timeMs": 1774600010456,
"signatureB64": "BASE64_SIGNATURE_BY_SESSION_KEY",
"clientInfo": "Android 15; Pixel 9"
}
}
```
Строка для подписи:
```text
SESSION_LOGIN:{sessionId}:{timeMs}:{nonce}
```
Пример:
```text
SESSION_LOGIN:sess_7c5e5c4b:1774600010456:0e5bb0f4-c7d8-4efb-b44d-bf31a6126c66
```
### Дополнительная проверка `sessionKey`
Перед проверкой подписи сервер должен:
1. загрузить `active_sessions.session_key` по `sessionId`;
2. сравнить его со значением `payload.sessionKey`;
3. только после совпадения ключей проверять подпись.
Если ключ не совпадает, сервер должен возвращать ошибку о том, что ключ не соответствует актуальной версии.
Успешный ответ:
```json
{
"requestId": "slogin-001",
"status": 200,
"payload": {
"sessionId": "sess_7c5e5c4b",
"storagePwd": "BASE64_OR_APP_SPECIFIC_SECRET",
"authenticatedAtMs": 1774600010500
}
}
```
---
## 7. Работа со списком сессий
### `ListSessions`
Доступно только после успешного `SessionLogin`.
Запрос:
```json
{
"op": "ListSessions",
"requestId": "list-001",
"payload": {
}
}
```
Успешный ответ:
```json
{
"requestId": "list-001",
"status": 200,
"payload": {
"sessions": [
{
"sessionId": "sess_7c5e5c4b",
"sessionKey": "ed25519/BASE64_PUBLIC_KEY",
"clientInfo": "Android 15; Pixel 9",
"lastAuthenticatedAtMs": 1774600010500,
"createdAtMs": 1774600000201,
"geo": "RU/Moscow"
}
]
}
}
```
### `CloseActiveSession`
Доступно только после успешного `SessionLogin`.
Запрос:
```json
{
"op": "CloseActiveSession",
"requestId": "close-001",
"payload": {
"sessionId": "sess_7c5e5c4b"
}
}
```
Успешный ответ:
```json
{
"requestId": "close-001",
"status": 200,
"payload": {
"closed": true,
"sessionId": "sess_7c5e5c4b"
}
}
```
---
## 8. Ошибки и коды отказа
Минимально стоит стандартизовать такие ответы:
- `400 BAD_REQUEST` — не хватает поля или неверный формат.
- `401 UNAUTHORIZED` — challenge не был пройден или соединение не авторизовано.
- `403 INVALID_SIGNATURE` — подпись не прошла проверку.
- `403 DEVICE_KEY_NOT_ACTUAL` — присланный `deviceKey` не совпадает с актуальным ключом пользователя.
- `403 SESSION_KEY_NOT_ACTUAL` — присланный `sessionKey` не совпадает с актуальным ключом сессии.
- `404 SESSION_NOT_FOUND` — сессия не существует или уже закрыта.
- `409 NONCE_ALREADY_USED` — challenge уже использован.
- `410 CHALLENGE_EXPIRED` — nonce устарел.
- `422 UNSUPPORTED_KEY_ALGORITHM` — префикс `session_key` не поддерживается сервером.
- `429 TOO_MANY_ATTEMPTS` — лимит попыток исчерпан.
Пример:
```json
{
"requestId": "create-001",
"status": 422,
"error": "UNSUPPORTED_KEY_ALGORITHM",
"message": "sessionKey prefix is not supported"
}
```
---
## 9. Совместимость с текущей реализацией сервера
По текущему состоянию кода сервер уже использует схему:
- `AuthChallenge(login)`
- `CreateAuthSession(login, sessionKey, storagePwd, timeMs, authNonce, deviceKey, signatureB64, clientInfo)`
- `SessionChallenge(sessionId)`
- `SessionLogin(sessionId, sessionKey, timeMs, signatureB64, clientInfo)`
Текущая строка подписи для `CreateAuthSession` в коде:
```text
AUTH_CREATE_SESSION:{login}:{sessionKey}:{storagePwd}:{timeMs}:{authNonce}
```
Перед проверкой подписи сервер также должен сверять:
- `payload.deviceKey` с актуальным `solana_users.device_key`;
- `payload.sessionKey` с актуальным `active_sessions.session_key`.
### Рекомендуемый путь миграции
1. Ввести новую версию контракта `CreateAuthSession`.
2. На сервере хранить `session_key` целиком одной строкой.
3. На сервере распознавать префикс алгоритма в `sessionKey`.
4. В `CreateAuthSession` передавать и сверять `deviceKey`.
5. В `SessionLogin` передавать и сверять `sessionKey`.
6. Использовать подпись строки:
```text
AUTH_CREATE_SESSION:{login}:{sessionKey}:{storagePwd}:{timeMs}:{authNonce}
```
---
## 10. Практические требования к клиентам
- Клиент должен сам хранить приватную часть `session_key`.
- Приватная часть `device key` никогда не отправляется на сервер.
- `session_key` должен быть новым для каждой новой сессии.
- `storagePwd` должен генерироваться как криптографически стойкое значение.
- Клиент должен учитывать допустимый дрейф времени и синхронизацию часов.
- Клиент не должен повторно использовать старый `authNonce` или `nonce`.
---
## 11. Короткое резюме
- Да, клиент сам создаёт `session_key`.
- Да, клиент сам передаёт `storagePwd`.
- Для `session_key` имеет смысл ввести префикс алгоритма, например `ed25519/...`.
- Для `CreateAuthSession` клиент должен дополнительно передавать `deviceKey`, а сервер должен сверять его с актуальным ключом пользователя.
- Для `SessionLogin` клиент должен дополнительно передавать `sessionKey`, а сервер должен сверять его с актуальным ключом сессии.
- Для `CreateAuthSession` рекомендуется подписывать не только `login`, `timeMs` и `authNonce`, но также `sessionKey` и `storagePwd`.
- Для разработчиков клиентов лучше сразу документировать API через полные JSON-примеры запросов и ответов.
+173
View File
@@ -0,0 +1,173 @@
# API для разработчиков: Регистрация пользователя
Этот файл описывает временный раздел API, связанный с заведением пользователя на сервере и проверкой, существует ли пользователь.
Сейчас здесь два метода:
- `AddUser` — временная серверная регистрация пользователя;
- `GetUser` — временная серверная проверка существования пользователя и чтение его базовых данных.
Их логика пока вспомогательная и dev-oriented: сервер сам хранит эти данные локально и сам отвечает на existence-check. В будущем оба сценария должны быть заменены на нормальную работу напрямую через Solana, но пока этот контракт нужен клиентам для разработки и интеграции.
## Статус документа
Это временная глава API.
Текущая регистрация пользователя и текущая проверка, существует пользователь или нет, пока реализованы как серверные dev/test операции. В будущем и регистрация, и проверка identity должны идти напрямую через Solana.
---
## 1. Операция `AddUser`
### Назначение
Временная регистрация локального пользователя на сервере.
Сервер:
- создаёт запись в `solana_users`;
- создаёт стартовое состояние в `blockchain_state`.
### Запрос
```json
{
"op": "AddUser",
"requestId": "reg-001",
"payload": {
"login": "anya",
"blockchainName": "anya-001",
"solanaKey": "BASE64_32_PUBLIC_KEY",
"blockchainKey": "BASE64_32_PUBLIC_KEY",
"deviceKey": "BASE64_32_PUBLIC_KEY",
"bchLimit": 1000000
}
}
```
### Успешный ответ
```json
{
"op": "AddUser",
"requestId": "reg-001",
"status": 200,
"ok": true,
"payload": {
}
}
```
### Пример ошибки
```json
{
"op": "AddUser",
"requestId": "reg-001",
"status": 409,
"ok": false,
"error": "USER_ALREADY_EXISTS",
"message": "Пользователь с таким login уже существует",
"payload": {
}
}
```
### Специфические коды ошибок `AddUser`
- `400 / BAD_FIELDS` — не переданы обязательные поля регистрации.
- `400 / BAD_BLOCKCHAIN_NAME``blockchainName` не соответствует формату `<login>-NNN`.
- `400 / BAD_KEY_FORMAT` — один из ключей не является корректным `Base64(32 bytes)`.
- `409 / USER_ALREADY_EXISTS` — пользователь с таким `login` уже есть.
- `409 / BLOCKCHAIN_ALREADY_EXISTS` — такой `blockchainName` уже занят.
- `409 / BLOCKCHAIN_STATE_ALREADY_EXISTS` — стартовое состояние blockchain уже существует.
- `501 / DB_ERROR` — ошибка БД при создании пользователя.
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
---
## 2. Операция `GetUser`
### Назначение
Временная серверная проверка, существует пользователь или нет.
Важно:
- это временное решение;
- позже клиент должен проверять existence/identity напрямую через Solana;
- на финальный production flow не стоит жёстко завязывать архитектуру клиента на `GetUser`.
### Запрос
```json
{
"op": "GetUser",
"requestId": "user-001",
"payload": {
"login": "anya"
}
}
```
### Успешный ответ: пользователь существует
```json
{
"op": "GetUser",
"requestId": "user-001",
"status": 200,
"ok": true,
"payload": {
"exists": true,
"login": "Anya",
"blockchainName": "anya-001",
"solanaKey": "BASE64_32_PUBLIC_KEY",
"blockchainKey": "BASE64_32_PUBLIC_KEY",
"deviceKey": "BASE64_32_PUBLIC_KEY"
}
}
```
### Успешный ответ: пользователя нет
```json
{
"op": "GetUser",
"requestId": "user-001",
"status": 200,
"ok": true,
"payload": {
"exists": false
}
}
```
### Пример ошибки
```json
{
"op": "GetUser",
"requestId": "user-001",
"status": 400,
"ok": false,
"error": "BAD_FIELDS",
"message": "Некорректные поля: login",
"payload": {
}
}
```
### Специфические коды ошибок `GetUser`
- `400 / BAD_FIELDS` — не передан или пуст `login`.
- `501 / DB_ERROR` — ошибка БД при поиске пользователя.
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
---
## 3. Короткое резюме
- `AddUser` — временная регистрация пользователя на сервере.
- `GetUser` — временная проверка существования пользователя на сервере.
- И регистрация, и existence-check позже должны быть переведены на Solana.
+279
View File
@@ -0,0 +1,279 @@
# API для разработчиков: Авторизация
Этот файл описывает именно этапы авторизации клиента, то есть как создать новую сессию и как войти в уже существующую.
Здесь четыре метода:
- `AuthChallenge`
- `CreateAuthSession`
- `SessionChallenge`
- `SessionLogin`
Логика раздела такая:
- сначала клиент либо начинает создание новой сессии через `deviceKey`;
- либо начинает вход в уже созданную сессию через `sessionKey`;
- сервер на первом шаге выдаёт challenge/nonce;
- на втором шаге клиент присылает подписанный ответ;
- сервер сверяет актуальные публичные ключи и только потом проверяет подпись.
Ниже в документе сначала описан сценарий, а потом зафиксированы точные форматы запросов и ответов.
## 1. Поток авторизации
Поддерживаются два сценария:
1. Создание новой сессии:
`AuthChallenge` -> `CreateAuthSession`
2. Вход в существующую сессию:
`SessionChallenge` -> `SessionLogin`
`deviceKey` используется для создания новой сессии.
`sessionKey` используется для входа в уже созданную сессию.
`sessionKey` передаётся и хранится целиком одной строкой, например:
```text
ed25519/BASE64_PUBLIC_KEY
```
---
## 2. `AuthChallenge`
### Запрос
```json
{
"op": "AuthChallenge",
"requestId": "auth-001",
"payload": {
"login": "alice"
}
}
```
### Успешный ответ
```json
{
"op": "AuthChallenge",
"requestId": "auth-001",
"status": 200,
"ok": true,
"payload": {
"authNonce": "8f2f0f71-0b1c-4ab2-8f5d-0bc5d6f6aa11"
}
}
```
### Специфические коды ошибок `AuthChallenge`
- `400 / EMPTY_LOGIN` — пустой `login`.
- `400 / ALREADY_AUTHED` — по текущему соединению уже выполнена авторизация.
- `422 / UNKNOWN_USER` — пользователь с таким `login` не найден.
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера, если появится вне штатного сценария.
---
## 3. `CreateAuthSession`
### Запрос
```json
{
"op": "CreateAuthSession",
"requestId": "create-001",
"payload": {
"login": "alice",
"sessionKey": "ed25519/BASE64_PUBLIC_KEY",
"storagePwd": "BASE64_OR_APP_SPECIFIC_SECRET",
"timeMs": 1774600000123,
"authNonce": "nonce",
"deviceKey": "BASE64_DEVICE_PUBLIC_KEY",
"signatureB64": "BASE64_SIGNATURE",
"clientInfo": "Android 15; Pixel 9"
}
}
```
### Строка для подписи
```text
AUTH_CREATE_SESSION:{login}:{sessionKey}:{storagePwd}:{timeMs}:{authNonce}
```
### Дополнительная проверка ключа
Перед проверкой подписи сервер должен:
1. взять актуальный `solana_users.device_key`;
2. сравнить его с `payload.deviceKey`;
3. только потом проверять подпись.
Если ключ не совпадает, сервер возвращает ошибку `DEVICE_KEY_NOT_ACTUAL`.
На будущее:
- для ротации `device_key` желательно добавить перепроверку через Solana.
### Успешный ответ
```json
{
"op": "CreateAuthSession",
"requestId": "create-001",
"status": 200,
"ok": true,
"payload": {
"sessionId": "sess_7c5e5c4b"
}
}
```
### Специфические коды ошибок `CreateAuthSession`
- `400 / NO_STEP1_CONTEXT` — для данного соединения не был корректно выполнен `AuthChallenge`.
- `400 / EMPTY_LOGIN` — пустой `login`.
- `400 / LOGIN_MISMATCH``login` не совпадает с тем, для кого был выдан `authNonce`.
- `501 / DB_ERROR_USER_LOOKUP` — ошибка БД при повторном чтении пользователя.
- `422 / USER_NOT_FOUND` — пользователь не найден.
- `501 / NO_LOGIN` — у пользователя на сервере не заполнен `login`.
- `400 / EMPTY_STORAGE_PWD` — пустой `storagePwd`.
- `400 / EMPTY_SESSION_KEY` — пустой `sessionKey`.
- `422 / UNSUPPORTED_KEY_ALGORITHM` — префикс алгоритма в `sessionKey` или `deviceKey` не поддерживается текущим сервером.
- `400 / BAD_BASE64` — неверный Base64 в `sessionKey`, `deviceKey` или `signatureB64`.
- `400 / EMPTY_SIGNATURE` — пустая подпись.
- `400 / TIME_SKEW` — время клиента отличается от серверного больше допустимого окна.
- `400 / NO_DEVICE_KEY` — у пользователя в БД отсутствует `deviceKey`.
- `400 / EMPTY_AUTH_NONCE` — пустой `authNonce`.
- `400 / AUTH_NONCE_MISMATCH``authNonce` не соответствует значению из `AuthChallenge`.
- `400 / EMPTY_DEVICE_KEY` — в запросе не передан `deviceKey`.
- `422 / DEVICE_KEY_NOT_ACTUAL``deviceKey` не совпадает с актуальной версией на сервере.
- `422 / BAD_SIGNATURE` — подпись не прошла проверку.
- `501 / DB_ERROR_SESSION_CREATE` — ошибка БД при создании записи активной сессии.
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
---
## 4. `SessionChallenge`
### Запрос
```json
{
"op": "SessionChallenge",
"requestId": "sch-001",
"payload": {
"sessionId": "sess_7c5e5c4b"
}
}
```
### Успешный ответ
```json
{
"op": "SessionChallenge",
"requestId": "sch-001",
"status": 200,
"ok": true,
"payload": {
"nonce": "0e5bb0f4-c7d8-4efb-b44d-bf31a6126c66"
}
}
```
### Специфические коды ошибок `SessionChallenge`
- `400 / EMPTY_SESSION_ID` — пустой `sessionId`.
- `501 / DB_ERROR` — ошибка БД при чтении сессии.
- `422 / SESSION_NOT_FOUND` — сессия не найдена.
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
---
## 5. `SessionLogin`
### Запрос
```json
{
"op": "SessionLogin",
"requestId": "slogin-001",
"payload": {
"sessionId": "sess_7c5e5c4b",
"sessionKey": "ed25519/BASE64_PUBLIC_KEY",
"timeMs": 1774600010456,
"signatureB64": "BASE64_SIGNATURE",
"clientInfo": "Android 15; Pixel 9"
}
}
```
### Строка для подписи
```text
SESSION_LOGIN:{sessionId}:{timeMs}:{nonce}
```
### Дополнительная проверка ключа
Перед проверкой подписи сервер должен:
1. взять `active_sessions.session_key`;
2. сравнить его с `payload.sessionKey`;
3. только потом проверять подпись.
Если ключ не совпадает, сервер возвращает ошибку `SESSION_KEY_NOT_ACTUAL`.
### Успешный ответ
```json
{
"op": "SessionLogin",
"requestId": "slogin-001",
"status": 200,
"ok": true,
"payload": {
"storagePwd": "BASE64_OR_APP_SPECIFIC_SECRET"
}
}
```
### Специфические коды ошибок `SessionLogin`
- `400 / EMPTY_SESSION_ID` — пустой `sessionId`.
- `400 / NO_CHALLENGE` — перед `SessionLogin` не был успешно выполнен `SessionChallenge` либо nonce уже истёк.
- `400 / SESSION_ID_MISMATCH` — nonce был выдан для другого `sessionId`.
- `400 / TIME_SKEW` — время клиента отличается от серверного больше допустимого окна.
- `400 / EMPTY_SIGNATURE` — пустая подпись.
- `400 / EMPTY_SESSION_KEY` — пустой `sessionKey`.
- `501 / DB_ERROR` — ошибка БД при чтении сессии.
- `422 / SESSION_NOT_FOUND` — сессия не найдена.
- `501 / NO_SESSION_KEY` — у сессии отсутствует `session_key`.
- `422 / SESSION_KEY_NOT_ACTUAL` — переданный `sessionKey` не совпадает с актуальной версией на сервере.
- `422 / UNSUPPORTED_KEY_ALGORITHM` — префикс алгоритма в `sessionKey` не поддерживается текущим сервером.
- `400 / BAD_BASE64` — неверный Base64 в `sessionKey` или `signatureB64`.
- `422 / BAD_SIGNATURE` — подпись не прошла проверку.
- `501 / DB_ERROR_USER_LOOKUP` — ошибка БД при чтении пользователя для этой сессии.
- `422 / USER_NOT_FOUND_FOR_SESSION` — пользователь, которому принадлежит сессия, не найден.
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
---
## 6. Пример ошибки
```json
{
"op": "SessionLogin",
"requestId": "slogin-001",
"status": 403,
"ok": false,
"error": "SESSION_KEY_NOT_ACTUAL",
"message": "session_key не соответствует актуальной версии",
"payload": {
}
}
```
+116
View File
@@ -0,0 +1,116 @@
# API для разработчиков: Управление сессиями
Этот файл описывает методы, которые используются уже после успешной авторизации пользователя в сессию.
Здесь два метода:
- `ListSessions` — получить список активных сессий пользователя;
- `CloseActiveSession` — закрыть одну из активных сессий.
Логика раздела такая:
- сначала пользователь проходит `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",
"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` — непредвиденная внутренняя ошибка сервера.
---
## 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": {
}
}
```