173 lines
5.1 KiB
TypeScript
173 lines
5.1 KiB
TypeScript
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 }
|
|
);
|
|
}
|