diff --git a/DEBUG_502_CALLBACK.md b/DEBUG_502_CALLBACK.md new file mode 100644 index 00000000..109a0523 --- /dev/null +++ b/DEBUG_502_CALLBACK.md @@ -0,0 +1,224 @@ +# Debug Erreur 502 - Callback Keycloak + +## 🔍 Situation Actuelle + +**URL** : `https://hub.slm-lab.net/api/auth/callback/keycloak?...` + +**Logs observés** : +- ✅ Profile callback : OK +- ✅ JWT callback : OK (rôles extraits depuis access token) +- ❌ Session callback : **PAS DE LOGS** (ne s'exécute pas ou échoue silencieusement) +- ❌ Erreur 502 Nginx + +## 🎯 Hypothèses + +### Hypothèse 1 : Session callback échoue silencieusement +Le session callback pourrait échouer avant d'atteindre les logs, causant une exception non gérée. + +### Hypothèse 2 : Problème avec token.email ou token.name +Si `token.email` ou `token.name` sont `undefined` et que le code s'attend à des valeurs, cela pourrait causer une erreur. + +### Hypothèse 3 : Timeout ou problème de mémoire +Le callback pourrait prendre trop de temps ou consommer trop de mémoire. + +### Hypothèse 4 : Problème avec NEXTAUTH_URL ou NEXTAUTH_SECRET +Configuration manquante ou incorrecte. + +## ✅ Corrections Appliquées + +### 1. Logs détaillés dans session callback +- Logs au début et à la fin +- Logs de chaque étape +- Logs des valeurs de token + +### 2. Try-catch complet +- Capture toutes les erreurs +- Logs détaillés de l'erreur +- Stack trace complète + +### 3. Validation des champs requis +- Vérification de `token.sub` (user ID) +- Gestion des valeurs `undefined` + +### 4. Events NextAuth +- `signIn` event pour tracker l'authentification +- `error` event pour capturer les erreurs NextAuth +- `signOut` event pour tracking + +## 🔍 Prochaines Étapes d'Investigation + +### Étape 1 : Vérifier les nouveaux logs + +Après redémarrage du serveur, vous devriez voir : + +``` +=== SESSION CALLBACK START === +Token error: undefined +Has accessToken: true +Has refreshToken: true +Token role: [...] +Token sub: ... +... +=== SESSION CALLBACK END === +``` + +**Si vous ne voyez PAS ces logs** : +- Le session callback ne s'exécute pas du tout +- Il y a une erreur avant d'atteindre le callback +- Problème dans NextAuth lui-même + +**Si vous voyez une erreur** : +- Les logs détaillés indiqueront exactement où ça échoue + +### Étape 2 : Vérifier les events NextAuth + +Vous devriez voir : +``` +=== NEXTAUTH SIGNIN EVENT === +User: ... ... +Account: keycloak +Profile: ... +``` + +**Si vous voyez `=== NEXTAUTH ERROR EVENT ===`** : +- L'erreur sera loggée avec détails + +### Étape 3 : Vérifier les variables d'environnement + +**Vérifier dans `.env` ou `.env.local`** : +```bash +NEXTAUTH_URL=https://hub.slm-lab.net +NEXTAUTH_SECRET=... (doit être défini) +KEYCLOAK_ISSUER=https://connect.slm-lab.net/realms/cercle +KEYCLOAK_CLIENT_ID=... +KEYCLOAK_CLIENT_SECRET=... +``` + +**Commandes pour vérifier** : +```bash +# Vérifier que les variables sont chargées +node -e "console.log(process.env.NEXTAUTH_URL)" +node -e "console.log(process.env.NEXTAUTH_SECRET ? 'SET' : 'MISSING')" +``` + +### Étape 4 : Vérifier les logs Nginx + +**Si Nginx est devant Next.js**, vérifier les logs Nginx : +```bash +# Logs d'erreur Nginx +sudo tail -f /var/log/nginx/error.log + +# Logs d'accès Nginx +sudo tail -f /var/log/nginx/access.log +``` + +**Chercher** : +- Timeout errors +- Connection refused +- Upstream errors + +### Étape 5 : Vérifier les logs système + +**Vérifier si Next.js crash** : +```bash +# Logs système +journalctl -u nextjs -f + +# Ou si PM2 +pm2 logs + +# Ou si systemd +systemctl status nextjs +``` + +## 🛠️ Actions Immédiates + +### 1. Redémarrer le serveur Next.js +```bash +# Arrêter +pm2 stop neah +# Ou +systemctl stop nextjs + +# Redémarrer +pm2 start neah +# Ou +systemctl start nextjs +``` + +### 2. Tester à nouveau la connexion + +1. Aller sur `/signin` +2. Se connecter avec Keycloak +3. Observer les logs dans le terminal + +### 3. Partager les logs complets + +**Ce qu'il faut partager** : +- Tous les logs depuis le début de la connexion +- Les logs jusqu'à l'erreur 502 +- Les logs Nginx (si disponibles) +- Les logs système (si disponibles) + +## 🔧 Solutions Possibles + +### Solution 1 : Problème avec token.email ou token.name + +**Si les logs montrent** : +``` +Token email: undefined +Token name: undefined +``` + +**Correction** : Le JWT callback doit extraire email et name depuis le profil ou le token d'accès. + +### Solution 2 : Problème avec NEXTAUTH_URL + +**Si NEXTAUTH_URL est incorrect** : +- NextAuth ne peut pas construire les URLs de callback +- Correction : Vérifier que `NEXTAUTH_URL` correspond à l'URL publique + +### Solution 3 : Problème avec NEXTAUTH_SECRET + +**Si NEXTAUTH_SECRET est manquant** : +- NextAuth ne peut pas signer les JWT +- Correction : Générer un secret et l'ajouter + +### Solution 4 : Timeout + +**Si le callback prend trop de temps** : +- Augmenter les timeouts Nginx +- Optimiser le code du callback + +## 📊 Checklist de Debugging + +- [ ] Serveur Next.js redémarré +- [ ] Logs `=== SESSION CALLBACK START ===` visibles +- [ ] Logs `=== SESSION CALLBACK END ===` visibles +- [ ] Pas d'erreur dans les logs +- [ ] Variables d'environnement vérifiées +- [ ] Logs Nginx vérifiés (si applicable) +- [ ] Logs système vérifiés (si applicable) + +## 🎯 Ce qu'on cherche + +**Dans les prochains logs, on cherche** : + +1. **Si on voit `=== SESSION CALLBACK START ===`** : + - ✅ Le callback s'exécute + - Chercher l'erreur dans les logs suivants + +2. **Si on NE voit PAS `=== SESSION CALLBACK START ===`** : + - ❌ Le callback ne s'exécute pas + - Problème dans NextAuth avant le callback + - Vérifier les events NextAuth + +3. **Si on voit `=== NEXTAUTH ERROR EVENT ===`** : + - ✅ NextAuth a capturé une erreur + - L'erreur sera loggée avec détails + +--- + +**Document créé le** : $(date) +**Statut** : En attente des nouveaux logs après redémarrage + diff --git a/app/api/auth/options.ts b/app/api/auth/options.ts index 54f64974..e0366f87 100644 --- a/app/api/auth/options.ts +++ b/app/api/auth/options.ts @@ -323,6 +323,17 @@ export const authOptions: NextAuthOptions = { token.username = keycloakProfile.preferred_username ?? ''; token.first_name = keycloakProfile.given_name ?? ''; token.last_name = keycloakProfile.family_name ?? ''; + // IMPORTANT: Set email and name for session callback + token.email = keycloakProfile.email ?? null; + token.name = keycloakProfile.name ?? keycloakProfile.preferred_username ?? null; + + console.log('JWT token populated:', { + hasSub: !!token.sub, + hasEmail: !!token.email, + hasName: !!token.name, + hasUsername: !!token.username, + rolesCount: cleanRoles.length, + }); // Return immediately on initial sign-in - don't try to refresh tokens we just received return token; @@ -392,43 +403,53 @@ export const authOptions: NextAuthOptions = { return refreshedToken; }, async session({ session, token }) { - console.log('=== SESSION CALLBACK ==='); - console.log('Token error:', token.error); - console.log('Has accessToken:', !!token.accessToken); - console.log('Has refreshToken:', !!token.refreshToken); - console.log('Token role:', token.role); - console.log('Token sub:', token.sub); - - // If session was invalidated or tokens are missing, return null to sign out - if (token.error === "SessionNotActive" || - token.error === "NoRefreshToken" || - !token.accessToken || - !token.refreshToken) { - console.log("❌ Session invalidated or tokens missing, user will be signed out", { - error: token.error, - hasAccessToken: !!token.accessToken, - hasRefreshToken: !!token.refreshToken - }); - - // Return null to make NextAuth treat user as unauthenticated - // This will trigger automatic redirect to sign-in page - // The client-side code will detect session invalidation by checking for - // session cookie existence when status is unauthenticated - return null as any; - } - - // For other errors, throw to trigger error handling - if (token.error) { - console.error("❌ Token error, throwing:", token.error); - throw new Error(token.error as string); - } - - const userRoles = Array.isArray(token.role) ? token.role : []; - console.log('User roles for session:', userRoles); - try { + console.log('=== SESSION CALLBACK START ==='); + console.log('Token error:', token.error); + console.log('Has accessToken:', !!token.accessToken); + console.log('Has refreshToken:', !!token.refreshToken); + console.log('Token role:', token.role); + console.log('Token sub:', token.sub); + console.log('Token email:', token.email); + console.log('Token name:', token.name); + console.log('Token username:', token.username); + + // If session was invalidated or tokens are missing, return null to sign out + if (token.error === "SessionNotActive" || + token.error === "NoRefreshToken" || + !token.accessToken || + !token.refreshToken) { + console.log("❌ Session invalidated or tokens missing, user will be signed out", { + error: token.error, + hasAccessToken: !!token.accessToken, + hasRefreshToken: !!token.refreshToken + }); + + // Return null to make NextAuth treat user as unauthenticated + // This will trigger automatic redirect to sign-in page + // The client-side code will detect session invalidation by checking for + // session cookie existence when status is unauthenticated + return null as any; + } + + // For other errors, throw to trigger error handling + if (token.error) { + console.error("❌ Token error, throwing:", token.error); + throw new Error(token.error as string); + } + + const userRoles = Array.isArray(token.role) ? token.role : []; + console.log('User roles for session:', userRoles); + + // Validate required fields + if (!token.sub) { + console.error('❌ Missing token.sub (user ID)'); + throw new Error('Missing user ID in token'); + } + + console.log('Creating session user object...'); session.user = { - id: (token.sub ?? '') as string, + id: token.sub as string, email: (token.email ?? null) as string | null, name: (token.name ?? null) as string | null, image: null, @@ -438,15 +459,31 @@ export const authOptions: NextAuthOptions = { role: userRoles, nextcloudInitialized: false, }; + + console.log('Setting session tokens...'); session.accessToken = token.accessToken as string | undefined; session.idToken = token.idToken as string | undefined; session.refreshToken = token.refreshToken as string | undefined; console.log('✅ Session created successfully'); - console.log('=============================='); + console.log('Session user id:', session.user.id); + console.log('Session user email:', session.user.email); + console.log('Session user roles:', session.user.role); + console.log('=== SESSION CALLBACK END ==='); return session; } catch (error) { - console.error('❌ Error creating session:', error); + console.error('❌❌❌ CRITICAL ERROR IN SESSION CALLBACK ❌❌❌'); + console.error('Error type:', error instanceof Error ? error.constructor.name : typeof error); + console.error('Error message:', error instanceof Error ? error.message : String(error)); + console.error('Error stack:', error instanceof Error ? error.stack : 'No stack trace'); + console.error('Token state:', { + hasSub: !!token.sub, + hasEmail: !!token.email, + hasAccessToken: !!token.accessToken, + hasRefreshToken: !!token.refreshToken, + role: token.role, + }); + // Re-throw to let NextAuth handle it throw error; } } @@ -456,6 +493,23 @@ export const authOptions: NextAuthOptions = { error: '/signin', }, debug: process.env.NODE_ENV === 'development', + // Add error handling events + events: { + async signIn({ user, account, profile }) { + console.log('=== NEXTAUTH SIGNIN EVENT ==='); + console.log('User:', user?.id, user?.email); + console.log('Account:', account?.provider); + console.log('Profile:', profile?.sub); + return true; + }, + async signOut() { + console.log('=== NEXTAUTH SIGNOUT EVENT ==='); + }, + async error({ error }) { + console.error('=== NEXTAUTH ERROR EVENT ==='); + console.error('Error:', error); + }, + }, }; // JWT interface is declared in the module declaration above