AuthenticationService.java

package com.learning.yasminishop.auth;

import com.learning.yasminishop.auth.dto.request.AuthenticationRequest;
import com.learning.yasminishop.auth.dto.request.LogoutRequest;
import com.learning.yasminishop.auth.dto.request.RefreshRequest;
import com.learning.yasminishop.auth.dto.request.RegisterRequest;
import com.learning.yasminishop.auth.dto.response.AuthenticationResponse;
import com.learning.yasminishop.auth.dto.response.TokenResponse;
import com.learning.yasminishop.common.configs.security.JwtService;
import com.learning.yasminishop.common.constant.PredefinedRole;
import com.learning.yasminishop.common.entity.InvalidToken;
import com.learning.yasminishop.common.entity.Role;
import com.learning.yasminishop.common.entity.User;
import com.learning.yasminishop.common.exception.AppException;
import com.learning.yasminishop.common.exception.ErrorCode;
import com.learning.yasminishop.role.RoleRepository;
import com.learning.yasminishop.token.InvalidTokenRepository;
import com.learning.yasminishop.user.UserRepository;
import com.learning.yasminishop.user.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashSet;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AuthenticationService {


    private final PasswordEncoder passwordEncoder;
    private final JwtService jwtService;

    private final UserRepository userRepository;
    private final InvalidTokenRepository invalidTokenRepository;
    private final RoleRepository roleRepository;

    private final UserMapper userMapper;

    @Transactional
    public AuthenticationResponse register(RegisterRequest registerRequest) {

        if (userRepository.existsByEmail(registerRequest.getEmail()))
            throw new AppException(ErrorCode.EMAIL_ALREADY_EXISTS);

        HashSet<Role> roles = new HashSet<>();
        roleRepository.findById(PredefinedRole.USER_ROLE)
                .ifPresent(roles::add);

        User user = userMapper.toUser(registerRequest);
        user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
        user.setRoles(roles);
        user.setIsActive(true);

        userRepository.save(user);

        var tokens = TokenResponse.builder()
                .accessToken(jwtService.generateAccessToken(user))
                .refreshToken(jwtService.generateRefreshToken(user))
                .build();

        return AuthenticationResponse.builder()
                .tokens(tokens)
                .build();
    }

    public AuthenticationResponse authenticate(
            AuthenticationRequest request
    ) {

        var user = userRepository.findByEmail(request.getEmail())
                .orElseThrow(() -> new AppException(ErrorCode.EMAIL_OR_PASSWORD_INCORRECT));

        if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
            throw new AppException(ErrorCode.EMAIL_OR_PASSWORD_INCORRECT);
        }

        // check if user is active
        if (Boolean.FALSE.equals(user.getIsActive())) {
            throw new AppException(ErrorCode.USER_NOT_ACTIVE);
        }

        var tokens = TokenResponse.builder()
                .accessToken(jwtService.generateAccessToken(user))
                .refreshToken(jwtService.generateRefreshToken(user))
                .build();

        return AuthenticationResponse.builder()
                .user(userMapper.toUserResponse(user))
                .tokens(tokens)
                .build();
    }

    @Transactional
    public AuthenticationResponse refresh(RefreshRequest request) {
        String refreshToken = request.getRefreshToken();
        if (jwtService.isTokenValid(refreshToken)) {

            String userEmail = jwtService.extractUserEmail(refreshToken);
            var user = userRepository.findByEmail(userEmail)
                    .orElseThrow(() -> new AppException(ErrorCode.INVALID_TOKEN));

            // Add the refresh token to the invalid token list
            InvalidToken invalidToken = InvalidToken.builder()
                    .idToken(jwtService.extractIdToken(refreshToken))
                    .expiryDate(jwtService.extractExpiration(refreshToken))
                    .build();

            invalidTokenRepository.save(invalidToken);

            // Generate new access token and refresh token
            var tokens = TokenResponse.builder()
                    .accessToken(jwtService.generateAccessToken(user))
                    .refreshToken(jwtService.generateRefreshToken(user))
                    .build();

            return AuthenticationResponse.builder()
                    .tokens(tokens)
                    .build();
        } else {
            throw new AppException(ErrorCode.INVALID_TOKEN);
        }
    }

    @Transactional
    public void logout(LogoutRequest request) {
        if (jwtService.isTokenValid(request.getToken())) {
            String idToken = jwtService.extractIdToken(request.getToken());
            invalidTokenRepository.save(InvalidToken.builder()
                    .idToken(idToken)
                    .expiryDate(jwtService.extractExpiration(request.getToken()))
                    .build());
            SecurityContextHolder.clearContext();
        } else {
            throw new AppException(ErrorCode.INVALID_TOKEN);
        }


    }
}