fix(remote-access): allow short-lived revoked token grace
This commit is contained in:
parent
130ab3bbdc
commit
308f7b5712
2 changed files with 49 additions and 10 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue