Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,15 @@ public enum EmailVerificationPurpose {

@JsonCreator
public static EmailVerificationPurpose from(String value) {
if (value == null) {
throw new BusinessException(ErrorCode.INVALID_INPUT_VALUE);
}

return Arrays.stream(values())
.filter(purpose -> purpose.value.equals(value))
.filter(purpose ->
purpose.value.equalsIgnoreCase(value)
|| purpose.name().equalsIgnoreCase(value)
)
.findFirst()
.orElseThrow(() -> new BusinessException(ErrorCode.INVALID_INPUT_VALUE));
}
Expand Down
35 changes: 29 additions & 6 deletions src/main/java/ceos/ipx/domain/auth/service/EmailSenderService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ceos.ipx.domain.auth.service;

import ceos.ipx.domain.auth.dto.EmailVerificationPurpose;
import ceos.ipx.global.exception.BusinessException;
import ceos.ipx.global.exception.ErrorCode;
import lombok.RequiredArgsConstructor;
Expand All @@ -8,6 +9,7 @@
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import org.springframework.mail.javamail.JavaMailSender;

@Slf4j
@Service
Expand All @@ -16,23 +18,44 @@ public class EmailSenderService {

private final JavaMailSender javaMailSender;

public void sendVerificationCode(String toEmail, String code) {
public void sendVerificationCode(String toEmail, String code, EmailVerificationPurpose purpose) {
try {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(toEmail);
message.setSubject("[IPX] 이메일 인증 코드 안내");
message.setText(createVerificationCodeMessage(code));
message.setSubject(createSubject(purpose));
message.setText(createVerificationCodeMessage(code, purpose));

javaMailSender.send(message);

log.info("이메일 인증 코드 발송 완료. email={}", toEmail);
log.info("이메일 인증 코드 발송 완료. email={}, purpose={}", toEmail, purpose);
} catch (MailException e) {
log.error("이메일 인증 코드 발송 실패. email={}", toEmail, e);
log.error("이메일 인증 코드 발송 실패. email={}, purpose={}", toEmail, purpose, e);
throw new BusinessException(ErrorCode.EMAIL_SEND_FAILED);
}
}

private String createVerificationCodeMessage(String code) {
private String createSubject(EmailVerificationPurpose purpose) {
if (purpose == EmailVerificationPurpose.PASSWORD_RESET) {
return "[IPX] 비밀번호 재설정 인증 코드 안내";
}

return "[IPX] 이메일 인증 코드 안내";
}

private String createVerificationCodeMessage(String code, EmailVerificationPurpose purpose) {
if (purpose == EmailVerificationPurpose.PASSWORD_RESET) {
return """
안녕하세요. IPX입니다.

비밀번호 재설정을 위한 인증 코드는 아래와 같습니다.

인증 코드: %s

인증 코드는 3분 동안 유효합니다.
본인이 비밀번호 재설정을 요청하지 않았다면 이 메일을 무시해주세요.
""".formatted(code);
}

return """
안녕하세요. IPX입니다.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import ceos.ipx.domain.auth.dto.EmailVerificationPurpose;
import ceos.ipx.domain.auth.dto.EmailVerificationSendRequest;
import ceos.ipx.domain.auth.dto.EmailVerificationSendResponse;
import ceos.ipx.domain.user.entity.User;
import ceos.ipx.domain.user.entity.UserProvider;
import ceos.ipx.domain.user.repository.UserRepository;
import ceos.ipx.global.exception.BusinessException;
import ceos.ipx.global.exception.ErrorCode;
Expand All @@ -21,13 +23,17 @@ public EmailVerificationSendResponse sendEmailVerificationCode(EmailVerification
String email = request.getEmail();
EmailVerificationPurpose purpose = request.getPurpose();

if (purpose == EmailVerificationPurpose.SIGNUP && userRepository.existsByEmail(email)) {
throw new BusinessException(ErrorCode.EMAIL_ALREADY_EXISTS);
if (purpose == EmailVerificationPurpose.SIGNUP) {
validateSignupEmail(email);
}

if (purpose == EmailVerificationPurpose.PASSWORD_RESET) {
validatePasswordResetEmail(email);
}

String verificationCode = emailVerificationService.createAndSaveVerificationCode(purpose, email);

emailSenderService.sendVerificationCode(email, verificationCode);
emailSenderService.sendVerificationCode(email, verificationCode, purpose);

return EmailVerificationSendResponse.builder()
.email(email)
Expand All @@ -36,4 +42,19 @@ public EmailVerificationSendResponse sendEmailVerificationCode(EmailVerification
.resendAvailableIn(emailVerificationService.getResendAvailableInSeconds())
.build();
}

private void validateSignupEmail(String email) {
if (userRepository.existsByEmail(email)) {
throw new BusinessException(ErrorCode.EMAIL_ALREADY_EXISTS);
}
}

private void validatePasswordResetEmail(String email) {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));

if (user.getProvider() != UserProvider.LOCAL) {
throw new BusinessException(ErrorCode.SOCIAL_ACCOUNT_PASSWORD_RESET_NOT_ALLOWED);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@

@Schema(description = "Terms agreement item")
public record TermsAgreementRequest(
@Schema(description = "Terms type", example = "SERVICE")
@NotBlank(message = "Terms type is required.")
@Schema(description = "약관 타입", example = "SERVICE")
@NotBlank(message = "약관 타입은 필수입니다.")
String type,

@Schema(description = "Whether the user agreed to the terms", example = "true")
@NotNull(message = "Terms agreement status is required.")
@Schema(description = "약관 동의 여부", example = "true")
@NotNull(message = "약관 동의 여부는 필수입니다.")
Boolean agreed
) {
}
}
1 change: 1 addition & 0 deletions src/main/java/ceos/ipx/global/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public enum ErrorCode {
EMAIL_SEND_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "AU009", "이메일 발송에 실패했습니다."),
EMAIL_VERIFICATION_CODE_EXPIRED(HttpStatus.GONE, "AU010", "인증 코드가 만료되었거나 존재하지 않습니다."),
EMAIL_VERIFICATION_CODE_MISMATCH(HttpStatus.BAD_REQUEST, "AU011", "인증 코드가 일치하지 않습니다."),
SOCIAL_ACCOUNT_PASSWORD_RESET_NOT_ALLOWED(HttpStatus.FORBIDDEN, "AU012", "소셜 로그인 계정은 비밀번호를 재설정할 수 없습니다."),

UNAUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "SC001", "인증이 필요합니다."),
ACCESS_DENIED(HttpStatus.FORBIDDEN, "SC002", "해당 요청에 권한이 없습니다.");
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ spring:
redis:
host: localhost
port: 6379
password: ${REDIS_PASSWORD:}

mail:
host: smtp.gmail.com
Expand Down
Loading