09 12 25
Авторификация работает и тест авторификации проходит. (создание пользователя, два этапа создания сессии и рефреш сессии)
This commit is contained in:
parent
2ed4f6d666
commit
888bb1595f
@ -1,6 +1,5 @@
|
|||||||
package shine.db;
|
package shine.db;
|
||||||
|
|
||||||
|
|
||||||
import utils.config.AppConfig;
|
import utils.config.AppConfig;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
@ -12,6 +11,15 @@ import java.sql.DriverManager;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DatabaseInitializer — создание новой SQLite-БД по схеме SHiNE.
|
||||||
|
*
|
||||||
|
* Читает путь к файлу БД из application.properties (db.path),
|
||||||
|
* при необходимости удаляет старый файл и создаёт таблицы:
|
||||||
|
* - solana_users
|
||||||
|
* - active_sessions
|
||||||
|
* - users_params
|
||||||
|
*/
|
||||||
public class DatabaseInitializer {
|
public class DatabaseInitializer {
|
||||||
|
|
||||||
public static void createNewDB(String[] args) {
|
public static void createNewDB(String[] args) {
|
||||||
@ -75,9 +83,9 @@ public class DatabaseInitializer {
|
|||||||
login TEXT NOT NULL,
|
login TEXT NOT NULL,
|
||||||
loginId INTEGER NOT NULL PRIMARY KEY,
|
loginId INTEGER NOT NULL PRIMARY KEY,
|
||||||
bchId INTEGER NOT NULL,
|
bchId INTEGER NOT NULL,
|
||||||
pubkey0 TEXT,
|
loginKey TEXT, -- основной публичный ключ (логин)
|
||||||
pubkey1 TEXT,
|
deviceKey TEXT, -- публичный ключ устройства
|
||||||
bchLimit INTEGER -- может быть NULL
|
bchLimit INTEGER -- может быть NULL
|
||||||
);
|
);
|
||||||
""");
|
""");
|
||||||
|
|
||||||
@ -87,22 +95,29 @@ public class DatabaseInitializer {
|
|||||||
""");
|
""");
|
||||||
|
|
||||||
// 2. Таблица active_sessions
|
// 2. Таблица active_sessions
|
||||||
|
// sessionId теперь TEXT (base64 от 32 байт), а не INTEGER.
|
||||||
st.executeUpdate("""
|
st.executeUpdate("""
|
||||||
CREATE TABLE IF NOT EXISTS active_sessions (
|
CREATE TABLE IF NOT EXISTS active_sessions (
|
||||||
sessionId INTEGER NOT NULL PRIMARY KEY,
|
sessionId TEXT NOT NULL PRIMARY KEY,
|
||||||
session_pwd TEXT NOT NULL,
|
loginId INTEGER NOT NULL,
|
||||||
loginId INTEGER NOT NULL,
|
sessionPwd TEXT NOT NULL,
|
||||||
time_ms INTEGER NOT NULL,
|
storagePwd TEXT NOT NULL,
|
||||||
pubkey_num INTEGER NOT NULL,
|
sessionCreatedAtMs INTEGER NOT NULL,
|
||||||
push_endpoint TEXT,
|
lastAuthirificatedAtMs INTEGER NOT NULL,
|
||||||
push_p256dh_key TEXT,
|
pushEndpoint TEXT,
|
||||||
push_auth_key TEXT,
|
pushP256dhKey TEXT,
|
||||||
|
pushAuthKey TEXT,
|
||||||
FOREIGN KEY (loginId) REFERENCES solana_users(loginId)
|
FOREIGN KEY (loginId) REFERENCES solana_users(loginId)
|
||||||
);
|
);
|
||||||
""");
|
""");
|
||||||
|
|
||||||
|
st.executeUpdate("""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_active_sessions_loginId
|
||||||
|
ON active_sessions (loginId);
|
||||||
|
""");
|
||||||
|
|
||||||
// 3. Таблица users_params
|
// 3. Таблица users_params
|
||||||
// Важно: пара (loginId, param) должна быть уникальна
|
// Пара (loginId, param) должна быть уникальна.
|
||||||
st.executeUpdate("""
|
st.executeUpdate("""
|
||||||
CREATE TABLE IF NOT EXISTS users_params (
|
CREATE TABLE IF NOT EXISTS users_params (
|
||||||
loginId INTEGER NOT NULL,
|
loginId INTEGER NOT NULL,
|
||||||
|
|||||||
@ -5,7 +5,11 @@ import shine.db.entities.ActiveSession;
|
|||||||
|
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
|
|
||||||
/** Здесь мы храним данные об активных сессиях пользователя (для wss соединений). */
|
/**
|
||||||
|
* DAO для таблицы active_sessions.
|
||||||
|
*
|
||||||
|
* Здесь мы храним данные об активных сессиях пользователя (для wss-соединений).
|
||||||
|
*/
|
||||||
public final class ActiveSessionsDAO {
|
public final class ActiveSessionsDAO {
|
||||||
|
|
||||||
private static volatile ActiveSessionsDAO instance;
|
private static volatile ActiveSessionsDAO instance;
|
||||||
@ -25,18 +29,21 @@ public final class ActiveSessionsDAO {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Вставка новой сессии.
|
||||||
|
*/
|
||||||
public void insert(ActiveSession session) throws SQLException {
|
public void insert(ActiveSession session) throws SQLException {
|
||||||
String sql = """
|
String sql = """
|
||||||
INSERT INTO active_sessions (
|
INSERT INTO active_sessions (
|
||||||
sessionId,
|
sessionId,
|
||||||
loginId,
|
loginId,
|
||||||
session_pwd,
|
sessionPwd,
|
||||||
storage_pwd,
|
storagePwd,
|
||||||
session_created_ms,
|
sessionCreatedAtMs,
|
||||||
last_auth_ms,
|
lastAuthirificatedAtMs,
|
||||||
push_endpoint,
|
pushEndpoint,
|
||||||
push_p256dh_key,
|
pushP256dhKey,
|
||||||
push_auth_key
|
pushAuthKey
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""";
|
""";
|
||||||
|
|
||||||
@ -50,22 +57,26 @@ public final class ActiveSessionsDAO {
|
|||||||
ps.setString(7, session.getPushEndpoint());
|
ps.setString(7, session.getPushEndpoint());
|
||||||
ps.setString(8, session.getPushP256dhKey());
|
ps.setString(8, session.getPushP256dhKey());
|
||||||
ps.setString(9, session.getPushAuthKey());
|
ps.setString(9, session.getPushAuthKey());
|
||||||
|
|
||||||
ps.executeUpdate();
|
ps.executeUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить сессию по sessionId.
|
||||||
|
*/
|
||||||
public ActiveSession getBySessionId(String sessionId) throws SQLException {
|
public ActiveSession getBySessionId(String sessionId) throws SQLException {
|
||||||
String sql = """
|
String sql = """
|
||||||
SELECT
|
SELECT
|
||||||
sessionId,
|
sessionId,
|
||||||
loginId,
|
loginId,
|
||||||
session_pwd,
|
sessionPwd,
|
||||||
storage_pwd,
|
storagePwd,
|
||||||
session_created_ms,
|
sessionCreatedAtMs,
|
||||||
last_auth_ms,
|
lastAuthirificatedAtMs,
|
||||||
push_endpoint,
|
pushEndpoint,
|
||||||
push_p256dh_key,
|
pushP256dhKey,
|
||||||
push_auth_key
|
pushAuthKey
|
||||||
FROM active_sessions
|
FROM active_sessions
|
||||||
WHERE sessionId = ?
|
WHERE sessionId = ?
|
||||||
""";
|
""";
|
||||||
@ -81,6 +92,23 @@ public final class ActiveSessionsDAO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновить только lastAuthirificatedAtMs для конкретной сессии.
|
||||||
|
*/
|
||||||
|
public void updateLastAuthirificatedAtMs(String sessionId, long lastAuthMs) throws SQLException {
|
||||||
|
String sql = """
|
||||||
|
UPDATE active_sessions
|
||||||
|
SET lastAuthirificatedAtMs = ?
|
||||||
|
WHERE sessionId = ?
|
||||||
|
""";
|
||||||
|
|
||||||
|
try (PreparedStatement ps = db.getConnection().prepareStatement(sql)) {
|
||||||
|
ps.setLong(1, lastAuthMs);
|
||||||
|
ps.setString(2, sessionId);
|
||||||
|
ps.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Удаление записи по sessionId.
|
* Удаление записи по sessionId.
|
||||||
* Если записи нет — просто ничего не удалит (0 строк).
|
* Если записи нет — просто ничего не удалит (0 строк).
|
||||||
@ -94,42 +122,24 @@ public final class ActiveSessionsDAO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Обновить поле last_auth_ms (lastAuthirificatedAtMs) для конкретной сессии.
|
|
||||||
* Остальные поля записи не меняются.
|
|
||||||
*/
|
|
||||||
public void updateLastAuthirificatedAtMs(String sessionId, long newTimeMs) throws SQLException {
|
|
||||||
String sql = """
|
|
||||||
UPDATE active_sessions
|
|
||||||
SET last_auth_ms = ?
|
|
||||||
WHERE sessionId = ?
|
|
||||||
""";
|
|
||||||
|
|
||||||
try (PreparedStatement ps = db.getConnection().prepareStatement(sql)) {
|
|
||||||
ps.setLong(1, newTimeMs);
|
|
||||||
ps.setString(2, sessionId);
|
|
||||||
ps.executeUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ActiveSession mapRow(ResultSet rs) throws SQLException {
|
private ActiveSession mapRow(ResultSet rs) throws SQLException {
|
||||||
String sessionId = rs.getString("sessionId");
|
String sessionId = rs.getString("sessionId");
|
||||||
long loginId = rs.getLong("loginId");
|
long loginId = rs.getLong("loginId");
|
||||||
String sessionPwd = rs.getString("session_pwd");
|
String sessionPwd = rs.getString("sessionPwd");
|
||||||
String storagePwd = rs.getString("storage_pwd");
|
String storagePwd = rs.getString("storagePwd");
|
||||||
long sessionCreatedMs = rs.getLong("session_created_ms");
|
long sessionCreatedAtMs = rs.getLong("sessionCreatedAtMs");
|
||||||
long lastAuthMs = rs.getLong("last_auth_ms");
|
long lastAuthirificatedAtMs = rs.getLong("lastAuthirificatedAtMs");
|
||||||
String pushEndpoint = rs.getString("push_endpoint");
|
String pushEndpoint = rs.getString("pushEndpoint");
|
||||||
String pushP256dhKey = rs.getString("push_p256dh_key");
|
String pushP256dhKey = rs.getString("pushP256dhKey");
|
||||||
String pushAuthKey = rs.getString("push_auth_key");
|
String pushAuthKey = rs.getString("pushAuthKey");
|
||||||
|
|
||||||
return new ActiveSession(
|
return new ActiveSession(
|
||||||
sessionId,
|
sessionId,
|
||||||
loginId,
|
loginId,
|
||||||
sessionPwd,
|
sessionPwd,
|
||||||
storagePwd,
|
storagePwd,
|
||||||
sessionCreatedMs,
|
sessionCreatedAtMs,
|
||||||
lastAuthMs,
|
lastAuthirificatedAtMs,
|
||||||
pushEndpoint,
|
pushEndpoint,
|
||||||
pushP256dhKey,
|
pushP256dhKey,
|
||||||
pushAuthKey
|
pushAuthKey
|
||||||
|
|||||||
@ -7,8 +7,17 @@ import java.sql.*;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/** Здесь храним данные об пользователях - локальная копия того что есть в солане */
|
/**
|
||||||
|
* SolanaUsersDAO — локальная таблица пользователей из Solana.
|
||||||
|
*
|
||||||
|
* Колонки:
|
||||||
|
* - login TEXT
|
||||||
|
* - loginId INTEGER (PK)
|
||||||
|
* - bchId INTEGER
|
||||||
|
* - loginKey TEXT
|
||||||
|
* - deviceKey TEXT
|
||||||
|
* - bchLimit INTEGER (может быть NULL)
|
||||||
|
*/
|
||||||
public final class SolanaUsersDAO {
|
public final class SolanaUsersDAO {
|
||||||
|
|
||||||
private static volatile SolanaUsersDAO instance;
|
private static volatile SolanaUsersDAO instance;
|
||||||
@ -29,7 +38,7 @@ public final class SolanaUsersDAO {
|
|||||||
|
|
||||||
public void insert(SolanaUser user) throws SQLException {
|
public void insert(SolanaUser user) throws SQLException {
|
||||||
String sql = """
|
String sql = """
|
||||||
INSERT INTO solana_users (login, loginId, bchId, pubkey0, pubkey1, bchLimit)
|
INSERT INTO solana_users (login, loginId, bchId, loginKey, deviceKey, bchLimit)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
""";
|
""";
|
||||||
|
|
||||||
@ -37,8 +46,8 @@ public final class SolanaUsersDAO {
|
|||||||
ps.setString(1, user.getLogin());
|
ps.setString(1, user.getLogin());
|
||||||
ps.setLong(2, user.getLoginId());
|
ps.setLong(2, user.getLoginId());
|
||||||
ps.setLong(3, user.getBchId());
|
ps.setLong(3, user.getBchId());
|
||||||
ps.setString(4, user.getPubkey0());
|
ps.setString(4, user.getLoginKey());
|
||||||
ps.setString(5, user.getPubkey1());
|
ps.setString(5, user.getDeviceKey());
|
||||||
|
|
||||||
if (user.getBchLimit() != null) {
|
if (user.getBchLimit() != null) {
|
||||||
ps.setInt(6, user.getBchLimit());
|
ps.setInt(6, user.getBchLimit());
|
||||||
@ -52,7 +61,7 @@ public final class SolanaUsersDAO {
|
|||||||
|
|
||||||
public SolanaUser getByLoginId(long loginId) throws SQLException {
|
public SolanaUser getByLoginId(long loginId) throws SQLException {
|
||||||
String sql = """
|
String sql = """
|
||||||
SELECT login, loginId, bchId, pubkey0, pubkey1, bchLimit
|
SELECT login, loginId, bchId, loginKey, deviceKey, bchLimit
|
||||||
FROM solana_users
|
FROM solana_users
|
||||||
WHERE loginId = ?
|
WHERE loginId = ?
|
||||||
""";
|
""";
|
||||||
@ -69,7 +78,7 @@ public final class SolanaUsersDAO {
|
|||||||
|
|
||||||
public SolanaUser getByLogin(String login) throws SQLException {
|
public SolanaUser getByLogin(String login) throws SQLException {
|
||||||
String sql = """
|
String sql = """
|
||||||
SELECT login, loginId, bchId, pubkey0, pubkey1, bchLimit
|
SELECT login, loginId, bchId, loginKey, deviceKey, bchLimit
|
||||||
FROM solana_users
|
FROM solana_users
|
||||||
WHERE LOWER(login) = LOWER(?)
|
WHERE LOWER(login) = LOWER(?)
|
||||||
""";
|
""";
|
||||||
@ -86,7 +95,7 @@ public final class SolanaUsersDAO {
|
|||||||
|
|
||||||
public List<SolanaUser> searchByLoginPrefix(String prefix) throws SQLException {
|
public List<SolanaUser> searchByLoginPrefix(String prefix) throws SQLException {
|
||||||
String sql = """
|
String sql = """
|
||||||
SELECT login, loginId, bchId, pubkey0, pubkey1, bchLimit
|
SELECT login, loginId, bchId, loginKey, deviceKey, bchLimit
|
||||||
FROM solana_users
|
FROM solana_users
|
||||||
WHERE LOWER(login) LIKE ?
|
WHERE LOWER(login) LIKE ?
|
||||||
ORDER BY login
|
ORDER BY login
|
||||||
@ -111,8 +120,8 @@ public final class SolanaUsersDAO {
|
|||||||
rs.getLong("loginId"),
|
rs.getLong("loginId"),
|
||||||
rs.getString("login"),
|
rs.getString("login"),
|
||||||
rs.getLong("bchId"),
|
rs.getLong("bchId"),
|
||||||
rs.getString("pubkey0"),
|
rs.getString("loginKey"),
|
||||||
rs.getString("pubkey1"),
|
rs.getString("deviceKey"),
|
||||||
rs.getObject("bchLimit") != null ? rs.getInt("bchLimit") : null
|
rs.getObject("bchLimit") != null ? rs.getInt("bchLimit") : null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,30 +1,34 @@
|
|||||||
package shine.db.entities;
|
package shine.db.entities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ActiveSession — запись об активной сессии пользователя.
|
* Модель активной сессии (таблица active_sessions).
|
||||||
*
|
*
|
||||||
* Поля:
|
* Поля соответствуют схеме:
|
||||||
* - sessionId – строка (base64 от 32 байт)
|
*
|
||||||
* - loginId – long
|
* CREATE TABLE active_sessions (
|
||||||
* - sessionPwd – строка (секрет шага 1)
|
* sessionId TEXT NOT NULL PRIMARY KEY,
|
||||||
* - storagePwd – строка (секрет клиента для хранения данных)
|
* loginId INTEGER NOT NULL,
|
||||||
* - sessionCreatedAtMs – long (время создания)
|
* sessionPwd TEXT NOT NULL,
|
||||||
* - lastAuthirificatedAtMs – long (последнее подтверждение/refresh)
|
* storagePwd TEXT NOT NULL,
|
||||||
* - pushEndpoint – строка (WebPush, пока null/пусто)
|
* sessionCreatedAtMs INTEGER NOT NULL,
|
||||||
* - pushP256dhKey – строка (WebPush, пока null/пусто)
|
* lastAuthirificatedAtMs INTEGER NOT NULL,
|
||||||
* - pushAuthKey – строка (WebPush, пока null/пусто)
|
* pushEndpoint TEXT,
|
||||||
|
* pushP256dhKey TEXT,
|
||||||
|
* pushAuthKey TEXT,
|
||||||
|
* FOREIGN KEY (loginId) REFERENCES solana_users(loginId)
|
||||||
|
* );
|
||||||
*/
|
*/
|
||||||
public class ActiveSession {
|
public class ActiveSession {
|
||||||
|
|
||||||
private String sessionId;
|
private String sessionId; // TEXT base64(32 bytes)
|
||||||
private long loginId;
|
private long loginId; // INTEGER
|
||||||
private String sessionPwd;
|
private String sessionPwd; // TEXT
|
||||||
private String storagePwd;
|
private String storagePwd; // TEXT
|
||||||
private long sessionCreatedAtMs;
|
private long sessionCreatedAtMs; // INTEGER
|
||||||
private long lastAuthirificatedAtMs;
|
private long lastAuthirificatedAtMs; // INTEGER
|
||||||
private String pushEndpoint;
|
private String pushEndpoint; // TEXT (nullable)
|
||||||
private String pushP256dhKey;
|
private String pushP256dhKey; // TEXT (nullable)
|
||||||
private String pushAuthKey;
|
private String pushAuthKey; // TEXT (nullable)
|
||||||
|
|
||||||
public ActiveSession() {
|
public ActiveSession() {
|
||||||
}
|
}
|
||||||
@ -49,9 +53,12 @@ public class ActiveSession {
|
|||||||
this.pushAuthKey = pushAuthKey;
|
this.pushAuthKey = pushAuthKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- getters / setters ---
|
||||||
|
|
||||||
public String getSessionId() {
|
public String getSessionId() {
|
||||||
return sessionId;
|
return sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSessionId(String sessionId) {
|
public void setSessionId(String sessionId) {
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
}
|
}
|
||||||
@ -59,6 +66,7 @@ public class ActiveSession {
|
|||||||
public long getLoginId() {
|
public long getLoginId() {
|
||||||
return loginId;
|
return loginId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLoginId(long loginId) {
|
public void setLoginId(long loginId) {
|
||||||
this.loginId = loginId;
|
this.loginId = loginId;
|
||||||
}
|
}
|
||||||
@ -66,6 +74,7 @@ public class ActiveSession {
|
|||||||
public String getSessionPwd() {
|
public String getSessionPwd() {
|
||||||
return sessionPwd;
|
return sessionPwd;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSessionPwd(String sessionPwd) {
|
public void setSessionPwd(String sessionPwd) {
|
||||||
this.sessionPwd = sessionPwd;
|
this.sessionPwd = sessionPwd;
|
||||||
}
|
}
|
||||||
@ -73,6 +82,7 @@ public class ActiveSession {
|
|||||||
public String getStoragePwd() {
|
public String getStoragePwd() {
|
||||||
return storagePwd;
|
return storagePwd;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStoragePwd(String storagePwd) {
|
public void setStoragePwd(String storagePwd) {
|
||||||
this.storagePwd = storagePwd;
|
this.storagePwd = storagePwd;
|
||||||
}
|
}
|
||||||
@ -80,6 +90,7 @@ public class ActiveSession {
|
|||||||
public long getSessionCreatedAtMs() {
|
public long getSessionCreatedAtMs() {
|
||||||
return sessionCreatedAtMs;
|
return sessionCreatedAtMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSessionCreatedAtMs(long sessionCreatedAtMs) {
|
public void setSessionCreatedAtMs(long sessionCreatedAtMs) {
|
||||||
this.sessionCreatedAtMs = sessionCreatedAtMs;
|
this.sessionCreatedAtMs = sessionCreatedAtMs;
|
||||||
}
|
}
|
||||||
@ -87,6 +98,7 @@ public class ActiveSession {
|
|||||||
public long getLastAuthirificatedAtMs() {
|
public long getLastAuthirificatedAtMs() {
|
||||||
return lastAuthirificatedAtMs;
|
return lastAuthirificatedAtMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLastAuthirificatedAtMs(long lastAuthirificatedAtMs) {
|
public void setLastAuthirificatedAtMs(long lastAuthirificatedAtMs) {
|
||||||
this.lastAuthirificatedAtMs = lastAuthirificatedAtMs;
|
this.lastAuthirificatedAtMs = lastAuthirificatedAtMs;
|
||||||
}
|
}
|
||||||
@ -94,6 +106,7 @@ public class ActiveSession {
|
|||||||
public String getPushEndpoint() {
|
public String getPushEndpoint() {
|
||||||
return pushEndpoint;
|
return pushEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPushEndpoint(String pushEndpoint) {
|
public void setPushEndpoint(String pushEndpoint) {
|
||||||
this.pushEndpoint = pushEndpoint;
|
this.pushEndpoint = pushEndpoint;
|
||||||
}
|
}
|
||||||
@ -101,6 +114,7 @@ public class ActiveSession {
|
|||||||
public String getPushP256dhKey() {
|
public String getPushP256dhKey() {
|
||||||
return pushP256dhKey;
|
return pushP256dhKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPushP256dhKey(String pushP256dhKey) {
|
public void setPushP256dhKey(String pushP256dhKey) {
|
||||||
this.pushP256dhKey = pushP256dhKey;
|
this.pushP256dhKey = pushP256dhKey;
|
||||||
}
|
}
|
||||||
@ -108,6 +122,7 @@ public class ActiveSession {
|
|||||||
public String getPushAuthKey() {
|
public String getPushAuthKey() {
|
||||||
return pushAuthKey;
|
return pushAuthKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPushAuthKey(String pushAuthKey) {
|
public void setPushAuthKey(String pushAuthKey) {
|
||||||
this.pushAuthKey = pushAuthKey;
|
this.pushAuthKey = pushAuthKey;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,23 @@
|
|||||||
package shine.db.entities;
|
package shine.db.entities;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Локальная копия пользователя из Solana.
|
||||||
|
*
|
||||||
|
* Храним:
|
||||||
|
* - login / loginId;
|
||||||
|
* - bchId — id персонального блокчейна;
|
||||||
|
* - loginKey — публичный ключ для логина / авторизации;
|
||||||
|
* - deviceKey — публичный ключ устройства (второй ключ);
|
||||||
|
* - bchLimit — лимит по количеству блоков / размеру цепочки (может быть null).
|
||||||
|
*/
|
||||||
public class SolanaUser {
|
public class SolanaUser {
|
||||||
|
|
||||||
private long loginId;
|
private long loginId;
|
||||||
private String login;
|
private String login;
|
||||||
private long bchId;
|
private long bchId;
|
||||||
private String pubkey0;
|
private String loginKey; // раньше pubkey0
|
||||||
private String pubkey1;
|
private String deviceKey; // раньше pubkey1
|
||||||
private Integer bchLimit; // может быть null
|
private Integer bchLimit; // может быть null
|
||||||
|
|
||||||
public SolanaUser() {
|
public SolanaUser() {
|
||||||
}
|
}
|
||||||
@ -15,14 +25,14 @@ public class SolanaUser {
|
|||||||
public SolanaUser(long loginId,
|
public SolanaUser(long loginId,
|
||||||
String login,
|
String login,
|
||||||
long bchId,
|
long bchId,
|
||||||
String pubkey0,
|
String loginKey,
|
||||||
String pubkey1,
|
String deviceKey,
|
||||||
Integer bchLimit) {
|
Integer bchLimit) {
|
||||||
this.loginId = loginId;
|
this.loginId = loginId;
|
||||||
this.login = login;
|
this.login = login;
|
||||||
this.bchId = bchId;
|
this.bchId = bchId;
|
||||||
this.pubkey0 = pubkey0;
|
this.loginKey = loginKey;
|
||||||
this.pubkey1 = pubkey1;
|
this.deviceKey = deviceKey;
|
||||||
this.bchLimit = bchLimit;
|
this.bchLimit = bchLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,20 +60,22 @@ public class SolanaUser {
|
|||||||
this.bchId = bchId;
|
this.bchId = bchId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPubkey0() {
|
/** Публичный ключ логина (основной ключ пользователя). */
|
||||||
return pubkey0;
|
public String getLoginKey() {
|
||||||
|
return loginKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPubkey0(String pubkey0) {
|
public void setLoginKey(String loginKey) {
|
||||||
this.pubkey0 = pubkey0;
|
this.loginKey = loginKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPubkey1() {
|
/** Публичный ключ устройства (device key). */
|
||||||
return pubkey1;
|
public String getDeviceKey() {
|
||||||
|
return deviceKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPubkey1(String pubkey1) {
|
public void setDeviceKey(String deviceKey) {
|
||||||
this.pubkey1 = pubkey1;
|
this.deviceKey = deviceKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getBchLimit() {
|
public Integer getBchLimit() {
|
||||||
|
|||||||
@ -3,27 +3,32 @@ package server.logic.ws_protocol.JSON.entyties.tempToTest;
|
|||||||
import server.logic.ws_protocol.JSON.entyties.NetRequest;
|
import server.logic.ws_protocol.JSON.entyties.NetRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Запрос AddUser.
|
* Запрос AddUser — временная/тестовая регистрация локального пользователя.
|
||||||
*.
|
*
|
||||||
* Ожидаемый JSON:
|
* Клиент отправляет:
|
||||||
|
*
|
||||||
* {
|
* {
|
||||||
* "op": "AddUser",
|
* "op": "AddUser",
|
||||||
* "requestId": "...",
|
* "requestId": "test-add-1",
|
||||||
* "login": "...",
|
* "payload": {
|
||||||
* "loginId": 123,
|
* "login": "anya",
|
||||||
* "bchId": 456,
|
* "loginId": 100211,
|
||||||
* "pubkey0": "...",
|
* "bchId": 4222,
|
||||||
* "pubkey1": "...",
|
* "loginKey": "base64-ed25519-public-key-login",
|
||||||
* "bchLimit": 1000
|
* "deviceKey": "base64-ed25519-public-key-device",
|
||||||
|
* "bchLimit": 1000000
|
||||||
|
* }
|
||||||
* }
|
* }
|
||||||
|
*
|
||||||
|
* Все поля лежат внутри payload.
|
||||||
*/
|
*/
|
||||||
public class NetAddUserRequest extends NetRequest {
|
public class NetAddUserRequest extends NetRequest {
|
||||||
|
|
||||||
private String login;
|
private String login;
|
||||||
private long loginId;
|
private long loginId;
|
||||||
private long bchId;
|
private long bchId;
|
||||||
private String pubkey0;
|
private String loginKey;
|
||||||
private String pubkey1;
|
private String deviceKey;
|
||||||
private Integer bchLimit;
|
private Integer bchLimit;
|
||||||
|
|
||||||
public String getLogin() {
|
public String getLogin() {
|
||||||
@ -50,20 +55,20 @@ public class NetAddUserRequest extends NetRequest {
|
|||||||
this.bchId = bchId;
|
this.bchId = bchId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPubkey0() {
|
public String getLoginKey() {
|
||||||
return pubkey0;
|
return loginKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPubkey0(String pubkey0) {
|
public void setLoginKey(String loginKey) {
|
||||||
this.pubkey0 = pubkey0;
|
this.loginKey = loginKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPubkey1() {
|
public String getDeviceKey() {
|
||||||
return pubkey1;
|
return deviceKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPubkey1(String pubkey1) {
|
public void setDeviceKey(String deviceKey) {
|
||||||
this.pubkey1 = pubkey1;
|
this.deviceKey = deviceKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getBchLimit() {
|
public Integer getBchLimit() {
|
||||||
|
|||||||
@ -4,8 +4,17 @@ import server.logic.ws_protocol.JSON.entyties.NetResponse;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Успешный ответ на AddUser.
|
* Успешный ответ на AddUser.
|
||||||
* Дополнительных полей нет — достаточно status=200.
|
*
|
||||||
|
* Сейчас дополнительных полей нет — достаточно status=200.
|
||||||
|
*
|
||||||
|
* Пример:
|
||||||
|
* {
|
||||||
|
* "op": "AddUser",
|
||||||
|
* "requestId": "test-add-1",
|
||||||
|
* "status": 200,
|
||||||
|
* "payload": { }
|
||||||
|
* }
|
||||||
*/
|
*/
|
||||||
public class NetAddUserResponse extends NetResponse {
|
public class NetAddUserResponse extends NetResponse {
|
||||||
// Можно потом добавить какие-то данные, если понадобится.
|
// При необходимости сюда можно добавить, например, флаг created/updated и т.п.
|
||||||
}
|
}
|
||||||
|
|||||||
@ -119,7 +119,7 @@ public class NetAuthSessionNewStep2Handler implements JsonMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- выбираем публичный ключ pubkey1 ---
|
// --- выбираем публичный ключ pubkey1 ---
|
||||||
String pubKeyB64 = user.getPubkey1();
|
String pubKeyB64 = user.getDeviceKey();
|
||||||
if (pubKeyB64 == null || pubKeyB64.isBlank()) {
|
if (pubKeyB64 == null || pubKeyB64.isBlank()) {
|
||||||
return NetExceptionResponseFactory.error(
|
return NetExceptionResponseFactory.error(
|
||||||
req,
|
req,
|
||||||
|
|||||||
@ -16,7 +16,25 @@ import shine.db.entities.SolanaUser;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Временный хэндлер AddUser (тестовая регистрация).
|
* Временный хэндлер AddUser (тестовая регистрация локального пользователя).
|
||||||
|
*
|
||||||
|
* Ожидаемый запрос (все поля в payload):
|
||||||
|
* {
|
||||||
|
* "op": "AddUser",
|
||||||
|
* "requestId": "...",
|
||||||
|
* "payload": {
|
||||||
|
* "login": "anya",
|
||||||
|
* "loginId": 100211,
|
||||||
|
* "bchId": 4222,
|
||||||
|
* "loginKey": "base64-pubkey-login",
|
||||||
|
* "deviceKey": "base64-pubkey-device",
|
||||||
|
* "bchLimit": 1000000
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* При успехе:
|
||||||
|
* - пользователь сохраняется в таблицу solana_users;
|
||||||
|
* - возвращается status=200 и пустой payload.
|
||||||
*/
|
*/
|
||||||
public class NetAddUserHandler implements JsonMessageHandler {
|
public class NetAddUserHandler implements JsonMessageHandler {
|
||||||
|
|
||||||
@ -28,15 +46,15 @@ public class NetAddUserHandler implements JsonMessageHandler {
|
|||||||
|
|
||||||
// Одна общая проверка всех ключевых полей
|
// Одна общая проверка всех ключевых полей
|
||||||
if (req.getLogin() == null || req.getLogin().isBlank()
|
if (req.getLogin() == null || req.getLogin().isBlank()
|
||||||
|| req.getPubkey0() == null || req.getPubkey0().isBlank()
|
|| req.getLoginKey() == null || req.getLoginKey().isBlank()
|
||||||
|| req.getPubkey1() == null || req.getPubkey1().isBlank()
|
|| req.getDeviceKey() == null || req.getDeviceKey().isBlank()
|
||||||
|| req.getBchLimit() == null) {
|
|| req.getBchLimit() == null) {
|
||||||
|
|
||||||
return NetExceptionResponseFactory.error(
|
return NetExceptionResponseFactory.error(
|
||||||
req,
|
req,
|
||||||
WireCodes.Status.BAD_REQUEST,
|
WireCodes.Status.BAD_REQUEST,
|
||||||
"BAD_FIELDS",
|
"BAD_FIELDS",
|
||||||
"Некорректные или пустые поля: login, pubkey0, pubkey1, bchLimit"
|
"Некорректные или пустые поля: login, loginKey, deviceKey, bchLimit"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,8 +65,8 @@ public class NetAddUserHandler implements JsonMessageHandler {
|
|||||||
req.getLoginId(),
|
req.getLoginId(),
|
||||||
req.getLogin(),
|
req.getLogin(),
|
||||||
req.getBchId(),
|
req.getBchId(),
|
||||||
req.getPubkey0(),
|
req.getLoginKey(),
|
||||||
req.getPubkey1(),
|
req.getDeviceKey(),
|
||||||
req.getBchLimit()
|
req.getBchLimit()
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -58,7 +76,7 @@ public class NetAddUserHandler implements JsonMessageHandler {
|
|||||||
resp.setOp(req.getOp());
|
resp.setOp(req.getOp());
|
||||||
resp.setRequestId(req.getRequestId());
|
resp.setRequestId(req.getRequestId());
|
||||||
resp.setStatus(WireCodes.Status.OK);
|
resp.setStatus(WireCodes.Status.OK);
|
||||||
// payload сам станет {} через JsonInboundProcessor
|
// payload станет {} через JsonInboundProcessor
|
||||||
log.info("✅ Пользователь добавлен: login={}, loginId={}", req.getLogin(), req.getLoginId());
|
log.info("✅ Пользователь добавлен: login={}, loginId={}", req.getLogin(), req.getLoginId());
|
||||||
return resp;
|
return resp;
|
||||||
|
|
||||||
|
|||||||
@ -8,3 +8,6 @@ user@p628065:~/docker/ws-server$ nohup java -jar ws-server.jar > server.log 2>&1
|
|||||||
|
|
||||||
перестартовать кадди
|
перестартовать кадди
|
||||||
user@p628065:~/docker/ws-server$ docker restart caddy
|
user@p628065:~/docker/ws-server$ docker restart caddy
|
||||||
|
|
||||||
|
очистить того кто держит порт 7070 :)
|
||||||
|
kill -9 $(lsof -t -i:7070)
|
||||||
|
|||||||
@ -1,242 +0,0 @@
|
|||||||
package Test;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import utils.crypto.Ed25519Util;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.WebSocket;
|
|
||||||
import java.net.http.WebSocket.Listener;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.CompletionStage;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
|
|
||||||
public class Test_AddUser_FirstAuth {
|
|
||||||
|
|
||||||
// Адрес сервера
|
|
||||||
private static final String WS_URI = "ws://localhost:7070/ws";
|
|
||||||
|
|
||||||
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
|
|
||||||
|
|
||||||
// Тестовые данные пользователя
|
|
||||||
private static final String TEST_LOGIN = "anya2";
|
|
||||||
private static final long TEST_LOGIN_ID = 100212L;
|
|
||||||
private static final long TEST_BCH_ID = 4222L;
|
|
||||||
private static final int TEST_BCH_LIMIT = 1_000_000;
|
|
||||||
|
|
||||||
// Тестовая пара ключей Ed25519 (стабильная, чтобы поведение не прыгало)
|
|
||||||
private static final byte[] TEST_PRIV_KEY;
|
|
||||||
private static final String TEST_PUBKEY_B64;
|
|
||||||
|
|
||||||
static {
|
|
||||||
// Можно сделать детерминированно от логина, чтобы всегда были одинаковые ключи
|
|
||||||
TEST_PRIV_KEY = Ed25519Util.generatePrivateKeyFromString("test-ed25519-" + TEST_LOGIN);
|
|
||||||
byte[] pub = Ed25519Util.derivePublicKey(TEST_PRIV_KEY);
|
|
||||||
TEST_PUBKEY_B64 = Ed25519Util.keyToBase64(pub);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
|
||||||
System.out.println("Подключаемся к " + WS_URI);
|
|
||||||
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
|
|
||||||
HttpClient client = HttpClient.newHttpClient();
|
|
||||||
|
|
||||||
ClientListener listener = new ClientListener(latch);
|
|
||||||
|
|
||||||
client.newWebSocketBuilder()
|
|
||||||
.buildAsync(URI.create(WS_URI), listener)
|
|
||||||
.join();
|
|
||||||
|
|
||||||
// Ждём, пока всё не завершится (успех/ошибка/закрытие)
|
|
||||||
latch.await();
|
|
||||||
System.out.println("Тест завершён, выходим.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- вспомогательные билдера JSON-запросов ---
|
|
||||||
|
|
||||||
// 1) Добавление пользователя
|
|
||||||
private static String buildAddUserJson() {
|
|
||||||
return """
|
|
||||||
{
|
|
||||||
"op": "AddUser",
|
|
||||||
"requestId": "test-add-1",
|
|
||||||
"payload": {
|
|
||||||
"login": "%s",
|
|
||||||
"loginId": %d,
|
|
||||||
"bchId": %d,
|
|
||||||
"pubkey0": "%s",
|
|
||||||
"pubkey1": "%s",
|
|
||||||
"bchLimit": %d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""".formatted(
|
|
||||||
TEST_LOGIN,
|
|
||||||
TEST_LOGIN_ID,
|
|
||||||
TEST_BCH_ID,
|
|
||||||
TEST_PUBKEY_B64, // pubkey0
|
|
||||||
TEST_PUBKEY_B64, // pubkey1 (для теста можно тот же)
|
|
||||||
TEST_BCH_LIMIT
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) Шаг 1 авторизации: запрос sessionPwd
|
|
||||||
private static String buildAuthStep1Json() {
|
|
||||||
return """
|
|
||||||
{
|
|
||||||
"op": "AuthSessionNewStep1",
|
|
||||||
"requestId": "test-auth-1",
|
|
||||||
"payload": {
|
|
||||||
"login": "%s"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""".formatted(TEST_LOGIN);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) Шаг 2 авторизации: подтверждение подписью
|
|
||||||
private static String buildAuthStep2Json(String sessionPwd) {
|
|
||||||
if (sessionPwd == null) {
|
|
||||||
sessionPwd = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
long timeMs = System.currentTimeMillis();
|
|
||||||
|
|
||||||
// preimage = loginId + timeMs + sessionPwd
|
|
||||||
String preimageStr = String.valueOf(TEST_LOGIN_ID) + timeMs + sessionPwd;
|
|
||||||
byte[] preimage = preimageStr.getBytes(StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
// Подписываем приватным ключом
|
|
||||||
byte[] sig = Ed25519Util.sign(preimage, TEST_PRIV_KEY);
|
|
||||||
String sigB64 = Base64.getEncoder().encodeToString(sig);
|
|
||||||
|
|
||||||
return """
|
|
||||||
{
|
|
||||||
"op": "AuthSessionNewStep2",
|
|
||||||
"requestId": "test-auth-2",
|
|
||||||
"payload": {
|
|
||||||
"loginId": %d,
|
|
||||||
"sigNum": 0,
|
|
||||||
"timeMs": %d,
|
|
||||||
"signatureB64": "%s"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""".formatted(
|
|
||||||
TEST_LOGIN_ID,
|
|
||||||
timeMs,
|
|
||||||
sigB64
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ================== LISTENER ==================
|
|
||||||
|
|
||||||
// Внутренний Listener, который сам по шагам шлёт запросы и печатает ответы
|
|
||||||
private static class ClientListener implements Listener {
|
|
||||||
|
|
||||||
private final CountDownLatch latch;
|
|
||||||
private int step = 0; // 0 - AddUser, 1 - AuthStep1, 2 - AuthStep2
|
|
||||||
private String sessionPwdFromStep1;
|
|
||||||
|
|
||||||
ClientListener(CountDownLatch latch) {
|
|
||||||
this.latch = latch;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onOpen(WebSocket webSocket) {
|
|
||||||
System.out.println("✅ WebSocket подключен");
|
|
||||||
|
|
||||||
// Разрешаем приём первого сообщения
|
|
||||||
webSocket.request(1);
|
|
||||||
|
|
||||||
sendNextRequest(webSocket);
|
|
||||||
Listener.super.onOpen(webSocket);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отправка следующего запроса в зависимости от шага
|
|
||||||
private void sendNextRequest(WebSocket webSocket) {
|
|
||||||
switch (step) {
|
|
||||||
case 0 -> {
|
|
||||||
String json = buildAddUserJson();
|
|
||||||
System.out.println();
|
|
||||||
System.out.println("📤 [Шаг 1] Отправляем AddUser:");
|
|
||||||
System.out.println(json);
|
|
||||||
webSocket.sendText(json, true);
|
|
||||||
}
|
|
||||||
case 1 -> {
|
|
||||||
String json = buildAuthStep1Json();
|
|
||||||
System.out.println();
|
|
||||||
System.out.println("📤 [Шаг 2] Отправляем AuthSessionNewStep1:");
|
|
||||||
System.out.println(json);
|
|
||||||
webSocket.sendText(json, true);
|
|
||||||
}
|
|
||||||
case 2 -> {
|
|
||||||
String json = buildAuthStep2Json(sessionPwdFromStep1);
|
|
||||||
System.out.println();
|
|
||||||
System.out.println("📤 [Шаг 3] Отправляем AuthSessionNewStep2 (подпись):");
|
|
||||||
System.out.println(json);
|
|
||||||
webSocket.sendText(json, true);
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
System.out.println("✅ Все шаги выполнены, закрываем соединение");
|
|
||||||
webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "all tests done");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletionStage<?> onText(WebSocket webSocket,
|
|
||||||
CharSequence data,
|
|
||||||
boolean last) {
|
|
||||||
String message = data.toString();
|
|
||||||
System.out.println("📥 Ответ на шаг " + (step + 1) + ":");
|
|
||||||
System.out.println(message);
|
|
||||||
System.out.println("-----------------------------------------------------");
|
|
||||||
|
|
||||||
// Если это ответ на шаг 2 (AuthSessionNewStep1) — достаем sessionPwd из payload
|
|
||||||
if (step == 1) {
|
|
||||||
sessionPwdFromStep1 = extractSessionPwd(message);
|
|
||||||
System.out.println("🔑 Извлечён sessionPwd: " + sessionPwdFromStep1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Переходим к следующему шагу
|
|
||||||
step++;
|
|
||||||
sendNextRequest(webSocket);
|
|
||||||
|
|
||||||
// Запрашиваем следующее входящее сообщение
|
|
||||||
webSocket.request(1);
|
|
||||||
|
|
||||||
return CompletableFuture.completedFuture(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(WebSocket webSocket, Throwable error) {
|
|
||||||
System.out.println("❌ Ошибка WebSocket-клиента: " + error.getMessage());
|
|
||||||
error.printStackTrace(System.out);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletionStage<?> onClose(WebSocket webSocket,
|
|
||||||
int statusCode,
|
|
||||||
String reason) {
|
|
||||||
System.out.println("🔚 Соединение закрыто. Код=" + statusCode + ", причина=" + reason);
|
|
||||||
latch.countDown();
|
|
||||||
return CompletableFuture.completedFuture(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String extractSessionPwd(String json) {
|
|
||||||
try {
|
|
||||||
JsonNode root = JSON_MAPPER.readTree(json);
|
|
||||||
JsonNode payload = root.get("payload");
|
|
||||||
if (payload != null && payload.has("sessionPwd")) {
|
|
||||||
return payload.get("sessionPwd").asText();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.out.println("⚠️ Не удалось распарсить sessionPwd из ответа: " + e.getMessage());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
497
src/main/java/Test/Test_AddUser_and_Authorification.java
Normal file
497
src/main/java/Test/Test_AddUser_and_Authorification.java
Normal file
@ -0,0 +1,497 @@
|
|||||||
|
package Test;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import utils.crypto.Ed25519Util;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.WebSocket;
|
||||||
|
import java.net.http.WebSocket.Listener;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionStage;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Полный тестовый сценарий:
|
||||||
|
*
|
||||||
|
* 1) AddUser — добавляем пользователя в локальную БД
|
||||||
|
* (loginKey и deviceKey разные).
|
||||||
|
*
|
||||||
|
* 2) AuthSessionNewStep1 — запрашиваем sessionPwd.
|
||||||
|
*
|
||||||
|
* 3) AuthSessionNewStep2 — подтверждаем владение deviceKey,
|
||||||
|
* создаётся сессия, сервер возвращает sessionId (строка).
|
||||||
|
*
|
||||||
|
* 4) Новое подключение:
|
||||||
|
* - отправляем SessionRefresh с тем же sessionId,
|
||||||
|
* но заведомо неверным sessionPwd
|
||||||
|
* (в консоль пишем: ожидаем ОТРИЦАТЕЛЬНЫЙ ответ).
|
||||||
|
*
|
||||||
|
* 5) Ещё одно новое подключение:
|
||||||
|
* - отправляем SessionRefresh с sessionId
|
||||||
|
* и корректным sessionPwd
|
||||||
|
* (в консоль пишем: ожидаем УСПЕШНЫЙ ответ).
|
||||||
|
*/
|
||||||
|
public class Test_AddUser_and_Authorification {
|
||||||
|
|
||||||
|
// Адрес сервера
|
||||||
|
private static final String WS_URI = "ws://localhost:7070/ws";
|
||||||
|
|
||||||
|
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
|
// Тестовые данные пользователя
|
||||||
|
private static final String TEST_LOGIN = "anya1";
|
||||||
|
private static final long TEST_LOGIN_ID = 100310L;
|
||||||
|
private static final long TEST_BCH_ID = 4222L;
|
||||||
|
private static final int TEST_BCH_LIMIT = 1_000_000;
|
||||||
|
|
||||||
|
// --- Тестовые пары ключей ---
|
||||||
|
// loginKey — ключ аккаунта (например, "основной")
|
||||||
|
// deviceKey — ключ устройства, которым подписываем авторизацию
|
||||||
|
|
||||||
|
private static final byte[] LOGIN_PRIV_KEY;
|
||||||
|
private static final String LOGIN_PUBKEY_B64;
|
||||||
|
|
||||||
|
private static final byte[] DEVICE_PRIV_KEY;
|
||||||
|
private static final String DEVICE_PUBKEY_B64;
|
||||||
|
|
||||||
|
static {
|
||||||
|
// Детерминированное "семя" для логин-ключа
|
||||||
|
LOGIN_PRIV_KEY = Ed25519Util.generatePrivateKeyFromString("test-ed25519-login-11" + TEST_LOGIN);
|
||||||
|
byte[] loginPub = Ed25519Util.derivePublicKey(LOGIN_PRIV_KEY);
|
||||||
|
LOGIN_PUBKEY_B64 = Ed25519Util.keyToBase64(loginPub);
|
||||||
|
|
||||||
|
// Детерминированное "семя" для девайс-ключа
|
||||||
|
DEVICE_PRIV_KEY = Ed25519Util.generatePrivateKeyFromString("test-ed25519-device-" + TEST_LOGIN);
|
||||||
|
byte[] devicePub = Ed25519Util.derivePublicKey(DEVICE_PRIV_KEY);
|
||||||
|
DEVICE_PUBKEY_B64 = Ed25519Util.keyToBase64(devicePub);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Глобальные переменные между сценариями ---
|
||||||
|
|
||||||
|
/** sessionPwd, выданный на шаге AuthSessionNewStep1. */
|
||||||
|
private static String GLOBAL_SESSION_PWD;
|
||||||
|
|
||||||
|
/** sessionId (строка, base64-32 байта), выданный на шаге AuthSessionNewStep2. */
|
||||||
|
private static String GLOBAL_SESSION_ID;
|
||||||
|
|
||||||
|
/** storagePwd, который мы отправили при AuthSessionNewStep2 (для информации). */
|
||||||
|
private static String GLOBAL_STORAGE_PWD_SENT;
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
System.out.println("Подключаемся к " + WS_URI);
|
||||||
|
|
||||||
|
// Сценарий 1: регистрация + первичная авторизация
|
||||||
|
runScenario_AddUser_And_FirstAuth();
|
||||||
|
|
||||||
|
// Сценарий 2: новое подключение, SessionRefresh с неверным sessionPwd
|
||||||
|
runScenario_SessionRefresh_WrongPwd();
|
||||||
|
|
||||||
|
// Сценарий 3: новое подключение, SessionRefresh с корректным sessionPwd
|
||||||
|
runScenario_SessionRefresh_CorrectPwd();
|
||||||
|
|
||||||
|
System.out.println("Все тесты завершены, выходим.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// SCENARIO 1: AddUser + Auth
|
||||||
|
// ==========================================================
|
||||||
|
|
||||||
|
private static void runScenario_AddUser_And_FirstAuth() throws Exception {
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("=== СЦЕНАРИЙ 1: AddUser + AuthSessionNewStep1 + AuthSessionNewStep2 ===");
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
|
||||||
|
WebSocket ws = client.newWebSocketBuilder()
|
||||||
|
.buildAsync(URI.create(WS_URI), new Listener() {
|
||||||
|
|
||||||
|
private int step = 0; // 0 - AddUser, 1 - AuthStep1, 2 - AuthStep2
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpen(WebSocket webSocket) {
|
||||||
|
System.out.println("✅ [S1] WebSocket подключен");
|
||||||
|
webSocket.request(1);
|
||||||
|
sendNextRequest(webSocket);
|
||||||
|
Listener.super.onOpen(webSocket);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendNextRequest(WebSocket webSocket) {
|
||||||
|
switch (step) {
|
||||||
|
case 0 -> {
|
||||||
|
String json = buildAddUserJson();
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("📤 [S1 / Шаг 1] Отправляем AddUser:");
|
||||||
|
System.out.println(json);
|
||||||
|
webSocket.sendText(json, true);
|
||||||
|
}
|
||||||
|
case 1 -> {
|
||||||
|
String json = buildAuthStep1Json();
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("📤 [S1 / Шаг 2] Отправляем AuthSessionNewStep1:");
|
||||||
|
System.out.println(json);
|
||||||
|
webSocket.sendText(json, true);
|
||||||
|
}
|
||||||
|
case 2 -> {
|
||||||
|
GLOBAL_STORAGE_PWD_SENT = generateFakeStoragePwd();
|
||||||
|
String json = buildAuthStep2Json(GLOBAL_SESSION_PWD, GLOBAL_STORAGE_PWD_SENT);
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("📤 [S1 / Шаг 3] Отправляем AuthSessionNewStep2 (подпись deviceKey):");
|
||||||
|
System.out.println(json);
|
||||||
|
webSocket.sendText(json, true);
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
System.out.println("✅ [S1] Все шаги выполнены, закрываем соединение");
|
||||||
|
webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "scenario1 done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletionStage<?> onText(WebSocket webSocket,
|
||||||
|
CharSequence data,
|
||||||
|
boolean last) {
|
||||||
|
String message = data.toString();
|
||||||
|
System.out.println("📥 [S1] Ответ на шаг " + (step + 1) + ":");
|
||||||
|
System.out.println(message);
|
||||||
|
System.out.println("-----------------------------------------------------");
|
||||||
|
|
||||||
|
// Шаг 2: получаем sessionPwd
|
||||||
|
if (step == 1) {
|
||||||
|
GLOBAL_SESSION_PWD = extractSessionPwd(message);
|
||||||
|
System.out.println("🔑 [S1] Извлечён sessionPwd: " + GLOBAL_SESSION_PWD);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Шаг 3: получаем sessionId
|
||||||
|
if (step == 2) {
|
||||||
|
GLOBAL_SESSION_ID = extractSessionId(message);
|
||||||
|
System.out.println("🆔 [S1] Извлечён sessionId: " + GLOBAL_SESSION_ID);
|
||||||
|
System.out.println(" (Эта sessionId и sessionPwd понадобятся в сценариях 2 и 3)");
|
||||||
|
}
|
||||||
|
|
||||||
|
step++;
|
||||||
|
sendNextRequest(webSocket);
|
||||||
|
webSocket.request(1);
|
||||||
|
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(WebSocket webSocket, Throwable error) {
|
||||||
|
System.out.println("❌ [S1] Ошибка WebSocket-клиента: " + error.getMessage());
|
||||||
|
error.printStackTrace(System.out);
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletionStage<?> onClose(WebSocket webSocket,
|
||||||
|
int statusCode,
|
||||||
|
String reason) {
|
||||||
|
System.out.println("🔚 [S1] Соединение закрыто. Код=" + statusCode + ", причина=" + reason);
|
||||||
|
latch.countDown();
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
}
|
||||||
|
}).join();
|
||||||
|
|
||||||
|
latch.await();
|
||||||
|
System.out.println("=== СЦЕНАРИЙ 1 завершён ===");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// SCENARIO 2: SessionRefresh с неправильным паролем
|
||||||
|
// ==========================================================
|
||||||
|
|
||||||
|
private static void runScenario_SessionRefresh_WrongPwd() throws Exception {
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("=== СЦЕНАРИЙ 2: SessionRefresh с НЕВЕРНЫМ sessionPwd ===");
|
||||||
|
System.out.println("Ожидаем ОТРИЦАТЕЛЬНЫЙ ответ сервера (UNVERIFIED / SESSION_PWD_MISMATCH и т.п.)");
|
||||||
|
|
||||||
|
if (GLOBAL_SESSION_ID == null || GLOBAL_SESSION_PWD == null) {
|
||||||
|
System.out.println("⚠️ Нет sessionId или sessionPwd из сценария 1, пропускаем сценарий 2.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
|
||||||
|
// Специально подменяем пароль, чтобы сервер его НЕ принял
|
||||||
|
String wrongPwd = GLOBAL_SESSION_PWD + "_WRONG";
|
||||||
|
|
||||||
|
WebSocket ws = client.newWebSocketBuilder()
|
||||||
|
.buildAsync(URI.create(WS_URI), new Listener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpen(WebSocket webSocket) {
|
||||||
|
System.out.println("✅ [S2] WebSocket подключен");
|
||||||
|
webSocket.request(1);
|
||||||
|
|
||||||
|
String json = buildSessionRefreshJson(GLOBAL_SESSION_ID, wrongPwd, "test-refresh-wrong-1");
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("📤 [S2] Отправляем SessionRefresh с НЕВЕРНЫМ sessionPwd:");
|
||||||
|
System.out.println(json);
|
||||||
|
webSocket.sendText(json, true);
|
||||||
|
Listener.super.onOpen(webSocket);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletionStage<?> onText(WebSocket webSocket,
|
||||||
|
CharSequence data,
|
||||||
|
boolean last) {
|
||||||
|
String message = data.toString();
|
||||||
|
System.out.println("📥 [S2] Ответ сервера (ожидаем ошибку):");
|
||||||
|
System.out.println(message);
|
||||||
|
System.out.println("-----------------------------------------------------");
|
||||||
|
System.out.println("💬 [S2] Если в ответе status != 200 и/или код ошибки про неверный пароль — это ПРАВИЛЬНОЕ поведение.");
|
||||||
|
|
||||||
|
webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "scenario2 done");
|
||||||
|
webSocket.request(1);
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(WebSocket webSocket, Throwable error) {
|
||||||
|
System.out.println("❌ [S2] Ошибка WebSocket-клиента: " + error.getMessage());
|
||||||
|
error.printStackTrace(System.out);
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletionStage<?> onClose(WebSocket webSocket,
|
||||||
|
int statusCode,
|
||||||
|
String reason) {
|
||||||
|
System.out.println("🔚 [S2] Соединение закрыто. Код=" + statusCode + ", причина=" + reason);
|
||||||
|
latch.countDown();
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
}
|
||||||
|
}).join();
|
||||||
|
|
||||||
|
latch.await();
|
||||||
|
System.out.println("=== СЦЕНАРИЙ 2 завершён ===");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// SCENARIO 3: SessionRefresh с правильными данными
|
||||||
|
// ==========================================================
|
||||||
|
|
||||||
|
private static void runScenario_SessionRefresh_CorrectPwd() throws Exception {
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("=== СЦЕНАРИЙ 3: SessionRefresh с КОРРЕКТНЫМ sessionPwd ===");
|
||||||
|
System.out.println("Ожидаем УСПЕШНЫЙ ответ сервера (status=200),");
|
||||||
|
System.out.println(" а в payload должен вернуться актуальный storagePwd (по твоей схеме).");
|
||||||
|
|
||||||
|
if (GLOBAL_SESSION_ID == null || GLOBAL_SESSION_PWD == null) {
|
||||||
|
System.out.println("⚠️ Нет sessionId или sessionPwd из сценария 1, пропускаем сценарий 3.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
|
||||||
|
WebSocket ws = client.newWebSocketBuilder()
|
||||||
|
.buildAsync(URI.create(WS_URI), new Listener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpen(WebSocket webSocket) {
|
||||||
|
System.out.println("✅ [S3] WebSocket подключен");
|
||||||
|
webSocket.request(1);
|
||||||
|
|
||||||
|
String json = buildSessionRefreshJson(GLOBAL_SESSION_ID, GLOBAL_SESSION_PWD, "test-refresh-ok-1");
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("📤 [S3] Отправляем SessionRefresh с КОРРЕКТНЫМ sessionPwd:");
|
||||||
|
System.out.println(json);
|
||||||
|
webSocket.sendText(json, true);
|
||||||
|
Listener.super.onOpen(webSocket);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletionStage<?> onText(WebSocket webSocket,
|
||||||
|
CharSequence data,
|
||||||
|
boolean last) {
|
||||||
|
String message = data.toString();
|
||||||
|
System.out.println("📥 [S3] Ответ сервера (ожидаем успех):");
|
||||||
|
System.out.println(message);
|
||||||
|
System.out.println("-----------------------------------------------------");
|
||||||
|
System.out.println("💬 [S3] Если status=200 — сессия успешно восстановлена.");
|
||||||
|
String storagePwdFromServer = extractStoragePwd(message);
|
||||||
|
System.out.println("🧾 [S3] storagePwd от сервера: " + storagePwdFromServer);
|
||||||
|
System.out.println(" (Может совпадать с тем, что был в шаге 2, или быть обновлённым — зависит от логики сервера)");
|
||||||
|
|
||||||
|
webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "scenario3 done");
|
||||||
|
webSocket.request(1);
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(WebSocket webSocket, Throwable error) {
|
||||||
|
System.out.println("❌ [S3] Ошибка WebSocket-клиента: " + error.getMessage());
|
||||||
|
error.printStackTrace(System.out);
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletionStage<?> onClose(WebSocket webSocket,
|
||||||
|
int statusCode,
|
||||||
|
String reason) {
|
||||||
|
System.out.println("🔚 [S3] Соединение закрыто. Код=" + statusCode + ", причина=" + reason);
|
||||||
|
latch.countDown();
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
}
|
||||||
|
}).join();
|
||||||
|
|
||||||
|
latch.await();
|
||||||
|
System.out.println("=== СЦЕНАРИЙ 3 завершён ===");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// JSON BUILDERS
|
||||||
|
// ==========================================================
|
||||||
|
|
||||||
|
// 1) AddUser с payload (loginKey != deviceKey)
|
||||||
|
private static String buildAddUserJson() {
|
||||||
|
return """
|
||||||
|
{
|
||||||
|
"op": "AddUser",
|
||||||
|
"requestId": "test-add-1",
|
||||||
|
"payload": {
|
||||||
|
"login": "%s",
|
||||||
|
"loginId": %d,
|
||||||
|
"bchId": %d,
|
||||||
|
"loginKey": "%s",
|
||||||
|
"deviceKey": "%s",
|
||||||
|
"bchLimit": %d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".formatted(
|
||||||
|
TEST_LOGIN,
|
||||||
|
TEST_LOGIN_ID,
|
||||||
|
TEST_BCH_ID,
|
||||||
|
LOGIN_PUBKEY_B64, // loginKey
|
||||||
|
DEVICE_PUBKEY_B64, // deviceKey
|
||||||
|
TEST_BCH_LIMIT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Шаг 1 авторизации: запрос sessionPwd
|
||||||
|
private static String buildAuthStep1Json() {
|
||||||
|
return """
|
||||||
|
{
|
||||||
|
"op": "AuthSessionNewStep1",
|
||||||
|
"requestId": "test-auth-1",
|
||||||
|
"payload": {
|
||||||
|
"login": "%s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".formatted(TEST_LOGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Шаг 2 авторизации: подтверждение подписью
|
||||||
|
// payload: storagePwd, timeMs, signatureB64
|
||||||
|
private static String buildAuthStep2Json(String sessionPwd, String storagePwd) {
|
||||||
|
if (sessionPwd == null) {
|
||||||
|
sessionPwd = "";
|
||||||
|
}
|
||||||
|
if (storagePwd == null || storagePwd.isBlank()) {
|
||||||
|
storagePwd = generateFakeStoragePwd();
|
||||||
|
}
|
||||||
|
|
||||||
|
long timeMs = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// preimage = "AUTHORIFICATED:" + timeMs + sessionPwd
|
||||||
|
String preimageStr = "AUTHORIFICATED:" + timeMs + sessionPwd;
|
||||||
|
byte[] preimage = preimageStr.getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
// Подписываем приватным ключом устройства (deviceKey)
|
||||||
|
byte[] sig = Ed25519Util.sign(preimage, DEVICE_PRIV_KEY);
|
||||||
|
String sigB64 = Base64.getEncoder().encodeToString(sig);
|
||||||
|
|
||||||
|
return """
|
||||||
|
{
|
||||||
|
"op": "AuthSessionNewStep2",
|
||||||
|
"requestId": "test-auth-2",
|
||||||
|
"payload": {
|
||||||
|
"storagePwd": "%s",
|
||||||
|
"timeMs": %d,
|
||||||
|
"signatureB64": "%s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".formatted(
|
||||||
|
storagePwd,
|
||||||
|
timeMs,
|
||||||
|
sigB64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) SessionRefresh: всё в payload
|
||||||
|
private static String buildSessionRefreshJson(String sessionId, String sessionPwd, String requestId) {
|
||||||
|
return """
|
||||||
|
{
|
||||||
|
"op": "SessionRefresh",
|
||||||
|
"requestId": "%s",
|
||||||
|
"payload": {
|
||||||
|
"sessionId": "%s",
|
||||||
|
"sessionPwd": "%s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".formatted(
|
||||||
|
requestId,
|
||||||
|
sessionId,
|
||||||
|
sessionPwd
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// просто для теста: base64 от 32 байт "storage" ключа
|
||||||
|
private static String generateFakeStoragePwd() {
|
||||||
|
byte[] data = new byte[32];
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
data[i] = (byte) (i + 1);
|
||||||
|
}
|
||||||
|
return Base64.getEncoder().encodeToString(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// JSON HELPERS
|
||||||
|
// ==========================================================
|
||||||
|
|
||||||
|
private static String extractSessionPwd(String json) {
|
||||||
|
try {
|
||||||
|
JsonNode root = JSON_MAPPER.readTree(json);
|
||||||
|
JsonNode payload = root.get("payload");
|
||||||
|
if (payload != null && payload.has("sessionPwd")) {
|
||||||
|
return payload.get("sessionPwd").asText();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("⚠️ Не удалось распарсить sessionPwd из ответа: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractSessionId(String json) {
|
||||||
|
try {
|
||||||
|
JsonNode root = JSON_MAPPER.readTree(json);
|
||||||
|
JsonNode payload = root.get("payload");
|
||||||
|
if (payload != null && payload.has("sessionId")) {
|
||||||
|
return payload.get("sessionId").asText();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("⚠️ Не удалось распарсить sessionId из ответа: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractStoragePwd(String json) {
|
||||||
|
try {
|
||||||
|
JsonNode root = JSON_MAPPER.readTree(json);
|
||||||
|
JsonNode payload = root.get("payload");
|
||||||
|
if (payload != null && payload.has("storagePwd")) {
|
||||||
|
return payload.get("storagePwd").asText();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("⚠️ Не удалось распарсить storagePwd из ответа: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user