cambios en el crear producto y en el refresh token

This commit is contained in:
2025-08-14 15:38:10 -04:00
parent 854b31f594
commit 6a28e141a9
19 changed files with 278 additions and 400 deletions

View File

@@ -52,7 +52,7 @@ const authConfig: NextAuthConfig = {
password: credentials?.password as string,
};
// Asigna el tipo `SignInActionResult` que ahora incluye `null`
// Asigna el tipo `SignInActionResult` que ahora incluye `null`
const response: SignInActionResult = await SignInAction(credential);
// **NUEVO: Manejar el caso `null` primero**
@@ -69,15 +69,15 @@ const authConfig: NextAuthConfig = {
response.type === 'UNKNOWN_ERROR') // Incluye todos los tipos de error posibles
) {
// Si es un error, lánzalo. Este camino termina aquí.
throw new CredentialsSignin( "Error en la API:" + response.message);
throw new CredentialsSignin("Error en la API:" + response.message);
}
if (!('user' in response)) {
// Esto solo ocurriría si SignInAction devolvió un objeto que no es null,
// no es un error conocido por 'type', PERO tampoco tiene la propiedad 'user'.
// Es un caso de respuesta inesperada del API.
console.error("Respuesta de SignInAction con formato inesperado: falta la propiedad 'user'.");
throw new CredentialsSignin("Error en el formato de la respuesta del servidor.");
if (!('user' in response)) {
// Esto solo ocurriría si SignInAction devolvió un objeto que no es null,
// no es un error conocido por 'type', PERO tampoco tiene la propiedad 'user'.
// Es un caso de respuesta inesperada del API.
console.error("Respuesta de SignInAction con formato inesperado: falta la propiedad 'user'.");
throw new CredentialsSignin("Error en el formato de la respuesta del servidor.");
}
return {
@@ -91,7 +91,7 @@ const authConfig: NextAuthConfig = {
refresh_token: response?.tokens.refresh_token ?? '',
refresh_expire_in: response?.tokens.refresh_expire_in ?? 0,
};
},
}),
@@ -102,46 +102,61 @@ const authConfig: NextAuthConfig = {
callbacks: {
async jwt({
token,
user
user,
}: {
token: any;
user: User;
}) {
// Si es un nuevo login, asignamos los datos
if (user) {
token.id = user.id;
token.username = user.username;
token.fullname = user.fullname;
token.email = user.email;
token.role = user.role;
token.access_token = user.access_token;
token.access_expire_in = user.access_expire_in;
token.refresh_token = user.refresh_token;
token.refresh_expire_in = user.refresh_expire_in;
}
try {
// Si es un nuevo login, asignamos los datos
if (user) {
token.id = user.id;
token.username = user.username;
token.fullname = user.fullname;
token.email = user.email;
token.role = user.role;
token.access_token = user.access_token;
token.access_expire_in = user.access_expire_in;
token.refresh_token = user.refresh_token;
token.refresh_expire_in = user.refresh_expire_in;
return token; // IMPORTANTE: retornamos el token aquí para evitar que entre en el siguiente 'if'
}
// Renovar access_token si ha expirado
if (Date.now() / 1000 > (token.access_expire_in as number)) {
if (Date.now() / 1000 > (token.refresh_expire_in as number)) {
// Verificar si el access_token ha expirado
const now = Date.now() / 1000;
if (now < (token.access_expire_in as number)) {
return token; // Si el token no ha expirado, lo retornamos sin cambios
}
// Si el access_token ha expirado, verificar el refresh_token
if (now > (token.refresh_expire_in as number)) {
console.log("Refresh token ha expirado. Forzando logout.");
return null; // Forzar logout
}
try {
const res = await resfreshTokenAction({
token: token.refresh_token as string,
});
if (!res) throw new Error('Failed to refresh token');
token.access_token = res.tokens.access_token;
token.access_expire_in = res.tokens.access_expire_in;
token.refresh_token = res.tokens.refresh_token;
token.refresh_expire_in = res.tokens.refresh_expire_in;
} catch (error) {
console.log("error: ",error);
return null;
}
}
// Si el access_token ha expirado pero el refresh_token es válido, renovar
console.log("Renovando token de acceso...");
const res = await resfreshTokenAction({
token: token.refresh_token as string,
});
return token;
console.log("Pepe");
if (!res) throw new Error('Fallo al renovar el token');
// Actualizar el token con la nueva información
return {
...token,
access_token: res.tokens.access_token,
access_expire_in: res.tokens.access_expire_in,
refresh_token: res.tokens.refresh_token,
refresh_expire_in: res.tokens.refresh_expire_in,
};
} catch (error) {
console.error("Error al renovar el token: ", error);
return null; // Fallo al renovar, forzar logout
}
},
async session({ session, token }: { session: Session; token: DefaultJWT }) {
session.access_token = token.access_token as string;

View File

@@ -1,19 +1,18 @@
'use server';
import { env } from '@/lib/env'; // Importamos la configuración de entorno validada
import { env } from '@/lib/env';
import axios 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
// No establecer Content-Type aquí. Axios lo manejará automáticamente con FormData.
baseURL: env.API_URL,
});
// Interceptor para incluir el token automáticamente en las peticiones
// ESTE INTERCEPTOR ESTÁ BIEN PARA EL RESTO DE LAS PETICIONES AUTENTICADAS
fetchApi.interceptors.request.use(async (config: any) => {
try {
// Importación dinámica para evitar la referencia circular
const { auth } = await import('@/lib/auth');
const { auth } = await import('@/lib/auth'); // Importación dinámica
const session = await auth();
const token = session?.access_token;
@@ -22,44 +21,35 @@ fetchApi.interceptors.request.use(async (config: any) => {
}
// **Importante:** Si el body es FormData, elimina el Content-Type para que Axios lo configure automáticamente.
// Esto es necesario porque 'multipart/form-data' requiere un 'boundary' que Axios añade.
if (config.data instanceof FormData) {
delete config.headers['Content-Type'];
} else {
// Para otros tipos de datos, asegura que el Content-Type sea 'application/json'
config.headers['Content-Type'] = 'application/json';
}
return config;
} catch (error) {
console.error('Error getting auth token:', error);
console.error('Error al obtener el token de autenticación para el interceptor:', error);
// IMPORTANTE: Si ocurre un error aquí, es mejor rechazar la promesa
// para que la solicitud no se envíe sin autorización.
return Promise.reject(error);
}
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 method - Método HTTP (GET, POST, PUT, PATCH, DELETE)
* @param data - Datos a enviar (puede ser un objeto JSON o FormData para archivos)
* @returns [error, data] -> Devuelve el error como objeto estructurado si hay fallo, o los datos validados
*/
// safeFetchApi sigue siendo útil para el resto de las llamadas que requieren autenticación
export const safeFetchApi = async <T extends z.ZodSchema<any>>(
schema: T,
url: string,
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' = 'GET',
data?: any, // Renombrado a 'data' para mayor claridad y consistencia con Axios
data?: any,
): Promise<
[{ type: string; message: string; details?: any } | null, z.infer<T> | null]
> => {
try {
console.log(url,method,data);
const response = await fetchApi({
method,
url,
data, // Axios usa 'data' para el body de POST/PUT/PATCH
data,
});
const parsed = schema.safeParse(response.data);

View File

@@ -0,0 +1,11 @@
import axios from 'axios';
import { env } from '@/lib/env'; // Asegúrate de que env está correctamente configurado
// Crea una instancia de Axios SÓLO para la API de refresh token
// Sin el interceptor que obtiene la sesión para evitar la dependencia circular
export const refreshApi = axios.create({
baseURL: env.API_URL,
headers: {
'Content-Type': 'application/json', // El refresh token se envía en el body JSON
},
});