meals-made-easy-api/src/main/java/app/mealsmadeeasy/api/auth/AuthServiceImpl.java
2026-01-15 15:29:40 -06:00

108 lines
4.3 KiB
Java

package app.mealsmadeeasy.api.auth;
import app.mealsmadeeasy.api.jwt.JwtService;
import app.mealsmadeeasy.api.user.User;
import jakarta.transaction.Transactional;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Service;
import java.time.OffsetDateTime;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service
public class AuthServiceImpl implements AuthService {
private final AuthenticationManager authenticationManager;
private final JwtService jwtService;
private final RefreshTokenRepository refreshTokenRepository;
private final long refreshTokenLifetime;
public AuthServiceImpl(
AuthenticationManager authenticationManager,
JwtService jwtService,
RefreshTokenRepository refreshTokenRepository,
@Value("${app.mealsmadeeasy.api.security.refresh-token-lifetime}") Long refreshTokenLifetime
) {
this.authenticationManager = authenticationManager;
this.jwtService = jwtService;
this.refreshTokenRepository = refreshTokenRepository;
this.refreshTokenLifetime = refreshTokenLifetime;
}
private RefreshToken createRefreshToken(User principal) {
final RefreshToken refreshTokenDraft = new RefreshToken();
refreshTokenDraft.setToken(UUID.randomUUID());
refreshTokenDraft.setIssued(OffsetDateTime.now());
refreshTokenDraft.setExpiration(OffsetDateTime.now().plusSeconds(this.refreshTokenLifetime));
refreshTokenDraft.setOwner(principal);
return this.refreshTokenRepository.save(refreshTokenDraft);
}
@Override
public LoginDetails login(String username, String password) throws LoginException {
try {
final Authentication authentication = this.authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password)
);
final User principal = (User) authentication.getPrincipal();
return new LoginDetails(
username,
this.jwtService.generateAccessToken(username),
this.createRefreshToken(principal)
);
} catch (AuthenticationException e) {
throw new LoginException(LoginExceptionReason.INVALID_CREDENTIALS, e);
}
}
@Override
@Transactional
public void logout(UUID refreshToken) {
this.refreshTokenRepository.findByToken(refreshToken).ifPresent(this.refreshTokenRepository::delete);
}
@Override
@Transactional
public LoginDetails refresh(@Nullable UUID refreshToken) throws LoginException {
if (refreshToken == null) {
throw new LoginException(LoginExceptionReason.NO_REFRESH_TOKEN, "No refresh token provided.");
}
final RefreshToken old = this.refreshTokenRepository.findByToken(refreshToken)
.orElseThrow(() -> new LoginException(
LoginExceptionReason.INVALID_REFRESH_TOKEN,
"No such refresh token: " + refreshToken
));
if (old.getRevoked() || old.getDeleted()) {
throw new LoginException(LoginExceptionReason.INVALID_REFRESH_TOKEN, "Invalid refresh token.");
}
if (old.getExpiration().isBefore(OffsetDateTime.now())) {
throw new LoginException(LoginExceptionReason.EXPIRED_REFRESH_TOKEN, "Refresh token is expired.");
}
final User principal = old.getOwner();
old.setDeleted(true);
this.refreshTokenRepository.save(old);
final String username = principal.getUsername();
return new LoginDetails(
username,
this.jwtService.generateAccessToken(username),
this.createRefreshToken(principal)
);
}
@Scheduled(fixedDelay = 60, timeUnit = TimeUnit.SECONDS)
public void cleanUpDeletedRefreshTokens() {
this.refreshTokenRepository.deleteAllWhereSoftDeleted();
}
}