27 03 25
Добавил документ для разработчиков (про сессии но не закончил) и исправил мекую ошибку с несопостовлениеминдексов
This commit is contained in:
parent
dabda362e6
commit
6d3719ba71
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,8 @@
|
|||||||
|
## папки с данными создавайемыми при работе сервера
|
||||||
|
data/
|
||||||
|
logs/
|
||||||
|
logs
|
||||||
|
|
||||||
.gradle
|
.gradle
|
||||||
build/
|
build/
|
||||||
!gradle/wrapper/gradle-wrapper.jar
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|||||||
@ -4,6 +4,9 @@
|
|||||||
|
|
||||||
## Список документов
|
## Список документов
|
||||||
|
|
||||||
|
0. **API/01_Auth_and_Sessions_API.md**
|
||||||
|
API-глава для разработчиков: транспортный JSON-конверт, форматы запросов/ответов, создание и вход в сессию, `session_key`, `storagePwd`, подписи и совместимость версий.
|
||||||
|
|
||||||
1. **01_Connection_and_Sessions.md**
|
1. **01_Connection_and_Sessions.md**
|
||||||
Процесс подключения к WebSocket, авторизация (двухшаговая), создание сессии, вход в существующую сессию, просмотр и закрытие сессий.
|
Процесс подключения к WebSocket, авторизация (двухшаговая), создание сессии, вход в существующую сессию, просмотр и закрытие сессий.
|
||||||
|
|
||||||
|
|||||||
@ -94,10 +94,13 @@
|
|||||||
- Есть счетчики `message_stats` (likes/replies/edits) на уровне БД.
|
- Есть счетчики `message_stats` (likes/replies/edits) на уровне БД.
|
||||||
- Есть `connections_state` как «текущее состояние», собранное триггерами.
|
- Есть `connections_state` как «текущее состояние», собранное триггерами.
|
||||||
|
|
||||||
## 6) Важный текущий риск
|
## 6) Статус унификации subType
|
||||||
|
|
||||||
В проекте есть **несовпадение констант subType между модулями** (`shine-server-blockchain` и `shine-server-db`):
|
Схема `subType` должна быть единой во всех модулях проекта:
|
||||||
- в blockchain-модуле `TEXT_POST=10, REPLY=20...`
|
|
||||||
- в db-модуле местами используются старые `TEXT_NEW=1, REPLY=2...`
|
|
||||||
|
|
||||||
Это нужно унифицировать, иначе часть триггеров/DAO будет работать некорректно.
|
- `TEXT_POST = 10`
|
||||||
|
- `TEXT_EDIT_POST = 11`
|
||||||
|
- `TEXT_REPLY = 20`
|
||||||
|
- `TEXT_EDIT_REPLY = 21`
|
||||||
|
|
||||||
|
Это правило должно одинаково использоваться в `shine-server-blockchain`, `shine-server-db`, SQL-триггерах и DAO.
|
||||||
|
|||||||
@ -2,35 +2,31 @@
|
|||||||
|
|
||||||
## Критичные вопросы
|
## Критичные вопросы
|
||||||
|
|
||||||
1. **Единая нумерация subType**
|
1. **Что считаем “сообщением канала” для counters**
|
||||||
- Подтверждаем ли окончательно новую схему (`POST=10, REPLY=20...`) во всех модулях (`db`, `triggers`, `dao`)?
|
|
||||||
|
|
||||||
2. **Что считаем “сообщением канала” для counters**
|
|
||||||
- Только `TEXT_POST`?
|
- Только `TEXT_POST`?
|
||||||
- Или ещё `TEXT_EDIT_POST` и/или `REPLY` в этом же line?
|
- Или ещё `TEXT_EDIT_POST` и/или `REPLY` в этом же line?
|
||||||
|
|
||||||
3. **Что такое “прочитано”**
|
2. **Что такое “прочитано”**
|
||||||
- По `this_line_number`?
|
- По `this_line_number`?
|
||||||
- По `block_number`?
|
- По `block_number`?
|
||||||
- По времени?
|
- По времени?
|
||||||
|
|
||||||
4. **Личные и публичные каналы**
|
3. **Личные и публичные каналы**
|
||||||
- Явно вводим `channelType` в API?
|
- Явно вводим `channelType` в API?
|
||||||
- Нужны ли отдельные private/dm каналы в MVP?
|
- Нужны ли отдельные private/dm каналы в MVP?
|
||||||
|
|
||||||
5. **Уведомления (лайк/reply/follow/friend)**
|
4. **Уведомления (лайк/reply/follow/friend)**
|
||||||
- Делаем сначала виртуальный канал (query-time), потом материализацию?
|
- Делаем сначала виртуальный канал (query-time), потом материализацию?
|
||||||
|
|
||||||
## Технический TODO (рекомендуемый порядок)
|
## Технический TODO (рекомендуемый порядок)
|
||||||
|
|
||||||
1. Унифицировать `MsgSubType` между модулями.
|
1. Добавить DAO для выборки каналов с counters.
|
||||||
2. Добавить DAO для выборки каналов с counters.
|
2. Добавить read-api handlers (3-4 операции, описанные в 04 документе).
|
||||||
3. Добавить read-api handlers (3-4 операции, описанные в 04 документе).
|
3. Добавить integration tests:
|
||||||
4. Добавить integration tests:
|
|
||||||
- подписка -> counters;
|
- подписка -> counters;
|
||||||
- read progress -> newMessages;
|
- read progress -> newMessages;
|
||||||
- thread graph на 100+ ответов.
|
- thread graph на 100+ ответов.
|
||||||
5. Добавить индекс(ы) под новые query-паттерны (по `line_code`, `to_*`, `msg_type/subtype`).
|
4. Добавить индекс(ы) под новые query-паттерны (по `line_code`, `to_*`, `msg_type/subtype`).
|
||||||
|
|
||||||
## Дополнительные идеи
|
## Дополнительные идеи
|
||||||
|
|
||||||
|
|||||||
471
Dev_Docs/API/01_Auth_and_Sessions_API.md
Normal file
471
Dev_Docs/API/01_Auth_and_Sessions_API.md
Normal file
@ -0,0 +1,471 @@
|
|||||||
|
# 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`
|
||||||
|
- последующих перевходов в уже созданную сессию
|
||||||
|
|
||||||
|
### `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",
|
||||||
|
"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` становится частью подтверждённого набора параметров создания сессии.
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```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",
|
||||||
|
"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
|
||||||
|
```
|
||||||
|
|
||||||
|
Успешный ответ:
|
||||||
|
|
||||||
|
```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`
|
||||||
|
|
||||||
|
Запрос:
|
||||||
|
|
||||||
|
```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` — подпись не прошла проверку.
|
||||||
|
- `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(storagePwd, sessionPubKeyB64, timeMs, signatureB64, clientInfo)`
|
||||||
|
- `SessionChallenge(sessionId)`
|
||||||
|
- `SessionLogin(sessionId, timeMs, signatureB64, clientInfo)`
|
||||||
|
|
||||||
|
Текущая строка подписи для `CreateAuthSession` в коде:
|
||||||
|
|
||||||
|
```text
|
||||||
|
AUTH_CREATE_SESSION:{login}:{timeMs}:{authNonce}
|
||||||
|
```
|
||||||
|
|
||||||
|
То есть **сейчас** `sessionPubKeyB64` и `storagePwd` ещё не входят в preimage подписи.
|
||||||
|
|
||||||
|
### Рекомендуемый путь миграции
|
||||||
|
|
||||||
|
1. Ввести новую версию контракта `CreateAuthSession`.
|
||||||
|
2. Добавить поле `sessionKey` вместо `sessionPubKeyB64`.
|
||||||
|
3. На сервере распознавать префикс алгоритма в `sessionKey`.
|
||||||
|
4. Перейти на подпись строки:
|
||||||
|
|
||||||
|
```text
|
||||||
|
AUTH_CREATE_SESSION:{login}:{sessionKey}:{storagePwd}:{timeMs}:{authNonce}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. На переходный период поддерживать оба варианта:
|
||||||
|
- legacy: без `sessionKey` и `storagePwd` в подписи;
|
||||||
|
- vNext: с полным набором полей.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Практические требования к клиентам
|
||||||
|
|
||||||
|
- Клиент должен сам хранить приватную часть `session_key`.
|
||||||
|
- Приватная часть `device key` никогда не отправляется на сервер.
|
||||||
|
- `session_key` должен быть новым для каждой новой сессии.
|
||||||
|
- `storagePwd` должен генерироваться как криптографически стойкое значение.
|
||||||
|
- Клиент должен учитывать допустимый дрейф времени и синхронизацию часов.
|
||||||
|
- Клиент не должен повторно использовать старый `authNonce` или `nonce`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Короткое резюме
|
||||||
|
|
||||||
|
- Да, клиент сам создаёт `session_key`.
|
||||||
|
- Да, клиент сам передаёт `storagePwd`.
|
||||||
|
- Для `session_key` имеет смысл ввести префикс алгоритма, например `ed25519/...`.
|
||||||
|
- Для `CreateAuthSession` рекомендуется подписывать не только `login`, `timeMs` и `authNonce`, но также `sessionKey` и `storagePwd`.
|
||||||
|
- Для разработчиков клиентов лучше сразу документировать API через полные JSON-примеры запросов и ответов.
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
3240
logs/app.log
3240
logs/app.log
File diff suppressed because it is too large
Load Diff
@ -28,10 +28,10 @@ public final class DatabaseInitializer {
|
|||||||
|
|
||||||
/* ===================== TEXT (msg_type=1) ===================== */
|
/* ===================== TEXT (msg_type=1) ===================== */
|
||||||
|
|
||||||
public static final short TEXT_NEW = 1;
|
public static final short TEXT_POST = 10;
|
||||||
public static final short TEXT_REPLY = 2;
|
public static final short TEXT_EDIT_POST = 11;
|
||||||
public static final short TEXT_REPOST = 3;
|
public static final short TEXT_REPLY = 20;
|
||||||
public static final short TEXT_EDIT = 10;
|
public static final short TEXT_EDIT_REPLY = 21;
|
||||||
|
|
||||||
/* ===================== REACTION (msg_type=2) ===================== */
|
/* ===================== REACTION (msg_type=2) ===================== */
|
||||||
|
|
||||||
|
|||||||
@ -306,12 +306,13 @@ public final class DatabaseTriggersInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void createEditApplyTrigger(Statement st) throws SQLException {
|
private static void createEditApplyTrigger(Statement st) throws SQLException {
|
||||||
int EDIT = (int) DatabaseInitializer.TEXT_EDIT;
|
int EDIT_POST = (int) DatabaseInitializer.TEXT_EDIT_POST;
|
||||||
|
int EDIT_REPLY = (int) DatabaseInitializer.TEXT_EDIT_REPLY;
|
||||||
|
|
||||||
st.executeUpdate("""
|
st.executeUpdate("""
|
||||||
CREATE TRIGGER IF NOT EXISTS trg_blocks_edit_apply_ai
|
CREATE TRIGGER IF NOT EXISTS trg_blocks_edit_apply_ai
|
||||||
AFTER INSERT ON blocks
|
AFTER INSERT ON blocks
|
||||||
WHEN NEW.msg_type = 1 AND NEW.msg_sub_type = %d
|
WHEN NEW.msg_type = 1 AND NEW.msg_sub_type IN (%d, %d)
|
||||||
BEGIN
|
BEGIN
|
||||||
-- 1) помечаем исходный блок, что его "перекрыл" этот edit
|
-- 1) помечаем исходный блок, что его "перекрыл" этот edit
|
||||||
UPDATE blocks
|
UPDATE blocks
|
||||||
@ -346,6 +347,6 @@ public final class DatabaseTriggersInstaller {
|
|||||||
AND NEW.to_block_number IS NOT NULL
|
AND NEW.to_block_number IS NOT NULL
|
||||||
AND NEW.to_block_hash IS NOT NULL;
|
AND NEW.to_block_hash IS NOT NULL;
|
||||||
END;
|
END;
|
||||||
""".formatted(EDIT));
|
""".formatted(EDIT_POST, EDIT_REPLY));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -18,17 +18,17 @@ public final class MsgSubType {
|
|||||||
|
|
||||||
/* ===================== TEXT (msg_type=1) ===================== */
|
/* ===================== TEXT (msg_type=1) ===================== */
|
||||||
|
|
||||||
/** Новая публикация. */
|
/** POST — обычный пост в канале (в линии канала). */
|
||||||
public static final short TEXT_NEW = 1;
|
public static final short TEXT_POST = 10;
|
||||||
|
|
||||||
/** Ответ (reply). */
|
/** EDIT_POST — редактирование исходного поста. */
|
||||||
public static final short TEXT_REPLY = 2;
|
public static final short TEXT_EDIT_POST = 11;
|
||||||
|
|
||||||
/** Репост (repost). */
|
/** REPLY — ответ на сообщение. */
|
||||||
public static final short TEXT_REPOST = 3;
|
public static final short TEXT_REPLY = 20;
|
||||||
|
|
||||||
/** Редактирование (edit). */
|
/** EDIT_REPLY — редактирование исходного ответа. */
|
||||||
public static final short TEXT_EDIT = 10;
|
public static final short TEXT_EDIT_REPLY = 21;
|
||||||
|
|
||||||
/* ===================== REACTION (msg_type=2) ===================== */
|
/* ===================== REACTION (msg_type=2) ===================== */
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import java.util.List;
|
|||||||
* Возвращает по каждой активной подписке (FOLLOW) + "сам на себя":
|
* Возвращает по каждой активной подписке (FOLLOW) + "сам на себя":
|
||||||
* - login цели (channelLogin)
|
* - login цели (channelLogin)
|
||||||
* - blockchainName цели (channelBchName)
|
* - blockchainName цели (channelBchName)
|
||||||
* - count публикаций (TEXT_NEW)
|
* - count публикаций (TEXT_POST)
|
||||||
* - last publication: bytes оригинального блока (для timestamp)
|
* - last publication: bytes оригинального блока (для timestamp)
|
||||||
* - last publication: bytes актуального блока (edit или orig) — для текста превью
|
* - last publication: bytes актуального блока (edit или orig) — для текста превью
|
||||||
*
|
*
|
||||||
@ -92,7 +92,7 @@ public final class SubscriptionsDAO {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Получить список подписок (активные FOLLOW) + "сам на себя" и по каждой:
|
* Получить список подписок (активные FOLLOW) + "сам на себя" и по каждой:
|
||||||
* - count публикаций (TEXT_NEW)
|
* - count публикаций (TEXT_POST)
|
||||||
* - последнюю публикацию (orig bytes) + её edit (если есть)
|
* - последнюю публикацию (orig bytes) + её edit (если есть)
|
||||||
*
|
*
|
||||||
* Поведение при 0 публикаций:
|
* Поведение при 0 публикаций:
|
||||||
@ -207,11 +207,11 @@ public final class SubscriptionsDAO {
|
|||||||
|
|
||||||
// pub_counts
|
// pub_counts
|
||||||
ps.setInt(i++, MSG_TYPE_TEXT);
|
ps.setInt(i++, MSG_TYPE_TEXT);
|
||||||
ps.setInt(i++, (int) MsgSubType.TEXT_NEW);
|
ps.setInt(i++, (int) MsgSubType.TEXT_POST);
|
||||||
|
|
||||||
// last_pub
|
// last_pub
|
||||||
ps.setInt(i++, MSG_TYPE_TEXT);
|
ps.setInt(i++, MSG_TYPE_TEXT);
|
||||||
ps.setInt(i++, (int) MsgSubType.TEXT_NEW);
|
ps.setInt(i++, (int) MsgSubType.TEXT_POST);
|
||||||
|
|
||||||
try (ResultSet rs = ps.executeQuery()) {
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
|
|||||||
@ -16,7 +16,7 @@ package shine.db.entities;
|
|||||||
* Плюс поля индексации:
|
* Плюс поля индексации:
|
||||||
* - msg_type / msg_sub_type
|
* - msg_type / msg_sub_type
|
||||||
* - to_* (если есть target)
|
* - to_* (если есть target)
|
||||||
* - edited_by_block_number (для TEXT_EDIT)
|
* - edited_by_block_number (для TEXT_EDIT_POST / TEXT_EDIT_REPLY)
|
||||||
*/
|
*/
|
||||||
public class BlockEntry {
|
public class BlockEntry {
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user