From bc965148ed903457ef2eb7b4c9a8f9582e7006bf Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 17 Oct 2025 17:53:00 -0500 Subject: [PATCH] Enhance authentication middleware and store access logic This commit introduces improvements to the authentication middleware by adding detailed error handling for network issues in production and validating the Amplify configuration. Additionally, the store access middleware is updated to allow users with a free plan to access the '/my-store' route for plan selection, while simplifying the redirection logic for users without subscriptions. These changes enhance user experience and error resilience in the application. --- lib/debug/auth-debug.ts | 118 ++++++++++++++++++++++++++++++ middlewares/auth/auth.ts | 59 ++++++++++++++- middlewares/store-access/store.ts | 30 +++----- 3 files changed, 185 insertions(+), 22 deletions(-) create mode 100644 lib/debug/auth-debug.ts diff --git a/lib/debug/auth-debug.ts b/lib/debug/auth-debug.ts new file mode 100644 index 00000000..c354e8ed --- /dev/null +++ b/lib/debug/auth-debug.ts @@ -0,0 +1,118 @@ +/* + * Copyright 2025 Fasttify LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NextRequest } from 'next/server'; +import outputs from '@/amplify_outputs.json'; + +/** + * Función de debugging para problemas de autenticación en producción + */ +export function debugAuthIssues(request: NextRequest) { + const isProduction = process.env.NODE_ENV === 'production'; + + if (!isProduction) { + return null; + } + + const cookies = request.headers?.get('cookie') || ''; + const userAgent = request.headers?.get('user-agent') || ''; + const referer = request.headers?.get('referer') || ''; + + // Verificar configuración de Amplify + const amplifyConfig = { + hasAuth: !!outputs.auth, + userPoolId: outputs.auth?.user_pool_id, + region: outputs.auth?.aws_region, + clientId: outputs.auth?.user_pool_client_id, + identityPoolId: outputs.auth?.identity_pool_id, + oauthDomain: outputs.auth?.oauth?.domain, + redirectUris: outputs.auth?.oauth?.redirect_sign_in_uri, + }; + + // Verificar cookies de Cognito + const cognitoCookies = { + hasAnyCognito: cookies.includes('CognitoIdentityServiceProvider'), + hasAmplify: cookies.includes('aws-amplify'), + cookieCount: cookies.split(';').length, + cognitoPatterns: [/CognitoIdentityServiceProvider[^=]*=([^;]+)/g, /aws-amplify[^=]*=([^;]+)/g] + .map((pattern) => { + const matches = [...cookies.matchAll(pattern)]; + return matches.map((match) => match[1]); + }) + .flat(), + }; + + // Verificar headers importantes + const headers = { + host: request.headers?.get('host'), + origin: request.headers?.get('origin'), + xForwardedFor: request.headers?.get('x-forwarded-for'), + xForwardedProto: request.headers?.get('x-forwarded-proto'), + }; + + const debugInfo = { + timestamp: new Date().toISOString(), + path: request.nextUrl.pathname, + amplifyConfig, + cognitoCookies, + headers, + userAgent: userAgent.substring(0, 100), + referer: referer.substring(0, 100), + }; + + console.log('Auth Debug Info:', JSON.stringify(debugInfo, null, 2)); + + return debugInfo; +} + +/** + * Verifica si la configuración de Amplify es válida para producción + */ +export function validateAmplifyConfig() { + const requiredFields = [ + 'auth.user_pool_id', + 'auth.aws_region', + 'auth.user_pool_client_id', + 'auth.identity_pool_id', + 'auth.oauth.domain', + 'auth.oauth.redirect_sign_in_uri', + ]; + + const missingFields = requiredFields.filter((field) => { + const keys = field.split('.'); + let value: any = outputs; + for (const key of keys) { + value = value?.[key]; + } + return !value; + }); + + if (missingFields.length > 0) { + console.error('Missing required Amplify config fields:', missingFields); + return false; + } + + // Verificar que las URLs de redirección incluyan el dominio de producción + const redirectUris = outputs.auth?.oauth?.redirect_sign_in_uri || []; + const hasProductionUrl = redirectUris.some((uri) => uri.includes('fasttify.com') || uri.includes('www.fasttify.com')); + + if (!hasProductionUrl) { + console.error('No production redirect URI found in Amplify config'); + return false; + } + + return true; +} diff --git a/middlewares/auth/auth.ts b/middlewares/auth/auth.ts index d074a075..0f366fc9 100644 --- a/middlewares/auth/auth.ts +++ b/middlewares/auth/auth.ts @@ -17,6 +17,7 @@ import { runWithAmplifyServerContext } from '@/utils/client/AmplifyUtils'; import { fetchAuthSession } from 'aws-amplify/auth/server'; import { getLastVisitedStore } from '@/lib/cookies/last-store'; +import { debugAuthIssues, validateAmplifyConfig } from '@/lib/debug/auth-debug'; import { NextRequest, NextResponse } from 'next/server'; import NodeCache from 'node-cache'; @@ -49,7 +50,6 @@ export function clearUserSessionCache(request: NextRequest): void { } function getCacheKey(request: NextRequest): string { - // Usar un hash simple de las cookies principales de autenticación const cookies = request.headers?.get('cookie') || ''; // Buscar el ID de usuario en las cookies para crear una clave estable @@ -58,8 +58,31 @@ function getCacheKey(request: NextRequest): string { return `user-${userIdMatch[1]}`; } - // Fallback: usar toda la cadena de cookies como clave - return cookies ? `cookies-${cookies.length}` : 'no-auth'; + // Buscar cualquier cookie de Cognito para crear una clave única + const cognitoMatch = cookies.match(/CognitoIdentityServiceProvider[^=]*=([^;]+)/); + if (cognitoMatch) { + return `cognito-${cognitoMatch[1]}`; + } + + // Buscar cookies de AWS Amplify (formato alternativo) + const amplifyMatch = cookies.match(/aws-amplify[^=]*=([^;]+)/); + if (amplifyMatch) { + return `amplify-${amplifyMatch[1]}`; + } + + // Si no hay cookies de Cognito, usar un hash de todas las cookies para evitar conflictos + if (cookies) { + // Crear un hash simple pero único basado en el contenido de las cookies + let hash = 0; + for (let i = 0; i < cookies.length; i++) { + const char = cookies.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; // Convertir a 32bit integer + } + return `cookies-${Math.abs(hash)}`; + } + + return 'no-auth'; } export async function getSession(request: NextRequest, response: NextResponse, forceRefresh = true) { @@ -92,6 +115,25 @@ export async function getSession(request: NextRequest, response: NextResponse, f return result; } catch (error) { console.error('Error fetching user session:', error); + + // En producción, ser más permisivo con errores de red/temporales + const isProduction = process.env.NODE_ENV === 'production'; + const isNetworkError = + error instanceof Error && + (error.message.includes('network') || + error.message.includes('timeout') || + error.message.includes('ECONNRESET') || + error.message.includes('ENOTFOUND')); + + // Si es un error de red en producción, intentar usar caché existente + if (isProduction && isNetworkError) { + const cached = sessionCache.get(cacheKey); + if (cached) { + console.log('Using cached session due to network error'); + return cached; + } + } + // Limpiar caché en caso de error sessionCache.del(cacheKey); return null; @@ -101,9 +143,20 @@ export async function getSession(request: NextRequest, response: NextResponse, f } export async function handleAuthenticationMiddleware(request: NextRequest, response: NextResponse) { + // Validar configuración de Amplify en producción + const isProduction = process.env.NODE_ENV === 'production'; + if (isProduction && !validateAmplifyConfig()) { + console.error('Invalid Amplify configuration detected'); + } + const session = await getSession(request, response); if (!session) { + // Debug detallado en producción + if (isProduction) { + debugAuthIssues(request); + } + // Limpiar caché cuando no hay sesión válida clearUserSessionCache(request); return NextResponse.redirect(new URL('/login', request.url), { status: 302 }); diff --git a/middlewares/store-access/store.ts b/middlewares/store-access/store.ts index 13444501..7f3e15f1 100644 --- a/middlewares/store-access/store.ts +++ b/middlewares/store-access/store.ts @@ -74,7 +74,13 @@ export async function handleStoreMiddleware(request: NextRequest, response: Next const validPlans = ['Royal', 'Majestic', 'Imperial']; if (!userPlan || !validPlans.includes(userPlan)) { - // Usuario con plan 'free' o sin plan - verificar si tiene suscripción en DB + // Usuario con plan 'free' o sin plan - permitir acceso a /my-store para selección de planes + if (path === '/my-store') { + // Permitir acceso a /my-store para usuarios con plan free + return response; + } + + // Para otras rutas, verificar si tiene suscripción en DB try { const { data: subscriptions } = await cookiesClient.models.UserSubscription.listUserSubscriptionByUserId({ userId, @@ -91,27 +97,13 @@ export async function handleStoreMiddleware(request: NextRequest, response: Next return NextResponse.redirect(new URL('/my-store', request.url), { status: 302 }); } } else { - // No tiene suscripción - usuario nuevo - const lastStoreId = getLastVisitedStore(request); - if (lastStoreId) { - return NextResponse.redirect(new URL(`/store/${lastStoreId}/access_account/checkout`, request.url), { - status: 302, - }); - } else { - return NextResponse.redirect(new URL('/my-store', request.url), { status: 302 }); - } + // No tiene suscripción - redirigir a /my-store para selección de planes + return NextResponse.redirect(new URL('/my-store', request.url), { status: 302 }); } } catch (error) { console.error('Error checking subscription:', error); - // En caso de error, redirigir a última tienda - const lastStoreId = getLastVisitedStore(request); - if (lastStoreId) { - return NextResponse.redirect(new URL(`/store/${lastStoreId}/access_account/checkout`, request.url), { - status: 302, - }); - } else { - return NextResponse.redirect(new URL('/my-store', request.url), { status: 302 }); - } + // En caso de error, redirigir a /my-store + return NextResponse.redirect(new URL('/my-store', request.url), { status: 302 }); } }