CustomUserDetailsService.java

package com.stocks.stockease.security;

import java.util.Collections;

import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.stocks.stockease.model.User;
import com.stocks.stockease.repository.UserRepository;

/**
 * Custom UserDetailsService implementation for Spring Security authentication.
 * 
 * Bridges StockEase User domain model with Spring Security's UserDetails contract.
 * Invoked by AuthenticationManager during login flow to load user authorities.
 * 
 * Integration points:
 * - Called by AuthenticationManager during credential validation
 * - Loads User from database by username
 * - Converts role string to Spring Security GrantedAuthority
 * - Returns UserDetails with populated username, password, and authorities
 * 
 * Exception handling:
 * - Throws UsernameNotFoundException if user not found (triggers 401 Unauthorized)
 * Disabled in 'docs' profile for CI/CD documentation generation.
 * 
 * @author Team StockEase
 * @version 1.0
 * @since 2025-01-01
 */
@Service
@org.springframework.context.annotation.Profile("!docs")
public class CustomUserDetailsService implements UserDetailsService {

    /**
     * Repository for accessing User entities from database.
     */
    private final UserRepository userRepository;

    /**
     * Constructor for dependency injection.
     * 
     * @param userRepository repository for user data access
     */
    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    /**
     * Loads user details by username for Spring Security authentication.
     * 
     * Execution flow:
     * 1. Query database for User by username
     * 2. If not found: throw UsernameNotFoundException (triggers 401 response)
     * 3. Convert domain User to Spring UserDetails:
     *    - Username: from User entity
     *    - Password: BCrypt-encoded hash (never plaintext)
     *    - Authorities: Role (ADMIN or USER) wrapped as GrantedAuthority
     * 4. Return to AuthenticationManager for credential comparison
     * 
     * @param username the username to look up
     * @return Spring Security UserDetails (username, password, authorities)
     * @throws UsernameNotFoundException if user not found in database
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // Query database for User entity by username
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));

        // Convert User domain model to Spring Security UserDetails
        // Password is already BCrypt-encoded in database, AuthenticationManager will hash submitted password and compare
        return new org.springframework.security.core.userdetails.User(
                    user.getUsername(),
                    user.getPassword(), // BCrypt-encoded password hash (from database)
                    Collections.singletonList(new SimpleGrantedAuthority(user.getRole())) // Convert role to authority
        );
    }
}