This commit is contained in:
AidarKC
2025-12-05 10:56:34 +03:00
parent 5d8dd86c96
commit fc748a744c
23 changed files with 1069 additions and 307 deletions
+2
View File
@@ -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') // модуль для работы с БД содержит и сущности из БД и саму работу с БД
}
@@ -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 +
'}';
}
@@ -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() {
@@ -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 : "";
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
@@ -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 за разумное число попыток");
}
}
@@ -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;
}
}
@@ -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;
@@ -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;
}
}