Skip to content

Commit b6c600b

Browse files
authored
[TB-16] 이메일 인증 기능 (#20)
1 parent 778a7f0 commit b6c600b

File tree

15 files changed

+213
-2
lines changed

15 files changed

+213
-2
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ dependencies {
4040
implementation 'software.amazon.awssdk:s3:2.25.14' // aws s3 (버전 체크 필요)
4141
implementation 'com.auth0:java-jwt:4.4.0'
4242
implementation 'com.google.guava:guava:32.1.2-jre'
43+
implementation 'org.springframework.boot:spring-boot-starter-mail'
4344
}
4445

4546
tasks.withType(Test).configureEach {

src/main/java/com/ClubAccount_BE/auth/adapter/in/web/signin/SignInController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public TokenResponse signIn(@Valid @RequestBody SignInRequest signInRequest, Htt
2828
.httpOnly(true)
2929
.path("/")
3030
.maxAge(Duration.ofDays(7))
31-
.sameSite("None")
31+
.sameSite("Lax")
3232
.secure(false)
3333
.build();
3434
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());

src/main/java/com/ClubAccount_BE/auth/adapter/in/web/token/TokenController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public AccessTokenResponse createNewToken(
3131
.httpOnly(true)
3232
.path("/")
3333
.maxAge(Duration.ofDays(7))
34-
.sameSite("None")
34+
.sameSite("Lax")
3535
.secure(false)
3636
.build();
3737

src/main/java/com/ClubAccount_BE/core/config/SecurityConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
3939
API_V1_PREFIX + "/users/password",
4040
API_V1_PREFIX + "/health",
4141
API_V1_PREFIX + "/users/sign-up/check-duplicate-auth-id",
42+
API_V1_PREFIX + "/email/send",
43+
API_V1_PREFIX + "/email/verify",
4244
"/api-docs",
4345
"/swagger-custom-ui.html",
4446
"/v3/api-docs",
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.ClubAccount_BE.user.adapter.in.email;
2+
3+
import com.ClubAccount_BE.user.adapter.in.email.dto.request.EmailSendRequest;
4+
import com.ClubAccount_BE.user.adapter.in.email.dto.request.EmailVerifyRequest;
5+
import com.ClubAccount_BE.user.adapter.in.email.dto.response.EmailVerifyResponse;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.tags.Tag;
8+
import jakarta.validation.Valid;
9+
import org.springframework.http.ResponseEntity;
10+
import org.springframework.web.bind.annotation.RequestBody;
11+
12+
@Tag(name = "Email", description = "이메일 인증 관련 API")
13+
public interface EmailApiPresentation {
14+
15+
@Operation(summary = "이메일 인증 코드 전송")
16+
ResponseEntity<Void> sendVerificationEmail(@Valid @RequestBody EmailSendRequest request);
17+
18+
@Operation(summary = "이메일 인증 코드 검증")
19+
ResponseEntity<EmailVerifyResponse> verifyCode(@Valid @RequestBody EmailVerifyRequest request);
20+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.ClubAccount_BE.user.adapter.in.email;
2+
3+
import com.ClubAccount_BE.user.adapter.in.email.dto.request.EmailSendRequest;
4+
import com.ClubAccount_BE.user.adapter.in.email.dto.request.EmailVerifyRequest;
5+
import com.ClubAccount_BE.user.adapter.in.email.dto.response.EmailVerifyResponse;
6+
import com.ClubAccount_BE.user.application.service.email.EmailService;
7+
import jakarta.validation.Valid;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.http.ResponseEntity;
10+
import org.springframework.web.bind.annotation.*;
11+
12+
@RestController
13+
@RequestMapping("/api/v1/email")
14+
@RequiredArgsConstructor
15+
public class EmailController implements EmailApiPresentation {
16+
17+
private final EmailService emailService;
18+
19+
@PostMapping("/send")
20+
@Override
21+
public ResponseEntity<Void> sendVerificationEmail(@RequestBody @Valid EmailSendRequest request) {
22+
emailService.sendVerificationEmail(request.getEmail());
23+
return ResponseEntity.ok().build();
24+
}
25+
26+
@PostMapping(value = "/verify", produces = "application/json")
27+
@Override
28+
public ResponseEntity<EmailVerifyResponse> verifyCode(@RequestBody @Valid EmailVerifyRequest request) {
29+
boolean verified = emailService.verifyCode(request.getEmail(), request.getCode());
30+
return ResponseEntity.ok(new EmailVerifyResponse(verified, verified ? "✅ 인증 성공" : "❌ 인증 실패"));
31+
}
32+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.ClubAccount_BE.user.adapter.in.email.dto.request;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import jakarta.validation.constraints.Email;
5+
import jakarta.validation.constraints.NotBlank;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
@Getter
10+
@NoArgsConstructor
11+
public class EmailSendRequest {
12+
@NotBlank
13+
@Email(message = "유효한 이메일 형식이 아닙니다.")
14+
@Schema(name = "email", example = "thinkboo@example.com")
15+
private String email;
16+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.ClubAccount_BE.user.adapter.in.email.dto.request;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import jakarta.validation.constraints.Email;
5+
import jakarta.validation.constraints.NotBlank;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
10+
@Getter
11+
@NoArgsConstructor
12+
public class EmailVerifyRequest {
13+
@NotBlank
14+
@Email(message = "유효한 이메일 형식이 아닙니다.")
15+
@Schema(name = "email", example = "thinkboo@example.com")
16+
private String email;
17+
18+
@NotBlank
19+
@Schema(name = "code", example = "0A0012")
20+
private String code;
21+
}
22+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.ClubAccount_BE.user.adapter.in.email.dto.response;
2+
3+
import lombok.Getter;
4+
import lombok.NoArgsConstructor;
5+
6+
@Getter
7+
@NoArgsConstructor
8+
public class EmailVerifyResponse {
9+
private boolean success;
10+
private String message;
11+
12+
public EmailVerifyResponse(boolean success, String message) {
13+
this.success = success;
14+
this.message = message;
15+
}
16+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.ClubAccount_BE.user.adapter.out.persistence;
2+
3+
import com.ClubAccount_BE.user.adapter.out.persistence.repository.VerificationRepository;
4+
import com.ClubAccount_BE.user.application.port.out.email.VerificationCodePort;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.stereotype.Repository;
7+
8+
@RequiredArgsConstructor
9+
@Repository
10+
public class InMemoryVerificationAdapter implements VerificationCodePort {
11+
12+
private final VerificationRepository verificationRepository;
13+
14+
@Override
15+
public void saveCode(String email, String code) {
16+
verificationRepository.saveCode(email, code);
17+
}
18+
19+
@Override
20+
public String getCode(String email) {
21+
return verificationRepository.getCode(email);
22+
}
23+
}

0 commit comments

Comments
 (0)