fix(remote-access): allow short-lived revoked token grace

This commit is contained in:
Esdras Renan 2025-11-11 16:46:54 -03:00
parent 130ab3bbdc
commit 308f7b5712
2 changed files with 49 additions and 10 deletions

View file

@ -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<string, unknown> {
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) {