refac: gera url de reset de senha com base no .env

This commit is contained in:
2026-05-18 19:28:14 -05:00
parent 7153caa0ac
commit c1ad6b4cfe
5 changed files with 22 additions and 16 deletions

View File

@@ -15,3 +15,4 @@ JWT_PUBLIC_KEY_PEM="<replace-with-escaped-pem>"
REFRESH_TOKEN_PEPPER="change-me" REFRESH_TOKEN_PEPPER="change-me"
PASSWORD_RESET_TTL_SEC="900" PASSWORD_RESET_TTL_SEC="900"
PASSWORD_RESET_TOKEN_PEPPER="change-me" PASSWORD_RESET_TOKEN_PEPPER="change-me"
PASSWORD_RESET_BASE_URL="http://localhost:3000"

View File

@@ -2,7 +2,13 @@
export default defineNuxtConfig({ export default defineNuxtConfig({
compatibilityDate: '2025-07-15', compatibilityDate: '2025-07-15',
devtools: { enabled: true }, devtools: { enabled: true },
modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt', '@vee-validate/nuxt', 'vue-sonner/nuxt', '@nuxt/icon'], modules: [
'@nuxtjs/tailwindcss',
'@pinia/nuxt',
'@vee-validate/nuxt',
'vue-sonner/nuxt',
'@nuxt/icon'
],
runtimeConfig: { runtimeConfig: {
databaseUrl: process.env.DATABASE_URL, databaseUrl: process.env.DATABASE_URL,
jwtIssuer: process.env.JWT_ISSUER ?? 'https://auth.local', jwtIssuer: process.env.JWT_ISSUER ?? 'https://auth.local',
@@ -14,17 +20,12 @@ export default defineNuxtConfig({
jwtKid: process.env.JWT_KID ?? 'auth-key-1', jwtKid: process.env.JWT_KID ?? 'auth-key-1',
refreshTokenPepper: process.env.REFRESH_TOKEN_PEPPER ?? '', refreshTokenPepper: process.env.REFRESH_TOKEN_PEPPER ?? '',
passwordResetTtlSec: process.env.PASSWORD_RESET_TTL_SEC ?? '900', passwordResetTtlSec: process.env.PASSWORD_RESET_TTL_SEC ?? '900',
passwordResetTokenPepper: process.env.PASSWORD_RESET_TOKEN_PEPPER ?? '' passwordResetTokenPepper: process.env.PASSWORD_RESET_TOKEN_PEPPER ?? '',
passwordResetBaseUrl: process.env.PASSWORD_RESET_BASE_URL ?? ''
}, },
vite: { vite: {
optimizeDeps: { optimizeDeps: {
include: [ include: ['@vue/devtools-core', '@vue/devtools-kit', 'zod', '@vee-validate/zod', 'vue-sonner']
'@vue/devtools-core',
'@vue/devtools-kit',
'zod',
'@vee-validate/zod',
'vue-sonner',
]
} }
} }
}) })

View File

@@ -11,6 +11,7 @@ export interface AuthRuntimeConfig {
kid: string kid: string
refreshTokenPepper: string refreshTokenPepper: string
passwordResetTokenPepper: string passwordResetTokenPepper: string
passwordResetBaseUrl: string
} }
/** /**
@@ -85,6 +86,7 @@ export function getAuthRuntimeConfig(event?: H3Event): AuthRuntimeConfig {
const refreshTokenPepper = String(runtimeConfig.refreshTokenPepper ?? '').trim() const refreshTokenPepper = String(runtimeConfig.refreshTokenPepper ?? '').trim()
const passwordResetTokenPepper = const passwordResetTokenPepper =
String(runtimeConfig.passwordResetTokenPepper ?? '').trim() || refreshTokenPepper String(runtimeConfig.passwordResetTokenPepper ?? '').trim() || refreshTokenPepper
const passwordResetBaseUrl = String(runtimeConfig.passwordResetBaseUrl ?? '').trim()
return { return {
issuer, issuer,
@@ -99,6 +101,7 @@ export function getAuthRuntimeConfig(event?: H3Event): AuthRuntimeConfig {
privateKeyPem: normalizePem(String(runtimeConfig.jwtPrivateKeyPem ?? ''), 'JWT private key'), privateKeyPem: normalizePem(String(runtimeConfig.jwtPrivateKeyPem ?? ''), 'JWT private key'),
publicKeyPem: normalizePem(String(runtimeConfig.jwtPublicKeyPem ?? ''), 'JWT public key'), publicKeyPem: normalizePem(String(runtimeConfig.jwtPublicKeyPem ?? ''), 'JWT public key'),
refreshTokenPepper, refreshTokenPepper,
passwordResetTokenPepper passwordResetTokenPepper,
passwordResetBaseUrl
} }
} }

View File

@@ -1,5 +1,5 @@
import { Prisma } from '@prisma/client' import { Prisma } from '@prisma/client'
import { createError, readBody, setResponseStatus, type H3Event } from 'h3' import { createError, getRequestURL, readBody, setResponseStatus, type H3Event } from 'h3'
import { signAccessToken } from './jwt' import { signAccessToken } from './jwt'
import { getAuthRuntimeConfig } from './auth-config' import { getAuthRuntimeConfig } from './auth-config'
@@ -202,6 +202,7 @@ export async function handleForgotPassword(event: H3Event) {
const expiresAt = new Date(now.getTime() + config.passwordResetTtlSec * 1000) const expiresAt = new Date(now.getTime() + config.passwordResetTtlSec * 1000)
const rawResetToken = generateRawPasswordResetToken() const rawResetToken = generateRawPasswordResetToken()
const tokenHash = hashPasswordResetToken(rawResetToken, config.passwordResetTokenPepper) const tokenHash = hashPasswordResetToken(rawResetToken, config.passwordResetTokenPepper)
const resetBaseUrl = config.passwordResetBaseUrl || getRequestURL(event).origin
const user = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { email }, where: { email },
@@ -233,7 +234,7 @@ export async function handleForgotPassword(event: H3Event) {
message: 'If the email exists, recovery instructions were generated', message: 'If the email exists, recovery instructions were generated',
recovery: { recovery: {
reset_token: rawResetToken, reset_token: rawResetToken,
reset_url: buildPasswordResetPreviewUrl(config.issuer, rawResetToken), reset_url: buildPasswordResetPreviewUrl(resetBaseUrl, rawResetToken),
expires_in: config.passwordResetTtlSec expires_in: config.passwordResetTtlSec
} }
} }

View File

@@ -23,13 +23,13 @@ export function generateRawPasswordResetToken(): string {
/** /**
* Monta uma URL de preview para facilitar testes locais sem SMTP. * Monta uma URL de preview para facilitar testes locais sem SMTP.
* *
* @param issuer Base do serviço de auth. * @param baseUrl URL pública usada para abrir a tela de redefinição.
* @param token Token bruto de recuperação. * @param token Token bruto de recuperação.
* @returns URL completa (ou fallback relativo) com o token. * @returns URL completa (ou fallback relativo) com o token.
*/ */
export function buildPasswordResetPreviewUrl(issuer: string, token: string): string { export function buildPasswordResetPreviewUrl(baseUrl: string, token: string): string {
try { try {
const url = new URL('/auth/reset-password', issuer) const url = new URL('/auth/reset-password', baseUrl)
url.searchParams.set('token', token) url.searchParams.set('token', token)
return url.toString() return url.toString()