AuthController.java

package com.smartsupplypro.inventory.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import com.smartsupplypro.inventory.model.AppUser;
import com.smartsupplypro.inventory.repository.AppUserRepository;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

/**
 * Authentication controller for user profile and logout operations.
 *
 * <p>Provides current user profile data and API logout functionality.
 * Works with OAuth2 authentication principals and Spring Security.</p>
 *
 * @see AppUserRepository
 * @see <a href="file:../../../../../../docs/architecture/patterns/controller-patterns.md">Controller Patterns</a>
 */
@RestController
@RequestMapping("/api")
public class AuthController {

    private final AppUserRepository appUserRepository;

    public AuthController(AppUserRepository appUserRepository) {
        this.appUserRepository = appUserRepository;
    }

    /**
     * DTO for user profile response with frontend-specific fields.
     *
     * @param email      user email address
     * @param fullName   user display name
     * @param role       user role (ADMIN/USER)
     * @param pictureUrl optional profile picture URL
     */
    public record AppUserProfileDTO(
            String email,
            String fullName,
            String role,
            String pictureUrl
    ) {}

    /**
     * Gets authenticated user's profile information.
     *
     * @param principal OAuth2 authentication principal
     * @return user profile with email, name, role, and optional picture
     * @throws ResponseStatusException 401 if not authenticated or user not found
     */
    @GetMapping("/me")
    public AppUserProfileDTO me(@AuthenticationPrincipal OAuth2User principal) {
        if (principal == null) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "No authentication provided");
        }
        
        // Enterprise Comment: OAuth2 Identity Resolution
        // 1. Extract email from OAuth2 provider (Google)
        // 2. Load corresponding AppUser entity (created during first login)
        // 3. Return frontend-friendly profile shape
        String email = principal.getAttribute("email");
        if (email == null) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Email not provided by OAuth2 provider");
        }

        AppUser user = appUserRepository.findByEmail(email)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found"));

        String picture = principal.getAttribute("picture");

        return new AppUserProfileDTO(
                user.getEmail(),
                user.getName(),              // map to fullName
                user.getRole().name(),       // single role string
                picture
        );
    }
    /**
     * Gets user's granted authorities for authorization checks.
     *
     * @param principal OAuth2 authentication principal
     * @return sorted list of authority strings (e.g., ROLE_USER, ROLE_ADMIN)
     * @throws ResponseStatusException 401 if not authenticated
     */
    @GetMapping("/me/authorities")
    public java.util.List<String> meAuthorities(
            @AuthenticationPrincipal OAuth2User principal) {
        if (principal == null) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "No authentication");
        }
        return principal.getAuthorities().stream()
                .map(org.springframework.security.core.GrantedAuthority::getAuthority)
                .distinct()
                .sorted()
                .toList();
    }

    /**
     * API logout endpoint for programmatic clients.
     *
     * <p>Invalidates session and expires cookies.
     * For browser clients, prefer the standard POST /logout endpoint.</p>
     *
     * @param request  HTTP servlet request
     * @param response HTTP servlet response
     * @return 204 No Content response
     */
    @PostMapping("/auth/logout")
    public ResponseEntity<Void> apiLogout(HttpServletRequest request, HttpServletResponse response) {
        new SecurityContextLogoutHandler().logout(request, response, null);

        // Enterprise Comment: Cookie Expiration Strategy
        // Explicitly expire session cookies for API clients with secure settings
        // Required for proper logout in SPA and mobile applications
        ResponseCookie jsess = ResponseCookie.from("JSESSIONID", "")
                .path("/")
                .httpOnly(true)
                .secure(true)
                .sameSite("None")
                .maxAge(0)
                .build();

        ResponseCookie session = ResponseCookie.from("SESSION", "")
                .path("/")
                .httpOnly(true)
                .secure(true)
                .sameSite("None")
                .maxAge(0)
                .build();

        return ResponseEntity.noContent()
                .header("Set-Cookie", jsess.toString())
                .header("Set-Cookie", session.toString())
                .build();
    }
}