From 051ee134c4427a9040ff5c978fb70197146f2f35 Mon Sep 17 00:00:00 2001 From: Steven Date: Sat, 11 Oct 2025 00:27:30 -0500 Subject: [PATCH 1/2] Refactor authentication middleware to centralize session verification Removed the handleAccountSettings middleware and integrated centralized authentication checks using handleAuthenticationMiddleware across various ownership and store access middleware. This change streamlines session validation and improves code maintainability by reducing redundancy in authentication logic. --- middleware.ts | 26 +------------ middlewares/auth/auth.ts | 41 +------------------- middlewares/ownership/collectionOwnership.ts | 15 +++---- middlewares/ownership/pagesOwnership.ts | 15 +++---- middlewares/ownership/productOwnership.ts | 15 +++---- middlewares/store-access/store.ts | 16 ++++---- middlewares/store-access/storeAccess.ts | 14 ++++--- middlewares/subscription/subscription.ts | 14 ++++--- 8 files changed, 51 insertions(+), 105 deletions(-) diff --git a/middleware.ts b/middleware.ts index 12bc3de2..0ef18d28 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,7 +1,4 @@ -import { - handleAuthenticatedRedirectMiddleware, - handleAuthenticationMiddlewareNoRefresh, -} from '@/middlewares/auth/auth'; +import { handleAuthenticatedRedirectMiddleware } from '@/middlewares/auth/auth'; import { handleDomainRouting, analyzeDomain } from '@/middlewares/domain-handling/domainHandler'; import { handleCollectionOwnershipMiddleware } from '@/middlewares/ownership/collectionOwnership'; import { handlePagesOwnershipMiddleware } from '@/middlewares/ownership/pagesOwnership'; @@ -19,8 +16,6 @@ const PROTECTED_ROUTES = { OAUTH_CALLBACK: '/auth/callback', /** Rutas de órdenes */ ORDERS: '/orders', - /** Configuración de cuenta */ - ACCOUNT_SETTINGS: '/account-settings', /** Pasos iniciales de configuración */ FIRST_STEPS: '/first-steps', /** Página de selección de tienda */ @@ -326,23 +321,6 @@ async function handleStoreAccess( return await next(); } -/** - * Handler para proteger rutas de configuración de cuenta - * @param request - Petición entrante - * @param next - Función para continuar con el siguiente handler - * @returns Respuesta del middleware de autenticación o null para continuar - */ -async function handleAccountSettings( - request: NextRequest, - next: () => Promise -): Promise { - if (request.nextUrl.pathname.startsWith(PROTECTED_ROUTES.ACCOUNT_SETTINGS)) { - return await handleAuthenticationMiddlewareNoRefresh(request, NextResponse.next()); - } - - return await next(); -} - /** * Handler para manejar rutas de configuración inicial de tienda * @param request - Petición entrante @@ -425,12 +403,10 @@ export async function middleware(request: NextRequest): Promise { handlePagesOwnership, handleCollectionOwnership, handleStoreAccess, - handleAccountSettings, handleStoreSetup, handleLoginRedirect, ]; - // Ejecutar recursivamente todos los handlers return await executeHandlers(handlers, request); } diff --git a/middlewares/auth/auth.ts b/middlewares/auth/auth.ts index fef39cc5..fc2aadfa 100644 --- a/middlewares/auth/auth.ts +++ b/middlewares/auth/auth.ts @@ -54,22 +54,12 @@ function getCacheKey(request: NextRequest): string { export async function getSession(request: NextRequest, response: NextResponse, forceRefresh = true) { const cacheKey = getCacheKey(request); - // Debug logs temporales - console.log('🔍 [GET SESSION DEBUG]', { - pathname: request.nextUrl.pathname, - forceRefresh, - cacheKey, - hasCached: !forceRefresh ? !!sessionCache.get(cacheKey) : 'N/A', - }); - // Verificar cache si no es forceRefresh if (!forceRefresh) { const cached = sessionCache.get(cacheKey); if (cached) { - console.log('✅ [GET SESSION] Cache hit:', cacheKey); return cached; } - console.log('❌ [GET SESSION] Cache miss:', cacheKey); } return runWithAmplifyServerContext({ @@ -79,21 +69,14 @@ export async function getSession(request: NextRequest, response: NextResponse, f const session = await fetchAuthSession(contextSpec, { forceRefresh }); const result = session.tokens !== undefined ? session : null; - console.log('🔍 [FETCH AUTH SESSION]', { - hasTokens: !!session?.tokens, - result: !!result, - forceRefresh, - }); - // Guardar en cache solo si la sesión es válida if (result && result.tokens) { sessionCache.set(cacheKey, result); - console.log('💾 [GET SESSION] Saved to cache:', cacheKey); } return result; } catch (error) { - console.error('❌ [GET SESSION] Error fetching user session:', error); + console.error('Error fetching user session:', error); return null; } }, @@ -110,30 +93,10 @@ export async function handleAuthenticationMiddleware(request: NextRequest, respo return response; } -export async function handleAuthenticationMiddlewareNoRefresh(request: NextRequest, response: NextResponse) { - const session = await getSession(request, response, false); - - if (!session) { - return NextResponse.redirect(new URL('/login', request.url)); - } - - return response; -} - export async function handleAuthenticatedRedirectMiddleware(request: NextRequest, response: NextResponse) { const session = await getSession(request, response, false); - // Debug logs temporales - console.log('🔍 [LOGIN REDIRECT DEBUG]', { - pathname: request.nextUrl.pathname, - hasSession: !!session, - sessionTokens: !!(session as any)?.tokens, - cookies: request.headers.get('cookie')?.substring(0, 100) + '...', - cacheKey: getCacheKey(request), - }); - if (session) { - console.log('✅ [LOGIN REDIRECT] Usuario autenticado detectado, redirigiendo...'); const lastStoreId = getLastVisitedStore(request); if (lastStoreId) { @@ -141,8 +104,6 @@ export async function handleAuthenticatedRedirectMiddleware(request: NextRequest } else { return NextResponse.redirect(new URL('/my-store', request.url)); } - } else { - console.log('❌ [LOGIN REDIRECT] No hay sesión, permitiendo acceso a /login'); } return response; diff --git a/middlewares/ownership/collectionOwnership.ts b/middlewares/ownership/collectionOwnership.ts index fc64358d..d78df074 100644 --- a/middlewares/ownership/collectionOwnership.ts +++ b/middlewares/ownership/collectionOwnership.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { getSession, type AuthSession } from '@/middlewares/auth/auth'; +import { getSession, handleAuthenticationMiddleware, type AuthSession } from '@/middlewares/auth/auth'; import { cookiesClient } from '@/utils/client/AmplifyUtils'; import { NextRequest, NextResponse } from 'next/server'; @@ -38,14 +38,15 @@ export async function handleCollectionOwnershipMiddleware(request: NextRequest) return NextResponse.next(); } - // Verificar autenticación del usuario - // Usar cache para evitar múltiples forceRefresh en la misma request - const session = await getSession(request, NextResponse.next(), false); - - if (!session) { - return NextResponse.redirect(new URL('/login', request.url)); + // Verificar autenticación usando el middleware centralizado + const authResponse = await handleAuthenticationMiddleware(request, NextResponse.next()); + if (authResponse) { + return authResponse; // Si hay redirección de auth, retornarla } + // Obtener la sesión del usuario (ya validada) + const session = await getSession(request, NextResponse.next(), false); + const userId = (session as AuthSession).tokens?.idToken?.payload?.['cognito:username']; if (!userId || typeof userId !== 'string') { diff --git a/middlewares/ownership/pagesOwnership.ts b/middlewares/ownership/pagesOwnership.ts index 8ff7b391..3e8bff34 100644 --- a/middlewares/ownership/pagesOwnership.ts +++ b/middlewares/ownership/pagesOwnership.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { getSession, type AuthSession } from '@/middlewares/auth/auth'; +import { getSession, handleAuthenticationMiddleware, type AuthSession } from '@/middlewares/auth/auth'; import { cookiesClient } from '@/utils/client/AmplifyUtils'; import { NextRequest, NextResponse } from 'next/server'; @@ -38,14 +38,15 @@ export async function handlePagesOwnershipMiddleware(request: NextRequest) { return NextResponse.next(); } - // Verificar autenticación del usuario - // Usar cache para evitar múltiples forceRefresh en la misma request - const session = await getSession(request, NextResponse.next(), false); - - if (!session) { - return NextResponse.redirect(new URL('/login', request.url)); + // Verificar autenticación usando el middleware centralizado + const authResponse = await handleAuthenticationMiddleware(request, NextResponse.next()); + if (authResponse) { + return authResponse; // Si hay redirección de auth, retornarla } + // Obtener la sesión del usuario (ya validada) + const session = await getSession(request, NextResponse.next(), false); + const userId = (session as AuthSession).tokens?.idToken?.payload?.['cognito:username']; if (!userId || typeof userId !== 'string') { diff --git a/middlewares/ownership/productOwnership.ts b/middlewares/ownership/productOwnership.ts index 89d0ed84..72288ffa 100644 --- a/middlewares/ownership/productOwnership.ts +++ b/middlewares/ownership/productOwnership.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { getSession, type AuthSession } from '@/middlewares/auth/auth'; +import { getSession, handleAuthenticationMiddleware, type AuthSession } from '@/middlewares/auth/auth'; import { cookiesClient } from '@/utils/client/AmplifyUtils'; import { NextRequest, NextResponse } from 'next/server'; @@ -38,14 +38,15 @@ export async function handleProductOwnershipMiddleware(request: NextRequest) { return NextResponse.next(); } - // Verificar autenticación del usuario - // Usar cache para evitar múltiples forceRefresh en la misma request - const session = await getSession(request, NextResponse.next(), false); - - if (!session) { - return NextResponse.redirect(new URL('/login', request.url)); + // Verificar autenticación usando el middleware centralizado + const authResponse = await handleAuthenticationMiddleware(request, NextResponse.next()); + if (authResponse) { + return authResponse; // Si hay redirección de auth, retornarla } + // Obtener la sesión del usuario (ya validada) + const session = await getSession(request, NextResponse.next(), false); + const userId = (session as AuthSession).tokens?.idToken?.payload?.['cognito:username']; if (!userId || typeof userId !== 'string') { diff --git a/middlewares/store-access/store.ts b/middlewares/store-access/store.ts index 73e28a76..770cdf74 100644 --- a/middlewares/store-access/store.ts +++ b/middlewares/store-access/store.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { getSession, type AuthSession } from '@/middlewares/auth/auth'; +import { getSession, handleAuthenticationMiddleware, type AuthSession } from '@/middlewares/auth/auth'; import { cookiesClient } from '@/utils/client/AmplifyUtils'; import { NextRequest, NextResponse } from 'next/server'; @@ -52,16 +52,18 @@ async function checkStoreLimit(userId: string, plan: string) { } export async function handleStoreMiddleware(request: NextRequest, response: NextResponse) { - // Usar cache para evitar múltiples forceRefresh en la misma request - const session = await getSession(request, response, false); - - if (!session) { - return NextResponse.redirect(new URL('/login', request.url)); + // Verificar autenticación usando el middleware centralizado + const authResponse = await handleAuthenticationMiddleware(request, response); + if (authResponse) { + return authResponse; // Si hay redirección de auth, retornarla } + // Obtener la sesión del usuario (ya validada) + const session = await getSession(request, response, false); + const userId = (session as AuthSession).tokens?.idToken?.payload?.['cognito:username']; const userPlan = (session as AuthSession).tokens?.idToken?.payload?.['custom:plan']; - const hasValidSubscription = await hasValidPlan(session); + const hasValidSubscription = await hasValidPlan(session as AuthSession); if (!hasValidSubscription) { return NextResponse.redirect(new URL('/pricing', request.url)); diff --git a/middlewares/store-access/storeAccess.ts b/middlewares/store-access/storeAccess.ts index 09d00c4a..1586ec7b 100644 --- a/middlewares/store-access/storeAccess.ts +++ b/middlewares/store-access/storeAccess.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { getSession, type AuthSession } from '@/middlewares/auth/auth'; +import { getSession, handleAuthenticationMiddleware, type AuthSession } from '@/middlewares/auth/auth'; import { cookiesClient } from '@/utils/client/AmplifyUtils'; import { NextRequest, NextResponse } from 'next/server'; @@ -23,13 +23,15 @@ import { NextRequest, NextResponse } from 'next/server'; * Verifica que el usuario tenga acceso a la tienda solicitada y un plan de suscripción válido */ export async function handleStoreAccessMiddleware(request: NextRequest) { - // Obtener la sesión del usuario - const session = await getSession(request, NextResponse.next(), false); - // Verificar autenticación - if (!session || !(session as AuthSession).tokens) { - return NextResponse.redirect(new URL('/login', request.url)); + // Verificar autenticación usando el middleware centralizado + const authResponse = await handleAuthenticationMiddleware(request, NextResponse.next()); + if (authResponse) { + return authResponse; // Si hay redirección de auth, retornarla } + // Obtener la sesión del usuario (ya validada) + const session = await getSession(request, NextResponse.next(), false); + // Verificar plan de suscripción válido ANTES de verificar acceso a tienda const userPlan: string | undefined = (session as AuthSession).tokens?.idToken?.payload?.['custom:plan'] as | string diff --git a/middlewares/subscription/subscription.ts b/middlewares/subscription/subscription.ts index c8c7b417..f29fcd4e 100644 --- a/middlewares/subscription/subscription.ts +++ b/middlewares/subscription/subscription.ts @@ -15,16 +15,18 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getSession, type AuthSession } from '@/middlewares/auth/auth'; +import { getSession, handleAuthenticationMiddleware, type AuthSession } from '@/middlewares/auth/auth'; export async function handleSubscriptionMiddleware(request: NextRequest, response: NextResponse) { - // Usar cache para evitar múltiples forceRefresh en la misma request - const session = await getSession(request, response, false); - - if (!session) { - return NextResponse.redirect(new URL('/pricing', request.url)); + // Verificar autenticación usando el middleware centralizado + const authResponse = await handleAuthenticationMiddleware(request, response); + if (authResponse) { + return authResponse; // Si hay redirección de auth, retornarla } + // Obtener la sesión del usuario (ya validada) + const session = await getSession(request, response, false); + const userPlan: string | undefined = (session as AuthSession).tokens?.idToken?.payload?.['custom:plan'] as | string | undefined; From 27bb04605fe2276894b54d56d7d8be61704bf138 Mon Sep 17 00:00:00 2001 From: Steven Date: Sat, 11 Oct 2025 00:31:59 -0500 Subject: [PATCH 2/2] Update middleware tests to replace authentication middleware with store middleware Refactor tests in middleware.test.ts to remove references to handleAuthenticationMiddlewareNoRefresh and replace them with handleStoreMiddleware. Adjust test cases to validate the correct execution of store middleware when handling requests to the '/my-store' path, ensuring proper integration of authentication logic within the store access context. --- test/unit/middlewares/middleware.test.ts | 29 ++++++++++++------------ 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/test/unit/middlewares/middleware.test.ts b/test/unit/middlewares/middleware.test.ts index 703c57f7..2cf3bf8b 100644 --- a/test/unit/middlewares/middleware.test.ts +++ b/test/unit/middlewares/middleware.test.ts @@ -4,7 +4,6 @@ import { middleware } from '@/middleware'; // Mock de los middlewares específicos jest.mock('@/middlewares/auth/auth', () => ({ handleAuthenticationMiddleware: jest.fn(), - handleAuthenticationMiddlewareNoRefresh: jest.fn(), handleAuthenticatedRedirectMiddleware: jest.fn(), })); @@ -197,9 +196,9 @@ describe('Main Middleware Security Tests', () => { const mainDomainRequest = { nextUrl: { - pathname: '/account-settings', + pathname: '/my-store', clone: () => ({ - pathname: '/account-settings', + pathname: '/my-store', }), }, headers: { @@ -207,14 +206,14 @@ describe('Main Middleware Security Tests', () => { }, } as unknown as NextRequest; - const { handleAuthenticationMiddlewareNoRefresh } = require('@/middlewares/auth/auth'); - handleAuthenticationMiddlewareNoRefresh.mockReturnValue({ type: 'auth' }); + const { handleStoreMiddleware } = require('@/middlewares/store-access/store'); + handleStoreMiddleware.mockReturnValue({ type: 'store' }); const result = await middleware(mainDomainRequest); - // Debería ejecutar el middleware de autenticación para el dominio principal - expect(handleAuthenticationMiddlewareNoRefresh).toHaveBeenCalled(); - expect(result).toEqual({ type: 'auth' }); + // Debería ejecutar el middleware de store que internamente usa handleAuthenticationMiddleware + expect(handleStoreMiddleware).toHaveBeenCalled(); + expect(result).toEqual({ type: 'store' }); }); it('should handle main domain correctly in development', async () => { @@ -222,9 +221,9 @@ describe('Main Middleware Security Tests', () => { const mainDomainRequest = { nextUrl: { - pathname: '/account-settings', + pathname: '/my-store', clone: () => ({ - pathname: '/account-settings', + pathname: '/my-store', }), }, headers: { @@ -232,14 +231,14 @@ describe('Main Middleware Security Tests', () => { }, } as unknown as NextRequest; - const { handleAuthenticationMiddlewareNoRefresh } = require('@/middlewares/auth/auth'); - handleAuthenticationMiddlewareNoRefresh.mockReturnValue({ type: 'auth' }); + const { handleStoreMiddleware } = require('@/middlewares/store-access/store'); + handleStoreMiddleware.mockReturnValue({ type: 'store' }); const result = await middleware(mainDomainRequest); - // Debería ejecutar el middleware de autenticación para el dominio principal - expect(handleAuthenticationMiddlewareNoRefresh).toHaveBeenCalled(); - expect(result).toEqual({ type: 'auth' }); + // Debería ejecutar el middleware de store que internamente usa handleAuthenticationMiddleware + expect(handleStoreMiddleware).toHaveBeenCalled(); + expect(result).toEqual({ type: 'store' }); }); });