Ограничил access servers до двух в UI и сервере

This commit is contained in:
AidarKC
2026-08-11 17:21:07 +04:00
parent ee5d86003b
commit 4b0a934e51
8 changed files with 109 additions and 47 deletions
@@ -53,12 +53,16 @@ BEGIN
s.updated_at_ms,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
FROM solana_user_pda_current u
CROSS JOIN LATERAL jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) AS access_server(login_value)
CROSS JOIN LATERAL (
SELECT login_value
FROM jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) WITH ORDINALITY AS access_server(login_value, ord)
WHERE ord <= 2
) AS access_server
JOIN solana_user_pda_current s
ON LOWER(s.login) = LOWER(btrim(access_server.login_value))
AND s.is_server = TRUE
@@ -92,12 +96,16 @@ BEGIN
FOR affected_user IN
SELECT u.login
FROM solana_user_pda_current u
CROSS JOIN LATERAL jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) AS access_server(login_value)
CROSS JOIN LATERAL (
SELECT login_value
FROM jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) WITH ORDINALITY AS access_server(login_value, ord)
WHERE ord <= 2
) AS access_server
WHERE LOWER(btrim(access_server.login_value)) = LOWER(p_server_login)
LOOP
PERFORM shine_refresh_user_access_servers_for_user(affected_user.login);
@@ -162,12 +162,16 @@ BEGIN
s.updated_at_ms,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
FROM solana_user_pda_current u
CROSS JOIN LATERAL jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) AS access_server(login_value)
CROSS JOIN LATERAL (
SELECT login_value
FROM jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) WITH ORDINALITY AS access_server(login_value, ord)
WHERE ord <= 2
) AS access_server
JOIN solana_user_pda_current s
ON LOWER(s.login) = LOWER(btrim(access_server.login_value))
AND s.is_server = TRUE
@@ -201,12 +205,16 @@ BEGIN
FOR affected_user IN
SELECT u.login
FROM solana_user_pda_current u
CROSS JOIN LATERAL jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) AS access_server(login_value)
CROSS JOIN LATERAL (
SELECT login_value
FROM jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) WITH ORDINALITY AS access_server(login_value, ord)
WHERE ord <= 2
) AS access_server
WHERE LOWER(btrim(access_server.login_value)) = LOWER(p_server_login)
LOOP
PERFORM shine_refresh_user_access_servers_for_user(affected_user.login);
@@ -27,6 +27,7 @@ public final class SolanaUserPdaImportService {
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final HttpClient HTTP = HttpClient.newHttpClient();
private static final String MAGIC = "SHiNE";
private static final int MAX_EFFECTIVE_ACCESS_SERVERS = 2;
private SolanaUserPdaImportService() {}
@@ -87,6 +88,7 @@ public final class SolanaUserPdaImportService {
String serverAddress = safe(serverProfile.serverAddress());
if (serverAddress.isBlank()) continue;
routes.putIfAbsent(normalized, new ParsedServerRoute(normalized, serverAddress));
if (routes.size() >= MAX_EFFECTIVE_ACCESS_SERVERS) break;
}
return new ArrayList<>(routes.values());
}
@@ -234,7 +236,13 @@ public final class SolanaUserPdaImportService {
int n = u8(raw, c++);
String accessServerLogin = new String(raw, c, n, StandardCharsets.UTF_8);
c += n;
accessServers.add(normalizeLogin(accessServerLogin));
String normalizedAccessServerLogin = normalizeLogin(accessServerLogin);
if (normalizedAccessServerLogin == null || accessServers.contains(normalizedAccessServerLogin)) {
continue;
}
if (accessServers.size() < MAX_EFFECTIVE_ACCESS_SERVERS) {
accessServers.add(normalizedAccessServerLogin);
}
}
} else if (blockType == 50) {
int sessionsMode = u8(raw, c++);
@@ -1057,12 +1057,16 @@ public final class PostgresStorageRepository
s.updated_at_ms,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
FROM solana_user_pda_current u
CROSS JOIN LATERAL jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) AS access_server(login_value)
CROSS JOIN LATERAL (
SELECT login_value
FROM jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) WITH ORDINALITY AS access_server(login_value, ord)
WHERE ord <= 2
) AS access_server
JOIN solana_user_pda_current s
ON LOWER(s.login) = LOWER(btrim(access_server.login_value))
AND s.is_server = TRUE
@@ -1100,12 +1104,16 @@ public final class PostgresStorageRepository
FOR affected_user IN
SELECT u.login
FROM solana_user_pda_current u
CROSS JOIN LATERAL jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) AS access_server(login_value)
CROSS JOIN LATERAL (
SELECT login_value
FROM jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) WITH ORDINALITY AS access_server(login_value, ord)
WHERE ord <= 2
) AS access_server
WHERE LOWER(btrim(access_server.login_value)) = LOWER(p_server_login)
LOOP
PERFORM shine_refresh_user_access_servers_for_user(affected_user.login);
+2 -2
View File
@@ -1,2 +1,2 @@
client.version=1.5.34
server.version=1.4.10
client.version=1.5.35
server.version=1.4.11
+19 -4
View File
@@ -7,6 +7,7 @@ import { readShineUserPda, updateShineUserPdaOnSolana } from '../services/shine-
import { getTopupSiteUrl } from '../services/solana-wallet-service.js';
export const pageMeta = { id: 'access-servers-view', title: 'Серверы доступа' };
const MAX_ACCESS_SERVERS = 2;
function escapeHtml(value) {
return String(value || '')
@@ -39,8 +40,9 @@ function uniqueLogins(values) {
if (!login || seen.has(login)) return;
seen.add(login);
result.push(login);
if (result.length >= MAX_ACCESS_SERVERS) return;
});
return result;
return result.slice(0, MAX_ACCESS_SERVERS);
}
function shortenSignature(value) {
@@ -309,7 +311,7 @@ export function render({ navigate }) {
addTitle.textContent = 'Добавить сервер доступа';
const addHint = document.createElement('p');
addHint.className = 'meta-muted';
addHint.textContent = 'Введите логин сервера или несколько первых букв, затем выберите сервер из подсказок.';
addHint.textContent = 'Можно использовать максимум два сервера доступа. Введите логин сервера или несколько первых букв, затем выберите сервер из подсказок.';
const addInput = document.createElement('input');
addInput.className = 'input';
addInput.type = 'text';
@@ -329,7 +331,9 @@ export function render({ navigate }) {
addCard.append(addTitle, addHint, addInput, suggestEl, addStatus, addButton);
const refreshAddButton = () => {
addButton.disabled = operationBusy || (!selectedCandidate && !normalizeLogin(addInput.value));
addButton.disabled = operationBusy
|| currentAccessServers.length >= MAX_ACCESS_SERVERS
|| (!selectedCandidate && !normalizeLogin(addInput.value));
};
const setOperationBusy = (busy) => {
@@ -348,7 +352,9 @@ export function render({ navigate }) {
addInput.value = candidate.login;
addStatus.textContent = `Выбран сервер @${candidate.login}${candidate.url ? ` (${candidate.url})` : ''}`;
} else {
addStatus.textContent = 'Для изменения списка понадобится подпись root key.';
addStatus.textContent = currentAccessServers.length >= MAX_ACCESS_SERVERS
? 'У пользователя уже выбраны два сервера доступа. Сначала отключите один из них.'
: 'Для изменения списка понадобится подпись root key.';
}
};
@@ -457,12 +463,17 @@ export function render({ navigate }) {
}
currentAccessServers = rows;
renderServerList();
refreshAddButton();
listStatus.textContent = rows.length
? `Найдено серверов доступа: ${rows.length}`
: 'В PDA пользователя пока нет серверов доступа.';
if (rows.length >= MAX_ACCESS_SERVERS) {
addStatus.textContent = 'У пользователя уже выбраны два сервера доступа. Сначала отключите один из них.';
}
} catch (error) {
currentAccessServers = [];
renderServerList();
refreshAddButton();
listStatus.textContent = error?.message || 'Не удалось прочитать список серверов доступа.';
}
};
@@ -654,6 +665,10 @@ export function render({ navigate }) {
addStatus.textContent = `Сервер @${login} уже есть в списке.`;
return;
}
if (currentAccessServers.length >= MAX_ACCESS_SERVERS) {
addStatus.textContent = 'Нельзя добавить больше двух серверов доступа. Сначала отключите один из текущих.';
return;
}
try {
const resolved = await resolveShineServerByServerLogin({ serverLogin: login, solanaEndpoint });
@@ -69,7 +69,7 @@ export async function resolveShineServerForUserLogin({ login, solanaEndpoint, fa
solanaEndpoint,
});
const accessServers = Array.isArray(parsedUser?.accessServers)
? parsedUser.accessServers.map((value) => normalizeShineServerLogin(value)).filter(Boolean)
? parsedUser.accessServers.map((value) => normalizeShineServerLogin(value)).filter(Boolean).slice(0, 2)
: [];
const pickedServerLogin = accessServers[0] || normalizeShineServerLogin(fallbackServerLogin);
if (!pickedServerLogin) {
+18 -3
View File
@@ -30,6 +30,7 @@ const BLOCK_TYPE_TRUSTED_STATE = 70;
const SESSIONS_MODE_MIXED = 1;
const SESSION_TYPE_USER = 1;
const SESSION_TYPE_HOMESERVER = 100;
const MAX_EFFECTIVE_ACCESS_SERVERS = 2;
let solanaLibPromise = null;
function loadSolanaLib() {
@@ -41,6 +42,19 @@ function normalizeLogin(login) {
return String(login || '').trim().toLowerCase();
}
function normalizeAccessServers(values) {
const seen = new Set();
const result = [];
for (const value of Array.isArray(values) ? values : []) {
const login = normalizeLogin(value);
if (!login || seen.has(login)) continue;
seen.add(login);
result.push(login);
if (result.length >= MAX_EFFECTIVE_ACCESS_SERVERS) break;
}
return result;
}
function pushU32LE(buf, value) {
const n = Number(value) >>> 0;
buf.push(n & 0xff, (n >>> 8) & 0xff, (n >>> 16) & 0xff, (n >>> 24) & 0xff);
@@ -202,7 +216,7 @@ function serializeCreateUserPdaArgs(args) {
buf.push(syncServers.length & 0xff);
for (const value of syncServers) pushStrU8(buf, value);
}
const accessServers = Array.isArray(args.accessServers) ? args.accessServers : [];
const accessServers = normalizeAccessServers(args.accessServers);
buf.push(accessServers.length & 0xff);
for (const value of accessServers) pushStrU8(buf, value);
buf.push(Number(args.sessionsMode || SESSIONS_MODE_MIXED) & 0xff);
@@ -252,7 +266,7 @@ function serializeUpdateUserPdaArgs(args) {
buf.push(syncServers.length & 0xff);
for (const value of syncServers) pushStrU8(buf, value);
}
const accessServers = Array.isArray(args.accessServers) ? args.accessServers : [];
const accessServers = normalizeAccessServers(args.accessServers);
buf.push(accessServers.length & 0xff);
for (const value of accessServers) pushStrU8(buf, value);
buf.push(Number(args.sessionsMode || SESSIONS_MODE_MIXED) & 0xff);
@@ -337,7 +351,7 @@ function createPdaState({
addressFormatVersion: serverProfile?.addressFormatVersion ?? 0,
serverAddress: serverProfile?.serverAddress ?? '',
syncServers: serverProfile?.syncServers ? [...serverProfile.syncServers] : [],
accessServers: Array.isArray(accessServers) ? [...accessServers] : [],
accessServers: normalizeAccessServers(accessServers),
sessionsMode: Number(sessionsMode || SESSIONS_MODE_MIXED) & 0xff,
sessions: Array.isArray(sessions) ? sessions.map((x) => ({
sessionType: Number(x?.sessionType || 0) & 0xff,
@@ -493,6 +507,7 @@ export function parseShineUserPda(dataBytes) {
const accessCount = reader.readU8();
accessServers = [];
for (let j = 0; j < accessCount; j += 1) accessServers.push(reader.readStrU8());
accessServers = normalizeAccessServers(accessServers);
continue;
}
if (blockType === BLOCK_TYPE_SESSIONS) {