Fix admin rename payload and harden RustDesk ID sync
This commit is contained in:
parent
bd1bd4bef1
commit
f7ad7f6a17
4 changed files with 146 additions and 6 deletions
|
|
@ -207,26 +207,64 @@ pub fn ensure_rustdesk(
|
|||
}
|
||||
};
|
||||
|
||||
let mut final_id = reported_id.clone();
|
||||
|
||||
if let Some(expected) = custom_id.as_ref() {
|
||||
if expected != &reported_id {
|
||||
log_event(&format!(
|
||||
"ID retornado difere do determinístico ({expected}) -> aplicando {reported_id}"
|
||||
"ID retornado difere do determinístico ({expected}) -> reaplicando ID determinístico"
|
||||
));
|
||||
|
||||
let reapplied = match set_custom_id(&exe_path, expected) {
|
||||
Ok(_) => {
|
||||
match query_id_with_retries(&exe_path, 3) {
|
||||
Ok(rechecked) => {
|
||||
if &rechecked == expected {
|
||||
log_event(&format!("ID determinístico aplicado com sucesso: {rechecked}"));
|
||||
final_id = rechecked;
|
||||
true
|
||||
} else {
|
||||
log_event(&format!(
|
||||
"ID ainda difere após reaplicação (esperado {expected}, reportado {rechecked}); forçando persistência do determinístico nos perfis"
|
||||
));
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
log_event(&format!(
|
||||
"Falha ao consultar ID após reaplicação: {error}; forçando persistência do determinístico nos perfis"
|
||||
));
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
log_event(&format!(
|
||||
"Falha ao reaplicar ID determinístico ({expected}): {error}; persistindo determinístico nos perfis mesmo assim"
|
||||
));
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if !reapplied {
|
||||
final_id = expected.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
ensure_remote_id_files(&reported_id);
|
||||
|
||||
ensure_remote_id_files(&final_id);
|
||||
|
||||
let version = query_version(&exe_path).ok().or(installed_version);
|
||||
|
||||
let result = RustdeskProvisioningResult {
|
||||
id: reported_id.clone(),
|
||||
id: final_id.clone(),
|
||||
password: password.clone(),
|
||||
installed_version: version.clone(),
|
||||
updated: freshly_installed,
|
||||
last_provisioned_at: Utc::now().timestamp_millis(),
|
||||
};
|
||||
|
||||
log_event(&format!("Provisionamento concluído. ID final: {reported_id}. Versão: {:?}", version));
|
||||
log_event(&format!("Provisionamento concluído. ID final: {final_id}. Versão: {:?}", version));
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2166,6 +2166,37 @@ function normalizeRemoteAccessList(raw: unknown): RemoteAccessEntry[] {
|
|||
return entries
|
||||
}
|
||||
|
||||
async function removeDuplicateRemoteAccessEntries(
|
||||
ctx: MutationCtx,
|
||||
tenantId: string,
|
||||
currentMachineId: Id<"machines">,
|
||||
provider: string,
|
||||
identifier: string,
|
||||
now: number
|
||||
) {
|
||||
const machines = await ctx.db
|
||||
.query("machines")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
|
||||
const providerLc = provider.toLowerCase()
|
||||
const identifierLc = identifier.toLowerCase()
|
||||
|
||||
for (const device of machines) {
|
||||
if (device._id === currentMachineId) continue
|
||||
const entries = normalizeRemoteAccessList(device.remoteAccess)
|
||||
const filtered = entries.filter(
|
||||
(entry) =>
|
||||
entry.provider.toLowerCase() !== providerLc || entry.identifier.toLowerCase() !== identifierLc
|
||||
)
|
||||
if (filtered.length === entries.length) continue
|
||||
await ctx.db.patch(device._id, {
|
||||
remoteAccess: filtered.length > 0 ? filtered : null,
|
||||
updatedAt: now,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function upsertRemoteAccessSnapshotFromHeartbeat(
|
||||
ctx: MutationCtx,
|
||||
machine: Doc<"machines">,
|
||||
|
|
@ -2189,6 +2220,8 @@ async function upsertRemoteAccessSnapshotFromHeartbeat(
|
|||
snapshotSource: "heartbeat",
|
||||
provider,
|
||||
identifier,
|
||||
machineId: machine._id,
|
||||
hostname: machine.hostname,
|
||||
lastVerifiedAt: timestamp,
|
||||
}
|
||||
|
||||
|
|
@ -2423,6 +2456,7 @@ export const upsertRemoteAccessViaToken = mutation({
|
|||
notes: cleanedNotes,
|
||||
lastVerifiedAt: timestamp,
|
||||
metadata: {
|
||||
source: "machine-token",
|
||||
provider: trimmedProvider,
|
||||
identifier: trimmedIdentifier,
|
||||
url: normalizedUrl,
|
||||
|
|
@ -2430,6 +2464,9 @@ export const upsertRemoteAccessViaToken = mutation({
|
|||
password: cleanedPassword,
|
||||
notes: cleanedNotes,
|
||||
lastVerifiedAt: timestamp,
|
||||
machineId: machine._id,
|
||||
hostname: machine.hostname,
|
||||
tenantId: machine.tenantId,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -2438,6 +2475,8 @@ export const upsertRemoteAccessViaToken = mutation({
|
|||
? existingEntries.map((entry, index) => (index === existingIndex ? updatedEntry : entry))
|
||||
: [...existingEntries, updatedEntry]
|
||||
|
||||
await removeDuplicateRemoteAccessEntries(ctx, machine.tenantId, machine._id, trimmedProvider, trimmedIdentifier, timestamp)
|
||||
|
||||
await ctx.db.patch(machine._id, {
|
||||
remoteAccess: nextEntries,
|
||||
updatedAt: timestamp,
|
||||
|
|
|
|||
|
|
@ -49,10 +49,10 @@ export async function POST(request: Request) {
|
|||
|
||||
// Chamada por string reference (evita depender do tipo gerado imediatamente)
|
||||
const client = convex as unknown as { mutation: (name: string, args: unknown) => Promise<unknown> }
|
||||
// Nota: a mutation `machines:rename` não aceita tenantId; apenas machineId, actorId e hostname.
|
||||
await client.mutation("machines:rename", {
|
||||
machineId: parsed.data.machineId,
|
||||
actorId,
|
||||
tenantId,
|
||||
hostname: parsed.data.hostname,
|
||||
})
|
||||
|
||||
|
|
@ -62,4 +62,3 @@ export async function POST(request: Request) {
|
|||
return NextResponse.json({ error: "Falha ao renomear dispositivo" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
64
tests/api-admin-devices-rename.test.ts
Normal file
64
tests/api-admin-devices-rename.test.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { describe, expect, it, beforeEach, vi } from "vitest"
|
||||
|
||||
import { POST } from "@/app/api/admin/devices/rename/route"
|
||||
import { assertAuthenticatedSession } from "@/lib/auth-server"
|
||||
|
||||
vi.mock("@/lib/auth-server", () => ({
|
||||
assertAuthenticatedSession: vi.fn(),
|
||||
}))
|
||||
|
||||
const mutationMock = vi.fn()
|
||||
|
||||
vi.mock("convex/browser", () => ({
|
||||
ConvexHttpClient: vi.fn(() => ({ mutation: mutationMock })),
|
||||
}))
|
||||
|
||||
vi.mock("@/convex/_generated/api", () => ({
|
||||
api: { users: { ensureUser: "users:ensureUser" } },
|
||||
}))
|
||||
|
||||
describe("POST /api/admin/devices/rename", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks()
|
||||
process.env.NEXT_PUBLIC_CONVEX_URL = "https://convex.example.test"
|
||||
vi.mocked(assertAuthenticatedSession).mockResolvedValue({
|
||||
user: {
|
||||
id: "session-user",
|
||||
email: "agent@example.com",
|
||||
name: "Agent",
|
||||
role: "AGENT",
|
||||
tenantId: "tenant-atlas",
|
||||
avatarUrl: null,
|
||||
},
|
||||
session: { id: "sess", expiresAt: Date.now() + 1000 },
|
||||
})
|
||||
mutationMock.mockImplementation((name: string) => {
|
||||
if (name === "users:ensureUser") {
|
||||
return Promise.resolve({ _id: "user-123" })
|
||||
}
|
||||
return Promise.resolve({ ok: true })
|
||||
})
|
||||
})
|
||||
|
||||
it("envia somente machineId, actorId e hostname para machines:rename", async () => {
|
||||
const body = {
|
||||
machineId: "machine-123",
|
||||
hostname: "novo-host",
|
||||
}
|
||||
const req = new Request("http://localhost/api/admin/devices/rename", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
const res = await POST(req)
|
||||
expect(res.status).toBe(200)
|
||||
const renameCall = mutationMock.mock.calls.find(([name]) => name === "machines:rename")
|
||||
expect(renameCall).toBeDefined()
|
||||
expect(renameCall?.[1]).toEqual({
|
||||
machineId: "machine-123",
|
||||
actorId: "user-123",
|
||||
hostname: "novo-host",
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue