Skip to content

Commit

Permalink
feat-be: 이메일 인증 후, 회원가입 이메일의 인증 여부 확인 (#882)
Browse files Browse the repository at this point in the history
Co-authored-by: Kwoun Ki Ho <[email protected]>
  • Loading branch information
2 people authored and Dobby-Kim committed Oct 23, 2024
1 parent f149fe8 commit cf635ce
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 5 deletions.
4 changes: 4 additions & 0 deletions backend/src/docs/asciidoc/member.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ operation::member/signup[snippets="http-request,request-fields,http-response"]
==== 실패: 유효하지 않은 요청

operation::member/signup-fail/invalid-request[snippets="http-request,request-fields,http-response"]

==== 실패: 인증되지 않은 이메일

operation::member/signup-fail/not-verified-email[snippets="http-request,request-fields,http-response"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.cruru.email.exception;

import com.cruru.advice.UnauthorizedException;

public class NotVerifiedEmailException extends UnauthorizedException {

private static final String MESSAGE = "이메일 인증이 필요합니다.";

public NotVerifiedEmailException() {
super(MESSAGE);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,6 @@ public void verifyCode(VerifyCodeRequest request) {
String storedVerificationCode = emailRedisClient.getVerificationCode(email);

VerificationCodeUtil.verify(storedVerificationCode, inputVerificationCode);
emailRedisClient.saveVerifiedEmail(email);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.cruru.email.service;

import com.cruru.email.exception.NotVerifiedEmailException;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
Expand All @@ -9,17 +10,46 @@
@RequiredArgsConstructor
public class EmailRedisClient {

private static final String REDIS_PREFIX = "email_verification:";
private static final long VERIFICATION_CODE_EXPIRATION = 10;
private static final String VERIFICATION_CODE_PREFIX = "email_verification_code:";
private static final String VERIFIED_EMAIL_PREFIX = "email_verified:";
private static final int VERIFICATION_CODE_EXPIRATION = 10;
private static final int VERIFIED_EMAIL_EXPIRATION = 10;

private final RedisTemplate<String, String> redisTemplate;

public void saveVerificationCode(String email, String verificationCode) {
redisTemplate.opsForValue()
.set(REDIS_PREFIX + email, verificationCode, VERIFICATION_CODE_EXPIRATION, TimeUnit.MINUTES);
.set(
VERIFICATION_CODE_PREFIX + email,
verificationCode,
VERIFICATION_CODE_EXPIRATION,
TimeUnit.MINUTES
);
}

public String getVerificationCode(String email) {
return redisTemplate.opsForValue().get(REDIS_PREFIX + email);
return redisTemplate.opsForValue().get(VERIFICATION_CODE_PREFIX + email);
}

public void saveVerifiedEmail(String email) {
redisTemplate.opsForValue()
.set(
VERIFIED_EMAIL_PREFIX + email,
VerificationStatus.VERIFIED.getValue(),
VERIFIED_EMAIL_EXPIRATION,
TimeUnit.MINUTES
);
}

public void verifyEmail(String email) {
String verifiedStatus = redisTemplate.opsForValue().get(VERIFIED_EMAIL_PREFIX + email);
VerificationStatus status = VerificationStatus.fromValue(verifiedStatus);
checkVerification(status);
}

private void checkVerification(VerificationStatus status) {
if (!status.isVerified()) {
throw new NotVerifiedEmailException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.cruru.email.service;

import java.util.Arrays;
import lombok.Getter;

@Getter
public enum VerificationStatus {

VERIFIED("verified"),
NOT_VERIFIED("not_verified"),
;

private final String value;

VerificationStatus(String value) {
this.value = value;
}

public static VerificationStatus fromValue(String value) {
return Arrays.stream(VerificationStatus.values())
.filter(status -> status.getValue().equalsIgnoreCase(value))
.findFirst()
.orElse(NOT_VERIFIED);
}

public boolean isVerified() {
return this == VERIFIED;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.cruru.member.facade;

import com.cruru.club.service.ClubService;
import com.cruru.email.service.EmailRedisClient;
import com.cruru.member.controller.request.MemberCreateRequest;
import com.cruru.member.domain.Member;
import com.cruru.member.service.MemberService;
Expand All @@ -15,9 +16,11 @@ public class MemberFacade {

private final MemberService memberService;
private final ClubService clubService;
private final EmailRedisClient emailRedisClient;

@Transactional
public long create(MemberCreateRequest request) {
emailRedisClient.verifyEmail(request.email());
Member savedMember = memberService.create(request);
clubService.create(request.clubName(), savedMember);
return savedMember.getId();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.cruru.member.controller;

import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;

import com.cruru.email.exception.NotVerifiedEmailException;
import com.cruru.email.service.EmailRedisClient;
import com.cruru.member.controller.request.MemberCreateRequest;
import com.cruru.util.ControllerTest;
import io.restassured.RestAssured;
Expand All @@ -13,10 +17,14 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.boot.test.mock.mockito.MockBean;

@DisplayName("사용자 컨트롤러 테스트")
class MemberControllerTest extends ControllerTest {

@MockBean
private EmailRedisClient emailRedisClient;

private static Stream<MemberCreateRequest> InvalidMemberSignUpRequest() {
String validName = "크루루";
String validMail = "[email protected]";
Expand All @@ -40,6 +48,7 @@ private static Stream<MemberCreateRequest> InvalidMemberSignUpRequest() {
void create() {
// given
MemberCreateRequest request = new MemberCreateRequest("크루루", "[email protected]", "newPassword214!", "01012341234");
doNothing().when(emailRedisClient).verifyEmail(request.email());

// when&then
RestAssured.given(spec).log().all()
Expand All @@ -61,7 +70,10 @@ void create() {
@ParameterizedTest
@MethodSource("InvalidMemberSignUpRequest")
void create_invalidEmail(MemberCreateRequest request) {
// given&when&then
// given
doNothing().when(emailRedisClient).verifyEmail(request.email());

//when&then
RestAssured.given(spec).log().all()
.contentType(ContentType.JSON)
.body(request)
Expand All @@ -76,4 +88,27 @@ void create_invalidEmail(MemberCreateRequest request) {
.when().post("/v1/members/signup")
.then().log().all().statusCode(400);
}

@DisplayName("인증되지 않은 사용자가 회원가입할 경우, 401을 반환한다.")
@Test
void create_notVerifiedEmail() {
// given
MemberCreateRequest request = new MemberCreateRequest("크루루", "[email protected]", "newPassword214!", "01012341234");
doThrow(NotVerifiedEmailException.class).when(emailRedisClient).verifyEmail(request.email());

// when&then
RestAssured.given(spec).log().all()
.contentType(ContentType.JSON)
.body(request)
.filter(document("member/signup-fail/not-verified-email",
requestFields(
fieldWithPath("clubName").description("동아리명"),
fieldWithPath("email").description("인증되지 않은 사용자 이메일"),
fieldWithPath("password").description("사용자 패스워드"),
fieldWithPath("phone").description("사용자 전화번호")
)
))
.when().post("/v1/members/signup")
.then().log().all().statusCode(401);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.cruru.member.facade;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;

import com.cruru.email.exception.NotVerifiedEmailException;
import com.cruru.email.service.EmailRedisClient;
import com.cruru.member.controller.request.MemberCreateRequest;
import com.cruru.member.domain.Member;
import com.cruru.member.domain.repository.MemberRepository;
Expand All @@ -11,6 +16,7 @@
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;

@DisplayName("회원 파사드 서비스 테스트")
class MemberFacadeTest extends ServiceTest {
Expand All @@ -21,6 +27,9 @@ class MemberFacadeTest extends ServiceTest {
@Autowired
private MemberRepository memberRepository;

@MockBean
private EmailRedisClient emailRedisClient;

@DisplayName("사용자를 생성하면 ID를 반환한다.")
@Test
void create() {
Expand All @@ -30,6 +39,7 @@ void create() {
String password = "newPassword214!";
String phone = "01012341234";
MemberCreateRequest request = new MemberCreateRequest(clubName, email, password, phone);
doNothing().when(emailRedisClient).verifyEmail(email);

// when
long memberId = memberFacade.create(request);
Expand All @@ -42,4 +52,20 @@ void create() {
() -> assertThat(member.get().getPhone()).isEqualTo(phone)
);
}

@DisplayName("인증되지 않은 사용자로 사용자를 생성하면, 예외가 발생한다.")
@Test
void create_notVerifiedEmail() {
// given
String clubName = "크루루";
String email = "[email protected]";
String password = "newPassword214!";
String phone = "01012341234";
MemberCreateRequest request = new MemberCreateRequest(clubName, email, password, phone);
doThrow(NotVerifiedEmailException.class).when(emailRedisClient).verifyEmail(email);

// when&then
assertThatThrownBy(() -> memberFacade.create(request))
.isInstanceOf(NotVerifiedEmailException.class);
}
}

0 comments on commit cf635ce

Please sign in to comment.