Skip to content

Commit 158bed9

Browse files
authored
Merge pull request #349 from TaskFlow-CLAP/CLAP-294
CLAP-294 비밀번호 재설정 인증번호 전송 및 검증 API 구현
2 parents f0d9f34 + c6d9124 commit 158bed9

39 files changed

+371
-37
lines changed

src/main/java/clap/server/adapter/inbound/security/filter/JwtAuthenticationFilter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import clap.server.adapter.outbound.jwt.JwtClaims;
44
import clap.server.adapter.outbound.jwt.access.AccessTokenClaimKeys;
5-
import clap.server.application.port.outbound.auth.ForbiddenTokenPort;
5+
import clap.server.application.port.outbound.auth.forbidden.ForbiddenTokenPort;
66
import clap.server.application.port.outbound.auth.JwtProvider;
77
import clap.server.exception.JwtException;
88
import clap.server.exception.code.AuthErrorCode;
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+
}

src/main/java/clap/server/adapter/outbound/api/EmailClient.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import clap.server.adapter.outbound.api.dto.EmailTemplate;
44
import clap.server.adapter.outbound.api.dto.PushNotificationTemplate;
5-
import clap.server.application.port.outbound.webhook.SendEmailPort;
5+
import clap.server.application.port.outbound.email.SendEmailPort;
6+
import clap.server.application.port.outbound.webhook.SendWebhookEmailPort;
67
import clap.server.common.annotation.architecture.ExternalApiAdapter;
78
import clap.server.exception.AdapterException;
89
import clap.server.exception.code.NotificationErrorCode;
@@ -13,7 +14,7 @@
1314

1415
@ExternalApiAdapter
1516
@RequiredArgsConstructor
16-
public class EmailClient implements SendEmailPort {
17+
public class EmailClient implements SendEmailPort, SendWebhookEmailPort {
1718

1819
private final EmailTemplateBuilder emailTemplateBuilder;
1920
private final JavaMailSender mailSender;
@@ -51,4 +52,23 @@ public void sendInvitationEmail(String memberEmail, String receiverName, String
5152
throw new AdapterException(NotificationErrorCode.EMAIL_SEND_FAILED);
5253
}
5354
}
55+
56+
@Override
57+
public void sendVerificationEmail(String memberEmail, String receiverName, String verificationCode) {
58+
try {
59+
MimeMessage mimeMessage = mailSender.createMimeMessage();
60+
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
61+
62+
EmailTemplate template = emailTemplateBuilder.createVerificationCodeTemplate(memberEmail, receiverName, verificationCode);
63+
helper.setTo(template.email());
64+
helper.setSubject(template.subject());
65+
helper.setText(template.body(), true);
66+
67+
mailSender.send(mimeMessage);
68+
} catch (Exception e) {
69+
throw new AdapterException(NotificationErrorCode.EMAIL_SEND_FAILED);
70+
}
71+
}
72+
73+
5474
}

src/main/java/clap/server/adapter/outbound/api/EmailTemplateBuilder.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,14 @@ public EmailTemplate createInvitationTemplate(String receiver, String receiverNa
6969
String body = templateEngine.process(templateName, context);
7070
return new EmailTemplate(receiver, subject, body);
7171
}
72+
73+
public EmailTemplate createVerificationCodeTemplate(String receiver, String receiverName, String verificationCode) {
74+
Context context = new Context();
75+
String templateName = "verification";
76+
String subject = "[TaskFlow] 비밀번호 재설정 인증 번호";
77+
context.setVariable("verificationCode", verificationCode);
78+
context.setVariable("receiverName", receiverName);
79+
String body = templateEngine.process(templateName, context);
80+
return new EmailTemplate(receiver, subject, body);
81+
}
7282
}

src/main/java/clap/server/adapter/outbound/infrastructure/redis/forbidden/ForbiddenTokenAdapter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package clap.server.adapter.outbound.infrastructure.redis.forbidden;
22

3-
import clap.server.application.port.outbound.auth.ForbiddenTokenPort;
3+
import clap.server.application.port.outbound.auth.forbidden.ForbiddenTokenPort;
44
import clap.server.common.annotation.architecture.InfrastructureAdapter;
55
import clap.server.domain.model.auth.ForbiddenToken;
66
import lombok.RequiredArgsConstructor;

src/main/java/clap/server/adapter/outbound/infrastructure/redis/log/LoginLogAdapter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package clap.server.adapter.outbound.infrastructure.redis.log;
22

3-
import clap.server.application.port.outbound.auth.CommandLoginLogPort;
4-
import clap.server.application.port.outbound.auth.LoadLoginLogPort;
3+
import clap.server.application.port.outbound.auth.loginLog.CommandLoginLogPort;
4+
import clap.server.application.port.outbound.auth.loginLog.LoadLoginLogPort;
55
import clap.server.common.annotation.architecture.InfrastructureAdapter;
66
import clap.server.domain.model.auth.LoginLog;
77
import lombok.RequiredArgsConstructor;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package clap.server.adapter.outbound.infrastructure.redis.otp;
2+
3+
import clap.server.application.port.outbound.auth.otp.CommandOtpPort;
4+
import clap.server.application.port.outbound.auth.otp.LoadOtpPort;
5+
import clap.server.common.annotation.architecture.InfrastructureAdapter;
6+
import clap.server.domain.model.auth.Otp;
7+
import lombok.RequiredArgsConstructor;
8+
import lombok.extern.slf4j.Slf4j;
9+
10+
import java.util.Optional;
11+
12+
@Slf4j
13+
@InfrastructureAdapter
14+
@RequiredArgsConstructor
15+
public class OtpAdapter implements LoadOtpPort, CommandOtpPort {
16+
private final OtpRepository otpRepository;
17+
private final OtpMapper otpMapper;
18+
19+
@Override
20+
public void save(Otp otp) {
21+
OtpEntity refreshTokenEntity = otpMapper.toEntity(otp);
22+
otpRepository.save(refreshTokenEntity);
23+
}
24+
25+
@Override
26+
public void deleteByEmail(String email) {
27+
otpRepository.deleteById(email);
28+
}
29+
30+
@Override
31+
public Optional<Otp> findByEmail(String email) {
32+
return otpRepository.findById(email).map(otpMapper::toDomain);
33+
}
34+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package clap.server.adapter.outbound.infrastructure.redis.otp;
2+
3+
import lombok.*;
4+
import org.springframework.data.annotation.Id;
5+
import org.springframework.data.redis.core.RedisHash;
6+
7+
@RedisHash(value = "OTP", timeToLive = 300) // 300초(5분) 후 자동 삭제
8+
@Getter
9+
@Builder
10+
@ToString(of = {"email", "code"})
11+
public class OtpEntity {
12+
@Id
13+
private String email;
14+
private String code;
15+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package clap.server.adapter.outbound.infrastructure.redis.otp;
2+
3+
import clap.server.domain.model.auth.Otp;
4+
import org.mapstruct.InheritInverseConfiguration;
5+
import org.mapstruct.Mapper;
6+
7+
@Mapper(componentModel = "spring")
8+
public interface OtpMapper {
9+
@InheritInverseConfiguration
10+
Otp toDomain(final OtpEntity entity);
11+
12+
OtpEntity toEntity(final Otp domain);
13+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package clap.server.adapter.outbound.infrastructure.redis.otp;
2+
3+
import org.springframework.data.repository.CrudRepository;
4+
5+
public interface OtpRepository extends CrudRepository<OtpEntity, String>{
6+
}

0 commit comments

Comments
 (0)