AuthController.java

package com.stocks.stockease.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.stocks.stockease.dto.ApiResponse;
import com.stocks.stockease.dto.LoginRequest;
import com.stocks.stockease.model.User;
import com.stocks.stockease.repository.UserRepository;
import com.stocks.stockease.security.JwtUtil;

import jakarta.validation.Valid;

/**
 * REST controller for authentication operations.
 * 
 * Manages user login and JWT token generation for securing API access.
 * Integrates with Spring Security for credential validation and role-based access.
 * 
 * @author Team StockEase
 * @version 1.0
 * @since 2025-01-01
 */
@RestController
@RequestMapping("/api/auth")
public class AuthController {

    /**
     * Spring Security AuthenticationManager for credential validation.
     */
    private final AuthenticationManager authenticationManager;

    /**
     * JWT utility for token generation with role claims.
     */
    private final JwtUtil jwtUtil;

    /**
     * Repository for loading user records and roles from database.
     */
    private final UserRepository userRepository;

    /**
     * Constructor for dependency injection via Spring.
     * 
     * @param authenticationManager Spring Security credential validator
     * @param jwtUtil JWT token generator
     * @param userRepository user data access
     */
    public AuthController(AuthenticationManager authenticationManager, JwtUtil jwtUtil, UserRepository userRepository) {
        this.authenticationManager = authenticationManager;
        this.jwtUtil = jwtUtil;
        this.userRepository = userRepository;
    }

    /**
     * Authenticates user credentials and generates JWT token.
     * 
     * Validates username and password against stored user records via Spring Security.
     * Upon successful authentication, issues a signed JWT token with user role embedded
     * for subsequent API request authorization.
     * 
     * @param loginRequest contains username and password
     * @return JWT token wrapped in ApiResponse if authentication succeeds
     * @throws BadCredentialsException if username/password combination is invalid
     * @throws UsernameNotFoundException if user account does not exist
     * @throws org.springframework.validation.BindException if request validation fails
     */
    @PostMapping("/login")
    public ResponseEntity<ApiResponse<String>> login(@Valid @RequestBody LoginRequest loginRequest) {
        try {
            // Validate payload not empty (JSR-303 annotation handles format validation)
            if (loginRequest.getUsername().isBlank() || loginRequest.getPassword().isBlank()) {
                return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                    .body(new ApiResponse<>(false, "Username and password cannot be blank", null));
            }

            // Delegate to Spring Security AuthenticationManager for credential verification.
            // Throws BadCredentialsException if password doesn't match stored hash.
            authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                    loginRequest.getUsername(),
                    loginRequest.getPassword()
                )
            );

            // Load user details from database for role extraction.
            // Throws UsernameNotFoundException if user record missing (shouldn't happen if auth succeeded).
            User user = userRepository.findByUsername(loginRequest.getUsername())
                .orElseThrow(() -> new UsernameNotFoundException("User not found"));

            // Generate signed JWT token with username and role claims.
            // Token includes expiration time; client must refresh after expiry.
            String token = jwtUtil.generateToken(user.getUsername(), user.getRole());

            return ResponseEntity.ok(new ApiResponse<>(true, "Login successful", token));
        } catch (UsernameNotFoundException e) {
            // User not found - return 401 Unauthorized with generic message for security.
            // Avoid disclosing whether username exists (prevents user enumeration attacks).
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body(new ApiResponse<>(false, e.getMessage(), null));
        } catch (org.springframework.security.authentication.BadCredentialsException e) {
            // Invalid credentials (wrong password) - return 401 with generic message.
            // Do NOT reveal which part (username/password) was incorrect.
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body(new ApiResponse<>(false, "Invalid username or password", null));
        } catch (RuntimeException e) {
            // Catch unexpected server-side errors (e.g., database connection failures).
            // Return 500 Internal Server Error with generic message to avoid information leakage.
            // Stack trace is logged by GlobalExceptionHandler for debugging purposes.
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ApiResponse<>(false, "An unexpected error occurred", null));
        }
    }
}