SHA256
API сессий: добавить sessionType и clientPlatform
This commit is contained in:
@@ -178,6 +178,8 @@ public final class DatabaseInitializer {
|
||||
client_ip TEXT,
|
||||
client_info_from_client TEXT,
|
||||
client_info_from_request TEXT,
|
||||
session_type INTEGER NOT NULL DEFAULT 1,
|
||||
client_platform TEXT NOT NULL DEFAULT '',
|
||||
user_language TEXT,
|
||||
FOREIGN KEY (login) REFERENCES solana_users(login)
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.sql.Statement;
|
||||
public final class SqliteDbController {
|
||||
|
||||
private static volatile SqliteDbController instance;
|
||||
private static final int LATEST_SCHEMA_VERSION = 3;
|
||||
private static final int LATEST_SCHEMA_VERSION = 4;
|
||||
|
||||
private final String jdbcUrl;
|
||||
|
||||
@@ -86,6 +86,7 @@ public final class SqliteDbController {
|
||||
case 1 -> migrateToV1();
|
||||
case 2 -> migrateToV2();
|
||||
case 3 -> migrateToV3();
|
||||
case 4 -> migrateToV4();
|
||||
default -> throw new RuntimeException("Unknown DB migration target version: " + targetVersion);
|
||||
}
|
||||
}
|
||||
@@ -168,6 +169,26 @@ public final class SqliteDbController {
|
||||
}
|
||||
}
|
||||
|
||||
private void migrateToV4() {
|
||||
try (Connection c = DriverManager.getConnection(jdbcUrl);
|
||||
Statement st = c.createStatement()) {
|
||||
c.setAutoCommit(false);
|
||||
try {
|
||||
ensureActiveSessionsSessionTypeColumn(c, st);
|
||||
ensureActiveSessionsClientPlatformColumn(c, st);
|
||||
setSchemaVersion(c, 4);
|
||||
c.commit();
|
||||
} catch (Exception e) {
|
||||
try { c.rollback(); } catch (Exception ignored) {}
|
||||
throw new RuntimeException("DB migration to v4 failed", e);
|
||||
} finally {
|
||||
try { c.setAutoCommit(true); } catch (Exception ignored) {}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("DB migration to v4 failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureChat200StateTables(Statement st) throws SQLException {
|
||||
st.executeUpdate("""
|
||||
CREATE TABLE IF NOT EXISTS chat200_state (
|
||||
@@ -235,6 +256,28 @@ public final class SqliteDbController {
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureActiveSessionsSessionTypeColumn(Connection c, Statement st) throws SQLException {
|
||||
if (columnExists(c, "active_sessions", "session_type")) return;
|
||||
st.executeUpdate("ALTER TABLE active_sessions ADD COLUMN session_type INTEGER NOT NULL DEFAULT 1");
|
||||
}
|
||||
|
||||
private static void ensureActiveSessionsClientPlatformColumn(Connection c, Statement st) throws SQLException {
|
||||
if (columnExists(c, "active_sessions", "client_platform")) return;
|
||||
st.executeUpdate("ALTER TABLE active_sessions ADD COLUMN client_platform TEXT NOT NULL DEFAULT ''");
|
||||
}
|
||||
|
||||
private static boolean columnExists(Connection c, String tableName, String columnName) throws SQLException {
|
||||
try (Statement probe = c.createStatement();
|
||||
ResultSet rs = probe.executeQuery("PRAGMA table_info(" + tableName + ")")) {
|
||||
while (rs.next()) {
|
||||
if (columnName.equalsIgnoreCase(rs.getString("name"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void setSchemaVersion(Connection c, int version) throws SQLException {
|
||||
try (var ps = c.prepareStatement("""
|
||||
INSERT INTO db_schema_version (id, schema_version, updated_at_ms)
|
||||
|
||||
@@ -47,8 +47,10 @@ public final class ActiveSessionsDAO {
|
||||
client_ip,
|
||||
client_info_from_client,
|
||||
client_info_from_request,
|
||||
session_type,
|
||||
client_platform,
|
||||
user_language
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
@@ -64,7 +66,9 @@ public final class ActiveSessionsDAO {
|
||||
ps.setString(10, session.getClientIp());
|
||||
ps.setString(11, session.getClientInfoFromClient());
|
||||
ps.setString(12, session.getClientInfoFromRequest());
|
||||
ps.setString(13, session.getUserLanguage());
|
||||
ps.setInt(13, session.getSessionType());
|
||||
ps.setString(14, session.getClientPlatform());
|
||||
ps.setString(15, session.getUserLanguage());
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
@@ -92,6 +96,8 @@ public final class ActiveSessionsDAO {
|
||||
client_ip,
|
||||
client_info_from_client,
|
||||
client_info_from_request,
|
||||
session_type,
|
||||
client_platform,
|
||||
user_language
|
||||
FROM active_sessions
|
||||
WHERE session_id = ?
|
||||
@@ -127,6 +133,8 @@ public final class ActiveSessionsDAO {
|
||||
client_ip,
|
||||
client_info_from_client,
|
||||
client_info_from_request,
|
||||
session_type,
|
||||
client_platform,
|
||||
user_language
|
||||
FROM active_sessions
|
||||
WHERE login = ? COLLATE NOCASE
|
||||
@@ -179,6 +187,8 @@ public final class ActiveSessionsDAO {
|
||||
String clientIp,
|
||||
String clientInfoFromClient,
|
||||
String clientInfoFromRequest,
|
||||
int sessionType,
|
||||
String clientPlatform,
|
||||
String userLanguage
|
||||
) throws SQLException {
|
||||
|
||||
@@ -189,6 +199,8 @@ public final class ActiveSessionsDAO {
|
||||
client_ip = ?,
|
||||
client_info_from_client = ?,
|
||||
client_info_from_request = ?,
|
||||
session_type = ?,
|
||||
client_platform = ?,
|
||||
user_language = ?
|
||||
WHERE session_id = ?
|
||||
""";
|
||||
@@ -198,8 +210,10 @@ public final class ActiveSessionsDAO {
|
||||
ps.setString(2, clientIp);
|
||||
ps.setString(3, clientInfoFromClient);
|
||||
ps.setString(4, clientInfoFromRequest);
|
||||
ps.setString(5, userLanguage);
|
||||
ps.setString(6, sessionId);
|
||||
ps.setInt(5, sessionType);
|
||||
ps.setString(6, clientPlatform);
|
||||
ps.setString(7, userLanguage);
|
||||
ps.setString(8, sessionId);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
@@ -210,10 +224,12 @@ public final class ActiveSessionsDAO {
|
||||
String clientIp,
|
||||
String clientInfoFromClient,
|
||||
String clientInfoFromRequest,
|
||||
int sessionType,
|
||||
String clientPlatform,
|
||||
String userLanguage
|
||||
) throws SQLException {
|
||||
try (Connection c = db.getConnection()) {
|
||||
updateOnRefresh(c, sessionId, lastAuthMs, clientIp, clientInfoFromClient, clientInfoFromRequest, userLanguage);
|
||||
updateOnRefresh(c, sessionId, lastAuthMs, clientIp, clientInfoFromClient, clientInfoFromRequest, sessionType, clientPlatform, userLanguage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,6 +284,8 @@ public final class ActiveSessionsDAO {
|
||||
String clientIp = rs.getString("client_ip");
|
||||
String clientInfoFromClient = rs.getString("client_info_from_client");
|
||||
String clientInfoFromRequest = rs.getString("client_info_from_request");
|
||||
int sessionType = rs.getInt("session_type");
|
||||
String clientPlatform = rs.getString("client_platform");
|
||||
String userLanguage = rs.getString("user_language");
|
||||
|
||||
return new ActiveSessionEntry(
|
||||
@@ -283,6 +301,8 @@ public final class ActiveSessionsDAO {
|
||||
clientIp,
|
||||
clientInfoFromClient,
|
||||
clientInfoFromRequest,
|
||||
sessionType,
|
||||
clientPlatform,
|
||||
userLanguage
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ public class ActiveSessionEntry {
|
||||
private String clientIp;
|
||||
private String clientInfoFromClient;
|
||||
private String clientInfoFromRequest;
|
||||
private int sessionType;
|
||||
private String clientPlatform;
|
||||
private String userLanguage;
|
||||
|
||||
public ActiveSessionEntry() { }
|
||||
@@ -38,6 +40,8 @@ public class ActiveSessionEntry {
|
||||
String clientIp,
|
||||
String clientInfoFromClient,
|
||||
String clientInfoFromRequest,
|
||||
int sessionType,
|
||||
String clientPlatform,
|
||||
String userLanguage) {
|
||||
this.sessionId = sessionId;
|
||||
this.login = login;
|
||||
@@ -51,6 +55,8 @@ public class ActiveSessionEntry {
|
||||
this.clientIp = clientIp;
|
||||
this.clientInfoFromClient = clientInfoFromClient;
|
||||
this.clientInfoFromRequest = clientInfoFromRequest;
|
||||
this.sessionType = sessionType;
|
||||
this.clientPlatform = clientPlatform;
|
||||
this.userLanguage = userLanguage;
|
||||
}
|
||||
|
||||
@@ -90,6 +96,12 @@ public class ActiveSessionEntry {
|
||||
public String getClientInfoFromRequest() { return clientInfoFromRequest; }
|
||||
public void setClientInfoFromRequest(String clientInfoFromRequest) { this.clientInfoFromRequest = clientInfoFromRequest; }
|
||||
|
||||
public int getSessionType() { return sessionType; }
|
||||
public void setSessionType(int sessionType) { this.sessionType = sessionType; }
|
||||
|
||||
public String getClientPlatform() { return clientPlatform; }
|
||||
public void setClientPlatform(String clientPlatform) { this.clientPlatform = clientPlatform; }
|
||||
|
||||
public String getUserLanguage() { return userLanguage; }
|
||||
public void setUserLanguage(String userLanguage) { this.userLanguage = userLanguage; }
|
||||
}
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.auth;
|
||||
|
||||
import server.logic.ws_protocol.JSON.utils.AuthKeyUtils;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
final class AuthSessionTypeSupport {
|
||||
|
||||
static final int SESSION_TYPE_CLIENT = 1;
|
||||
static final int SESSION_TYPE_WALLET = 50;
|
||||
static final int SESSION_TYPE_HOMESERVER = 100;
|
||||
static final int SESSION_TYPE_MISMATCH_STATUS = 460;
|
||||
|
||||
private AuthSessionTypeSupport() {}
|
||||
|
||||
static int normalizeRequestedSessionType(Integer rawType) {
|
||||
return rawType == null ? SESSION_TYPE_CLIENT : rawType.intValue();
|
||||
}
|
||||
|
||||
static boolean isSupportedSessionType(int sessionType) {
|
||||
return sessionType == SESSION_TYPE_CLIENT
|
||||
|| sessionType == SESSION_TYPE_WALLET
|
||||
|| sessionType == SESSION_TYPE_HOMESERVER;
|
||||
}
|
||||
|
||||
static String normalizeClientPlatform(String clientPlatform) {
|
||||
if (clientPlatform == null) return "";
|
||||
String trimmed = clientPlatform.trim();
|
||||
if (trimmed.length() <= 64) return trimmed;
|
||||
return trimmed.substring(0, 64);
|
||||
}
|
||||
|
||||
static byte[] tryParseSessionPublicKey32(String sessionKeyApi) {
|
||||
if (sessionKeyApi == null || sessionKeyApi.isBlank()) return null;
|
||||
try {
|
||||
return AuthKeyUtils.parseEd25519PublicKey(sessionKeyApi, "sessionKey");
|
||||
} catch (Exception ignored) {
|
||||
try {
|
||||
byte[] raw = Base64.getDecoder().decode(sessionKeyApi.trim());
|
||||
return raw.length == 32 ? raw : null;
|
||||
} catch (Exception ignoredToo) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -213,6 +213,19 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
|
||||
clientInfoFromClient = clientInfoFromClient.substring(0, 50);
|
||||
}
|
||||
|
||||
int requestedSessionType = AuthSessionTypeSupport.normalizeRequestedSessionType(req.getSessionType());
|
||||
if (!AuthSessionTypeSupport.isSupportedSessionType(requestedSessionType)) {
|
||||
Net_Response err = NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"BAD_SESSION_TYPE",
|
||||
"Неподдерживаемый sessionType"
|
||||
);
|
||||
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: bad sessionType");
|
||||
return err;
|
||||
}
|
||||
String clientPlatform = AuthSessionTypeSupport.normalizeClientPlatform(req.getClientPlatform());
|
||||
|
||||
String deviceKeyFromDb = user.getDeviceKey();
|
||||
if (deviceKeyFromDb == null || deviceKeyFromDb.isBlank()) {
|
||||
Net_Response err = NetExceptionResponseFactory.error(
|
||||
@@ -315,6 +328,35 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
|
||||
return err;
|
||||
}
|
||||
|
||||
SolanaUserPdaImportService.SessionTypeCheckResult sessionTypeCheck;
|
||||
try {
|
||||
sessionTypeCheck = SolanaUserPdaImportService.checkSessionTypeAgainstPda(
|
||||
canonicalLogin,
|
||||
sessionKey,
|
||||
requestedSessionType
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.error("Ошибка проверки sessionType по Solana PDA для login={}", canonicalLogin, e);
|
||||
Net_Response err = NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.SERVER_DATA_ERROR,
|
||||
"SESSION_TYPE_PDA_CHECK_FAILED",
|
||||
"Ошибка проверки sessionType в Solana PDA"
|
||||
);
|
||||
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: sessionType pda check");
|
||||
return err;
|
||||
}
|
||||
if (sessionTypeCheck.hasPdaSessionRecord() && !sessionTypeCheck.matchesRequestedType()) {
|
||||
Net_Response err = NetExceptionResponseFactory.error(
|
||||
req,
|
||||
AuthSessionTypeSupport.SESSION_TYPE_MISMATCH_STATUS,
|
||||
"SESSION_TYPE_MISMATCH",
|
||||
"sessionType не совпадает с типом сессии в Solana PDA"
|
||||
);
|
||||
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: sessionType mismatch");
|
||||
return err;
|
||||
}
|
||||
|
||||
// --- генерируем sessionId ---
|
||||
String sessionId = generateRandom32B64Url();
|
||||
long now = System.currentTimeMillis();
|
||||
@@ -356,6 +398,8 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
|
||||
clientIp,
|
||||
clientInfoFromClient,
|
||||
clientInfoFromRequest,
|
||||
requestedSessionType,
|
||||
clientPlatform,
|
||||
userLanguage
|
||||
);
|
||||
|
||||
|
||||
+2
@@ -66,6 +66,8 @@ public class Net_ListSessions_Handler implements JsonMessageHandler {
|
||||
info.setSessionId(s.getSessionId());
|
||||
info.setClientInfoFromClient(s.getClientInfoFromClient());
|
||||
info.setClientInfoFromRequest(s.getClientInfoFromRequest());
|
||||
info.setSessionType(s.getSessionType());
|
||||
info.setClientPlatform(s.getClientPlatform());
|
||||
info.setLastAuthenticatedAtMs(s.getLastAuthirificatedAtMs());
|
||||
|
||||
String ip = s.getClientIp();
|
||||
|
||||
+39
@@ -216,6 +216,16 @@ public class Net_SessionLogin_Handler implements JsonMessageHandler {
|
||||
if (clientInfoFromClient != null && clientInfoFromClient.length() > 50) {
|
||||
clientInfoFromClient = clientInfoFromClient.substring(0, 50);
|
||||
}
|
||||
int requestedSessionType = AuthSessionTypeSupport.normalizeRequestedSessionType(req.getSessionType());
|
||||
if (!AuthSessionTypeSupport.isSupportedSessionType(requestedSessionType)) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"BAD_SESSION_TYPE",
|
||||
"Неподдерживаемый sessionType"
|
||||
);
|
||||
}
|
||||
String clientPlatform = AuthSessionTypeSupport.normalizeClientPlatform(req.getClientPlatform());
|
||||
|
||||
String clientIp = null;
|
||||
String clientInfoFromRequest = null;
|
||||
@@ -235,6 +245,31 @@ public class Net_SessionLogin_Handler implements JsonMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
SolanaUserPdaImportService.SessionTypeCheckResult sessionTypeCheck;
|
||||
try {
|
||||
sessionTypeCheck = SolanaUserPdaImportService.checkSessionTypeAgainstPda(
|
||||
session.getLogin(),
|
||||
sessionKeyFromReq,
|
||||
requestedSessionType
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.error("Ошибка проверки sessionType по Solana PDA для login={} sessionId={}", session.getLogin(), sessionId, e);
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.SERVER_DATA_ERROR,
|
||||
"SESSION_TYPE_PDA_CHECK_FAILED",
|
||||
"Ошибка проверки sessionType в Solana PDA"
|
||||
);
|
||||
}
|
||||
if (sessionTypeCheck.hasPdaSessionRecord() && !sessionTypeCheck.matchesRequestedType()) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
AuthSessionTypeSupport.SESSION_TYPE_MISMATCH_STATUS,
|
||||
"SESSION_TYPE_MISMATCH",
|
||||
"sessionType не совпадает с типом сессии в Solana PDA"
|
||||
);
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
try {
|
||||
ActiveSessionsDAO.getInstance().updateOnRefresh(
|
||||
@@ -243,6 +278,8 @@ public class Net_SessionLogin_Handler implements JsonMessageHandler {
|
||||
clientIp,
|
||||
clientInfoFromClient,
|
||||
clientInfoFromRequest,
|
||||
requestedSessionType,
|
||||
clientPlatform,
|
||||
userLanguage
|
||||
);
|
||||
} catch (SQLException e) {
|
||||
@@ -253,6 +290,8 @@ public class Net_SessionLogin_Handler implements JsonMessageHandler {
|
||||
session.setClientIp(clientIp);
|
||||
session.setClientInfoFromClient(clientInfoFromClient);
|
||||
session.setClientInfoFromRequest(clientInfoFromRequest);
|
||||
session.setSessionType(requestedSessionType);
|
||||
session.setClientPlatform(clientPlatform);
|
||||
session.setUserLanguage(userLanguage);
|
||||
|
||||
// ctx
|
||||
|
||||
+75
-6
@@ -14,7 +14,10 @@ import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Lazy-import пользователя из Solana PDA в локальную БД сервера.
|
||||
@@ -57,6 +60,28 @@ public final class SolanaUserPdaImportService {
|
||||
return usersDao.getByLogin(login);
|
||||
}
|
||||
|
||||
public static SessionTypeCheckResult checkSessionTypeAgainstPda(String loginRaw, String sessionKeyApi, int requestedSessionType) throws Exception {
|
||||
String login = normalizeLogin(loginRaw);
|
||||
if (login == null) return SessionTypeCheckResult.noRecord();
|
||||
|
||||
ParsedSolanaUser parsed = fetchFromSolana(login);
|
||||
if (parsed == null) return SessionTypeCheckResult.noRecord();
|
||||
|
||||
byte[] sessionPubKey32 = AuthSessionTypeSupport.tryParseSessionPublicKey32(sessionKeyApi);
|
||||
if (sessionPubKey32 == null) return SessionTypeCheckResult.noRecord();
|
||||
|
||||
for (ParsedSessionRecord session : parsed.sessions()) {
|
||||
if (constantTimeEquals(session.sessionPubKey32(), sessionPubKey32)) {
|
||||
if (session.sessionType() == requestedSessionType) {
|
||||
return SessionTypeCheckResult.match(session.sessionType(), session.sessionName());
|
||||
}
|
||||
return SessionTypeCheckResult.mismatch(session.sessionType(), session.sessionName());
|
||||
}
|
||||
}
|
||||
|
||||
return SessionTypeCheckResult.noRecord();
|
||||
}
|
||||
|
||||
private static ParsedSolanaUser fetchFromSolana(String login) throws Exception {
|
||||
String loginB58 = toBase58(login.getBytes(StandardCharsets.UTF_8));
|
||||
String lenB58 = toBase58(new byte[]{(byte) login.length()});
|
||||
@@ -135,6 +160,7 @@ public final class SolanaUserPdaImportService {
|
||||
byte[] blockchainKey32 = null;
|
||||
byte[] deviceKey32 = null;
|
||||
long paidLimitBytes = 0L;
|
||||
List<ParsedSessionRecord> sessions = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < blocksCount; i++) {
|
||||
int blockType = u8(raw, c++);
|
||||
@@ -196,11 +222,19 @@ public final class SolanaUserPdaImportService {
|
||||
int sessionsCount = u8(raw, c++);
|
||||
if (sessionsCount > 64) return null;
|
||||
for (int j = 0; j < sessionsCount; j++) {
|
||||
c += 1; // session_type
|
||||
c += 1; // session_version
|
||||
int sessionType = u8(raw, c++);
|
||||
int sessionVersion = u8(raw, c++);
|
||||
int n = u8(raw, c++);
|
||||
String sessionName = new String(raw, c, n, StandardCharsets.UTF_8);
|
||||
c += n;
|
||||
c += 32; // session_pub_key
|
||||
byte[] sessionPubKey32 = slice(raw, c, 32);
|
||||
c += 32;
|
||||
sessions.add(new ParsedSessionRecord(
|
||||
sessionType,
|
||||
sessionVersion,
|
||||
sessionName,
|
||||
sessionPubKey32
|
||||
));
|
||||
}
|
||||
} else if (blockType == 70) {
|
||||
c += 1;
|
||||
@@ -217,7 +251,8 @@ public final class SolanaUserPdaImportService {
|
||||
blockchainName,
|
||||
Base64.getEncoder().encodeToString(blockchainKey32),
|
||||
Base64.getEncoder().encodeToString(deviceKey32),
|
||||
paidLimitBytes
|
||||
paidLimitBytes,
|
||||
sessions
|
||||
);
|
||||
}
|
||||
|
||||
@@ -225,7 +260,7 @@ public final class SolanaUserPdaImportService {
|
||||
if (login == null) return null;
|
||||
String s = login.trim();
|
||||
if (s.isEmpty()) return null;
|
||||
return s.toLowerCase();
|
||||
return s.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private static int u8(byte[] b, int o) { return b[o] & 0xFF; }
|
||||
@@ -272,11 +307,45 @@ public final class SolanaUserPdaImportService {
|
||||
return remainder;
|
||||
}
|
||||
|
||||
private static boolean constantTimeEquals(byte[] a, byte[] b) {
|
||||
if (a == null || b == null || a.length != b.length) return false;
|
||||
int diff = 0;
|
||||
for (int i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
||||
return diff == 0;
|
||||
}
|
||||
|
||||
private record ParsedSolanaUser(
|
||||
String login,
|
||||
String blockchainName,
|
||||
String blockchainKeyB64,
|
||||
String deviceKeyB64,
|
||||
long paidLimitBytes
|
||||
long paidLimitBytes,
|
||||
List<ParsedSessionRecord> sessions
|
||||
) {}
|
||||
|
||||
private record ParsedSessionRecord(
|
||||
int sessionType,
|
||||
int sessionVersion,
|
||||
String sessionName,
|
||||
byte[] sessionPubKey32
|
||||
) {}
|
||||
|
||||
public record SessionTypeCheckResult(
|
||||
boolean hasPdaSessionRecord,
|
||||
boolean matchesRequestedType,
|
||||
int pdaSessionType,
|
||||
String sessionName
|
||||
) {
|
||||
static SessionTypeCheckResult noRecord() {
|
||||
return new SessionTypeCheckResult(false, true, 0, "");
|
||||
}
|
||||
|
||||
static SessionTypeCheckResult match(int pdaSessionType, String sessionName) {
|
||||
return new SessionTypeCheckResult(true, true, pdaSessionType, sessionName == null ? "" : sessionName);
|
||||
}
|
||||
|
||||
static SessionTypeCheckResult mismatch(int pdaSessionType, String sessionName) {
|
||||
return new SessionTypeCheckResult(true, false, pdaSessionType, sessionName == null ? "" : sessionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
@@ -41,6 +41,12 @@ public class Net_CreateAuthSession_Request extends Net_Request {
|
||||
/** Краткая строка от клиента (до 50 символов) с описанием устройства/клиента. */
|
||||
private String clientInfo;
|
||||
|
||||
/** Числовой код типа сессии. */
|
||||
private Integer sessionType;
|
||||
|
||||
/** Свободная строка платформы клиента, например Web / Android / ESP32. */
|
||||
private String clientPlatform;
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
}
|
||||
@@ -104,4 +110,20 @@ public class Net_CreateAuthSession_Request extends Net_Request {
|
||||
public void setClientInfo(String clientInfo) {
|
||||
this.clientInfo = clientInfo;
|
||||
}
|
||||
|
||||
public Integer getSessionType() {
|
||||
return sessionType;
|
||||
}
|
||||
|
||||
public void setSessionType(Integer sessionType) {
|
||||
this.sessionType = sessionType;
|
||||
}
|
||||
|
||||
public String getClientPlatform() {
|
||||
return clientPlatform;
|
||||
}
|
||||
|
||||
public void setClientPlatform(String clientPlatform) {
|
||||
this.clientPlatform = clientPlatform;
|
||||
}
|
||||
}
|
||||
|
||||
+22
@@ -52,6 +52,12 @@ public class Net_ListSessions_Response extends Net_Response {
|
||||
/** Краткая строка, собранная сервером из HTTP-запроса (UA, платформа и т.п.). */
|
||||
private String clientInfoFromRequest;
|
||||
|
||||
/** Числовой код типа сессии. */
|
||||
private int sessionType;
|
||||
|
||||
/** Свободная строка платформы, как её прислал клиент. */
|
||||
private String clientPlatform;
|
||||
|
||||
/** Строка геолокации вида "Country, City" или "unknown". */
|
||||
private String geo;
|
||||
|
||||
@@ -84,6 +90,22 @@ public class Net_ListSessions_Response extends Net_Response {
|
||||
this.clientInfoFromRequest = clientInfoFromRequest;
|
||||
}
|
||||
|
||||
public int getSessionType() {
|
||||
return sessionType;
|
||||
}
|
||||
|
||||
public void setSessionType(int sessionType) {
|
||||
this.sessionType = sessionType;
|
||||
}
|
||||
|
||||
public String getClientPlatform() {
|
||||
return clientPlatform;
|
||||
}
|
||||
|
||||
public void setClientPlatform(String clientPlatform) {
|
||||
this.clientPlatform = clientPlatform;
|
||||
}
|
||||
|
||||
public String getGeo() {
|
||||
return geo;
|
||||
}
|
||||
|
||||
+22
@@ -21,6 +21,12 @@ public class Net_SessionLogin_Request extends Net_Request {
|
||||
/** Краткая строка от клиента (до 50 символов) с описанием устройства/клиента. */
|
||||
private String clientInfo;
|
||||
|
||||
/** Числовой код типа сессии. */
|
||||
private Integer sessionType;
|
||||
|
||||
/** Свободная строка платформы клиента, например Web / Android / ESP32. */
|
||||
private String clientPlatform;
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
@@ -60,4 +66,20 @@ public class Net_SessionLogin_Request extends Net_Request {
|
||||
public void setClientInfo(String clientInfo) {
|
||||
this.clientInfo = clientInfo;
|
||||
}
|
||||
|
||||
public Integer getSessionType() {
|
||||
return sessionType;
|
||||
}
|
||||
|
||||
public void setSessionType(Integer sessionType) {
|
||||
this.sessionType = sessionType;
|
||||
}
|
||||
|
||||
public String getClientPlatform() {
|
||||
return clientPlatform;
|
||||
}
|
||||
|
||||
public void setClientPlatform(String clientPlatform) {
|
||||
this.clientPlatform = clientPlatform;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user