import { v } from "convex/values" import { mutation, query } from "./_generated/server" import type { Id, Doc } from "./_generated/dataModel" const DEFAULT_TENANT_ID = "default" export const USB_POLICY_VALUES = ["ALLOW", "BLOCK_ALL", "READONLY"] as const export type UsbPolicyValue = (typeof USB_POLICY_VALUES)[number] export const USB_POLICY_STATUS = ["PENDING", "APPLIED", "FAILED"] as const export type UsbPolicyStatus = (typeof USB_POLICY_STATUS)[number] export const setUsbPolicy = mutation({ args: { machineId: v.id("machines"), policy: v.string(), actorId: v.optional(v.id("users")), actorEmail: v.optional(v.string()), actorName: v.optional(v.string()), }, handler: async (ctx, args) => { const machine = await ctx.db.get(args.machineId) if (!machine) { throw new Error("Dispositivo nao encontrado") } if (!USB_POLICY_VALUES.includes(args.policy as UsbPolicyValue)) { throw new Error(`Politica USB invalida: ${args.policy}. Valores validos: ${USB_POLICY_VALUES.join(", ")}`) } const now = Date.now() const oldPolicy = machine.usbPolicy ?? "ALLOW" await ctx.db.patch(args.machineId, { usbPolicy: args.policy, usbPolicyStatus: "PENDING", usbPolicyError: undefined, usbPolicyAppliedAt: now, updatedAt: now, }) await ctx.db.insert("usbPolicyEvents", { tenantId: machine.tenantId, machineId: args.machineId, actorId: args.actorId, actorEmail: args.actorEmail, actorName: args.actorName, oldPolicy, newPolicy: args.policy, status: "PENDING", createdAt: now, }) return { ok: true, policy: args.policy, status: "PENDING" } }, }) export const reportUsbPolicyStatus = mutation({ args: { machineToken: v.string(), status: v.string(), error: v.optional(v.string()), currentPolicy: v.optional(v.string()), }, handler: async (ctx, args) => { const tokenHash = args.machineToken const tokenRecord = await ctx.db .query("machineTokens") .withIndex("by_token_hash", (q) => q.eq("tokenHash", tokenHash)) .first() if (!tokenRecord || tokenRecord.revoked) { throw new Error("Token de maquina invalido ou revogado") } if (tokenRecord.expiresAt < Date.now()) { throw new Error("Token de maquina expirado") } const machine = await ctx.db.get(tokenRecord.machineId) if (!machine) { throw new Error("Dispositivo nao encontrado") } if (!USB_POLICY_STATUS.includes(args.status as UsbPolicyStatus)) { throw new Error(`Status de politica USB invalido: ${args.status}`) } const now = Date.now() await ctx.db.patch(machine._id, { usbPolicyStatus: args.status, usbPolicyError: args.error, usbPolicyReportedAt: now, updatedAt: now, }) const latestEvent = await ctx.db .query("usbPolicyEvents") .withIndex("by_machine_created", (q) => q.eq("machineId", machine._id)) .order("desc") .first() if (latestEvent && latestEvent.status === "PENDING") { await ctx.db.patch(latestEvent._id, { status: args.status, error: args.error, appliedAt: args.status === "APPLIED" ? now : undefined, }) } return { ok: true } }, }) export const getUsbPolicy = query({ args: { machineId: v.id("machines"), }, handler: async (ctx, args) => { const machine = await ctx.db.get(args.machineId) if (!machine) { return null } return { policy: machine.usbPolicy ?? "ALLOW", status: machine.usbPolicyStatus ?? null, error: machine.usbPolicyError ?? null, appliedAt: machine.usbPolicyAppliedAt ?? null, reportedAt: machine.usbPolicyReportedAt ?? null, } }, }) export const getPendingUsbPolicy = query({ args: { machineToken: v.string(), }, handler: async (ctx, args) => { const tokenHash = args.machineToken const tokenRecord = await ctx.db .query("machineTokens") .withIndex("by_token_hash", (q) => q.eq("tokenHash", tokenHash)) .first() if (!tokenRecord || tokenRecord.revoked || tokenRecord.expiresAt < Date.now()) { return null } const machine = await ctx.db.get(tokenRecord.machineId) if (!machine) { return null } if (machine.usbPolicyStatus === "PENDING") { return { policy: machine.usbPolicy ?? "ALLOW", appliedAt: machine.usbPolicyAppliedAt, } } return null }, }) export const listUsbPolicyEvents = query({ args: { machineId: v.id("machines"), limit: v.optional(v.number()), }, handler: async (ctx, args) => { const limit = args.limit ?? 50 const events = await ctx.db .query("usbPolicyEvents") .withIndex("by_machine_created", (q) => q.eq("machineId", args.machineId)) .order("desc") .take(limit) return events.map((event) => ({ id: event._id, oldPolicy: event.oldPolicy, newPolicy: event.newPolicy, status: event.status, error: event.error, actorEmail: event.actorEmail, actorName: event.actorName, createdAt: event.createdAt, appliedAt: event.appliedAt, })) }, }) export const bulkSetUsbPolicy = mutation({ args: { machineIds: v.array(v.id("machines")), policy: v.string(), actorId: v.optional(v.id("users")), actorEmail: v.optional(v.string()), actorName: v.optional(v.string()), }, handler: async (ctx, args) => { if (!USB_POLICY_VALUES.includes(args.policy as UsbPolicyValue)) { throw new Error(`Politica USB invalida: ${args.policy}`) } const now = Date.now() const results: Array<{ machineId: Id<"machines">; success: boolean; error?: string }> = [] for (const machineId of args.machineIds) { try { const machine = await ctx.db.get(machineId) if (!machine) { results.push({ machineId, success: false, error: "Dispositivo nao encontrado" }) continue } const oldPolicy = machine.usbPolicy ?? "ALLOW" await ctx.db.patch(machineId, { usbPolicy: args.policy, usbPolicyStatus: "PENDING", usbPolicyError: undefined, usbPolicyAppliedAt: now, updatedAt: now, }) await ctx.db.insert("usbPolicyEvents", { tenantId: machine.tenantId, machineId, actorId: args.actorId, actorEmail: args.actorEmail, actorName: args.actorName, oldPolicy, newPolicy: args.policy, status: "PENDING", createdAt: now, }) results.push({ machineId, success: true }) } catch (err) { results.push({ machineId, success: false, error: String(err) }) } } return { results, total: args.machineIds.length, successful: results.filter((r) => r.success).length } }, })