Skip to content

Commit 3010405

Browse files
committed
CLAP-294 Feat: 비밀번호 재설정 이메일 전송 및 인증번호 검증 API 구현
<footer> - 관련: #346
1 parent 489528b commit 3010405

File tree

5 files changed

+106
-1
lines changed

5 files changed

+106
-1
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package clap.server.adapter.inbound.web.member;
2+
3+
import clap.server.adapter.inbound.security.service.SecurityUserDetails;
4+
import clap.server.application.port.inbound.member.SendVerificationEmailUsecase;
5+
import clap.server.application.port.inbound.member.VerifyEmailCodeUsecase;
6+
import clap.server.common.annotation.architecture.WebAdapter;
7+
import io.swagger.v3.oas.annotations.Operation;
8+
import io.swagger.v3.oas.annotations.tags.Tag;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
11+
import org.springframework.web.bind.annotation.PostMapping;
12+
import org.springframework.web.bind.annotation.RequestMapping;
13+
import org.springframework.web.bind.annotation.RequestParam;
14+
15+
@Tag(name = "00. Auth [인증번호]")
16+
@WebAdapter
17+
@RequiredArgsConstructor
18+
@RequestMapping("/api/members")
19+
public class EmailVerificationController {
20+
private final SendVerificationEmailUsecase sendVerificationEmailUsecase;
21+
private final VerifyEmailCodeUsecase verifyEmailCodeUsecase;
22+
23+
@Operation(summary = "인증번호 전송 API")
24+
@PostMapping("/verification/email")
25+
public void sendVerificationEmail(@AuthenticationPrincipal SecurityUserDetails userInfo){
26+
sendVerificationEmailUsecase.sendVerificationCode(userInfo.getUserId());
27+
}
28+
29+
@Operation(summary = "인증번호 검증 API")
30+
@PostMapping("/verification")
31+
public void sendVerificationEmail(@AuthenticationPrincipal SecurityUserDetails userInfo,
32+
@RequestParam String code){
33+
verifyEmailCodeUsecase.verifyEmailCode(userInfo.getUserId(), code);
34+
}
35+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package clap.server.application.port.inbound.member;
22

33
public interface VerifyEmailCodeUsecase {
4-
void verifyEmailCode(Long memberId);
4+
void verifyEmailCode(Long memberId, String code);
55
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package clap.server.application.service.auth;
2+
3+
import clap.server.application.port.inbound.domain.MemberService;
4+
import clap.server.application.port.inbound.member.SendVerificationEmailUsecase;
5+
import clap.server.application.port.inbound.member.VerifyEmailCodeUsecase;
6+
import clap.server.application.port.outbound.auth.otp.CommandOtpPort;
7+
import clap.server.application.port.outbound.auth.otp.LoadOtpPort;
8+
import clap.server.application.port.outbound.email.SendEmailPort;
9+
import clap.server.common.annotation.architecture.ApplicationService;
10+
import clap.server.common.utils.VerificationCodeGenerator;
11+
import clap.server.domain.model.auth.Otp;
12+
import clap.server.domain.model.member.Member;
13+
import clap.server.exception.ApplicationException;
14+
import clap.server.exception.code.AuthErrorCode;
15+
import lombok.RequiredArgsConstructor;
16+
import org.springframework.transaction.annotation.Transactional;
17+
18+
@ApplicationService
19+
@RequiredArgsConstructor
20+
public class EmailVerificationService implements SendVerificationEmailUsecase, VerifyEmailCodeUsecase {
21+
private final MemberService memberService;
22+
private final SendEmailPort sendEmailPort;
23+
private final CommandOtpPort commandOtpPort;
24+
private final LoadOtpPort loadOtpPort;
25+
26+
@Override
27+
@Transactional
28+
public void sendVerificationCode(Long memberId) {
29+
Member member = memberService.findActiveMember(memberId);
30+
String verificationCode = VerificationCodeGenerator.generateRandomCode();
31+
commandOtpPort.save(new Otp(member.getMemberInfo().getEmail(), verificationCode));
32+
sendEmailPort.sendVerificationEmail(member.getMemberInfo().getEmail(), member.getNickname(), verificationCode);
33+
}
34+
35+
36+
@Override
37+
@Transactional
38+
public void verifyEmailCode(Long memberId, String code) {
39+
Member member = memberService.findActiveMember(memberId);
40+
Otp otp = loadOtpPort.findByEmail(member.getMemberInfo().getEmail()).orElseThrow(
41+
() -> new ApplicationException(AuthErrorCode.VERIFICATION_CODE_EXPIRED)
42+
);
43+
44+
if(!code.equals(otp.code())){
45+
throw new ApplicationException(AuthErrorCode.VERIFICATION_CODE_MISMATCH);
46+
}
47+
else {
48+
commandOtpPort.deleteByEmail(member.getMemberInfo().getEmail());
49+
}
50+
}
51+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package clap.server.common.utils;
2+
3+
import java.util.Random;
4+
5+
public class VerificationCodeGenerator {
6+
7+
public static String generateRandomCode() {
8+
Random random = new Random();
9+
StringBuilder code = new StringBuilder();
10+
11+
for (int i = 0; i < 6; i++) {
12+
code.append(random.nextInt(10));
13+
}
14+
15+
return code.toString();
16+
}
17+
}

src/main/java/clap/server/exception/code/AuthErrorCode.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public enum AuthErrorCode implements BaseErrorCode {
2424
ACCOUNT_IS_LOCKED(HttpStatus.UNAUTHORIZED, "AUTH_015", "접근할 수 없는 계정입니다."),
2525
LOGIN_REQUEST_FAILED(HttpStatus.UNAUTHORIZED, "AUTH_016", "로그인에 실패하였습니다."),
2626
REFRESH_TOKEN_MISMATCHED(HttpStatus.UNAUTHORIZED, "AUTH_017", "리프레시 토큰이 일치하지 않습니다"),
27+
VERIFICATION_CODE_MISMATCH(HttpStatus.BAD_REQUEST, "AUTH_018", "인증번호가 일치하지 않습니다."),
28+
VERIFICATION_CODE_EXPIRED(HttpStatus.BAD_REQUEST, "AUTH_019", "만료된 인증번호입니다.")
2729
;
2830

2931
private final HttpStatus httpStatus;

0 commit comments

Comments
 (0)