Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

import com.daytodo.domain.auth.dto.AuthRequest;
import com.daytodo.domain.auth.dto.AuthResponse;
import com.daytodo.domain.auth.exception.code.AuthErrorCode;
import com.daytodo.domain.auth.service.AuthService;
import com.daytodo.global.apiPayload.exception.ProjectException;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
Expand Down Expand Up @@ -79,10 +83,41 @@ public ResponseEntity<Void> resendVerificationEmail(
return ResponseEntity.noContent().build();
}

// 이메일 인증 메일 안내 링크를 클릭했을 때 브라우저에 직접 노출되는 엔드포인트라
// JSON이 아닌 사용자가 읽을 수 있는 HTML 문구로 응답한다. (팀 요청 반영)
@Operation(summary = "이메일 인증 처리")
@GetMapping("/verify-email")
public AuthResponse.EmailVerified verifyEmail(@RequestParam String token) {
return authService.verifyEmail(token);
@GetMapping(value = "/verify-email", produces = MediaType.TEXT_HTML_VALUE)
public ResponseEntity<String> verifyEmail(@RequestParam String token) {
try {
authService.verifyEmail(token);
return ResponseEntity.ok(renderResultPage("이메일 인증이 완료되었습니다", "이제 앱으로 돌아가 로그인해주세요."));
} catch (ProjectException exception) {
String message = switch (exception.getErrorCode().getCode()) {
case "EXPIRED_VERIFICATION_TOKEN" ->
"인증 링크가 만료되었습니다. 앱에서 인증 메일을 다시 요청해주세요.";
case "INVALID_VERIFICATION_TOKEN" ->
"유효하지 않은 인증 링크입니다. 앱에서 인증 메일을 다시 요청해주세요.";
default -> "인증 처리 중 문제가 발생했습니다. 잠시 후 다시 시도해주세요.";
};
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(renderResultPage("이메일 인증에 실패했습니다", message));
}
}

private String renderResultPage(String title, String description) {
return """
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>%s</title>
</head>
<body style="font-family: sans-serif; text-align: center; padding-top: 80px;">
<h2>%s</h2>
<p>%s</p>
</body>
</html>
""".formatted(title, title, description);
}

@Operation(summary = "비밀번호 재설정 요청")
Expand Down
12 changes: 10 additions & 2 deletions src/main/java/com/daytodo/domain/auth/service/AuthMailService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
Expand All @@ -23,13 +24,20 @@ public class AuthMailService {

private final JavaMailSender mailSender;

@Value("${app.base-url:https://dev.daytodo.cloud}")
private String baseUrl;

@Async
public void sendVerificationEmail(String to, String token) {
String verificationLink = baseUrl + "/auth/verify-email?token=" + token;

SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject("[데이투두] 이메일 인증을 완료해주세요");
message.setText("아래 링크(또는 토큰)로 이메일 인증을 완료해주세요.\n\n인증 토큰: " + token
+ "\n\nGET /auth/verify-email?token=" + token);
message.setText("데이투두 회원가입을 환영합니다!\n\n"
+ "아래 링크를 클릭하면 이메일 인증이 완료됩니다.\n\n"
+ verificationLink
+ "\n\n본인이 요청하지 않았다면 이 메일을 무시하셔도 됩니다.");
sendSafely(message);
}

Expand Down
5 changes: 4 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ spring:
max-file-size: 5MB
max-request-size: 6MB

app:
base-url: ${APP_BASE_URL:https://dev.daytodo.cloud}

jwt:
token:
secretKey: ${JWT_SECRET_KEY}
Expand Down Expand Up @@ -75,4 +78,4 @@ firebase:
notification:
zone: ${NOTIFICATION_ZONE:Asia/Seoul}
course-reminder-cron: ${COURSE_REMINDER_CRON:0 0 9 * * *}
dispatch-delay-ms: ${NOTIFICATION_DISPATCH_DELAY_MS:60000}
dispatch-delay-ms: ${NOTIFICATION_DISPATCH_DELAY_MS:60000}
Loading