Skip to content

Commit

Permalink
refactor: authEmail, token
Browse files Browse the repository at this point in the history
  • Loading branch information
redcarrot1 committed Sep 3, 2024
1 parent dfafbc9 commit 2b52963
Show file tree
Hide file tree
Showing 15 changed files with 125 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class AuthEmailApi {

@PostMapping
@Operation(summary = "인증메일 전송", description = "해당 메일로 숫자 6자리의 인증 코드를 전송합니다. " +
"인증 메일 전송은 자정을 기준으로 최대 5번까지 가능합니다. 또한 인증 유효시간은 5분입니다.")
"인증 메일 전송은 자정을 기준으로 이메일당 최대 5번까지 가능합니다. 또한 인증 유효시간은 5분입니다.")
public ApiResponse<AuthEmailSendResponse> authEmailSend(@RequestBody @Valid AuthEmailSendRequest requestDto) {
AuthEmailInfo authEmailInfo = authEmailSendService.sendAuthEmail(requestDto.getTarget().toLowerCase());
return ApiUtils.success(AuthEmailSendResponse.from(authEmailInfo));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.playkuround.playkuroundserver.domain.auth.email.dto.AuthEmailInfo;
import com.playkuround.playkuroundserver.domain.auth.email.exception.NotKUEmailException;
import com.playkuround.playkuroundserver.domain.auth.email.exception.SendingLimitExceededException;
import com.playkuround.playkuroundserver.domain.common.DateTimeService;
import com.playkuround.playkuroundserver.infra.email.EmailService;
import com.playkuround.playkuroundserver.infra.email.Mail;
import lombok.RequiredArgsConstructor;
Expand All @@ -26,6 +27,7 @@ public class AuthEmailSendServiceImpl implements AuthEmailSendService {

private final EmailService emailService;
private final AuthEmailRepository authEmailRepository;
private final DateTimeService dateTimeService;
private final TemplateEngine templateEngine;

@Value("${authentication.email.domain}")
Expand Down Expand Up @@ -62,16 +64,19 @@ private void validateEmailDomain(String target) {
}

private long validateSendingCount(String target) {
LocalDateTime today = LocalDate.now().atStartOfDay();
long sendingCount = authEmailRepository.countByTargetAndCreatedAtAfter(target, today);
LocalDate today = dateTimeService.getLocalDateNow();
LocalDateTime startOfToday = today.atStartOfDay();
long sendingCount = authEmailRepository.countByTargetAndCreatedAtGreaterThanEqual(target, startOfToday);
if (sendingCount >= maxSendingCount) {
throw new SendingLimitExceededException();
}
return sendingCount;
}

private LocalDateTime saveAuthEmail(String target, String authenticationCode) {
LocalDateTime expiredAt = LocalDateTime.now().plusSeconds(codeExpirationSeconds);
LocalDateTime now = dateTimeService.getLocalDateTimeNow();
LocalDateTime expiredAt = now.plusSeconds(codeExpirationSeconds);

AuthEmail authEmail = AuthEmail.createAuthEmail(target, authenticationCode, expiredAt);
authEmailRepository.save(authEmail);
return expiredAt;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import com.playkuround.playkuroundserver.domain.auth.token.dto.TokenDto;
import com.playkuround.playkuroundserver.domain.common.DateTimeService;
import com.playkuround.playkuroundserver.domain.user.application.UserLoginService;
import com.playkuround.playkuroundserver.domain.user.dao.UserRepository;
import com.playkuround.playkuroundserver.domain.user.exception.UserNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
Expand All @@ -25,7 +25,6 @@
public class AuthEmailVerifyServiceImpl implements AuthEmailVerifyService {

private final TokenService tokenService;
private final UserRepository userRepository;
private final UserLoginService userLoginService;
private final AuthEmailRepository authEmailRepository;
private final DateTimeService dateTimeService;
Expand All @@ -38,13 +37,11 @@ public AuthVerifyEmailResult verifyAuthEmail(String code, String email) {
validateEmailAndCode(authEmail, code);
authEmail.changeInvalidate();

boolean existsUser = userRepository.existsByEmail(email);
if (existsUser) {
try {
TokenDto tokenDto = userLoginService.login(email);
return new TokenDtoResult(tokenDto);
}
else {
AuthVerifyToken authVerifyToken = tokenService.registerAuthVerifyToken();
} catch (UserNotFoundException e) {
AuthVerifyToken authVerifyToken = tokenService.saveAuthVerifyToken();
return new AuthVerifyTokenResult(authVerifyToken.getAuthVerifyToken());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,11 @@ public String generateCode(Set<CodeType> codeTypeSet, long codeLength) {

String characters = createCharacters(codeTypeSet);

StringBuilder codeBuilder = new StringBuilder();
Random random = new Random();
for (int i = 0; i < codeLength; i++) {
int index = random.nextInt(characters.length());
char randomChar = characters.charAt(index);
codeBuilder.append(randomChar);
}
return codeBuilder.toString();
return random.ints(codeLength, 0, characters.length())
.mapToObj(characters::charAt)
.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
.toString();
}

private void validateParameters(Set<CodeType> codeTypeSet, long codeLength) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ public interface AuthEmailRepository extends JpaRepository<AuthEmail, Long> {

Optional<AuthEmail> findFirstByTargetOrderByCreatedAtDesc(String target);

long countByTargetAndCreatedAtAfter(String target, LocalDateTime localDateTime);
long countByTargetAndCreatedAtGreaterThanEqual(String target, LocalDateTime localDateTime);

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import java.security.Key;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.UUID;

Expand Down Expand Up @@ -152,17 +153,17 @@ public String getTokenType(String token) {
}

public AuthVerifyToken createAuthVerifyTokenEntity() {
String key = UUID.randomUUID().toString();
String token = UUID.randomUUID().toString();
LocalDateTime now = dateTimeService.getLocalDateTimeNow();
return new AuthVerifyToken(key, now.plusSeconds(authVerifyTokenValidityInSeconds));
return new AuthVerifyToken(token, now.plusSeconds(authVerifyTokenValidityInSeconds));
}

public RefreshToken createRefreshTokenEntity(String username, String refreshToken) {
LocalDateTime now = dateTimeService.getLocalDateTimeNow();
return RefreshToken.builder()
.userEmail(username)
.refreshToken(refreshToken)
.expiredAt(now.plusSeconds(refreshTokenValidityInMilliseconds / 1000))
.expiredAt(now.plus(refreshTokenValidityInMilliseconds, ChronoUnit.MILLIS))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ public class TokenReissueService {

@Transactional
public TokenDto reissue(String refreshToken) {
String username = tokenManager.getUsernameFromToken(refreshToken);

if (!refreshTokenRepository.existsByUserEmail(username)) {
String userEmail = tokenManager.getUsernameFromToken(refreshToken);
if (!refreshTokenRepository.existsByUserEmail(userEmail)) {
throw new InvalidRefreshTokenException();
}

TokenDto tokenDto = tokenManager.createTokenDto(username);
tokenService.registerRefreshToken(username, tokenDto.getRefreshToken());
TokenDto tokenDto = tokenManager.createTokenDto(userEmail);
tokenService.saveRefreshToken(userEmail, tokenDto.getRefreshToken());

return tokenDto;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,22 @@ public class TokenService {
private final RefreshTokenRepository refreshTokenRepository;
private final AuthVerifyTokenRepository authVerifyTokenRepository;

public void registerRefreshToken(String username, String sRefreshToken) {
public void saveRefreshToken(String username, String sRefreshToken) {
refreshTokenRepository.deleteByUserEmail(username);

RefreshToken refreshToken = tokenManager.createRefreshTokenEntity(username, sRefreshToken);
refreshTokenRepository.save(refreshToken);
}

public void deleteRefreshTokenByUser(User user) {
refreshTokenRepository.deleteByUserEmail(user.getEmail());
}

public AuthVerifyToken registerAuthVerifyToken() {
public AuthVerifyToken saveAuthVerifyToken() {
AuthVerifyToken authVerifyToken = tokenManager.createAuthVerifyTokenEntity();
return authVerifyTokenRepository.save(authVerifyToken);
}

public void deleteRefreshTokenByUser(User user) {
refreshTokenRepository.deleteByUserEmail(user.getEmail());
}

@Transactional(readOnly = true)
public void validateAuthVerifyToken(String authVerifyToken) {
if (!authVerifyTokenRepository.existsByAuthVerifyToken(authVerifyToken)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(indexes = @Index(name = "idx_user_email", columnList = "userEmail"))
public class RefreshToken {

@Id
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
package com.playkuround.playkuroundserver.domain.auth.token.domain;

public enum TokenType {

ACCESS, REFRESH;

public static boolean isAccessToken(String tokenType) {
return TokenType.ACCESS.name().equals(tokenType);
}

ACCESS, REFRESH
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public TokenDto login(String userEmail) {
throw new UserNotFoundException();
}
TokenDto tokenDto = tokenManager.createTokenDto(userEmail);
tokenService.registerRefreshToken(userEmail, tokenDto.getRefreshToken());
tokenService.saveRefreshToken(userEmail, tokenDto.getRefreshToken());

return tokenDto;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.playkuround.playkuroundserver.domain.auth.email.dto.AuthEmailInfo;
import com.playkuround.playkuroundserver.domain.auth.email.exception.NotKUEmailException;
import com.playkuround.playkuroundserver.domain.auth.email.exception.SendingLimitExceededException;
import com.playkuround.playkuroundserver.domain.common.DateTimeService;
import com.playkuround.playkuroundserver.infra.email.EmailService;
import com.playkuround.playkuroundserver.infra.email.Mail;
import org.junit.jupiter.api.BeforeEach;
Expand All @@ -21,6 +22,7 @@
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import java.time.LocalDate;
import java.time.LocalDateTime;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -43,43 +45,53 @@ class AuthEmailSendServiceTest {
@Mock
private TemplateEngine templateEngine;

@Mock
private DateTimeService dateTimeService;

private final long codeLength = 6L;
private final long maxSendingCount = 3L;
private final String emailDomain = "test.com";
private final long codeExpirationSeconds = 300L;

@BeforeEach
void setUp() {
ReflectionTestUtils.setField(authEmailSendService, "codeLength", 6L);
ReflectionTestUtils.setField(authEmailSendService, "maxSendingCount", 3L);
ReflectionTestUtils.setField(authEmailSendService, "emailDomain", "test.com");
ReflectionTestUtils.setField(authEmailSendService, "codeExpirationSeconds", 300L);
ReflectionTestUtils.setField(authEmailSendService, "codeLength", codeLength);
ReflectionTestUtils.setField(authEmailSendService, "maxSendingCount", maxSendingCount);
ReflectionTestUtils.setField(authEmailSendService, "emailDomain", emailDomain);
ReflectionTestUtils.setField(authEmailSendService, "codeExpirationSeconds", codeExpirationSeconds);
}

@Test
@DisplayName("이메일 정상 정송")
@DisplayName("숫자로 이루어진 인증 번호가 저장되고, 이메일로 전송된다.")
void sendAuthEmail_1() {
// given
LocalDateTime now = LocalDateTime.of(2024, 9, 3, 13, 0, 0);
when(dateTimeService.getLocalDateTimeNow()).thenReturn(now);
when(dateTimeService.getLocalDateNow()).thenReturn(now.toLocalDate());

String target = "test@" + emailDomain;
long sendingCount = 0;
when(authEmailRepository.countByTargetAndCreatedAtGreaterThanEqual(target, now.toLocalDate().atStartOfDay()))
.thenReturn(sendingCount);

String content = "email content";
when(authEmailRepository.countByTargetAndCreatedAtAfter(any(String.class), any(LocalDateTime.class)))
.thenReturn(0L);
when(templateEngine.process(any(String.class), any(Context.class)))
.thenReturn(content);

// when
String target = "test@test.com";
AuthEmailInfo result = authEmailSendService.sendAuthEmail(target);

// then
assertThat(result.sendingCount()).isEqualTo(1L);
assertThat(result.sendingCount()).isEqualTo(sendingCount + 1);
assertThat(result.expiredAt()).isEqualTo(now.plusSeconds(codeExpirationSeconds));

ArgumentCaptor<AuthEmail> authEmailArgument = ArgumentCaptor.forClass(AuthEmail.class);
verify(authEmailRepository, times(1)).save(authEmailArgument.capture());
AuthEmail authEmail = authEmailArgument.getValue();
assertThat(authEmail.getTarget()).isEqualTo(target);
assertThat(authEmail.getCode()).containsPattern("[0-9]{6}");

ArgumentCaptor<Mail> mailArgument = ArgumentCaptor.forClass(Mail.class);
verify(emailService, times(1)).sendMail(mailArgument.capture());
Mail mail = mailArgument.getValue();
assertThat(mail.target()).isEqualTo(target);
assertThat(mail.content()).contains(content);
assertThat(mail.title()).isEqualTo("[플레이쿠라운드] 회원가입 인증코드입니다.");
assertThat(authEmail.getCode()).containsPattern("[0-9]{" + codeLength + "}");

verify(emailService, times(1)).sendMail(new Mail(target, "[플레이쿠라운드] 회원가입 인증코드입니다.", content));
}

@ParameterizedTest
Expand All @@ -94,11 +106,15 @@ void sendAuthEmail_2(String target) {
@DisplayName("하루 최대 전송횟수 이상은 메일을 보낼 수 없다.")
void sendAuthEmail_3() {
// given
when(authEmailRepository.countByTargetAndCreatedAtAfter(any(String.class), any(LocalDateTime.class)))
.thenReturn(3L);
LocalDate today = LocalDate.of(2024, 9, 3);
when(dateTimeService.getLocalDateNow()).thenReturn(today);

// when
assertThatThrownBy(() -> authEmailSendService.sendAuthEmail("email@test.com"))
String target = "email@test.com";
when(authEmailRepository.countByTargetAndCreatedAtGreaterThanEqual(target, today.atStartOfDay()))
.thenReturn(maxSendingCount);

// when & then
assertThatThrownBy(() -> authEmailSendService.sendAuthEmail(target))
.isInstanceOf(SendingLimitExceededException.class);
}

Expand Down
Loading

0 comments on commit 2b52963

Please sign in to comment.