import { NextRequest, NextResponse } from 'next/server'; /** * API Route pour Ă©changer le code d'autorisation contre des tokens */ export async function POST(request: NextRequest) { try { console.log('đŸ“„ POST /api/auth/token - RequĂȘte reçue:', { method: request.method, url: request.url, headers: { 'content-type': request.headers.get('content-type'), 'content-length': request.headers.get('content-length'), } }); let body; try { const rawBody = await request.text(); console.log('📄 Corps brut de la requĂȘte:', { length: rawBody.length, preview: rawBody.substring(0, 100) }); if (!rawBody || rawBody.trim() === '') { console.error('❌ Corps de la requĂȘte vide'); return NextResponse.json( { error: 'Corps de la requĂȘte vide' }, { status: 400 } ); } body = JSON.parse(rawBody); } catch (jsonError) { console.error('❌ Erreur parsing JSON:', jsonError.message); return NextResponse.json( { error: 'JSON invalide' }, { status: 400 } ); } const { code, state } = body; if (!code) { return NextResponse.json( { error: 'Code d\'autorisation manquant' }, { status: 400 } ); } // Configuration Keycloak const keycloakUrl = process.env.NEXT_PUBLIC_KEYCLOAK_URL || 'https://security.lions.dev'; const realm = process.env.NEXT_PUBLIC_KEYCLOAK_REALM || 'btpxpress'; const clientId = process.env.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID || 'btpxpress-frontend'; // PrĂ©parer l'URL de base const baseUrl = process.env.NODE_ENV === 'production' ? 'https://btpxpress.lions.dev' : 'http://localhost:3000'; // RĂ©cupĂ©rer le code verifier depuis les cookies const codeVerifier = request.cookies.get('pkce_code_verifier')?.value; console.log('đŸȘ Cookies reçus:', { codeVerifier: codeVerifier ? codeVerifier.substring(0, 20) + '...' : 'MANQUANT', hasCookie: !!codeVerifier }); if (!codeVerifier) { console.error('❌ Code verifier manquant dans les cookies'); return NextResponse.json( { error: 'Code verifier manquant' }, { status: 400 } ); } console.log('🔄 Échange de token:', { code: code.substring(0, 20) + '...', codeVerifier: codeVerifier.substring(0, 20) + '...', redirectUri: `${baseUrl}/dashboard` }); // PrĂ©parer les donnĂ©es pour l'Ă©change de token const tokenData = new URLSearchParams({ grant_type: 'authorization_code', client_id: clientId, code: code, redirect_uri: `${baseUrl}/dashboard`, code_verifier: codeVerifier, }); // Échanger le code contre des tokens const tokenResponse = await fetch( `${keycloakUrl}/realms/${realm}/protocol/openid-connect/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: tokenData.toString(), } ); if (!tokenResponse.ok) { const errorText = await tokenResponse.text(); console.error('❌ Erreur Ă©change token:', { status: tokenResponse.status, statusText: tokenResponse.statusText, error: errorText }); return NextResponse.json( { error: 'Échec de l\'Ă©change de token', details: errorText }, { status: 400 } ); } const tokens = await tokenResponse.json(); console.log('✅ Tokens reçus avec succĂšs:', { hasAccessToken: !!tokens.access_token, hasRefreshToken: !!tokens.refresh_token, hasIdToken: !!tokens.id_token }); // Supprimer le cookie du code verifier const response = NextResponse.json({ ...tokens, returnUrl: '/dashboard' // URL par dĂ©faut, sera remplacĂ©e cĂŽtĂ© client si returnUrl existe }); response.cookies.delete('pkce_code_verifier'); return response; } catch (error) { console.error('❌ Erreur lors de l\'Ă©change de token:', { message: error.message, stack: error.stack, name: error.name, cause: error.cause }); // Si c'est une erreur de code invalide, suggĂ©rer un nouveau cycle d'authentification if (error.message && error.message.includes('invalid_grant')) { console.log('🔄 Code d\'autorisation expirĂ©, nettoyage des cookies...'); const response = NextResponse.json( { error: 'Code d\'autorisation expirĂ©', details: 'Le code d\'autorisation a expirĂ©. Un nouveau cycle d\'authentification est nĂ©cessaire.', shouldRetry: true }, { status: 400 } ); // Nettoyer le cookie du code verifier expirĂ© response.cookies.delete('pkce_code_verifier'); return response; } return NextResponse.json( { error: 'Erreur interne du serveur', details: error.message }, { status: 500 } ); } } /** * Gestion des autres mĂ©thodes HTTP */ export async function GET() { return NextResponse.json( { error: 'MĂ©thode non supportĂ©e. Utilisez POST pour Ă©changer un code.' }, { status: 405 } ); }