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))
|
return toHex(sha256(token))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getActiveToken(
|
const REMOTE_ACCESS_TOKEN_GRACE_MS = 5 * 60 * 1000
|
||||||
ctx: MutationCtx,
|
|
||||||
|
async function getTokenRecord(
|
||||||
|
ctx: MutationCtx | QueryCtx,
|
||||||
tokenValue: string
|
tokenValue: string
|
||||||
): Promise<{ token: Doc<"machineTokens">; machine: Doc<"machines"> }> {
|
): Promise<{ token: Doc<"machineTokens">; machine: Doc<"machines"> }> {
|
||||||
const tokenHash = hashToken(tokenValue)
|
const tokenHash = hashToken(tokenValue)
|
||||||
|
|
@ -196,12 +198,6 @@ async function getActiveToken(
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new ConvexError("Token de dispositivo inválido")
|
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)
|
const machine = await ctx.db.get(token.machineId)
|
||||||
if (!machine) {
|
if (!machine) {
|
||||||
|
|
@ -211,6 +207,35 @@ async function getActiveToken(
|
||||||
return { token, machine }
|
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> {
|
function isObject(value: unknown): value is Record<string, unknown> {
|
||||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value)
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value)
|
||||||
}
|
}
|
||||||
|
|
@ -574,7 +599,12 @@ export const register = mutation({
|
||||||
|
|
||||||
for (const token of previousTokens) {
|
for (const token of previousTokens) {
|
||||||
if (!token.revoked) {
|
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) {
|
if (!token.revoked) {
|
||||||
await ctx.db.patch(token._id, {
|
await ctx.db.patch(token._id, {
|
||||||
revoked: true,
|
revoked: true,
|
||||||
|
revokedAt: now,
|
||||||
expiresAt: now,
|
expiresAt: now,
|
||||||
})
|
})
|
||||||
revokedCount += 1
|
revokedCount += 1
|
||||||
|
|
@ -2273,7 +2304,14 @@ export const upsertRemoteAccessViaToken = mutation({
|
||||||
notes: v.optional(v.string()),
|
notes: v.optional(v.string()),
|
||||||
},
|
},
|
||||||
handler: async (ctx, args) => {
|
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 trimmedProvider = args.provider.trim()
|
||||||
const trimmedIdentifier = args.identifier.trim()
|
const trimmedIdentifier = args.identifier.trim()
|
||||||
if (!trimmedProvider || !trimmedIdentifier) {
|
if (!trimmedProvider || !trimmedIdentifier) {
|
||||||
|
|
|
||||||
|
|
@ -661,6 +661,7 @@ export default defineSchema({
|
||||||
tokenHash: v.string(),
|
tokenHash: v.string(),
|
||||||
expiresAt: v.number(),
|
expiresAt: v.number(),
|
||||||
revoked: v.boolean(),
|
revoked: v.boolean(),
|
||||||
|
revokedAt: v.optional(v.number()),
|
||||||
createdAt: v.number(),
|
createdAt: v.number(),
|
||||||
lastUsedAt: v.optional(v.number()),
|
lastUsedAt: v.optional(v.number()),
|
||||||
usageCount: v.optional(v.number()),
|
usageCount: v.optional(v.number()),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue