From c1ad6b4cfe46fb366d340fe34b155b639feff409 Mon Sep 17 00:00:00 2001 From: Antonio Andre Date: Mon, 18 May 2026 19:28:14 -0500 Subject: [PATCH 1/3] refac: gera url de reset de senha com base no .env --- .env.example | 1 + nuxt.config.ts | 21 +++++++++++---------- server/utils/auth-config.ts | 5 ++++- server/utils/auth-service.ts | 5 +++-- server/utils/password-reset-token.ts | 6 +++--- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/.env.example b/.env.example index c26d80e..a65bf0c 100644 --- a/.env.example +++ b/.env.example @@ -15,3 +15,4 @@ JWT_PUBLIC_KEY_PEM="" REFRESH_TOKEN_PEPPER="change-me" PASSWORD_RESET_TTL_SEC="900" PASSWORD_RESET_TOKEN_PEPPER="change-me" +PASSWORD_RESET_BASE_URL="http://localhost:3000" diff --git a/nuxt.config.ts b/nuxt.config.ts index 1aa2711..2eda62f 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -2,7 +2,13 @@ export default defineNuxtConfig({ compatibilityDate: '2025-07-15', 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: { databaseUrl: process.env.DATABASE_URL, jwtIssuer: process.env.JWT_ISSUER ?? 'https://auth.local', @@ -14,17 +20,12 @@ export default defineNuxtConfig({ jwtKid: process.env.JWT_KID ?? 'auth-key-1', refreshTokenPepper: process.env.REFRESH_TOKEN_PEPPER ?? '', 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: { optimizeDeps: { - include: [ - '@vue/devtools-core', - '@vue/devtools-kit', - 'zod', - '@vee-validate/zod', - 'vue-sonner', - ] + include: ['@vue/devtools-core', '@vue/devtools-kit', 'zod', '@vee-validate/zod', 'vue-sonner'] } } -}) \ No newline at end of file +}) diff --git a/server/utils/auth-config.ts b/server/utils/auth-config.ts index 3741415..366e4d6 100644 --- a/server/utils/auth-config.ts +++ b/server/utils/auth-config.ts @@ -11,6 +11,7 @@ export interface AuthRuntimeConfig { kid: string refreshTokenPepper: string passwordResetTokenPepper: string + passwordResetBaseUrl: string } /** @@ -85,6 +86,7 @@ export function getAuthRuntimeConfig(event?: H3Event): AuthRuntimeConfig { const refreshTokenPepper = String(runtimeConfig.refreshTokenPepper ?? '').trim() const passwordResetTokenPepper = String(runtimeConfig.passwordResetTokenPepper ?? '').trim() || refreshTokenPepper + const passwordResetBaseUrl = String(runtimeConfig.passwordResetBaseUrl ?? '').trim() return { issuer, @@ -99,6 +101,7 @@ export function getAuthRuntimeConfig(event?: H3Event): AuthRuntimeConfig { privateKeyPem: normalizePem(String(runtimeConfig.jwtPrivateKeyPem ?? ''), 'JWT private key'), publicKeyPem: normalizePem(String(runtimeConfig.jwtPublicKeyPem ?? ''), 'JWT public key'), refreshTokenPepper, - passwordResetTokenPepper + passwordResetTokenPepper, + passwordResetBaseUrl } } diff --git a/server/utils/auth-service.ts b/server/utils/auth-service.ts index f4b6ed4..5a2c601 100644 --- a/server/utils/auth-service.ts +++ b/server/utils/auth-service.ts @@ -1,5 +1,5 @@ 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 { getAuthRuntimeConfig } from './auth-config' @@ -202,6 +202,7 @@ export async function handleForgotPassword(event: H3Event) { const expiresAt = new Date(now.getTime() + config.passwordResetTtlSec * 1000) const rawResetToken = generateRawPasswordResetToken() const tokenHash = hashPasswordResetToken(rawResetToken, config.passwordResetTokenPepper) + const resetBaseUrl = config.passwordResetBaseUrl || getRequestURL(event).origin const user = await prisma.user.findUnique({ where: { email }, @@ -233,7 +234,7 @@ export async function handleForgotPassword(event: H3Event) { message: 'If the email exists, recovery instructions were generated', recovery: { reset_token: rawResetToken, - reset_url: buildPasswordResetPreviewUrl(config.issuer, rawResetToken), + reset_url: buildPasswordResetPreviewUrl(resetBaseUrl, rawResetToken), expires_in: config.passwordResetTtlSec } } diff --git a/server/utils/password-reset-token.ts b/server/utils/password-reset-token.ts index 460f1af..95756c9 100644 --- a/server/utils/password-reset-token.ts +++ b/server/utils/password-reset-token.ts @@ -23,13 +23,13 @@ export function generateRawPasswordResetToken(): string { /** * 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. * @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 { - const url = new URL('/auth/reset-password', issuer) + const url = new URL('/auth/reset-password', baseUrl) url.searchParams.set('token', token) return url.toString() From 5fb43e0087067abcfc90a9a7d5356026425662d3 Mon Sep 17 00:00:00 2001 From: Antonio Andre Date: Mon, 18 May 2026 19:33:10 -0500 Subject: [PATCH 2/3] feat: implementa recuperacao de senha a partir do email --- .../(auth)/auth/reset-password/index.vue | 146 +++++++++++++ app/pages/(auth)/login/index.vue | 202 +++++++++--------- app/pages/(auth)/recuperar-senha/index.vue | 113 ++++++++++ 3 files changed, 361 insertions(+), 100 deletions(-) create mode 100644 app/pages/(auth)/auth/reset-password/index.vue create mode 100644 app/pages/(auth)/recuperar-senha/index.vue diff --git a/app/pages/(auth)/auth/reset-password/index.vue b/app/pages/(auth)/auth/reset-password/index.vue new file mode 100644 index 0000000..2ee9be3 --- /dev/null +++ b/app/pages/(auth)/auth/reset-password/index.vue @@ -0,0 +1,146 @@ + + + diff --git a/app/pages/(auth)/login/index.vue b/app/pages/(auth)/login/index.vue index 53435a4..25339cc 100644 --- a/app/pages/(auth)/login/index.vue +++ b/app/pages/(auth)/login/index.vue @@ -1,86 +1,88 @@ diff --git a/app/pages/(auth)/recuperar-senha/index.vue b/app/pages/(auth)/recuperar-senha/index.vue new file mode 100644 index 0000000..a2a4875 --- /dev/null +++ b/app/pages/(auth)/recuperar-senha/index.vue @@ -0,0 +1,113 @@ + + + From ef9fef66b8e1c76fb6a934f82df9f6e004dde120 Mon Sep 17 00:00:00 2001 From: Antonio Andre Date: Mon, 18 May 2026 19:41:33 -0500 Subject: [PATCH 3/3] feat: renderiza conteudos nas rotas / e /home --- app/pages/(protected)/home/index.vue | 139 ++++++++++++++++++++++++++- app/pages/index.vue | 62 +++++++++++- 2 files changed, 195 insertions(+), 6 deletions(-) diff --git a/app/pages/(protected)/home/index.vue b/app/pages/(protected)/home/index.vue index fc4cd8a..00bdaf8 100644 --- a/app/pages/(protected)/home/index.vue +++ b/app/pages/(protected)/home/index.vue @@ -1,11 +1,142 @@ diff --git a/app/pages/index.vue b/app/pages/index.vue index 97ec497..b9f8395 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -1,5 +1,63 @@ + +