05 01 25
Запрос для работы с параметрами пользователя работают!! И тесты на них проходят!!
This commit is contained in:
parent
eb122456ab
commit
a6a5089379
@ -14,9 +14,14 @@ import java.util.List;
|
|||||||
* - методы с Connection НЕ закрывают соединение
|
* - методы с Connection НЕ закрывают соединение
|
||||||
* - методы без Connection сами открывают и закрывают соединение
|
* - методы без Connection сами открывают и закрывают соединение
|
||||||
*
|
*
|
||||||
* ВАЖНО по логике времени:
|
* ЛОГИКА time_ms:
|
||||||
* - сам DAO делает "технический upsert"
|
* - БД принимает запись только если она "новее" (time_ms строго больше текущего).
|
||||||
* - правила "не принимать более старый time_ms" должны проверяться в handler-е, в транзакции.
|
* - Реализовано атомарно одним SQL: UPSERT + WHERE users_params.time_ms < excluded.time_ms
|
||||||
|
*
|
||||||
|
* Возврат результата:
|
||||||
|
* - upsertIfNewer(...) возвращает количество изменённых строк:
|
||||||
|
* 1 = вставили/обновили
|
||||||
|
* 0 = проигнорировали (запись уже новее или равная)
|
||||||
*/
|
*/
|
||||||
public final class UserParamsDAO {
|
public final class UserParamsDAO {
|
||||||
|
|
||||||
@ -34,10 +39,14 @@ public final class UserParamsDAO {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------- UPSERT --------------------
|
// -------------------- UPSERT (IF NEWER) --------------------
|
||||||
|
|
||||||
/** UPSERT с внешним соединением. Соединение НЕ закрывает. */
|
/**
|
||||||
public void upsert(Connection c, UserParamEntry e) throws SQLException {
|
* Атомарный UPSERT "только если новее".
|
||||||
|
*
|
||||||
|
* @return 1 если вставили/обновили; 0 если запись не тронули (existing.time_ms >= incoming.time_ms).
|
||||||
|
*/
|
||||||
|
public int upsertIfNewer(Connection c, UserParamEntry e) throws SQLException {
|
||||||
String sql = """
|
String sql = """
|
||||||
INSERT INTO users_params (
|
INSERT INTO users_params (
|
||||||
login,
|
login,
|
||||||
@ -53,6 +62,7 @@ public final class UserParamsDAO {
|
|||||||
value = excluded.value,
|
value = excluded.value,
|
||||||
device_key = excluded.device_key,
|
device_key = excluded.device_key,
|
||||||
signature = excluded.signature
|
signature = excluded.signature
|
||||||
|
WHERE users_params.time_ms < excluded.time_ms
|
||||||
""";
|
""";
|
||||||
|
|
||||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||||
@ -67,14 +77,14 @@ public final class UserParamsDAO {
|
|||||||
if (e.getSignature() != null) ps.setString(6, e.getSignature());
|
if (e.getSignature() != null) ps.setString(6, e.getSignature());
|
||||||
else ps.setNull(6, Types.VARCHAR);
|
else ps.setNull(6, Types.VARCHAR);
|
||||||
|
|
||||||
ps.executeUpdate();
|
return ps.executeUpdate(); // 1 или 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** UPSERT без внешнего соединения. Сам открывает/закрывает. */
|
/** То же самое, но сам открывает/закрывает соединение. */
|
||||||
public void upsert(UserParamEntry e) throws SQLException {
|
public int upsertIfNewer(UserParamEntry e) throws SQLException {
|
||||||
try (Connection c = db.getConnection()) {
|
try (Connection c = db.getConnection()) {
|
||||||
upsert(c, e);
|
return upsertIfNewer(c, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,32 +21,22 @@ import utils.crypto.Ed25519Util;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Net_UpsertUserParam_Handler
|
* Net_UpsertUserParam_Handler
|
||||||
*
|
*
|
||||||
* Делает:
|
* Делает (MVP, без "сессий"):
|
||||||
* 1) Проверяет, что пользователь существует и что device_key действительно его.
|
* 1) Проверка входных полей.
|
||||||
* 2) Проверяет, что нет "более нового" значения этого param (time_ms монотонно растёт).
|
* 2) Проверка подписи Ed25519 по device_key.
|
||||||
* 3) Проверяет подпись Ed25519 по device_key.
|
* 3) Проверка, что пользователь существует и что device_key принадлежит этому login.
|
||||||
* 4) Пишет в БД только если time_ms строго больше текущего сохранённого.
|
* 4) Атомарная запись в БД "только если time_ms новее" (UPSERT + WHERE).
|
||||||
*
|
*
|
||||||
* БОЛЬШОЙ КОММЕНТ ПРО АВТОРИЗАЦИЮ НА БУДУЩЕЕ:
|
* ВАЖНО:
|
||||||
* ---------------------------------------------------------------------------------
|
* - НИКАКИХ ручных транзакций / BEGIN здесь нет.
|
||||||
* Сейчас (MVP) этот эндпоинт намеренно не делает полноценную "сессию/авторизацию",
|
* - autoCommit=true, каждый statement завершённый сам по себе.
|
||||||
* потому что целостность обеспечивается криптографией: сервер проверяет подпись
|
* - Гонки не страшны: если за время проверок кто-то записал более новый time_ms,
|
||||||
* и то, что device_key принадлежит login.
|
* наш финальный UPSERT просто вернёт 0 обновлённых строк.
|
||||||
*
|
|
||||||
* В будущем, если понадобится "ограничить кто может писать параметры", можно добавить:
|
|
||||||
* - проверку активной сессии (active_sessions) и соответствие login в сессии;
|
|
||||||
* - rate-limit на пользователя;
|
|
||||||
* - отдельные права на запись конкретных param.
|
|
||||||
*
|
|
||||||
* Но возможно это вообще не потребуется, если модель безопасности строится
|
|
||||||
* строго на подписи и владении device_key (как сейчас).
|
|
||||||
* ---------------------------------------------------------------------------------
|
|
||||||
*/
|
*/
|
||||||
public class Net_UpsertUserParam_Handler implements JsonMessageHandler {
|
public class Net_UpsertUserParam_Handler implements JsonMessageHandler {
|
||||||
|
|
||||||
@ -79,6 +69,7 @@ public class Net_UpsertUserParam_Handler implements JsonMessageHandler {
|
|||||||
final String signatureB64 = req.getSignature().trim();
|
final String signatureB64 = req.getSignature().trim();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// ---------------- Base64 decode ----------------
|
||||||
byte[] pubKey32;
|
byte[] pubKey32;
|
||||||
byte[] sig64;
|
byte[] sig64;
|
||||||
try {
|
try {
|
||||||
@ -110,6 +101,7 @@ public class Net_UpsertUserParam_Handler implements JsonMessageHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------- Signature verify ----------------
|
||||||
String signText = ShineSignatureConstants.USER_PARAMETER_PREFIX
|
String signText = ShineSignatureConstants.USER_PARAMETER_PREFIX
|
||||||
+ login
|
+ login
|
||||||
+ param
|
+ param
|
||||||
@ -128,108 +120,68 @@ public class Net_UpsertUserParam_Handler implements JsonMessageHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------- DB checks + upsert ----------------
|
||||||
SqliteDbController db = SqliteDbController.getInstance();
|
SqliteDbController db = SqliteDbController.getInstance();
|
||||||
SolanaUsersDAO usersDAO = SolanaUsersDAO.getInstance();
|
SolanaUsersDAO usersDAO = SolanaUsersDAO.getInstance();
|
||||||
UserParamsDAO paramsDAO = UserParamsDAO.getInstance();
|
UserParamsDAO paramsDAO = UserParamsDAO.getInstance();
|
||||||
|
|
||||||
try (Connection c = db.getConnection()) {
|
try (Connection c = db.getConnection()) {
|
||||||
boolean oldAuto = c.getAutoCommit();
|
// 1) user exists
|
||||||
c.setAutoCommit(false);
|
SolanaUserEntry user = usersDAO.getByLogin(c, login);
|
||||||
|
if (user == null) {
|
||||||
try (Statement st = c.createStatement()) {
|
return NetExceptionResponseFactory.error(
|
||||||
st.execute("BEGIN IMMEDIATE");
|
req,
|
||||||
}
|
404,
|
||||||
|
"USER_NOT_FOUND",
|
||||||
try {
|
"Пользователь не найден"
|
||||||
SolanaUserEntry user = usersDAO.getByLogin(c, login);
|
|
||||||
if (user == null) {
|
|
||||||
c.rollback();
|
|
||||||
return NetExceptionResponseFactory.error(
|
|
||||||
req,
|
|
||||||
404,
|
|
||||||
"USER_NOT_FOUND",
|
|
||||||
"Пользователь не найден"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String userDeviceKey = user.getDeviceKey();
|
|
||||||
if (userDeviceKey == null || userDeviceKey.isBlank()) {
|
|
||||||
c.rollback();
|
|
||||||
return NetExceptionResponseFactory.error(
|
|
||||||
req,
|
|
||||||
WireCodes.Status.SERVER_DATA_ERROR,
|
|
||||||
"USER_DEVICE_KEY_EMPTY",
|
|
||||||
"У пользователя не задан deviceKey в БД"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!userDeviceKey.trim().equals(deviceKeyB64)) {
|
|
||||||
c.rollback();
|
|
||||||
return NetExceptionResponseFactory.error(
|
|
||||||
req,
|
|
||||||
403,
|
|
||||||
"DEVICE_KEY_MISMATCH",
|
|
||||||
"device_key не соответствует пользователю"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
UserParamEntry existing = paramsDAO.getByLoginAndParam(c, login, param);
|
|
||||||
|
|
||||||
// если есть более новое — запрет
|
|
||||||
if (existing != null && existing.getTimeMs() > timeMs) {
|
|
||||||
c.rollback();
|
|
||||||
return NetExceptionResponseFactory.error(
|
|
||||||
req,
|
|
||||||
409,
|
|
||||||
"PARAM_NEWER_EXISTS",
|
|
||||||
"Уже есть более новое значение этого параметра (time_ms больше)"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// если time_ms равен — ничего не делаем (твой кейс)
|
|
||||||
if (existing != null && existing.getTimeMs() == timeMs) {
|
|
||||||
c.commit();
|
|
||||||
c.setAutoCommit(oldAuto);
|
|
||||||
|
|
||||||
Net_UpsertUserParam_Response resp = new Net_UpsertUserParam_Response();
|
|
||||||
resp.setOp(req.getOp());
|
|
||||||
resp.setRequestId(req.getRequestId());
|
|
||||||
resp.setStatus(WireCodes.Status.OK);
|
|
||||||
|
|
||||||
log.info("ℹ️ UpsertUserParam noop (same time_ms): login={}, param={}, time_ms={}",
|
|
||||||
login, param, timeMs);
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// иначе existing==null или existingTime < timeMs -> пишем
|
|
||||||
UserParamEntry e = new UserParamEntry(
|
|
||||||
login,
|
|
||||||
param,
|
|
||||||
timeMs,
|
|
||||||
value,
|
|
||||||
deviceKeyB64,
|
|
||||||
signatureB64
|
|
||||||
);
|
);
|
||||||
|
|
||||||
paramsDAO.upsert(c, e);
|
|
||||||
|
|
||||||
c.commit();
|
|
||||||
c.setAutoCommit(oldAuto);
|
|
||||||
|
|
||||||
Net_UpsertUserParam_Response resp = new Net_UpsertUserParam_Response();
|
|
||||||
resp.setOp(req.getOp());
|
|
||||||
resp.setRequestId(req.getRequestId());
|
|
||||||
resp.setStatus(WireCodes.Status.OK);
|
|
||||||
|
|
||||||
log.info("✅ UpsertUserParam ok: login={}, param={}, time_ms={}", login, param, timeMs);
|
|
||||||
return resp;
|
|
||||||
|
|
||||||
} catch (SQLException ex) {
|
|
||||||
c.rollback();
|
|
||||||
throw ex;
|
|
||||||
} finally {
|
|
||||||
c.setAutoCommit(oldAuto);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2) device key must match the user's stored deviceKey
|
||||||
|
String userDeviceKey = user.getDeviceKey();
|
||||||
|
if (userDeviceKey == null || userDeviceKey.isBlank()) {
|
||||||
|
return NetExceptionResponseFactory.error(
|
||||||
|
req,
|
||||||
|
WireCodes.Status.SERVER_DATA_ERROR,
|
||||||
|
"USER_DEVICE_KEY_EMPTY",
|
||||||
|
"У пользователя не задан deviceKey в БД"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userDeviceKey.trim().equals(deviceKeyB64)) {
|
||||||
|
return NetExceptionResponseFactory.error(
|
||||||
|
req,
|
||||||
|
403,
|
||||||
|
"DEVICE_KEY_MISMATCH",
|
||||||
|
"device_key не соответствует пользователю"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) atomic upsert-if-newer
|
||||||
|
UserParamEntry e = new UserParamEntry(
|
||||||
|
login,
|
||||||
|
param,
|
||||||
|
timeMs,
|
||||||
|
value,
|
||||||
|
deviceKeyB64,
|
||||||
|
signatureB64
|
||||||
|
);
|
||||||
|
|
||||||
|
int changed = paramsDAO.upsertIfNewer(c, e);
|
||||||
|
|
||||||
|
Net_UpsertUserParam_Response resp = new Net_UpsertUserParam_Response();
|
||||||
|
resp.setOp(req.getOp());
|
||||||
|
resp.setRequestId(req.getRequestId());
|
||||||
|
resp.setStatus(WireCodes.Status.OK);
|
||||||
|
|
||||||
|
if (changed == 1) {
|
||||||
|
log.info("✅ UpsertUserParam applied: login={}, param={}, time_ms={}", login, param, timeMs);
|
||||||
|
} else {
|
||||||
|
// 0 строк — значит в БД уже есть time_ms >= incoming
|
||||||
|
log.info("ℹ️ UpsertUserParam ignored (not newer): login={}, param={}, time_ms={}", login, param, timeMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user