feat: adiciona fluxo de redefinição de senha e melhora página de configurações

- Adiciona página /recuperar para solicitar redefinição de senha
- Adiciona página /redefinir-senha para definir nova senha com token
- Cria APIs /api/auth/forgot-password e /api/auth/reset-password
- Adiciona notificação por e-mail quando ticket é criado
- Repagina página de configurações removendo informações técnicas
- Adiciona script de teste para todos os tipos de e-mail
- Corrige acentuações em templates de e-mail

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rever-tecnologia 2025-12-15 10:42:08 -03:00
parent 300179279a
commit 1bc08d3a5f
10 changed files with 1258 additions and 166 deletions

View file

@ -0,0 +1,101 @@
import crypto from "crypto"
import { render } from "@react-email/render"
import { NextResponse } from "next/server"
import { prisma } from "@/lib/prisma"
import { sendSmtpMail } from "@/server/email-smtp"
import SimpleNotificationEmail from "../../../../../emails/simple-notification-email"
function getSmtpConfig() {
const host = process.env.SMTP_HOST ?? process.env.SMTP_ADDRESS
const port = process.env.SMTP_PORT
const username = process.env.SMTP_USER ?? process.env.SMTP_USERNAME
const password = process.env.SMTP_PASS ?? process.env.SMTP_PASSWORD
const fromEmail = process.env.SMTP_FROM_EMAIL ?? process.env.MAILER_SENDER_EMAIL
const fromName = process.env.SMTP_FROM_NAME ?? "Raven"
if (!host || !port || !username || !password || !fromEmail) return null
return {
host,
port: Number(port),
username,
password,
from: `"${fromName}" <${fromEmail}>`,
tls: process.env.SMTP_SECURE === "true",
starttls: process.env.SMTP_SECURE !== "true",
rejectUnauthorized: false,
timeoutMs: 15000,
}
}
export async function POST(request: Request) {
try {
const body = await request.json()
const { email } = body
if (!email || typeof email !== "string") {
return NextResponse.json({ error: "E-mail é obrigatório" }, { status: 400 })
}
const normalizedEmail = email.toLowerCase().trim()
// Busca o usuário pelo e-mail (sem revelar se existe ou não por segurança)
const user = await prisma.authUser.findFirst({
where: { email: normalizedEmail },
})
// Sempre retorna sucesso para não revelar se o e-mail existe
if (!user) {
return NextResponse.json({ success: true })
}
// Gera um token seguro
const token = crypto.randomBytes(32).toString("hex")
const expiresAt = new Date(Date.now() + 60 * 60 * 1000) // 1 hora
// Remove tokens anteriores do mesmo usuário
await prisma.authVerification.deleteMany({
where: {
identifier: `password-reset:${user.id}`,
},
})
// Salva o novo token
await prisma.authVerification.create({
data: {
identifier: `password-reset:${user.id}`,
value: token,
expiresAt,
},
})
// Envia o e-mail
const smtp = getSmtpConfig()
if (!smtp) {
console.error("[FORGOT_PASSWORD] SMTP não configurado")
return NextResponse.json({ success: true }) // Não revela erro de configuração
}
const baseUrl = process.env.NEXT_PUBLIC_APP_URL ?? "https://tickets.esdrasrenan.com.br"
const resetUrl = `${baseUrl}/redefinir-senha?token=${token}`
const html = await render(
SimpleNotificationEmail({
title: "Redefinição de Senha",
message: `Olá, ${user.name ?? "usuário"}!\n\nRecebemos uma solicitação para redefinir a senha da sua conta.\n\nSe você não fez essa solicitação, pode ignorar este e-mail com segurança.\n\nEste link expira em 1 hora.`,
ctaLabel: "Redefinir Senha",
ctaUrl: resetUrl,
}),
{ pretty: true }
)
await sendSmtpMail(smtp, normalizedEmail, "Redefinição de Senha - Raven", html)
return NextResponse.json({ success: true })
} catch (error) {
console.error("[FORGOT_PASSWORD] Erro:", error)
return NextResponse.json({ error: "Erro ao processar solicitação" }, { status: 500 })
}
}