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 }); } }