Auto-expire revoked invites and allow reactivation

This commit is contained in:
Esdras Renan 2025-10-13 15:17:11 -03:00
parent 05f5af5ba6
commit b60f27b2dc
3 changed files with 96 additions and 3 deletions

View file

@ -9,10 +9,15 @@ import { env } from "@/lib/env"
import { prisma } from "@/lib/prisma"
import { computeInviteStatus, normalizeInvite, type NormalizedInvite } from "@/server/invite-utils"
type RevokePayload = {
type InviteAction = "revoke" | "reactivate"
type InvitePayload = {
action?: InviteAction
reason?: string
}
const REVOKE_RETENTION_MS = 7 * 24 * 60 * 60 * 1000
async function syncInvite(invite: NormalizedInvite) {
const convexUrl = env.NEXT_PUBLIC_CONVEX_URL
if (!convexUrl) return
@ -43,7 +48,8 @@ export async function PATCH(request: Request, context: { params: Promise<{ id: s
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
}
const body = (await request.json().catch(() => null)) as Partial<RevokePayload> | null
const body = (await request.json().catch(() => null)) as Partial<InvitePayload> | null
const action: InviteAction = body?.action === "reactivate" ? "reactivate" : "revoke"
const reason = typeof body?.reason === "string" && body.reason.trim() ? body.reason.trim() : null
const invite = await prisma.authInvite.findUnique({
@ -62,6 +68,42 @@ export async function PATCH(request: Request, context: { params: Promise<{ id: s
return NextResponse.json({ error: "Convite já aceito" }, { status: 400 })
}
if (action === "reactivate") {
if (status !== "revoked") {
return NextResponse.json({ error: "Convite não está revogado" }, { status: 400 })
}
if (!invite.revokedAt) {
return NextResponse.json({ error: "Convite revogado sem data. Não é possível reativar." }, { status: 400 })
}
const revokedAtMs = invite.revokedAt.getTime()
if (now.getTime() - revokedAtMs > REVOKE_RETENTION_MS) {
return NextResponse.json({ error: "Este convite foi revogado há mais de 7 dias" }, { status: 400 })
}
const updated = await prisma.authInvite.update({
where: { id: invite.id },
data: {
status: "pending",
revokedAt: null,
revokedById: null,
revokedReason: null,
},
})
const event = await prisma.authInviteEvent.create({
data: {
inviteId: invite.id,
type: "reactivated",
payload: Prisma.JsonNull,
actorId: session.user.id ?? null,
},
})
const normalized = normalizeInvite({ ...updated, events: [...invite.events, event] }, now)
await syncInvite(normalized)
return NextResponse.json({ invite: normalized })
}
if (status === "revoked") {
const normalized = normalizeInvite(invite, now)
await syncInvite(normalized)