Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a5d72c6
[CHORE] val 삭제
paragon0107 Sep 19, 2024
8e3e745
[CHORE] email 원시값 포장
paragon0107 Sep 20, 2024
d89d5c3
[CHORE] 널 체크 추가
paragon0107 Sep 20, 2024
cdd758b
[CHORE] 인증 코드 확인 dto 이름 수정
paragon0107 Sep 20, 2024
e7b44ef
[CHORE] email 용어 통일
paragon0107 Sep 20, 2024
3a0ab7c
[CHORE] 기존 mail 도메인 -> EmailVerification 도메인으로 수정
paragon0107 Sep 20, 2024
4904e64
[CHORE] 기존 mail 도메인 삭제
paragon0107 Sep 20, 2024
c06cf13
[CHORE] 메일 Send 기능 분리
paragon0107 Sep 20, 2024
fe65ee2
[CHORE] 쓸모없는 import문 삭제
paragon0107 Sep 20, 2024
56b6f25
[CHORE] 컨베션 적용
paragon0107 Sep 21, 2024
c6188ed
[CHORE] DTO 스트링으로 변경
paragon0107 Sep 21, 2024
b81b52d
[CHORE] 이메일 검증 로직 수정
paragon0107 Sep 21, 2024
469bc8c
[CHORE] 코드 검증 메서드 위치 변경
paragon0107 Sep 21, 2024
6a493be
[CHORE] 코드 검증 로직 수정
paragon0107 Sep 21, 2024
36e071d
[CHORE] 메서드 제거
paragon0107 Sep 21, 2024
9bd20f8
[CHORE] EmailValidator 관련리 라이브러리 추가
paragon0107 Sep 23, 2024
7653741
[CHORE] EmailValidator로 형식 증명
paragon0107 Sep 23, 2024
43681c3
[CHORE] 컨벤션 적용
paragon0107 Sep 23, 2024
e5630e9
[CHORE] 패키지명 변경
paragon0107 Sep 23, 2024
1ca1db8
[CHORE] 패키지명 변경
paragon0107 Sep 23, 2024
dbf9cf6
[CHORE] 패키지명 변경
paragon0107 Sep 23, 2024
420c273
[CHORE] 허용 API 수정
paragon0107 Sep 23, 2024
663e4d2
[CHORE] INIT_NUM 상수화
paragon0107 Sep 29, 2024
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
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ dependencies {

//thymeleaf
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

//Validation
implementation 'commons-validator:commons-validator:1.7'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class SecurityConfig {
"/api/v1/auth/reissue",
"/api/v1/members/password",
"/api/v1/members",
"/api/v1/mail/**",
"/api/v1/email-verification/**",
"/actuator/health"
};

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/tiki/server/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.tiki.server.auth.token.adapter.TokenFinder;
import com.tiki.server.auth.token.adapter.TokenSaver;
import com.tiki.server.auth.token.entity.Token;
import com.tiki.server.common.entity.Email;
import com.tiki.server.member.adapter.MemberFinder;
import com.tiki.server.member.entity.Member;
import com.tiki.server.member.exception.MemberException;
Expand Down Expand Up @@ -64,7 +65,7 @@ public ReissueGetResponse reissueToken(HttpServletRequest request) {
}

private Member checkMemberEmpty(SignInRequest request) {
return memberFinder.findByEmail(request.email()).orElseThrow(() -> new MemberException(INVALID_MEMBER));
return memberFinder.findByEmail(Email.from(request.email())).orElseThrow(() -> new MemberException(INVALID_MEMBER));
}

private void checkTokenEmpty(String token) {
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/com/tiki/server/common/entity/Email.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.tiki.server.common.entity;

import com.tiki.server.member.exception.MemberException;
import jakarta.persistence.Embeddable;
import lombok.*;
import org.apache.commons.validator.routines.EmailValidator;

import static com.tiki.server.emailverification.constants.EmailConstants.MAIL_FORMAT_AC_KR;
import static com.tiki.server.emailverification.constants.EmailConstants.MAIL_FORMAT_EDU;
import static com.tiki.server.member.message.ErrorCode.INVALID_EMAIL;

@Getter
@Embeddable
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Email {

private String email;
public static Email from(String email){
checkMailFormat(email);
return new Email(email);
}

private static void checkMailFormat(String email) {
if (!EmailValidator.getInstance().isValid(email) || !(email.endsWith(MAIL_FORMAT_EDU) || email.endsWith(MAIL_FORMAT_AC_KR))) {
throw new MemberException(INVALID_EMAIL);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.tiki.server.auth.exception.AuthException;
import com.tiki.server.common.dto.ErrorCodeResponse;
import com.tiki.server.mail.exception.MailException;
import com.tiki.server.emailverification.exception.EmailVerificationException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
Expand Down Expand Up @@ -67,8 +67,8 @@ public ResponseEntity<BaseResponse> externalException(ExternalException exceptio
return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage()));
}

@ExceptionHandler(MailException.class)
public ResponseEntity<BaseResponse> MailException(MailException exception) {
@ExceptionHandler(EmailVerificationException.class)
public ResponseEntity<BaseResponse> MailException(EmailVerificationException exception) {
log.error(exception.getMessage());
val errorCode = exception.getErrorCode();
return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.tiki.server.emailverification.adapter;

import com.tiki.server.common.support.RepositoryAdapter;
import com.tiki.server.emailverification.domain.EmailVerification;
import com.tiki.server.emailverification.exception.EmailVerificationException;
import com.tiki.server.emailverification.repository.EmailVerificationRepository;
import lombok.RequiredArgsConstructor;

import static com.tiki.server.emailverification.message.ErrorCode.INVALID_REQUEST;

@RepositoryAdapter
@RequiredArgsConstructor
public class EmailVerificationFinder {

private final EmailVerificationRepository mailRepository;

public EmailVerification findById(String email) {
return mailRepository.findById(email).orElseThrow(() -> new EmailVerificationException(INVALID_REQUEST));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.tiki.server.emailverification.adapter;

import com.tiki.server.common.support.RepositoryAdapter;
import com.tiki.server.emailverification.domain.EmailVerification;
import com.tiki.server.emailverification.repository.EmailVerificationRepository;
import lombok.RequiredArgsConstructor;

@RepositoryAdapter
@RequiredArgsConstructor
public class EmailVerificationSaver {

private final EmailVerificationRepository mailRepository;

public void save(EmailVerification mail) {
mailRepository.save(mail);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.tiki.server.mail.config;
package com.tiki.server.emailverification.config;

import com.sun.tools.jconsole.JConsoleContext;
import lombok.val;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -10,23 +8,23 @@

import java.util.Properties;

import static com.tiki.server.mail.constants.MailConstants.TIKI_EMAIL;
import static com.tiki.server.emailverification.constants.EmailConstants.TIKI_EMAIL;

@Configuration
public class MailConfig {
public class EmailConfig {

@Value("${MAIL.password}")
private String mailPassword;
private String emailPassword;

@Bean
public JavaMailSender setProperties() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("smtp.gmail.com");
mailSender.setPort(587);
mailSender.setUsername(TIKI_EMAIL);
mailSender.setPassword(mailPassword);
mailSender.setPassword(emailPassword);

val javaMailProperties = getProperties();
Properties javaMailProperties = getProperties();

mailSender.setJavaMailProperties(javaMailProperties);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.tiki.server.mail.constants;
package com.tiki.server.emailverification.constants;

public class MailConstants {
public class EmailConstants {

public static final String TIKI_EMAIL = "[email protected]";
public static final String MAIL_SUBJECT_SIGN_UP = "[Ti.Ki] 회원가입: 이메일 인증번호 안내";
Expand All @@ -9,4 +9,9 @@ public class MailConstants {
public static final String MAIL_FORMAT_EDU = ".edu";
public static final String MAIL_FORMAT_AC_KR = ".ac.kr";
public static final String TEMPLATE_NAME = "certification";
public static final int INIT_NUM = 0;
public static final int CODE_LENGTH = 6;
public static final int CODE_NUM_MAX_VALUE_PER_WORD = 10;
public static final String CERTIFICATION_PAGE_LOGO_IMAGE_VAR = "image";
public static final String CERTIFICATION_PAGE_CODEE_VAR = "code";
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.tiki.server.mail.controller;
package com.tiki.server.emailverification.controller;

import com.tiki.server.common.dto.BaseResponse;

import com.tiki.server.mail.controller.docs.MailControllerDocs;
import com.tiki.server.mail.dto.request.CodeCheck;
import com.tiki.server.mail.dto.request.MailRequest;
import com.tiki.server.mail.service.MailService;
import com.tiki.server.emailverification.controller.docs.EmailVerificationControllerDocs;
import com.tiki.server.emailverification.dto.request.EmailRequest;
import com.tiki.server.emailverification.dto.request.CodeVerificationRequest;
import com.tiki.server.emailverification.service.EmailVerificationService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -15,33 +15,32 @@

import static com.tiki.server.common.dto.SuccessResponse.success;
import static com.tiki.server.common.support.UriGenerator.getUri;
import static com.tiki.server.mail.message.SuccessMessage.SUCCESS_SEND_EMAIL;
import static com.tiki.server.mail.message.SuccessMessage.SUCCESS_VALIDATION;
import static com.tiki.server.emailverification.message.SuccessMessage.SUCCESS_SEND_EMAIL;
import static com.tiki.server.emailverification.message.SuccessMessage.SUCCESS_VALIDATION;


@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/mail")
public class MailController implements MailControllerDocs {
@RequestMapping("/api/v1/email-verification")
public class EmailVerificationController implements EmailVerificationControllerDocs {

private final MailService mailService;
private final EmailVerificationService emailVerificationService;

@PostMapping("/signup")
public ResponseEntity<BaseResponse> sendSignUpMail(@RequestBody MailRequest mailRequest) {
mailService.sendSignUp(mailRequest);
public ResponseEntity<BaseResponse> sendSignUpMail(@RequestBody EmailRequest mailRequest) {
emailVerificationService.sendSignUp(mailRequest);
return ResponseEntity.created(getUri("/")).body(success(SUCCESS_SEND_EMAIL.getMessage()));
}

@PostMapping("/password")
public ResponseEntity<BaseResponse> sendChangingPasswordMail(@RequestBody MailRequest mailRequest) {
mailService.sendChangingPassword(mailRequest);
public ResponseEntity<BaseResponse> sendChangingPasswordMail(@RequestBody EmailRequest mailRequest) {
emailVerificationService.sendChangingPassword(mailRequest);
return ResponseEntity.created(getUri("/")).body(success(SUCCESS_SEND_EMAIL.getMessage()));
}

@PostMapping("/checking")
public ResponseEntity<BaseResponse> checkCode(@RequestBody CodeCheck codeCheck) {
mailService.checkCode(codeCheck);
public ResponseEntity<BaseResponse> checkCode(@RequestBody CodeVerificationRequest codeVerificationRequest) {
emailVerificationService.checkCode(codeVerificationRequest);
return ResponseEntity.created(getUri("/")).body(success(SUCCESS_VALIDATION.getMessage()));
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package com.tiki.server.mail.controller.docs;
package com.tiki.server.emailverification.controller.docs;

import com.tiki.server.common.dto.BaseResponse;
import com.tiki.server.common.dto.ErrorResponse;
import com.tiki.server.common.dto.SuccessResponse;
import com.tiki.server.mail.dto.request.CodeCheck;
import com.tiki.server.mail.dto.request.MailRequest;
import com.tiki.server.emailverification.dto.request.EmailRequest;

import com.tiki.server.emailverification.dto.request.CodeVerificationRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
Expand All @@ -15,8 +14,8 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;

@Tag(name = "mail", description = "메일 인증 API")
public interface MailControllerDocs {
@Tag(name = "EmailVerification", description = "메일 인증 API")
public interface EmailVerificationControllerDocs {

@Operation(
summary = "회원가입 메일 전송",
Expand All @@ -40,7 +39,7 @@ public interface MailControllerDocs {
description = "서버 내부 오류",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))}
)
ResponseEntity<BaseResponse> sendSignUpMail(@RequestBody MailRequest mailRequest);
ResponseEntity<BaseResponse> sendSignUpMail(@RequestBody EmailRequest mailRequest);

@Operation(
summary = "비밀번호 재설정 메일 전송",
Expand All @@ -64,7 +63,7 @@ public interface MailControllerDocs {
description = "서버 내부 오류",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))}
)
ResponseEntity<BaseResponse> sendChangingPasswordMail(@RequestBody MailRequest mailRequest);
ResponseEntity<BaseResponse> sendChangingPasswordMail(@RequestBody EmailRequest mailRequest);

@Operation(
summary = "메일 인증",
Expand Down Expand Up @@ -92,5 +91,5 @@ public interface MailControllerDocs {
description = "서버 내부 오류",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))}
)
ResponseEntity<BaseResponse> checkCode(@RequestBody CodeCheck codeCheck);
ResponseEntity<BaseResponse> checkCode(@RequestBody CodeVerificationRequest verificationCodeRequest);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.tiki.server.emailverification.domain;

import com.tiki.server.common.entity.Email;
import com.tiki.server.emailverification.exception.EmailVerificationException;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.redis.core.RedisHash;

import static com.tiki.server.emailverification.message.ErrorCode.INVALID_MATCHED;
import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PRIVATE;

@Getter
@AllArgsConstructor(access = PRIVATE)
@Builder
@RedisHash(value = "mailVerification", timeToLive = 180)
public class EmailVerification {

@Id
@GeneratedValue(strategy = IDENTITY)
private String id;

private String code;

public static EmailVerification of(Email email, String code) {
return EmailVerification.builder().id(email.getEmail()).code(code).build();
}

public void verify(String code){
if(!this.code.equals(code)){
throw new EmailVerificationException(INVALID_MATCHED);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.tiki.server.emailverification.domain;

import com.tiki.server.common.entity.Email;
import com.tiki.server.emailverification.exception.EmailVerificationException;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring6.SpringTemplateEngine;

import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static com.tiki.server.emailverification.constants.EmailConstants.*;
import static com.tiki.server.emailverification.message.ErrorCode.MESSAGE_HELPER_ERROR;

@Component
@RequiredArgsConstructor
public class MailSender {

private final SpringTemplateEngine templateEngine;
private final JavaMailSender javaMailSender;

public EmailVerification sendVerificationMail(Email email, String subject) {
String code = generateRandomValue();
MimeMessage message = makeMessage(email, code, subject);
javaMailSender.send(message);
return EmailVerification.of(email, code);
}

private static String generateRandomValue() {
Random random = new Random();
return IntStream.range(INIT_NUM, CODE_LENGTH).mapToObj(i -> String.valueOf(random.nextInt(CODE_NUM_MAX_VALUE_PER_WORD))).collect(Collectors.joining());
}

private MimeMessage makeMessage(Email email, String code, String subject) {
MimeMessage message = javaMailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
helper.setFrom(TIKI_EMAIL);
helper.setTo(email.getEmail());
helper.setSubject(subject);
helper.setText(setContext(code), true);
helper.addInline(CERTIFICATION_PAGE_LOGO_IMAGE_VAR, new ClassPathResource(IMG_PATH));
return message;
} catch (Exception e) {
throw new EmailVerificationException(MESSAGE_HELPER_ERROR);
}
}

private String setContext(String code) {
Context context = new Context();
context.setVariable(CERTIFICATION_PAGE_CODEE_VAR, code);
return templateEngine.process(TEMPLATE_NAME, context);
}
}
Loading
Loading