diff --git a/Dev_Docs/API/03_Session_Management_API.md b/Dev_Docs/API/03_Session_Management_API.md index 13715a5..3bd2aa5 100644 --- a/Dev_Docs/API/03_Session_Management_API.md +++ b/Dev_Docs/API/03_Session_Management_API.md @@ -188,6 +188,8 @@ K9v3nQ4u8jYk0a2p7cD4mLx1zR0sT5wV6bN8eH3fQ1M } ``` +Если pairing должен работать **без доп. пароля**, клиент может включить его с пустым `passwordHash`. + ### Успешный ответ ```json @@ -205,7 +207,6 @@ K9v3nQ4u8jYk0a2p7cD4mLx1zR0sT5wV6bN8eH3fQ1M ### Ошибки -- `400 / EMPTY_PASSWORD_HASH` — попытка включить pairing без `passwordHash`. - `463 / PAIRING_REQUIRES_AUTH_SESSION` — операция вызвана без уже авторизованной доверенной сессии пользователя. ### 5.2. `StartEspPairing` @@ -229,6 +230,8 @@ K9v3nQ4u8jYk0a2p7cD4mLx1zR0sT5wV6bN8eH3fQ1M } ``` +Если на доверённом устройстве pairing включён **без доп. пароля**, новое устройство может отправить пустой `passwordHash`. + Поле `trustedSessionOnline` показывает, что у пользователя сейчас есть хотя бы одна онлайн доверенная сессия, способная принять pairing-заявку. ### Успешный ответ @@ -253,7 +256,6 @@ K9v3nQ4u8jYk0a2p7cD4mLx1zR0sT5wV6bN8eH3fQ1M ### Ошибки - `400 / EMPTY_LOGIN` -- `400 / EMPTY_PASSWORD_HASH` - `400 / EMPTY_REQUESTER_SESSION_KEY` - `400 / BAD_REQUESTER_SESSION_KEY` - `400 / BAD_SESSION_TYPE` diff --git a/Dev_Docs/Pending_Features/2026-06-14_2035_ui_подключение_по_коду.md b/Dev_Docs/Pending_Features/2026-06-14_2035_ui_подключение_по_коду.md index 0c5b2fe..92c0d3d 100644 --- a/Dev_Docs/Pending_Features/2026-06-14_2035_ui_подключение_по_коду.md +++ b/Dev_Docs/Pending_Features/2026-06-14_2035_ui_подключение_по_коду.md @@ -4,11 +4,13 @@ - в UI добавлен новый сценарий подключения устройства через доверенную уже авторизованную сессию пользователя; - на экране входа появилась кнопка `Войти через другое устройство`; - на доверённом устройстве в `Подключить устройство` появилась кнопка `Подключить по коду`; - - доверённое устройство может включить pairing-пароль, увидеть заявки, подтвердить подключение только с `device key` или с передачей выбранных ключей. + - доверённое устройство может включить pairing с доп. паролем или без него, увидеть заявки, подтвердить подключение только с `device key` или с передачей выбранных ключей. - что именно проверять: - - на уже авторизованном устройстве включить pairing-пароль; - - на новом устройстве открыть `Войти через другое устройство`, ввести `login + pairing password` и получить 7-значный код; + - на уже авторизованном устройстве включить pairing без доп. пароля; + - на новом устройстве открыть `Войти через другое устройство`, оставить галочку доп. пароля выключенной и получить 7-значный код; + - отдельно включить pairing с доп. паролем; + - на новом устройстве открыть `Войти через другое устройство`, включить галочку доп. пароля, ввести `login + pairing password` и получить 7-значный код; - на доверённом устройстве открыть `Подключить по коду`, найти заявку по коду и подтвердить её: - без доп. ключей; - с передачей выбранных ключей; diff --git a/Dev_Docs/Протоколы/ESP_Pairing_и_режимы_подключения.md b/Dev_Docs/Протоколы/ESP_Pairing_и_режимы_подключения.md index 282bff1..e9a44a7 100644 --- a/Dev_Docs/Протоколы/ESP_Pairing_и_режимы_подключения.md +++ b/Dev_Docs/Протоколы/ESP_Pairing_и_режимы_подключения.md @@ -36,7 +36,7 @@ Цель: -- новое устройство знает `login + pairing password`; +- новое устройство знает `login`, а `pairing password` используется только если он включён на доверённом устройстве; - сервер использует пароль только как фильтр от мусора; - реальное доверие даёт любая уже онлайн доверенная сессия пользователя; - сервер не выдаёт приватные ключи сам от себя. @@ -58,7 +58,7 @@ ## 3. Что именно делает сервер -- хранит включённость pairing и opaque `passwordHash`; +- хранит включённость pairing и optional opaque `passwordHash`; - хранит pending/approved/rejected pairing-заявки; - рассчитывает короткий код `shortCode` из `7` цифр; - рассчитывает длинный `fingerprintB58` из `SHA-256` заявки; @@ -101,6 +101,6 @@ Эта схема даёт нужное разделение доверия: -- пароль на сервере только отсеивает лишних; +- пароль на сервере, если он включён, только отсеивает лишних; - онлайн доверенная сессия решает, добавлять ли новую сессию; - сервер остаётся маршрутизатором и хранилищем состояния, а не владельцем секретов. diff --git a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/Net_StartEspPairing_Handler.java b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/Net_StartEspPairing_Handler.java index 9f5a399..4e32f11 100644 --- a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/Net_StartEspPairing_Handler.java +++ b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/Net_StartEspPairing_Handler.java @@ -55,9 +55,6 @@ public class Net_StartEspPairing_Handler implements JsonMessageHandler { return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_PAYLOAD_TYPE", "payloadType должен быть 1, 2 или 3"); } String passwordHash = EspPairingSupport.normalizeOpaqueHash(req.getPasswordHash()); - if (passwordHash == null) { - return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "EMPTY_PASSWORD_HASH", "Пустой passwordHash"); - } SolanaUserEntry user = SolanaUsersDAO.getInstance().getByLogin(login); if (user == null) { @@ -84,9 +81,14 @@ public class Net_StartEspPairing_Handler implements JsonMessageHandler { if (recentAttempts >= EspPairingSupport.REQUEST_RATE_LIMIT) { return NetExceptionResponseFactory.error(req, EspPairingSupport.STATUS_PAIRING_RATE_LIMIT, "PAIRING_RATE_LIMITED", "Слишком много pairing-запросов за короткое время"); } - if (!settings.getPasswordHash().equals(passwordHash)) { + String configuredPasswordHash = settings.getPasswordHash() == null ? "" : settings.getPasswordHash().trim(); + boolean requiresPassword = !configuredPasswordHash.isBlank(); + if (requiresPassword && !configuredPasswordHash.equals(passwordHash)) { return NetExceptionResponseFactory.error(req, 422, "PAIRING_PASSWORD_INVALID", "Неверный pairing-пароль"); } + if (!requiresPassword && passwordHash != null && !passwordHash.isBlank()) { + passwordHash = ""; + } String clientPlatform = AuthSessionTypeSupport.normalizeClientPlatform(req.getRequesterClientPlatform()); int ttlSeconds = EspPairingSupport.normalizeTtlSeconds(settings.getTtlSeconds()); diff --git a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/Net_UpsertEspPairingSettings_Handler.java b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/Net_UpsertEspPairingSettings_Handler.java index 03b9763..0d40845 100644 --- a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/Net_UpsertEspPairingSettings_Handler.java +++ b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/Net_UpsertEspPairingSettings_Handler.java @@ -29,9 +29,6 @@ public class Net_UpsertEspPairingSettings_Handler implements JsonMessageHandler boolean enabled = req.getEnabled() != null && req.getEnabled(); String passwordHash = EspPairingSupport.normalizeOpaqueHash(req.getPasswordHash()); int ttlSeconds = EspPairingSupport.normalizeTtlSeconds(req.getTtlSeconds()); - if (enabled && (passwordHash == null || passwordHash.isBlank())) { - return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "EMPTY_PASSWORD_HASH", "Для включения pairing нужен passwordHash"); - } long now = System.currentTimeMillis(); EspPairingSettingsEntry entry = new EspPairingSettingsEntry(); diff --git a/SHiNE-server/src/test/java/test/it/cases/IT_07_EspPairing.java b/SHiNE-server/src/test/java/test/it/cases/IT_07_EspPairing.java index 573429a..95970ad 100644 --- a/SHiNE-server/src/test/java/test/it/cases/IT_07_EspPairing.java +++ b/SHiNE-server/src/test/java/test/it/cases/IT_07_EspPairing.java @@ -79,6 +79,21 @@ public class IT_07_EspPairing { assertEquals("approved", JsonParsers.payloadText(statusResp, "state")); assertEquals("AQIDBA==", JsonParsers.payloadText(statusResp, "encryptedPayload")); + String upsertNoPasswordResp = clientWs.call( + "UpsertEspPairingSettings", + JsonBuilders.upsertEspPairingSettings(true, "", 180), + t + ); + assertEquals(200, JsonParsers.status(upsertNoPasswordResp), "UpsertEspPairingSettings without password must be 200"); + + SessionMaterial requesterNoPasswordMaterial = newSessionMaterial(); + String startNoPasswordResp = requesterWs.call( + "StartEspPairing", + JsonBuilders.startEspPairing(LOGIN, "", requesterNoPasswordMaterial.sessionKey(), 1, "Android", 1), + t + ); + assertEquals(200, JsonParsers.status(startNoPasswordResp), "StartEspPairing without password must be 200"); + String forbiddenResp = requesterWs.call( "ListEspPairingRequests#anonymous", JsonBuilders.listEspPairingRequests(), @@ -86,7 +101,7 @@ public class IT_07_EspPairing { ); assertErrorFormat(forbiddenResp, "ListEspPairingRequests", "PAIRING_REQUIRES_AUTH_SESSION"); - r.ok("ESP pairing: обычная доверенная сессия увидела запрос и подтвердила зашифрованный payload"); + r.ok("ESP pairing: доверенная сессия принимает заявки как с доп. паролем, так и без него"); } } catch (Throwable e) { r.fail("IT_07_EspPairing упал: " + e.getMessage()); diff --git a/VERSION.properties b/VERSION.properties index f562c45..cb06f40 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.195 -server.version=1.2.184 +client.version=1.2.196 +server.version=1.2.185 diff --git a/shine-UI/js/app.js b/shine-UI/js/app.js index 0cfe534..88065e2 100644 --- a/shine-UI/js/app.js +++ b/shine-UI/js/app.js @@ -42,7 +42,7 @@ import * as topupView from './pages/topup-view.js'; import * as devnetTopupView from './pages/devnet-topup-view.js'; import * as loginView from './pages/login-view.js?v=202606142055'; import * as loginCameraView from './pages/login-camera-view.js'; -import * as loginOtherDeviceView from './pages/login-other-device-view.js?v=202606142055'; +import * as loginOtherDeviceView from './pages/login-other-device-view.js?v=202606150010'; import * as loginPasswordView from './pages/login-password-view.js'; import * as keyStorageView from './pages/key-storage-view.js'; @@ -55,7 +55,7 @@ import * as serverSettingsView from './pages/server-settings-view.js'; import * as toolsSettingsView from './pages/tools-settings-view.js'; import * as deviceView from './pages/device-view.js?v=202606131435'; import * as connectDeviceView from './pages/connect-device-view.js?v=202606142055'; -import * as devicePairingView from './pages/device-pairing-view.js?v=202606142055'; +import * as devicePairingView from './pages/device-pairing-view.js?v=202606150010'; import * as deviceQrView from './pages/device-qr-view.js'; import * as deviceCameraView from './pages/device-camera-view.js'; import * as showKeysView from './pages/show-keys-view.js'; diff --git a/shine-UI/js/pages/device-pairing-view.js b/shine-UI/js/pages/device-pairing-view.js index 705b50e..7e28ae5 100644 --- a/shine-UI/js/pages/device-pairing-view.js +++ b/shine-UI/js/pages/device-pairing-view.js @@ -90,8 +90,12 @@ export function render({ navigate }) { settingsCard.className = 'card stack'; settingsCard.innerHTML = `
Пароль подключения
+