fix android shit + fix clients not showing if 2 clients have the same username and pc name as another client

This commit is contained in:
KingKDot
2026-03-21 00:10:47 -05:00
parent 5040074e97
commit 26f5d4ecc6
4 changed files with 108 additions and 2 deletions
@@ -2,6 +2,37 @@
package config package config
import (
"crypto/rand"
"encoding/hex"
"log"
"os"
"strings"
)
const androidMachineIDFile = "config/android_machine_id"
func platformMachineID() string { func platformMachineID() string {
return "" data, err := os.ReadFile(androidMachineIDFile)
if err == nil {
if id := strings.TrimSpace(string(data)); id != "" {
return id
}
}
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
log.Printf("[identity] WARNING: failed to generate android machine ID: %v", err)
return ""
}
id := hex.EncodeToString(b)
if err := os.MkdirAll("config", 0700); err != nil {
log.Printf("[identity] WARNING: failed to create config dir for android machine ID: %v", err)
return id
}
if err := os.WriteFile(androidMachineIDFile, []byte(id), 0600); err != nil {
log.Printf("[identity] WARNING: failed to persist android machine ID: %v", err)
}
return id
} }
+5
View File
@@ -662,6 +662,11 @@ export function clientExists(id: string): boolean {
return !!row?.id; return !!row?.id;
} }
export function getClientPublicKeyById(id: string): string | null {
const row = db.query<{ public_key: string | null }>(`SELECT public_key FROM clients WHERE id=? LIMIT 1`).get(id);
return row?.public_key ?? null;
}
export function listDistinctCountries(): { code: string; count: number }[] { export function listDistinctCountries(): { code: string; count: number }[] {
const rows = db const rows = db
.query<{ code: string; count: number }>( .query<{ code: string; count: number }>(
+64
View File
@@ -549,3 +549,67 @@ describe("purgatory — IP banning", () => {
expect(truncated).toBe("A".repeat(200)); expect(truncated).toBe("A".repeat(200));
}); });
}); });
describe("purgatory — HWID collision prevention", () => {
test("two different public keys produce different key fingerprints", async () => {
const kpA = await generateKeypair();
const kpB = await generateKeypair();
const fpA = computeKeyFingerprint(kpA.publicKeyBase64);
const fpB = computeKeyFingerprint(kpB.publicKeyBase64);
expect(fpA).not.toBe(fpB);
// fingerprints are 64-char hex
expect(fpA).toMatch(/^[a-f0-9]{64}$/);
expect(fpB).toMatch(/^[a-f0-9]{64}$/);
});
test("key fingerprint is deterministic for same public key", async () => {
const kp = await generateKeypair();
const fp1 = computeKeyFingerprint(kp.publicKeyBase64);
const fp2 = computeKeyFingerprint(kp.publicKeyBase64);
expect(fp1).toBe(fp2);
});
test("collision detection assigns different IDs to same-HWID machines", async () => {
// Simulate: Machine A and Machine B share HWID "colliding-hwid"
// but have different key pairs. After collision detection,
// Machine B should get the key fingerprint as its ID.
const kpA = await generateKeypair();
const kpB = await generateKeypair();
const sharedHwid = "colliding-hwid";
const fpA = computeKeyFingerprint(kpA.publicKeyBase64);
const fpB = computeKeyFingerprint(kpB.publicKeyBase64);
// Machine A: lookupClientByPublicKey → null, no existing client at ID → keeps HWID
const wsA = createMockWs({ clientId: sharedHwid });
// Simulate server logic: no existing key at this ID → keep URL ID
const existingPkAtId: string | null = null; // DB empty
if (existingPkAtId && existingPkAtId !== kpA.publicKeyBase64) {
wsA.data.clientId = fpA;
}
expect(wsA.data.clientId).toBe(sharedHwid); // Machine A keeps HWID
// Machine B: lookupClientByPublicKey → null, but ID already taken by PK_A
const wsB = createMockWs({ clientId: sharedHwid });
const existingPkAtIdForB: string | null = kpA.publicKeyBase64; // Machine A's key is stored at this ID
if (existingPkAtIdForB && existingPkAtIdForB !== kpB.publicKeyBase64) {
wsB.data.clientId = fpB;
}
expect(wsB.data.clientId).toBe(fpB); // Machine B gets reassigned
expect(wsB.data.clientId).not.toBe(sharedHwid);
});
test("no collision when same machine reconnects with same public key", async () => {
const kp = await generateKeypair();
const hwid = "same-machine-hwid";
const fp = computeKeyFingerprint(kp.publicKeyBase64);
const ws = createMockWs({ clientId: hwid });
// Same key already at this ID → no collision
const existingPkAtId: string | null = kp.publicKeyBase64;
if (existingPkAtId && existingPkAtId !== kp.publicKeyBase64) {
ws.data.clientId = fp;
}
expect(ws.data.clientId).toBe(hwid); // keeps original ID
});
});
@@ -3,7 +3,7 @@ import { v4 as uuidv4 } from "uuid";
import geoip from "geoip-lite"; import geoip from "geoip-lite";
import { logAudit, AuditAction } from "../../auditLog"; import { logAudit, AuditAction } from "../../auditLog";
import * as clientManager from "../../clientManager"; import * as clientManager from "../../clientManager";
import { clientExists, setOnlineState, upsertClientRow, getClientEnrollmentStatus, setClientEnrollmentStatus, lookupClientByPublicKey } from "../../db"; import { clientExists, setOnlineState, upsertClientRow, getClientEnrollmentStatus, setClientEnrollmentStatus, lookupClientByPublicKey, getClientPublicKeyById } from "../../db";
import { getConfig } from "../../config"; import { getConfig } from "../../config";
import { logger } from "../../logger"; import { logger } from "../../logger";
import { metrics } from "../../metrics"; import { metrics } from "../../metrics";
@@ -251,6 +251,12 @@ export async function handleWebSocketMessage(
ws.data.clientId = existing.id; ws.data.clientId = existing.id;
} else { } else {
enrollmentStatus = requireApproval ? "pending" : "approved"; enrollmentStatus = requireApproval ? "pending" : "approved";
const existingPk = getClientPublicKeyById(ws.data.clientId);
if (existingPk && existingPk !== publicKey) {
ws.data.clientId = keyFingerprint;
logger.info(`[purgatory] ID collision detected — reassigned to ${keyFingerprint}`);
}
} }
const resolvedId = ws.data.clientId; const resolvedId = ws.data.clientId;