/** * Serviço de Tokens de Acesso * Gera e valida tokens para acesso direto aos chamados * Sistema de Chamados Raven */ import { prisma } from "@/lib/prisma" import { randomBytes } from "crypto" // ============================================ // Tipos // ============================================ export interface GenerateTokenOptions { tenantId: string ticketId: string userId: string machineId?: string scope: "view" | "interact" | "rate" expiresInDays?: number } export interface ValidatedToken { id: string tenantId: string ticketId: string userId: string machineId: string | null scope: string expiresAt: Date usedAt: Date | null createdAt: Date } // ============================================ // Geração de Token // ============================================ /** * Gera um token seguro de acesso ao chamado */ export async function generateAccessToken(options: GenerateTokenOptions): Promise { const { tenantId, ticketId, userId, machineId, scope, expiresInDays = 7, } = options // Gera um token aleatório de 32 bytes (64 caracteres hex) const token = randomBytes(32).toString("hex") // Calcula a data de expiração const expiresAt = new Date() expiresAt.setDate(expiresAt.getDate() + expiresInDays) // Salva o token no banco await prisma.ticketAccessToken.create({ data: { tenantId, token, ticketId, userId, machineId, scope, expiresAt, }, }) return token } /** * Valida um token de acesso */ export async function validateAccessToken(token: string): Promise { const tokenRecord = await prisma.ticketAccessToken.findUnique({ where: { token }, }) if (!tokenRecord) { return null } // Verifica se o token expirou if (tokenRecord.expiresAt < new Date()) { return null } return { id: tokenRecord.id, tenantId: tokenRecord.tenantId, ticketId: tokenRecord.ticketId, userId: tokenRecord.userId, machineId: tokenRecord.machineId, scope: tokenRecord.scope, expiresAt: tokenRecord.expiresAt, usedAt: tokenRecord.usedAt, createdAt: tokenRecord.createdAt, } } /** * Marca um token como usado */ export async function markTokenAsUsed(token: string): Promise { await prisma.ticketAccessToken.update({ where: { token }, data: { usedAt: new Date() }, }) } /** * Invalida todos os tokens de um chamado */ export async function invalidateTicketTokens(ticketId: string): Promise { await prisma.ticketAccessToken.updateMany({ where: { ticketId }, data: { expiresAt: new Date() }, }) } /** * Invalida todos os tokens de um usuário */ export async function invalidateUserTokens(userId: string): Promise { await prisma.ticketAccessToken.updateMany({ where: { userId }, data: { expiresAt: new Date() }, }) } /** * Limpa tokens expirados (pode ser chamado periodicamente) */ export async function cleanupExpiredTokens(): Promise { const result = await prisma.ticketAccessToken.deleteMany({ where: { expiresAt: { lt: new Date(), }, }, }) return result.count } /** * Verifica se um token tem o escopo necessário */ export function hasScope(tokenScope: string, requiredScope: "view" | "interact" | "rate"): boolean { const scopeHierarchy: Record = { view: 1, interact: 2, rate: 3, } const tokenLevel = scopeHierarchy[tokenScope] ?? 0 const requiredLevel = scopeHierarchy[requiredScope] ?? 0 // rate é especial, só pode avaliar if (requiredScope === "rate") { return tokenScope === "rate" } // interact permite view // view só permite view return tokenLevel >= requiredLevel }