diff --git a/convex/machines.ts b/convex/machines.ts index 8409011..9bacb86 100644 --- a/convex/machines.ts +++ b/convex/machines.ts @@ -183,8 +183,10 @@ function hashToken(token: string) { return toHex(sha256(token)) } -async function getActiveToken( - ctx: MutationCtx, +const REMOTE_ACCESS_TOKEN_GRACE_MS = 5 * 60 * 1000 + +async function getTokenRecord( + ctx: MutationCtx | QueryCtx, tokenValue: string ): Promise<{ token: Doc<"machineTokens">; machine: Doc<"machines"> }> { const tokenHash = hashToken(tokenValue) @@ -196,12 +198,6 @@ async function getActiveToken( if (!token) { throw new ConvexError("Token de dispositivo inválido") } - if (token.revoked) { - throw new ConvexError("Token de dispositivo revogado") - } - if (token.expiresAt < Date.now()) { - throw new ConvexError("Token de dispositivo expirado") - } const machine = await ctx.db.get(token.machineId) if (!machine) { @@ -211,6 +207,35 @@ async function getActiveToken( return { token, machine } } +async function getTokenWithGrace( + ctx: MutationCtx | QueryCtx, + tokenValue: string, + options?: { allowGraceMs?: number } +): Promise<{ token: Doc<"machineTokens">; machine: Doc<"machines">; mode: "active" | "grace" }> { + const { token, machine } = await getTokenRecord(ctx, tokenValue) + const now = Date.now() + if (token.revoked) { + const graceMs = options?.allowGraceMs ?? 0 + const revokedAt = token.revokedAt ?? token.lastUsedAt ?? token.createdAt + if (!graceMs || now - revokedAt > graceMs) { + throw new ConvexError("Token de dispositivo revogado") + } + return { token, machine, mode: "grace" } + } + if (token.expiresAt < now) { + throw new ConvexError("Token de dispositivo expirado") + } + return { token, machine, mode: "active" } +} + +async function getActiveToken( + ctx: MutationCtx | QueryCtx, + tokenValue: string +): Promise<{ token: Doc<"machineTokens">; machine: Doc<"machines"> }> { + const { token, machine } = await getTokenWithGrace(ctx, tokenValue) + return { token, machine } +} + function isObject(value: unknown): value is Record { return Boolean(value) && typeof value === "object" && !Array.isArray(value) } @@ -574,7 +599,12 @@ export const register = mutation({ for (const token of previousTokens) { if (!token.revoked) { - await ctx.db.patch(token._id, { revoked: true, lastUsedAt: now }) + await ctx.db.patch(token._id, { + revoked: true, + revokedAt: now, + lastUsedAt: now, + expiresAt: now, + }) } } @@ -1972,6 +2002,7 @@ export const resetAgent = mutation({ if (!token.revoked) { await ctx.db.patch(token._id, { revoked: true, + revokedAt: now, expiresAt: now, }) revokedCount += 1 @@ -2273,7 +2304,14 @@ export const upsertRemoteAccessViaToken = mutation({ notes: v.optional(v.string()), }, handler: async (ctx, args) => { - const { machine } = await getActiveToken(ctx, args.machineToken) + const { machine, mode } = await getTokenWithGrace(ctx, args.machineToken, { + allowGraceMs: REMOTE_ACCESS_TOKEN_GRACE_MS, + }) + if (mode === "grace") { + console.warn("[remote-access] Token revogado aceito dentro da janela de graça", { + machineId: machine._id, + }) + } const trimmedProvider = args.provider.trim() const trimmedIdentifier = args.identifier.trim() if (!trimmedProvider || !trimmedIdentifier) { diff --git a/convex/schema.ts b/convex/schema.ts index 432c827..666fa8f 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -661,6 +661,7 @@ export default defineSchema({ tokenHash: v.string(), expiresAt: v.number(), revoked: v.boolean(), + revokedAt: v.optional(v.number()), createdAt: v.number(), lastUsedAt: v.optional(v.number()), usageCount: v.optional(v.number()),