JwtFilter.java

package com.archiweb.security;

import com.archiweb.repository.UserRepository;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
public class JwtFilter extends OncePerRequestFilter {

    private final JwtService jwtService;
    private final UserRepository userRepository;

    public JwtFilter(JwtService jwtService, UserRepository userRepository) {
        this.jwtService = jwtService;
        this.userRepository = userRepository;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain)
            throws ServletException, IOException {
        try {
            String path = request.getServletPath() != null ? request.getServletPath() : "";
            String requestURI = request.getRequestURI() != null ? request.getRequestURI() : "";
            
            // Log toutes les requêtes newsletter pour debug
            if (path.contains("/newsletter") || (requestURI != null && requestURI.contains("/newsletter"))) {
                System.out.println("🔍 JwtFilter - Requête newsletter reçue: path=" + path + ", URI=" + requestURI + " | Method: " + request.getMethod());
                System.out.println("🔍 JwtFilter - Authorization header IMMEDIAT: " + (request.getHeader("Authorization") != null ? "Présent" : "Absent"));
            }

        // ✅ Routes publiques : on ignore le JWT
        // IMPORTANT: Pour /api/newsletter/subscribe, on doit faire une comparaison exacte ou avec un slash après
        // car /api/newsletter/subscribers/count commence aussi par /api/newsletter/subscribe !
        boolean isPublicRoute = path.startsWith("/api/auth")
                || path.startsWith("/swagger")
                || path.startsWith("/v3")
                || path.startsWith("/api/rgpd")
                || path.startsWith("/api/contact/send")
                || path.equals("/api/newsletter/subscribe") // Comparaison exacte pour éviter le match avec /subscribers/count
                || path.startsWith("/api/settings")
                || path.startsWith("/actuator");
        
        if (path.contains("/newsletter")) {
            System.out.println("🔍 JwtFilter - Check route publique pour: " + path + " -> " + isPublicRoute);
            if (path.equals("/api/newsletter/subscribe")) {
                System.out.println("🔍 JwtFilter - MATCH EXACT avec /api/newsletter/subscribe: " + path.equals("/api/newsletter/subscribe"));
            }
        }
        
        if (isPublicRoute) {
            if (path.contains("/newsletter")) {
                System.out.println("🔍 JwtFilter - Route publique newsletter détectée, on ignore JWT: " + path);
            }
            chain.doFilter(request, response);
            return;
        }

        if (path.contains("/newsletter")) {
            System.out.println("🔍 JwtFilter - Route newsletter PROTÉGÉE, on continue la validation: " + path);
        }

        // ✅ Vérifie la présence du header Authorization
        final String authHeader = request.getHeader("Authorization");
        if (path.contains("/newsletter")) {
            System.out.println("🔍 JwtFilter - Authorization header: " + (authHeader != null ? "Présent" : "Absent"));
        }
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            if (path.contains("/newsletter")) {
                System.out.println("❌ JwtFilter - Pas de token JWT valide pour: " + path);
            }
            chain.doFilter(request, response);
            return;
        }

        // ✅ Extraction du token
        String token = authHeader.substring(7);
        if (path.contains("/newsletter")) {
            System.out.println("🔍 JwtFilter - Validation du token pour: " + path);
            System.out.println("🔍 JwtFilter - Token (premiers 20 chars): " + (token.length() > 20 ? token.substring(0, 20) + "..." : token));
        }
        boolean isValid = jwtService.validateToken(token);
        if (path.contains("/newsletter")) {
            System.out.println("🔍 JwtFilter - Résultat validation: " + isValid);
        }
        if (!isValid) {
            if (path.contains("/newsletter")) {
                System.out.println("❌ JwtFilter - Token invalide pour: " + path);
            }
            chain.doFilter(request, response);
            return;
        }
        
        if (path.contains("/newsletter")) {
            System.out.println("✅ JwtFilter - Token valide pour: " + path);
        }

        // ✅ Extraction de l'email depuis le token
        String email = jwtService.extractEmail(token);
        String tokenRole = jwtService.extractRole(token);

        // ✅ Recherche de l'utilisateur et définition du contexte de sécurité
        userRepository.findByEmail(email).ifPresentOrElse(user -> {

            // 🔹 Création d'un UserDetails avec ses rôles
            // Pour les utilisateurs OAuth (sans mot de passe), on utilise un placeholder
            // car Spring Security nécessite un password non-null, mais il ne sera jamais utilisé
            String password = user.getPassword() != null ? user.getPassword() : "{noop}OAUTH_USER_NO_PASSWORD";
            
            // Extraire le rôle de l'utilisateur depuis la base de données (source de vérité)
            // Le rôle dans le token peut être obsolète, on utilise toujours celui de la DB
            String roleName = user.getRole() != null ? user.getRole().getName() : "ROLE_USER";
            
            UserDetails userDetails = org.springframework.security.core.userdetails.User
                    .withUsername(user.getEmail())
                    .password(password)
                    .authorities(roleName) // exemple : ROLE_ADMIN
                    .build();
            
            // Log pour débogage des problèmes de permissions
            if (path.contains("/admin/") || path.contains("/wins/home-delivery") || path.contains("/stats/") || path.contains("/contact/messages") || path.contains("/newsletter/")) {
                System.out.println("🔍 DEBUG JwtFilter - Path: " + path);
                System.out.println("🔍 DEBUG JwtFilter - User: " + email + ", DB Role: " + roleName + ", Token Role: " + tokenRole);
                System.out.println("🔍 DEBUG JwtFilter - Authorities: " + userDetails.getAuthorities());
                if (path.contains("/contact/messages")) {
                    System.out.println("✅ DEBUG JwtFilter - Route contact/messages détectée, autorités: " + userDetails.getAuthorities());
                }
                if (path.contains("/newsletter/")) {
                    System.out.println("✅ DEBUG JwtFilter - Route newsletter détectée, autorités: " + userDetails.getAuthorities());
                }
            }

            // 🔹 Injection dans le contexte Spring Security
            UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(
                            userDetails,
                            null,
                            userDetails.getAuthorities()
                    );

            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }, () -> {
            // Si l'utilisateur n'est pas trouvé, on log pour débogage
            System.out.println("DEBUG JwtFilter - Utilisateur non trouvé pour email: " + email);
        });

        if (path.contains("/newsletter")) {
            System.out.println("✅ JwtFilter - Fin du traitement pour: " + path + ", on passe à la chaîne suivante");
        }

        // ✅ Poursuite du filtre
        chain.doFilter(request, response);
        } catch (Exception e) {
            String path = request.getServletPath() != null ? request.getServletPath() : "";
            System.err.println("❌ JwtFilter - Exception pour: " + path + " -> " + e.getMessage());
            e.printStackTrace();
            throw e;
        }
    }
}