SHA256
04 12 25
This commit is contained in:
@@ -22,6 +22,8 @@ dependencies {
|
||||
implementation 'ch.qos.logback:logback-classic:1.5.6'
|
||||
|
||||
implementation project(':shine-server-config') // модуль с настройками
|
||||
implementation project(':shine-server-crypto') // модуль сервера для работы с криптографией
|
||||
implementation project(':shine-server-blockchain') // модуль для работы с блокчейном
|
||||
implementation project(':shine-server-db') // модуль для работы с БД содержит и сущности из БД и саму работу с БД
|
||||
|
||||
}
|
||||
|
||||
+36
-63
@@ -1,5 +1,8 @@
|
||||
package server.logic.ws_protocol.JSON;
|
||||
|
||||
import shine.db.entities.SolanaUser;
|
||||
import shine.db.entities.ActiveSession;
|
||||
|
||||
/**
|
||||
* ConnectionContext — контекст состояния одного WebSocket-соединения.
|
||||
* Живёт ровно столько же, сколько живёт подключение.
|
||||
@@ -7,41 +10,49 @@ package server.logic.ws_protocol.JSON;
|
||||
public class ConnectionContext {
|
||||
|
||||
// Статусы аутентификации
|
||||
public static final int AUTH_STATUS_NONE = 0; // ананимный или не авторизованный пользователь
|
||||
public static final int AUTH_STATUS_NONE = 0; // анонимный или не авторизованный пользователь
|
||||
public static final int AUTH_STATUS_USER = 1; // авторизованный пользователь
|
||||
// public static final int AUTH_STATUS_ANON = 2; // анонимный (зарезервировано на будущее)
|
||||
|
||||
private String login;
|
||||
private Long loginId;
|
||||
// Полный пользователь из БД (solana_users)
|
||||
private SolanaUser solanaUser;
|
||||
|
||||
// Активная сессия из БД (active_sessions)
|
||||
private ActiveSession activeSession;
|
||||
|
||||
private Long sessionId;
|
||||
private String sessionPwd;
|
||||
|
||||
// Данные пользователя / блокчейна
|
||||
private Long bchId;
|
||||
private String pubkey0;
|
||||
private String pubkey1;
|
||||
private Integer bchLimit;
|
||||
|
||||
private int authenticationStatus = AUTH_STATUS_NONE;
|
||||
|
||||
// --- getters / setters ---
|
||||
// --- SolanaUser / ActiveSession ---
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
public SolanaUser getSolanaUser() {
|
||||
return solanaUser;
|
||||
}
|
||||
|
||||
public void setLogin(String login) {
|
||||
this.login = login;
|
||||
public void setSolanaUser(SolanaUser solanaUser) {
|
||||
this.solanaUser = solanaUser;
|
||||
}
|
||||
|
||||
public ActiveSession getActiveSession() {
|
||||
return activeSession;
|
||||
}
|
||||
|
||||
public void setActiveSession(ActiveSession activeSession) {
|
||||
this.activeSession = activeSession;
|
||||
}
|
||||
|
||||
// --- Удобные геттеры для логина ---
|
||||
|
||||
public String getLogin() {
|
||||
return solanaUser != null ? solanaUser.getLogin() : null;
|
||||
}
|
||||
|
||||
public Long getLoginId() {
|
||||
return loginId;
|
||||
return solanaUser != null ? solanaUser.getLoginId() : null;
|
||||
}
|
||||
|
||||
public void setLoginId(Long loginId) {
|
||||
this.loginId = loginId;
|
||||
}
|
||||
// --- sessionId / sessionPwd ---
|
||||
|
||||
public Long getSessionId() {
|
||||
return sessionId;
|
||||
@@ -59,37 +70,7 @@ public class ConnectionContext {
|
||||
this.sessionPwd = sessionPwd;
|
||||
}
|
||||
|
||||
public Long getBchId() {
|
||||
return bchId;
|
||||
}
|
||||
|
||||
public void setBchId(Long bchId) {
|
||||
this.bchId = bchId;
|
||||
}
|
||||
|
||||
public String getPubkey0() {
|
||||
return pubkey0;
|
||||
}
|
||||
|
||||
public void setPubkey0(String pubkey0) {
|
||||
this.pubkey0 = pubkey0;
|
||||
}
|
||||
|
||||
public String getPubkey1() {
|
||||
return pubkey1;
|
||||
}
|
||||
|
||||
public void setPubkey1(String pubkey1) {
|
||||
this.pubkey1 = pubkey1;
|
||||
}
|
||||
|
||||
public Integer getBchLimit() {
|
||||
return bchLimit;
|
||||
}
|
||||
|
||||
public void setBchLimit(Integer bchLimit) {
|
||||
this.bchLimit = bchLimit;
|
||||
}
|
||||
// --- auth status ---
|
||||
|
||||
public int getAuthenticationStatus() {
|
||||
return authenticationStatus;
|
||||
@@ -108,29 +89,21 @@ public class ConnectionContext {
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
login = null;
|
||||
loginId = null;
|
||||
solanaUser = null;
|
||||
activeSession = null;
|
||||
|
||||
sessionId = null;
|
||||
sessionPwd = null;
|
||||
|
||||
bchId = null;
|
||||
pubkey0 = null;
|
||||
pubkey1 = null;
|
||||
bchLimit = null;
|
||||
|
||||
authenticationStatus = AUTH_STATUS_NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConnectionContext{" +
|
||||
"login='" + login + '\'' +
|
||||
", loginId=" + loginId +
|
||||
"login='" + getLogin() + '\'' +
|
||||
", loginId=" + getLoginId() +
|
||||
", sessionId=" + sessionId +
|
||||
", bchId=" + bchId +
|
||||
", pubkey0='" + pubkey0 + '\'' +
|
||||
", pubkey1='" + pubkey1 + '\'' +
|
||||
", bchLimit=" + bchLimit +
|
||||
", authenticationStatus=" + authenticationStatus +
|
||||
'}';
|
||||
}
|
||||
|
||||
+6
-2
@@ -2,9 +2,11 @@ package server.logic.ws_protocol.JSON;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.*;
|
||||
import server.logic.ws_protocol.JSON.entyties.Auth.NetAuthSessionNewStep1Request;
|
||||
import server.logic.ws_protocol.JSON.entyties.Auth.NetAuthSessionNewStep2Request;
|
||||
import server.logic.ws_protocol.JSON.entyties.Auth.NetSessionRefreshRequest;
|
||||
import server.logic.ws_protocol.JSON.handlers.*;
|
||||
import server.logic.ws_protocol.JSON.entyties.tempToTest.NetAddUserRequest;
|
||||
import server.logic.ws_protocol.JSON.handlers.auth.NetAuthSessionNewStep2Handler;
|
||||
import server.logic.ws_protocol.JSON.handlers.tempToTest.NetAddUserHandler;
|
||||
import server.logic.ws_protocol.JSON.handlers.auth.NetAuthSessionNewStep1Handler;
|
||||
import server.logic.ws_protocol.JSON.handlers.auth.NetSessionRefreshHandler;
|
||||
@@ -25,14 +27,16 @@ public final class JsonHandlerRegistry {
|
||||
private static final Map<String, JsonMessageHandler> HANDLERS = Map.of(
|
||||
"SessionRefresh", new NetSessionRefreshHandler(),
|
||||
"AddUser", new NetAddUserHandler(),
|
||||
"AuthSessionNewStep1", new NetAuthSessionNewStep1Handler()
|
||||
"AuthSessionNewStep1", new NetAuthSessionNewStep1Handler(),
|
||||
"AuthSessionNewStep2", new NetAuthSessionNewStep2Handler()
|
||||
// сюда потом добавишь другие операции
|
||||
);
|
||||
|
||||
private static final Map<String, Class<? extends NetRequest>> REQUEST_TYPES = Map.of(
|
||||
"SessionRefresh", NetSessionRefreshRequest.class,
|
||||
"AddUser", NetAddUserRequest.class,
|
||||
"AuthSessionNewStep1", NetAuthSessionNewStep1Request.class
|
||||
"AuthSessionNewStep1", NetAuthSessionNewStep1Request.class,
|
||||
"AuthSessionNewStep2", NetAuthSessionNewStep2Request.class
|
||||
);
|
||||
|
||||
private JsonHandlerRegistry() {
|
||||
|
||||
+99
-64
@@ -1,78 +1,95 @@
|
||||
package server.logic.ws_protocol.JSON;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.JSON.entyties.NetExceptionResponse;
|
||||
import server.logic.ws_protocol.JSON.entyties.NetRequest;
|
||||
import server.logic.ws_protocol.JSON.entyties.NetResponse;
|
||||
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JsonInboundProcessor — отдельный класс для обработки JSON-сообщений.
|
||||
* JsonInboundProcessor — обработка JSON-сообщений.
|
||||
*
|
||||
* 1) Парсит общий пакет (op, requestId, payload).
|
||||
* 1) Парсит общий пакет (op, requestId,...).
|
||||
* 2) По op выбирает класс запроса и хэндлер.
|
||||
* 3) Маппит JSON → NetRequest через ObjectMapper.
|
||||
* 4) Вызывает хэндлер, получает NetResponse.
|
||||
* 5) Собирает JSON-ответ и возвращает строкой.
|
||||
* 5) Собирает JSON-ответ:
|
||||
* {
|
||||
* "op": ...,
|
||||
* "requestId": ...,
|
||||
* "status": ...,
|
||||
* "payload": { все поля response, кроме op/requestId/status/payload }
|
||||
* }
|
||||
*/
|
||||
public final class JsonInboundProcessor {
|
||||
private static final Logger log = LoggerFactory.getLogger(JsonInboundProcessor.class);
|
||||
|
||||
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
|
||||
private static final ObjectMapper JSON_MAPPER = new ObjectMapper()
|
||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
|
||||
/**
|
||||
* op → хэндлер.
|
||||
* Регистрация вынесена в JsonHandlerRegistry.
|
||||
*/
|
||||
private static final Map<String, JsonMessageHandler> JSON_HANDLERS =
|
||||
JsonHandlerRegistry.getHandlers();
|
||||
|
||||
/**
|
||||
* op → класс запроса.
|
||||
*/
|
||||
private static final Map<String, Class<? extends NetRequest>> JSON_REQUEST_TYPES =
|
||||
JsonHandlerRegistry.getRequestTypes();
|
||||
|
||||
private JsonInboundProcessor() {}
|
||||
|
||||
/**
|
||||
* Обработка входящего JSON-сообщения.
|
||||
*
|
||||
* @param json исходная строка от клиента
|
||||
* @param ctx контекст текущего WebSocket-соединения
|
||||
* @return JSON-строка ответа
|
||||
*/
|
||||
public static String processJson(String json, ConnectionContext ctx) {
|
||||
String op = null;
|
||||
String requestId = null;
|
||||
|
||||
try {
|
||||
if (json == null || json.isBlank()) {
|
||||
return buildErrorJson(null, null, WireCodes.Status.BAD_REQUEST,
|
||||
"EMPTY_JSON", "Пустое JSON-сообщение");
|
||||
NetExceptionResponse err = NetExceptionResponseFactory.error(
|
||||
null,
|
||||
null,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"EMPTY_JSON",
|
||||
"Пустое JSON-сообщение"
|
||||
);
|
||||
return writeResponse(err);
|
||||
}
|
||||
|
||||
// 1. Парсим общий пакет
|
||||
JsonNode root = JSON_MAPPER.readTree(json);
|
||||
|
||||
// 2. op и requestId
|
||||
String op = getTextOrNull(root, "op");
|
||||
if (op == null || op.isEmpty()) {
|
||||
return buildErrorJson(null, null, WireCodes.Status.BAD_REQUEST,
|
||||
"NO_OP", "Поле 'op' отсутствует или пустое");
|
||||
}
|
||||
op = getTextOrNull(root, "op");
|
||||
requestId = getTextOrNull(root, "requestId");
|
||||
|
||||
String requestId = getTextOrNull(root, "requestId");
|
||||
if (op == null || op.isEmpty()) {
|
||||
NetExceptionResponse err = NetExceptionResponseFactory.error(
|
||||
null,
|
||||
requestId,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"NO_OP",
|
||||
"Поле 'op' отсутствует или пустое"
|
||||
);
|
||||
return writeResponse(err);
|
||||
}
|
||||
|
||||
JsonMessageHandler handler = JSON_HANDLERS.get(op);
|
||||
Class<? extends NetRequest> reqClass = JSON_REQUEST_TYPES.get(op);
|
||||
|
||||
if (handler == null || reqClass == null) {
|
||||
return buildErrorJson(op, requestId, WireCodes.Status.BAD_REQUEST,
|
||||
"UNKNOWN_OP", "Неизвестная операция: " + op);
|
||||
NetExceptionResponse err = NetExceptionResponseFactory.error(
|
||||
op,
|
||||
requestId,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"UNKNOWN_OP",
|
||||
"Неизвестная операция: " + op
|
||||
);
|
||||
return writeResponse(err);
|
||||
}
|
||||
|
||||
// 3. Маппим JSON → нужный NetRequest
|
||||
@@ -80,43 +97,42 @@ public final class JsonInboundProcessor {
|
||||
|
||||
NetResponse response;
|
||||
|
||||
// 4. Трай-кэтч вокруг хэндлера (важно!)
|
||||
// 4. Трай-кэтч вокруг хэндлера
|
||||
try {
|
||||
response = handler.handle(request, ctx);
|
||||
} catch (Exception handlerError) {
|
||||
log.error("💥 Ошибка внутри хэндлера '{}'", op, handlerError);
|
||||
return buildErrorJson(op, requestId,
|
||||
NetExceptionResponse err = NetExceptionResponseFactory.error(
|
||||
op,
|
||||
requestId,
|
||||
WireCodes.Status.INTERNAL_ERROR,
|
||||
"INTERNAL_HANDLER_ERROR",
|
||||
"Неожиданная ошибка при обработке операции: " + op);
|
||||
"Неожиданная ошибка при обработке операции: " + op
|
||||
);
|
||||
return writeResponse(err);
|
||||
}
|
||||
|
||||
// Если хэндлер не выставил op/requestId
|
||||
// На всякий случай: если хэндлер не выставил op/requestId
|
||||
if (response.getOp() == null) response.setOp(op);
|
||||
if (response.getRequestId() == null) response.setRequestId(requestId);
|
||||
|
||||
// 5. Формируем JSON
|
||||
ObjectNode out = JSON_MAPPER.createObjectNode();
|
||||
out.put("op", response.getOp());
|
||||
out.put("requestId", response.getRequestId());
|
||||
out.put("status", response.getStatus());
|
||||
|
||||
if (response.getPayload() != null) {
|
||||
out.set("payload", JSON_MAPPER.valueToTree(response.getPayload()));
|
||||
} else {
|
||||
out.putNull("payload");
|
||||
}
|
||||
|
||||
return JSON_MAPPER.writeValueAsString(out);
|
||||
// 5. Универсальная сборка ответа
|
||||
return writeResponse(response);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Ошибка при обработке JSON-сообщения", e);
|
||||
return buildErrorJson("Unknown", null, WireCodes.Status.INTERNAL_ERROR,
|
||||
"INTERNAL_ERROR", "Внутренняя ошибка сервера");
|
||||
NetExceptionResponse err = NetExceptionResponseFactory.error(
|
||||
op != null ? op : "Unknown",
|
||||
requestId,
|
||||
WireCodes.Status.INTERNAL_ERROR,
|
||||
"INTERNAL_ERROR",
|
||||
"Внутренняя ошибка сервера"
|
||||
);
|
||||
return writeResponse(err);
|
||||
}
|
||||
}
|
||||
|
||||
// --- helper'ы ---
|
||||
// --- helpers ---
|
||||
|
||||
private static String getTextOrNull(JsonNode node, String field) {
|
||||
if (node == null || !node.has(field) || node.get(field).isNull()) return null;
|
||||
@@ -124,32 +140,51 @@ public final class JsonInboundProcessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация JSON-ошибки
|
||||
* Унифицированная сериализация любого NetResponse в формат:
|
||||
* {
|
||||
* "op": ...,
|
||||
* "requestId": ...,
|
||||
* "status": ...,
|
||||
* "payload": { ... }
|
||||
* }
|
||||
*/
|
||||
private static String buildErrorJson(String op,
|
||||
String requestId,
|
||||
int status,
|
||||
String errorCode,
|
||||
String errorMessage) {
|
||||
private static String writeResponse(NetResponse response) {
|
||||
try {
|
||||
ObjectNode root = JSON_MAPPER.createObjectNode();
|
||||
// Конвертируем полный объект ответа в ObjectNode
|
||||
ObjectNode full = JSON_MAPPER.convertValue(response, ObjectNode.class);
|
||||
|
||||
// То, что должно остаться наверху:
|
||||
String op = full.hasNonNull("op") ? full.get("op").asText() : null;
|
||||
String requestId = full.hasNonNull("requestId") ? full.get("requestId").asText() : null;
|
||||
int status = full.hasNonNull("status") ? full.get("status").asInt() : 0;
|
||||
|
||||
// Удаляем базовые поля и payload из "полного" объекта,
|
||||
// всё остальное отправляем внутрь payload.
|
||||
full.remove("op");
|
||||
full.remove("requestId");
|
||||
full.remove("status");
|
||||
full.remove("payload");
|
||||
|
||||
ObjectNode root = JSON_MAPPER.createObjectNode();
|
||||
if (op != null) root.put("op", op); else root.putNull("op");
|
||||
if (requestId != null) root.put("requestId", requestId); else root.putNull("requestId");
|
||||
|
||||
root.put("status", status);
|
||||
|
||||
ObjectNode payload = root.putObject("payload");
|
||||
payload.put("code", errorCode);
|
||||
payload.put("message", errorMessage);
|
||||
// payload — это всё, что осталось от full (может быть пустым объектом {})
|
||||
root.set("payload", full);
|
||||
|
||||
return JSON_MAPPER.writeValueAsString(root);
|
||||
} catch (Exception e) {
|
||||
return "{\"op\":\"" + (op != null ? op : "") +
|
||||
"\",\"requestId\":\"" + (requestId != null ? requestId : "") +
|
||||
"\",\"status\":" + status +
|
||||
",\"payload\":{\"code\":\"" + errorCode +
|
||||
"\",\"message\":\"" + errorMessage + "\"}}";
|
||||
// Совсем аварийный случай — сериализация ответа сломалась.
|
||||
return "{\"op\":\"" + safe(response.getOp()) +
|
||||
"\",\"requestId\":\"" + safe(response.getRequestId()) +
|
||||
"\",\"status\":" + response.getStatus() +
|
||||
",\"payload\":{\"code\":\"SERIALIZATION_ERROR\",\"message\":\"" +
|
||||
"Ошибка сериализации ответа\"}}";
|
||||
}
|
||||
}
|
||||
|
||||
private static String safe(String s) {
|
||||
return s != null ? s : "";
|
||||
}
|
||||
}
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package server.logic.ws_protocol.JSON.entyties.Auth;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.NetRequest;
|
||||
|
||||
/**
|
||||
* Шаг 2 авторизации: клиент подтверждает владение ключом.
|
||||
*
|
||||
* JSON:
|
||||
* {
|
||||
* "op": "AuthSessionNewStep2",
|
||||
* "requestId": "...",
|
||||
* "loginId": 100211,
|
||||
* "sigNum": 0, // номер подписи: 0 или 1
|
||||
* "timeMs": 1733310000000, // время в миллисекундах с 1970-01-01
|
||||
* "signatureB64": "..." // подпись base64 от строки loginId+timeMs+sessionPwd
|
||||
* }
|
||||
*/
|
||||
public class NetAuthSessionNewStep2Request extends NetRequest {
|
||||
|
||||
private long loginId;
|
||||
private int sigNum; // 0 или 1
|
||||
private long timeMs; // миллисекунды с 1970
|
||||
private String signatureB64;
|
||||
|
||||
public long getLoginId() {
|
||||
return loginId;
|
||||
}
|
||||
|
||||
public void setLoginId(long loginId) {
|
||||
this.loginId = loginId;
|
||||
}
|
||||
|
||||
public int getSigNum() {
|
||||
return sigNum;
|
||||
}
|
||||
|
||||
public void setSigNum(int sigNum) {
|
||||
this.sigNum = sigNum;
|
||||
}
|
||||
|
||||
public long getTimeMs() {
|
||||
return timeMs;
|
||||
}
|
||||
|
||||
public void setTimeMs(long timeMs) {
|
||||
this.timeMs = timeMs;
|
||||
}
|
||||
|
||||
public String getSignatureB64() {
|
||||
return signatureB64;
|
||||
}
|
||||
|
||||
public void setSignatureB64(String signatureB64) {
|
||||
this.signatureB64 = signatureB64;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package server.logic.ws_protocol.JSON.entyties.Auth;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.NetResponse;
|
||||
|
||||
/**
|
||||
* Ответ на AuthSessionNewStep2.
|
||||
*
|
||||
* Успешный JSON:
|
||||
* {
|
||||
* "op": "AuthSessionNewStep2",
|
||||
* "requestId": "...",
|
||||
* "status": 200,
|
||||
* "payload": {
|
||||
* "sessionId": 1234567890
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
public class NetAuthSessionNewStep2Response extends NetResponse {
|
||||
|
||||
private Long sessionId;
|
||||
|
||||
public Long getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(Long sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
}
|
||||
+20
-2
@@ -3,12 +3,30 @@ package server.logic.ws_protocol.JSON.entyties;
|
||||
/**
|
||||
* Ответ с ошибкой (любой отказ).
|
||||
*
|
||||
* В payload лежит:
|
||||
* В payload будет:
|
||||
* {
|
||||
* "code": "...",
|
||||
* "message": "..."
|
||||
* }
|
||||
*/
|
||||
public class NetExceptionResponse extends NetResponse {
|
||||
// Ничего дополнительного: код/текст ошибки лежат в payload (Map или DTO).
|
||||
|
||||
private String code;
|
||||
private String message;
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-10
@@ -11,7 +11,6 @@ import shine.db.dao.SolanaUsersDAO;
|
||||
import shine.db.entities.SolanaUser;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Map;
|
||||
|
||||
public class NetAuthSessionNewStep1Handler implements JsonMessageHandler {
|
||||
|
||||
@@ -55,16 +54,10 @@ public class NetAuthSessionNewStep1Handler implements JsonMessageHandler {
|
||||
);
|
||||
}
|
||||
|
||||
// 3) Заполняем контекст полями пользователя
|
||||
ctx.setLogin(solanaUser.getLogin());
|
||||
ctx.setLoginId(solanaUser.getLoginId());
|
||||
ctx.setBchId(solanaUser.getBchId());
|
||||
ctx.setPubkey0(solanaUser.getPubkey0());
|
||||
ctx.setPubkey1(solanaUser.getPubkey1());
|
||||
ctx.setBchLimit(solanaUser.getBchLimit());
|
||||
// 3) Заполняем контекст целиком пользователем
|
||||
ctx.setSolanaUser(solanaUser);
|
||||
|
||||
// 4) Генерируем надёжный sessionPwd
|
||||
// SecureRandom + время → достаточно
|
||||
String sessionPwd = Long.toHexString(System.nanoTime()) +
|
||||
Long.toHexString(RANDOM.nextLong());
|
||||
|
||||
@@ -75,7 +68,7 @@ public class NetAuthSessionNewStep1Handler implements JsonMessageHandler {
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
resp.setPayload(Map.of("sessionPwd", sessionPwd));
|
||||
resp.setSessionPwd(sessionPwd); // 🔴 Больше не трогаем payload
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.auth;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
import server.logic.ws_protocol.JSON.entyties.NetRequest;
|
||||
import server.logic.ws_protocol.JSON.entyties.NetResponse;
|
||||
import server.logic.ws_protocol.JSON.entyties.Auth.NetAuthSessionNewStep2Request;
|
||||
import server.logic.ws_protocol.JSON.entyties.Auth.NetAuthSessionNewStep2Response;
|
||||
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import shine.db.dao.ActiveSessionsDAO;
|
||||
import shine.db.entities.ActiveSession;
|
||||
import shine.db.entities.SolanaUser;
|
||||
import utils.crypto.Ed25519Util;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Base64;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* Шаг 2 авторизации: проверка подписи и создание сессии.
|
||||
*
|
||||
* Клиент присылает:
|
||||
* - loginId
|
||||
* - sigNum (0 или 1)
|
||||
* - timeMs
|
||||
* - signatureB64 от строки (loginId + timeMs + sessionPwd)
|
||||
*/
|
||||
public class NetAuthSessionNewStep2Handler implements JsonMessageHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(NetAuthSessionNewStep2Handler.class);
|
||||
|
||||
@Override
|
||||
public NetResponse handle(NetRequest baseReq, ConnectionContext ctx) throws Exception {
|
||||
NetAuthSessionNewStep2Request req = (NetAuthSessionNewStep2Request) baseReq;
|
||||
|
||||
// --- базовые проверки контекста ---
|
||||
if (ctx == null || ctx.getSolanaUser() == null || ctx.getSessionPwd() == null) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"NO_STEP1_CONTEXT",
|
||||
"Шаг 1 авторизации не был корректно выполнен для данного соединения"
|
||||
);
|
||||
}
|
||||
|
||||
if (!ctx.isAnonymous()) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"ALREADY_AUTHED",
|
||||
"Пользователь уже авторизован по текущему соединению"
|
||||
);
|
||||
}
|
||||
|
||||
SolanaUser user = ctx.getSolanaUser();
|
||||
long reqLoginId = req.getLoginId();
|
||||
Long ctxLoginId = user.getLoginId();
|
||||
|
||||
if (ctxLoginId == null || ctxLoginId != reqLoginId) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.UNVERIFIED,
|
||||
"LOGIN_ID_MISMATCH",
|
||||
"loginId в запросе не совпадает с пользователем из шага 1"
|
||||
);
|
||||
}
|
||||
|
||||
int sigNum = req.getSigNum();
|
||||
if (sigNum != 0 && sigNum != 1) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"BAD_SIG_NUM",
|
||||
"Номер подписи должен быть 0 или 1"
|
||||
);
|
||||
}
|
||||
|
||||
String signatureB64 = req.getSignatureB64();
|
||||
if (signatureB64 == null || signatureB64.isBlank()) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"EMPTY_SIGNATURE",
|
||||
"Пустая цифровая подпись"
|
||||
);
|
||||
}
|
||||
|
||||
// --- выбираем публичный ключ по sigNum ---
|
||||
String pubKeyB64 = (sigNum == 0) ? user.getPubkey0() : user.getPubkey1();
|
||||
if (pubKeyB64 == null || pubKeyB64.isBlank()) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"NO_PUBKEY",
|
||||
"Отсутствует публичный ключ для выбранного номера подписи"
|
||||
);
|
||||
}
|
||||
|
||||
byte[] publicKey32;
|
||||
byte[] signature64;
|
||||
try {
|
||||
publicKey32 = Ed25519Util.keyFromBase64(pubKeyB64);
|
||||
signature64 = Base64.getDecoder().decode(signatureB64);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"BAD_BASE64",
|
||||
"Некорректный формат Base64 для ключа или подписи"
|
||||
);
|
||||
}
|
||||
|
||||
// --- собираем строку для подписи: loginId + timeMs + sessionPwd ---
|
||||
long timeMs = req.getTimeMs();
|
||||
String preimageStr = String.valueOf(reqLoginId) + timeMs + ctx.getSessionPwd();
|
||||
byte[] preimage = preimageStr.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
boolean sigOk = Ed25519Util.verify(preimage, signature64, publicKey32);
|
||||
if (!sigOk) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.UNVERIFIED,
|
||||
"BAD_SIGNATURE",
|
||||
"Подпись не прошла проверку"
|
||||
);
|
||||
}
|
||||
|
||||
// --- создаём уникальный sessionId и записываем в БД ---
|
||||
ActiveSessionsDAO dao = ActiveSessionsDAO.getInstance();
|
||||
long sessionId;
|
||||
ActiveSession activeSession;
|
||||
|
||||
try {
|
||||
sessionId = generateUniqueSessionId(dao);
|
||||
long nowMs = System.currentTimeMillis();
|
||||
|
||||
activeSession = new ActiveSession(
|
||||
sessionId,
|
||||
ctx.getSessionPwd(),
|
||||
reqLoginId,
|
||||
nowMs,
|
||||
(short) sigNum, // pubkeyNum
|
||||
null, // pushEndpoint
|
||||
null, // pushP256dhKey
|
||||
null // pushAuthKey
|
||||
);
|
||||
|
||||
dao.insert(activeSession);
|
||||
} catch (SQLException e) {
|
||||
log.error("Ошибка БД при создании новой сессии для loginId={}", reqLoginId, e);
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.SERVER_DATA_ERROR,
|
||||
"DB_ERROR_SESSION_CREATE",
|
||||
"Ошибка БД при создании сессии"
|
||||
);
|
||||
}
|
||||
|
||||
// --- обновляем контекст ---
|
||||
ctx.setActiveSession(activeSession);
|
||||
ctx.setSessionId(sessionId);
|
||||
ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_USER);
|
||||
|
||||
// --- формируем ответ ---
|
||||
NetAuthSessionNewStep2Response resp = new NetAuthSessionNewStep2Response();
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
resp.setSessionId(sessionId); // попадёт в payload.sessionId
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация уникального sessionId с проверкой в БД.
|
||||
*/
|
||||
private long generateUniqueSessionId(ActiveSessionsDAO dao) throws SQLException {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
long candidate = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE);
|
||||
ActiveSession existing = dao.getBySessionId(candidate);
|
||||
if (existing == null) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
throw new SQLException("Не удалось сгенерировать уникальный sessionId за разумное число попыток");
|
||||
}
|
||||
}
|
||||
+40
-14
@@ -1,5 +1,7 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.auth;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
import server.logic.ws_protocol.JSON.entyties.NetRequest;
|
||||
import server.logic.ws_protocol.JSON.entyties.NetResponse;
|
||||
@@ -9,23 +11,19 @@ import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import shine.db.dao.ActiveSessionsDAO;
|
||||
import shine.db.dao.SolanaUsersDAO;
|
||||
import shine.db.entities.ActiveSession;
|
||||
import shine.db.entities.SolanaUser;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Хэндлер SessionRefresh.
|
||||
*
|
||||
* Логика:
|
||||
* - берём sessionId и sessionPwd из запроса;
|
||||
* - ищем сессию в БД;
|
||||
* - если не нашли или пароль не совпал → NetExceptionResponse;
|
||||
* - если всё ок:
|
||||
* * обновляем ConnectionContext (sessionId, sessionPwd, статус USER);
|
||||
* * возвращаем NetSessionRefreshResponse со статусом 200.
|
||||
*/
|
||||
public class NetSessionRefreshHandler implements JsonMessageHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(NetSessionRefreshHandler.class);
|
||||
|
||||
@Override
|
||||
public NetResponse handle(NetRequest request, ConnectionContext ctx) throws Exception {
|
||||
NetSessionRefreshRequest req = (NetSessionRefreshRequest) request;
|
||||
@@ -42,12 +40,12 @@ public class NetSessionRefreshHandler implements JsonMessageHandler {
|
||||
);
|
||||
}
|
||||
|
||||
ActiveSessionsDAO dao = ActiveSessionsDAO.getInstance();
|
||||
ActiveSessionsDAO sessionsDao = ActiveSessionsDAO.getInstance();
|
||||
ActiveSession session;
|
||||
try {
|
||||
session = dao.getBySessionId(sessionId);
|
||||
session = sessionsDao.getBySessionId(sessionId);
|
||||
} catch (SQLException e) {
|
||||
// Ошибка БД → внутренняя ошибка сервера
|
||||
log.error("Ошибка БД при поиске сессии sessionId={}", sessionId, e);
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.SERVER_DATA_ERROR,
|
||||
@@ -75,20 +73,48 @@ public class NetSessionRefreshHandler implements JsonMessageHandler {
|
||||
);
|
||||
}
|
||||
|
||||
// --- достаём пользователя по loginId из сессии ---
|
||||
SolanaUser solanaUser = null;
|
||||
Long loginId = null;
|
||||
try {
|
||||
loginId = session.getLoginId();
|
||||
if (loginId != null) {
|
||||
SolanaUsersDAO usersDao = SolanaUsersDAO.getInstance();
|
||||
solanaUser = usersDao.getByLoginId(loginId);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error("Ошибка БД при поиске пользователя по loginId={} из сессии", loginId, e);
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.SERVER_DATA_ERROR,
|
||||
"DB_ERROR_USER_LOOKUP",
|
||||
"Ошибка доступа к базе данных при получении пользователя для сессии"
|
||||
);
|
||||
}
|
||||
|
||||
if (loginId != null && solanaUser == null) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.UNVERIFIED,
|
||||
"USER_NOT_FOUND_FOR_SESSION",
|
||||
"Пользователь для данной сессии не найден"
|
||||
);
|
||||
}
|
||||
|
||||
// Всё хорошо — обновляем контекст соединения
|
||||
if (ctx != null) {
|
||||
ctx.setActiveSession(session);
|
||||
ctx.setSolanaUser(solanaUser);
|
||||
ctx.setSessionId(sessionId);
|
||||
ctx.setSessionPwd(sessionPwd);
|
||||
// Если потом добавишь в ActiveSession login / loginId — можно здесь и их проставлять
|
||||
ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_USER);
|
||||
}
|
||||
|
||||
// И возвращаем OK без доп. данных
|
||||
// И возвращаем OK без доп. полей (payload будет {}).
|
||||
NetSessionRefreshResponse resp = new NetSessionRefreshResponse();
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
resp.setPayload(null); // или Map.of("ok", true)
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-8
@@ -16,13 +16,7 @@ import shine.db.entities.SolanaUser;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Временный Хэндлер AddUser. Используется для тестовой регистрации!!!!!!!!
|
||||
*
|
||||
* Логика:
|
||||
* - берём login, loginId, bchId, pubkey0, pubkey1, bchLimit;
|
||||
* - создаём SolanaUser и вставляем через SolanaUsersDAO;
|
||||
* - если всё ОК → NetAddUserResponse со статусом 200;
|
||||
* - если ошибка БД или некорректные данные → NetExceptionResponse.
|
||||
* Временный хэндлер AddUser (тестовая регистрация).
|
||||
*/
|
||||
public class NetAddUserHandler implements JsonMessageHandler {
|
||||
|
||||
@@ -64,7 +58,7 @@ public class NetAddUserHandler implements JsonMessageHandler {
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
resp.setPayload(null); // можно поставить Map.of("ok", true)
|
||||
// payload сам станет {} через JsonInboundProcessor
|
||||
log.info("✅ Пользователь добавлен: login={}, loginId={}", req.getLogin(), req.getLoginId());
|
||||
return resp;
|
||||
|
||||
|
||||
+20
-8
@@ -1,12 +1,8 @@
|
||||
package server.logic.ws_protocol.JSON.utils;
|
||||
|
||||
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.NetExceptionResponse;
|
||||
import server.logic.ws_protocol.JSON.entyties.NetRequest;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Фабрика ошибок для JSON-протокола.
|
||||
* Создаёт единообразные NetExceptionResponse.
|
||||
@@ -26,11 +22,27 @@ public final class NetExceptionResponseFactory {
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(status);
|
||||
resp.setPayload(Map.of(
|
||||
"code", code,
|
||||
"message", message
|
||||
));
|
||||
resp.setCode(code);
|
||||
resp.setMessage(message);
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Вариант для случаев, когда NetRequest ещё не распарсен,
|
||||
* но мы уже знаем op и requestId (или они null).
|
||||
*/
|
||||
public static NetExceptionResponse error(String op,
|
||||
String requestId,
|
||||
int status,
|
||||
String code,
|
||||
String message) {
|
||||
|
||||
NetExceptionResponse resp = new NetExceptionResponse();
|
||||
resp.setOp(op);
|
||||
resp.setRequestId(requestId);
|
||||
resp.setStatus(status);
|
||||
resp.setCode(code);
|
||||
resp.setMessage(message);
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user