TrustedDeviceLogin API и настройки входа через устройство

Что сделано:\n- публичный API сценария входа через доверенное устройство переведён на TrustedDeviceLogin\n- добавлен GetTrustedDeviceLoginSettings\n- отсутствие записи настроек на сервере теперь трактуется как enabled=true и hasPassword=false\n- ttlSeconds убран из клиентского API, TTL заявки фиксирован на сервере: 300 секунд\n- в shine-UI добавлен отдельный экран настроек входа через устройство и статус на основном экране\n- browser wallet переведён на новые TrustedDeviceLogin операции\n- в wallet добавлен выбор rootKey/deviceKey для будущего запроса подписи\n- документация API обновлена\n\nЧто ещё не проверено вручную end-to-end:\n- полный сценарий UI/plugin после этого деплоя не прогонялся руками до конца\n- сам signaling подписи в wallet всё ещё не реализован
This commit is contained in:
AidarKC
2026-06-18 14:19:31 +04:00
parent cf2152dcfc
commit 56db6d0add
29 changed files with 689 additions and 241 deletions
@@ -11,6 +11,7 @@ import server.logic.ws_protocol.JSON.handlers.auth.Net_ListEspPairingRequests_Ha
import server.logic.ws_protocol.JSON.handlers.auth.Net_ApproveEspPairing_Handler;
import server.logic.ws_protocol.JSON.handlers.auth.Net_CancelEspPairing_Handler;
import server.logic.ws_protocol.JSON.handlers.auth.Net_GetEspPairingStatus_Handler;
import server.logic.ws_protocol.JSON.handlers.auth.Net_GetTrustedDeviceLoginSettings_Handler;
import server.logic.ws_protocol.JSON.handlers.auth.Net_RejectEspPairing_Handler;
// --- NEW v2 session login ---
@@ -30,6 +31,7 @@ import server.logic.ws_protocol.JSON.handlers.auth.entyties.Net_ListEspPairingRe
import server.logic.ws_protocol.JSON.handlers.auth.entyties.Net_ApproveEspPairing_Request;
import server.logic.ws_protocol.JSON.handlers.auth.entyties.Net_CancelEspPairing_Request;
import server.logic.ws_protocol.JSON.handlers.auth.entyties.Net_GetEspPairingStatus_Request;
import server.logic.ws_protocol.JSON.handlers.auth.entyties.Net_GetTrustedDeviceLoginSettings_Request;
import server.logic.ws_protocol.JSON.handlers.auth.entyties.Net_RejectEspPairing_Request;
import server.logic.ws_protocol.JSON.handlers.auth.entyties.Net_SessionChallenge_Request;
import server.logic.ws_protocol.JSON.handlers.auth.entyties.Net_SessionLogin_Request;
@@ -142,6 +144,14 @@ public final class JsonHandlerRegistry {
Map.entry("RejectEspPairing", new Net_RejectEspPairing_Handler()),
Map.entry("CancelEspPairing", new Net_CancelEspPairing_Handler()),
Map.entry("GetEspPairingStatus", new Net_GetEspPairingStatus_Handler()),
Map.entry("GetTrustedDeviceLoginSettings", new Net_GetTrustedDeviceLoginSettings_Handler()),
Map.entry("UpsertTrustedDeviceLoginSettings", new Net_UpsertEspPairingSettings_Handler()),
Map.entry("StartTrustedDeviceLogin", new Net_StartEspPairing_Handler()),
Map.entry("ListTrustedDeviceLoginRequests", new Net_ListEspPairingRequests_Handler()),
Map.entry("ApproveTrustedDeviceLogin", new Net_ApproveEspPairing_Handler()),
Map.entry("RejectTrustedDeviceLogin", new Net_RejectEspPairing_Handler()),
Map.entry("CancelTrustedDeviceLogin", new Net_CancelEspPairing_Handler()),
Map.entry("GetTrustedDeviceLoginStatus", new Net_GetEspPairingStatus_Handler()),
// --- blockchain ---
Map.entry("AddBlock", new Net_AddBlock_Handler()),
@@ -207,6 +217,14 @@ public final class JsonHandlerRegistry {
Map.entry("RejectEspPairing", Net_RejectEspPairing_Request.class),
Map.entry("CancelEspPairing", Net_CancelEspPairing_Request.class),
Map.entry("GetEspPairingStatus", Net_GetEspPairingStatus_Request.class),
Map.entry("GetTrustedDeviceLoginSettings", Net_GetTrustedDeviceLoginSettings_Request.class),
Map.entry("UpsertTrustedDeviceLoginSettings", Net_UpsertEspPairingSettings_Request.class),
Map.entry("StartTrustedDeviceLogin", Net_StartEspPairing_Request.class),
Map.entry("ListTrustedDeviceLoginRequests", Net_ListEspPairingRequests_Request.class),
Map.entry("ApproveTrustedDeviceLogin", Net_ApproveEspPairing_Request.class),
Map.entry("RejectTrustedDeviceLogin", Net_RejectEspPairing_Request.class),
Map.entry("CancelTrustedDeviceLogin", Net_CancelEspPairing_Request.class),
Map.entry("GetTrustedDeviceLoginStatus", Net_GetEspPairingStatus_Request.class),
// --- blockchain ---
Map.entry("AddBlock", Net_AddBlock_Request.class),
@@ -0,0 +1,43 @@
package server.logic.ws_protocol.JSON.handlers.auth;
import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.JSON.handlers.auth.entyties.Net_GetTrustedDeviceLoginSettings_Request;
import server.logic.ws_protocol.JSON.handlers.auth.entyties.Net_GetTrustedDeviceLoginSettings_Response;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import shine.db.dao.EspPairingSettingsDAO;
import shine.db.entities.EspPairingSettingsEntry;
public class Net_GetTrustedDeviceLoginSettings_Handler implements JsonMessageHandler {
@Override
public Net_Response handle(Net_Request baseReq, ConnectionContext ctx) throws Exception {
Net_GetTrustedDeviceLoginSettings_Request req = (Net_GetTrustedDeviceLoginSettings_Request) baseReq;
if (!EspPairingSupport.isTrustedUserSession(ctx)) {
return NetExceptionResponseFactory.error(
req,
EspPairingSupport.STATUS_PAIRING_REQUIRES_AUTH_SESSION,
"PAIRING_REQUIRES_AUTH_SESSION",
"Операция доступна только для авторизованной доверенной сессии пользователя"
);
}
EspPairingSettingsEntry entry = EspPairingSettingsDAO.getInstance().getByLogin(ctx.getLogin());
boolean enabled = entry == null || entry.isEnabled();
boolean hasPassword = enabled
&& entry != null
&& entry.getPasswordHash() != null
&& !entry.getPasswordHash().trim().isBlank();
Net_GetTrustedDeviceLoginSettings_Response resp = new Net_GetTrustedDeviceLoginSettings_Response();
resp.setOp(req.getOp());
resp.setRequestId(req.getRequestId());
resp.setStatus(200);
resp.setEnabled(enabled);
resp.setHasPassword(hasPassword);
return resp;
}
}
@@ -67,7 +67,8 @@ public class Net_StartEspPairing_Handler implements JsonMessageHandler {
String canonicalLogin = user.getLogin();
EspPairingSettingsEntry settings = EspPairingSettingsDAO.getInstance().getByLogin(canonicalLogin);
if (settings == null || !settings.isEnabled()) {
boolean enabled = settings == null || settings.isEnabled();
if (!enabled) {
return NetExceptionResponseFactory.error(req, 422, "PAIRING_NOT_AVAILABLE", "Для этого login pairing недоступен");
}
@@ -85,7 +86,9 @@ 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-запросов за короткое время");
}
String configuredPasswordHash = settings.getPasswordHash() == null ? "" : settings.getPasswordHash().trim();
String configuredPasswordHash = settings == null || settings.getPasswordHash() == null
? ""
: settings.getPasswordHash().trim();
boolean requiresPassword = !configuredPasswordHash.isBlank();
boolean suppliedPassword = passwordHash != null && !passwordHash.isBlank();
if ((requiresPassword && !configuredPasswordHash.equals(passwordHash))
@@ -94,7 +97,7 @@ public class Net_StartEspPairing_Handler implements JsonMessageHandler {
}
String clientPlatform = AuthSessionTypeSupport.normalizeClientPlatform(req.getRequesterClientPlatform());
int ttlSeconds = EspPairingSupport.normalizeTtlSeconds(settings.getTtlSeconds());
int ttlSeconds = EspPairingSupport.DEFAULT_TTL_SECONDS;
List<ConnectionContext> approverConnections = EspPairingSupport.findOnlineTrustedConnections(canonicalLogin);
if (approverConnections.isEmpty()) {
return NetExceptionResponseFactory.error(
@@ -143,7 +146,7 @@ public class Net_StartEspPairing_Handler implements JsonMessageHandler {
payload.put("fingerprintB58", entry.getFingerprintB58());
payload.put("createdAtMs", entry.getCreatedAtMs());
payload.put("expiresAtMs", entry.getExpiresAtMs());
delivered |= WsEventSender.sendEvent(targetCtx, "IncomingEspPairingRequest", eventId, payload);
delivered |= WsEventSender.sendEvent(targetCtx, "IncomingTrustedDeviceLoginRequest", eventId, payload);
}
if (delivered) {
EspPairingRequestsDAO.getInstance().updateDeliveryFlag(entry.getPairingId(), true, System.currentTimeMillis());
@@ -37,14 +37,12 @@ public class Net_UpsertEspPairingSettings_Handler implements JsonMessageHandler
"passwordHash должен быть пустым или иметь формат sha256$<64 hex>"
);
}
int ttlSeconds = EspPairingSupport.normalizeTtlSeconds(req.getTtlSeconds());
long now = System.currentTimeMillis();
EspPairingSettingsEntry entry = new EspPairingSettingsEntry();
entry.setLogin(ctx.getLogin());
entry.setEnabled(enabled);
entry.setPasswordHash(passwordHash == null ? "" : passwordHash);
entry.setTtlSeconds(ttlSeconds);
entry.setPasswordHash(enabled && passwordHash != null ? passwordHash : "");
entry.setTtlSeconds(EspPairingSupport.DEFAULT_TTL_SECONDS);
entry.setFailedAttempts(0);
entry.setFirstFailedAtMs(0L);
entry.setBlockedUntilMs(0L);
@@ -56,7 +54,7 @@ public class Net_UpsertEspPairingSettings_Handler implements JsonMessageHandler
resp.setRequestId(req.getRequestId());
resp.setStatus(WireCodes.Status.OK);
resp.setEnabled(enabled);
resp.setTtlSeconds(ttlSeconds);
resp.setHasPassword(enabled && passwordHash != null && !passwordHash.isBlank());
return resp;
}
}
@@ -0,0 +1,6 @@
package server.logic.ws_protocol.JSON.handlers.auth.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
public class Net_GetTrustedDeviceLoginSettings_Request extends Net_Request {
}
@@ -0,0 +1,24 @@
package server.logic.ws_protocol.JSON.handlers.auth.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
public class Net_GetTrustedDeviceLoginSettings_Response extends Net_Response {
private boolean enabled;
private boolean hasPassword;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isHasPassword() {
return hasPassword;
}
public void setHasPassword(boolean hasPassword) {
this.hasPassword = hasPassword;
}
}
@@ -4,7 +4,7 @@ import server.logic.ws_protocol.JSON.entyties.Net_Response;
public class Net_UpsertEspPairingSettings_Response extends Net_Response {
private boolean enabled;
private int ttlSeconds;
private boolean hasPassword;
public boolean isEnabled() {
return enabled;
@@ -14,11 +14,11 @@ public class Net_UpsertEspPairingSettings_Response extends Net_Response {
this.enabled = enabled;
}
public int getTtlSeconds() {
return ttlSeconds;
public boolean isHasPassword() {
return hasPassword;
}
public void setTtlSeconds(int ttlSeconds) {
this.ttlSeconds = ttlSeconds;
public void setHasPassword(boolean hasPassword) {
this.hasPassword = hasPassword;
}
}