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 let Some(expected) = custom_id.as_ref() {
|
||||||
if expected != &reported_id {
|
if expected != &reported_id {
|
||||||
log_event(&format!(
|
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 version = query_version(&exe_path).ok().or(installed_version);
|
||||||
|
|
||||||
let result = RustdeskProvisioningResult {
|
let result = RustdeskProvisioningResult {
|
||||||
id: reported_id.clone(),
|
id: final_id.clone(),
|
||||||
password: password.clone(),
|
password: password.clone(),
|
||||||
installed_version: version.clone(),
|
installed_version: version.clone(),
|
||||||
updated: freshly_installed,
|
updated: freshly_installed,
|
||||||
last_provisioned_at: Utc::now().timestamp_millis(),
|
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)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2166,6 +2166,37 @@ function normalizeRemoteAccessList(raw: unknown): RemoteAccessEntry[] {
|
||||||
return entries
|
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(
|
async function upsertRemoteAccessSnapshotFromHeartbeat(
|
||||||
ctx: MutationCtx,
|
ctx: MutationCtx,
|
||||||
machine: Doc<"machines">,
|
machine: Doc<"machines">,
|
||||||
|
|
@ -2189,6 +2220,8 @@ async function upsertRemoteAccessSnapshotFromHeartbeat(
|
||||||
snapshotSource: "heartbeat",
|
snapshotSource: "heartbeat",
|
||||||
provider,
|
provider,
|
||||||
identifier,
|
identifier,
|
||||||
|
machineId: machine._id,
|
||||||
|
hostname: machine.hostname,
|
||||||
lastVerifiedAt: timestamp,
|
lastVerifiedAt: timestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2423,6 +2456,7 @@ export const upsertRemoteAccessViaToken = mutation({
|
||||||
notes: cleanedNotes,
|
notes: cleanedNotes,
|
||||||
lastVerifiedAt: timestamp,
|
lastVerifiedAt: timestamp,
|
||||||
metadata: {
|
metadata: {
|
||||||
|
source: "machine-token",
|
||||||
provider: trimmedProvider,
|
provider: trimmedProvider,
|
||||||
identifier: trimmedIdentifier,
|
identifier: trimmedIdentifier,
|
||||||
url: normalizedUrl,
|
url: normalizedUrl,
|
||||||
|
|
@ -2430,6 +2464,9 @@ export const upsertRemoteAccessViaToken = mutation({
|
||||||
password: cleanedPassword,
|
password: cleanedPassword,
|
||||||
notes: cleanedNotes,
|
notes: cleanedNotes,
|
||||||
lastVerifiedAt: timestamp,
|
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.map((entry, index) => (index === existingIndex ? updatedEntry : entry))
|
||||||
: [...existingEntries, updatedEntry]
|
: [...existingEntries, updatedEntry]
|
||||||
|
|
||||||
|
await removeDuplicateRemoteAccessEntries(ctx, machine.tenantId, machine._id, trimmedProvider, trimmedIdentifier, timestamp)
|
||||||
|
|
||||||
await ctx.db.patch(machine._id, {
|
await ctx.db.patch(machine._id, {
|
||||||
remoteAccess: nextEntries,
|
remoteAccess: nextEntries,
|
||||||
updatedAt: timestamp,
|
updatedAt: timestamp,
|
||||||
|
|
|
||||||
|
|
@ -49,10 +49,10 @@ export async function POST(request: Request) {
|
||||||
|
|
||||||
// Chamada por string reference (evita depender do tipo gerado imediatamente)
|
// Chamada por string reference (evita depender do tipo gerado imediatamente)
|
||||||
const client = convex as unknown as { mutation: (name: string, args: unknown) => Promise<unknown> }
|
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", {
|
await client.mutation("machines:rename", {
|
||||||
machineId: parsed.data.machineId,
|
machineId: parsed.data.machineId,
|
||||||
actorId,
|
actorId,
|
||||||
tenantId,
|
|
||||||
hostname: parsed.data.hostname,
|
hostname: parsed.data.hostname,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -62,4 +62,3 @@ export async function POST(request: Request) {
|
||||||
return NextResponse.json({ error: "Falha ao renomear dispositivo" }, { status: 500 })
|
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