From f88ab2a97149860e120f0df50cc9b9a15fd54ff1 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 23 Mar 2026 10:20:48 -0400 Subject: [PATCH] Cambios en refresh token para no dar error. No actualiza access token de la cookie/session. Elimina access token de la cookie para forzar cerrar la session en caso de error --- apps/api/src/features/auth/auth.controller.ts | 8 +- apps/api/src/features/auth/auth.service.ts | 2 +- .../feactures/auth/actions/logout-action.ts | 50 +++++++--- .../auth/actions/refresh-token-action.ts | 3 +- .../feactures/auth/schemas/refreshToken.ts | 15 ++- apps/web/lib/auth-token.ts | 36 +++++-- apps/web/lib/fetch.api2.ts | 98 ------------------- 7 files changed, 85 insertions(+), 127 deletions(-) delete mode 100644 apps/web/lib/fetch.api2.ts diff --git a/apps/api/src/features/auth/auth.controller.ts b/apps/api/src/features/auth/auth.controller.ts index 495e1d3..dcfc296 100644 --- a/apps/api/src/features/auth/auth.controller.ts +++ b/apps/api/src/features/auth/auth.controller.ts @@ -8,7 +8,7 @@ import { AuthService } from './auth.service'; @Controller('auth') export class AuthController { - constructor(private readonly authService: AuthService) {} + constructor(private readonly authService: AuthService) { } @Public() @HttpCode(200) @@ -28,6 +28,8 @@ export class AuthController { return await this.authService.signIn(signInUserDto); } + @Public() + @HttpCode(200) @Post('sign-out') //@RequirePermissions('auth:sign-out') async signOut(@Body() signOutUserDto: SignOutUserDto) { @@ -47,6 +49,10 @@ export class AuthController { @Patch('refresh') //@RequirePermissions('auth:refresh-token') async refreshToken(@Body() refreshTokenDto: any) { + // console.log('REFRESCANDO'); + // console.log(refreshTokenDto); + // console.log('-----------'); + return await this.authService.refreshToken(refreshTokenDto); } diff --git a/apps/api/src/features/auth/auth.service.ts b/apps/api/src/features/auth/auth.service.ts index 7bb4670..63350ca 100644 --- a/apps/api/src/features/auth/auth.service.ts +++ b/apps/api/src/features/auth/auth.service.ts @@ -40,7 +40,7 @@ export class AuthService { private readonly config: ConfigService, @Inject(DRIZZLE_PROVIDER) private drizzle: NodePgDatabase, private readonly mailService: MailService, - ) {} + ) { } //Decode Tokens // Método para decodificar el token y obtener los datos completos diff --git a/apps/web/feactures/auth/actions/logout-action.ts b/apps/web/feactures/auth/actions/logout-action.ts index 9cd2368..0dd18ba 100644 --- a/apps/web/feactures/auth/actions/logout-action.ts +++ b/apps/web/feactures/auth/actions/logout-action.ts @@ -1,27 +1,47 @@ 'use server'; -import { safeFetchApi } from '@/lib'; +// import { safeFetchApi } from '@/lib'; +import { refreshApi } from '@/lib/refreshApi'; // Importa la nueva instancia import { cookies } from 'next/headers'; import { logoutResponseSchema } from '../schemas/logout'; export const logoutAction = async (user_id: string) => { - const payload = { user_id }; + try { + const response = await refreshApi.post('/auth/sign-out', { user_id }); - const [error, data] = await safeFetchApi( - logoutResponseSchema, - '/auth/sign-out', - 'POST', - payload, - ); + const parsed = logoutResponseSchema.safeParse(response.data); - if (error) { - console.error('Error:', error); - // Devuelve un objeto con la propiedad 'type' para que el callback de NextAuth lo reconozca como un error - return { - type: 'API_ERROR', - message: error.message, - }; + if (!parsed.success) { + console.error('Error de validación en la respuesta de refresh token:', { + errors: parsed.error.errors, + receivedData: response.data, + }); + return null; + } + + return parsed.data; + } catch (error: any) { // Captura el error para acceso a error.response + console.error('Error al cerrar sesion:', error.response?.data || error.message); + return null; } + + // const payload = { user_id }; + // const [error, data] = await safeFetchApi( + // logoutResponseSchema, + // '/auth/sign-out', + // 'POST', + // payload, + // ); + + // if (error) { + // console.error('Error:', error); + // // Devuelve un objeto con la propiedad 'type' para que el callback de NextAuth lo reconozca como un error + // return { + // type: 'API_ERROR', + // message: error.message, + // }; + // } + (await cookies()).delete('refresh_token'); }; diff --git a/apps/web/feactures/auth/actions/refresh-token-action.ts b/apps/web/feactures/auth/actions/refresh-token-action.ts index fc8111b..e75d067 100644 --- a/apps/web/feactures/auth/actions/refresh-token-action.ts +++ b/apps/web/feactures/auth/actions/refresh-token-action.ts @@ -1,4 +1,3 @@ -// auth/actions/refresh-token-action.ts 'use server'; import { refreshApi } from '@/lib/refreshApi'; // Importa la nueva instancia import { @@ -10,7 +9,7 @@ export const resfreshTokenAction = async (refreshToken: RefreshTokenValue) => { try { const response = await refreshApi.patch('/auth/refresh', refreshToken); - const parsed = RefreshTokenResponseSchema.safeParse(response.data); + const parsed = RefreshTokenResponseSchema.safeParse(response.data); if (!parsed.success) { console.error('Error de validación en la respuesta de refresh token:', { diff --git a/apps/web/feactures/auth/schemas/refreshToken.ts b/apps/web/feactures/auth/schemas/refreshToken.ts index a71dd66..b00b0b4 100644 --- a/apps/web/feactures/auth/schemas/refreshToken.ts +++ b/apps/web/feactures/auth/schemas/refreshToken.ts @@ -4,12 +4,19 @@ import { tokensSchema } from './login'; // Esquema para el refresh token export const refreshTokenSchema = z.object({ - token: z.string(), + refreshToken: z.string(), }); export type RefreshTokenValue = z.infer; // Esquema final para la respuesta del backend -export const RefreshTokenResponseSchema = z.object({ - tokens: tokensSchema, -}); +// export const RefreshTokenResponseSchema = z.object({ +// // tokens: tokensSchema, +// access_token: z.string(), +// access_expire_in: z.number(), +// refresh_token: z.string(), +// refresh_expire_in: z.number() +// }); + +export const RefreshTokenResponseSchema = tokensSchema + diff --git a/apps/web/lib/auth-token.ts b/apps/web/lib/auth-token.ts index 8169dd3..91165f9 100644 --- a/apps/web/lib/auth-token.ts +++ b/apps/web/lib/auth-token.ts @@ -6,7 +6,12 @@ import { cache } from 'react'; export const getValidAccessToken = cache(async () => { const session = await auth(); - if (!session?.access_token) return null; + if (!session?.access_token) { + // console.log('No hay Access Token'); + return null + } + // console.log('Si hay Access Token'); + const now = Math.floor(Date.now() / 1000); // Restamos 10s para tener margen de seguridad @@ -14,24 +19,35 @@ export const getValidAccessToken = cache(async () => { // A. Si es válido, lo retornamos directo if (isValid) return session.access_token; + // console.log('Access Token Expiró'); + // B. Si expiró, buscamos la cookie const cookieStore = cookies(); - const refreshToken = (await cookieStore).get('refresh_token')?.value; + const refreshTokenCookie = await cookieStore + const refreshToken = refreshTokenCookie.get('refresh_token')?.value; + + if (!refreshToken) { + // console.log('No hay Refresh Token'); + return null + } // No hay refresh token, fin del juego + // console.log('Si hay Refresh Token'); - if (!refreshToken) return null; // No hay refresh token, fin del juego // C. Intentamos refrescar - const newTokens = await resfreshTokenAction({ token: refreshToken }); + const newTokens = await resfreshTokenAction({ refreshToken }); if (!newTokens) { + // console.log('No hay token nuevo'); // Si falla el refresh (token revocado o expirado), borramos cookie (await cookieStore).delete('refresh_token'); + (await cookieStore).delete('authjs.session-token'); return null; } + // console.log('Si hay token nuevo'); // D. Guardamos el nuevo refresh token en cookie y retornamos el access token - (await cookieStore).set('refresh_token', newTokens.tokens.refresh_token, { + (await cookieStore).set('refresh_token', newTokens.refresh_token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', @@ -39,5 +55,13 @@ export const getValidAccessToken = cache(async () => { maxAge: 7 * 24 * 60 * 60, }); - return newTokens.tokens.access_token; + // (await cookieStore).set('authjs.session-token', newTokens.access_token, { + // httpOnly: true, + // secure: process.env.NODE_ENV === 'production', + // sameSite: 'lax', + // path: '/', + // maxAge: 7 * 24 * 60 * 60, + // }); + + return newTokens.access_token; }); diff --git a/apps/web/lib/fetch.api2.ts b/apps/web/lib/fetch.api2.ts deleted file mode 100644 index 9dfd4ca..0000000 --- a/apps/web/lib/fetch.api2.ts +++ /dev/null @@ -1,98 +0,0 @@ -'use server'; -import { env } from '@/lib/env'; // Importamos la configuración de entorno validada -import axios, { InternalAxiosRequestConfig } from 'axios'; -import { z } from 'zod'; - -// Crear instancia de Axios con la URL base validada -const fetchApi = axios.create({ - baseURL: env.API_URL, // Aquí usamos env.API_URL en vez de process.env.BACKEND_URL - headers: { - 'Content-Type': 'application/json', - }, -}); - -// Interceptor para incluir el token automáticamente en las peticiones -fetchApi.interceptors.request.use( - async (config: InternalAxiosRequestConfig) => { - try { - const { getValidAccessToken } = await import('@/lib/auth-token'); - const token = await getValidAccessToken(); - - if (token) { - config.headers.set('Authorization', `Bearer ${token}`); - } - } catch (err) { - console.error('Error getting auth token:', err); - } - return config; - }, -); - -/** - * Función para hacer peticiones con validación de respuesta - * @param schema - Esquema de Zod para validar la respuesta - * @param url - Endpoint a consultar - * @param config - Configuración opcional de Axios - * @returns [error, data] -> Devuelve el error como objeto estructurado si hay fallo, o los datos validados - */ -export const safeFetchApi = async >( - schema: T, - url: string, - method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' = 'GET', - body?: any, -): Promise< - [{ type: string; message: string; details?: any } | null, z.infer | null] -> => { - try { - const response = await fetchApi({ - method, - url, - data: body, - }); - - const parsed = schema.safeParse(response.data); - - if (!parsed.success) { - console.error('Validation Error Details:', { - errors: parsed.error.errors, - receivedData: response.data, - expectedSchema: schema, - data: response.data.data, - }); - // console.error(parsed.error.errors) - return [ - { - type: 'VALIDATION_ERROR', - message: 'Validation error', - details: parsed.error.errors, - }, - null, - ]; - } - - return [null, parsed.data]; - } catch (error: any) { - const errorDetails = { - status: error.response?.status, - statusText: error.response?.statusText, - message: error.message, - url: error.config?.url, - method: error.config?.method, - requestData: error.config?.data, - responseData: error.response?.data, - headers: error.config?.headers, - }; - - // console.log(error) - return [ - { - type: 'API_ERROR', - message: error.response?.data?.message || 'Unknown API error', - details: errorDetails, - }, - null, - ]; - } -}; - -export { fetchApi };