Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 1 addition & 25 deletions middleware.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 */
Expand Down Expand Up @@ -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<NextResponse | null>
): Promise<NextResponse | null> {
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
Expand Down Expand Up @@ -425,12 +403,10 @@ export async function middleware(request: NextRequest): Promise<NextResponse> {
handlePagesOwnership,
handleCollectionOwnership,
handleStoreAccess,
handleAccountSettings,
handleStoreSetup,
handleLoginRedirect,
];

// Ejecutar recursivamente todos los handlers
return await executeHandlers(handlers, request);
}

Expand Down
41 changes: 1 addition & 40 deletions middlewares/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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;
}
},
Expand All @@ -110,39 +93,17 @@ 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) {
return NextResponse.redirect(new URL(`/store/${lastStoreId}/home`, request.url));
} 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;
Expand Down
15 changes: 8 additions & 7 deletions middlewares/ownership/collectionOwnership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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') {
Expand Down
15 changes: 8 additions & 7 deletions middlewares/ownership/pagesOwnership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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') {
Expand Down
15 changes: 8 additions & 7 deletions middlewares/ownership/productOwnership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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') {
Expand Down
16 changes: 9 additions & 7 deletions middlewares/store-access/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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));
Expand Down
14 changes: 8 additions & 6 deletions middlewares/store-access/storeAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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
Expand Down
14 changes: 8 additions & 6 deletions middlewares/subscription/subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
29 changes: 14 additions & 15 deletions test/unit/middlewares/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}));

Expand Down Expand Up @@ -197,49 +196,49 @@ describe('Main Middleware Security Tests', () => {

const mainDomainRequest = {
nextUrl: {
pathname: '/account-settings',
pathname: '/my-store',
clone: () => ({
pathname: '/account-settings',
pathname: '/my-store',
}),
},
headers: {
get: jest.fn().mockReturnValue('fasttify.com'),
},
} 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 () => {
process.env.APP_ENV = 'development';

const mainDomainRequest = {
nextUrl: {
pathname: '/account-settings',
pathname: '/my-store',
clone: () => ({
pathname: '/account-settings',
pathname: '/my-store',
}),
},
headers: {
get: jest.fn().mockReturnValue('localhost:3000'),
},
} 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' });
});
});

Expand Down