Conversation
Walkthrough비밀번호 재설정 기능 추가: 이메일 인증 코드 발송(기존 로직 확장) 및 인증 후 비밀번호 변경 엔드포인트/서비스/엔티티/예외 코드·핸들러 추가. 이메일 템플릿 통합 및 Redis 만료 시간 단축(5분→3분). 예외 타입을 UserHandler로 통일. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant UserController
participant UserRepository
participant EmailService
participant Redis
participant MailProvider as Mail Provider
User->>UserController: POST /api/users/password-reset/request (email)
UserController->>UserRepository: existsByEmail(email)
alt 사용자 없음
UserRepository-->>UserController: false
UserController-->>User: USER_NOT_FOUND (400)
else 사용자 있음
UserRepository-->>UserController: true
UserController->>EmailService: sendEmailForPwd(email)
EmailService->>EmailService: 인증 코드 생성 (6자리)
EmailService->>Redis: SET email:code (TTL 180s)
EmailService->>MailProvider: 발송 요청 (비밀번호 재설정 템플릿)
MailProvider-->>EmailService: 발송 완료
EmailService-->>UserController: EmailSentResponse (180초)
UserController-->>User: 성공 응답
end
sequenceDiagram
actor User
participant UserController
participant UserService
participant EmailService as EmailService (Redis 검증)
participant Redis
participant UserRepository
participant PasswordEncoder
User->>UserController: POST /api/users/password-reset/confirm (email, password)
UserController->>UserService: passwordReset(email, password)
UserService->>EmailService: verifyEmailCode(email)
EmailService->>Redis: GET email:code
alt 인증 코드 없음/불일치
EmailService-->>UserService: USER_EMAIL_AUTH_INVALID
UserService-->>UserController: 예외 전달
UserController-->>User: 400 응답
else 인증 성공
EmailService-->>UserService: 인증 확인
UserService->>UserRepository: findByEmail(email)
alt 사용자 없음
UserRepository-->>UserService: 빈 응답
UserService-->>UserController: USER_NOT_FOUND
UserController-->>User: 400 응답
else 사용자 있음
UserRepository-->>UserService: User 엔티티
UserService->>PasswordEncoder: encode(newPassword)
PasswordEncoder-->>UserService: encodedPassword
alt 새 비밀번호 == 기존(해시 비교 필요)
UserService-->>UserController: USER_PASSWORD_SAME_AS_OLD
UserController-->>User: 400 응답
else 변경 허용
UserService->>User: user.resetPassword(encodedPassword)
UserService->>Redis: DEL email:code
UserService-->>UserController: 성공
UserController-->>User: 성공 응답
end
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/EmailService.java (2)
73-76:⚠️ Potential issue | 🟡 Minor비밀번호 재설정인데 제목이 “회원가입 인증번호”로 나가고 있어요.
사용자 입장에서는 “가입” 안내가 뜨면 혼란스럽습니다. 예: 이미 가입한 사용자가 비밀번호 재설정 메일을 받았는데 ‘회원가입’이라고 표시됨. 흐름별 제목을 분리하는 게 좋습니다.✉️ 개선 예시 (제목 파라미터화)
- private EmailSentResponse emailSendTemplate(String toEmail) { + private EmailSentResponse emailSendTemplate(String toEmail, String subject) { @@ - message.setSubject("whereyouad 회원가입 인증번호"); + message.setSubject(subject); @@ - return emailSendTemplate(toEmail); + return emailSendTemplate(toEmail, "whereyouad 회원가입 인증번호"); @@ - return emailSendTemplate(toEmail); //정상적으로 이메일 발송 + return emailSendTemplate(toEmail, "whereyouad 비밀번호 재설정 인증번호"); //정상적으로 이메일 발송
58-59:⚠️ Potential issue | 🟠 Major인증코드 생성에 SecureRandom을 사용해주세요.
현재
Math.random()을 사용하고 있는데, 이는 선형 합동 생성기(LCG) 기반이라 예측 가능합니다. 회원가입과 비밀번호 재설정이라는 보안에 민감한 작업에 사용되는 만큼 암호학적으로 안전한 난수 생성이 필요합니다.🔐 개선 예시 (SecureRandom 사용)
+import java.security.SecureRandom; @@ + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); @@ private String createCode() { - return String.valueOf((int)(Math.random() * (900000)) + 100000); + return String.format("%06d", SECURE_RANDOM.nextInt(1_000_000)); }
🤖 Fix all issues with AI agents
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/EmailService.java`:
- Around line 40-46: The sendEmailForPwd method leaks account existence by
throwing UserException when userRepository.existsByEmail(toEmail) is false;
change it so it always returns the same EmailSentResponse regardless of
existence: call emailSendTemplate(toEmail) only when exists, but when not exists
do not throw—return the identical success response and optionally log the
non-existent attempt internally. Update sendEmailForPwd to remove the
UserException path, keep userRepository.existsByEmail(toEmail) check to decide
whether to send the email, and ensure the public behavior and response shape
remain identical in both branches.
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/UserService.java`:
- Around line 61-69: The passwordReset method currently relies on the same Redis
key ("VERIFIED:"+email) used for signup verification; separate the concerns by
checking a dedicated password-reset verification key instead (e.g.,
"PASSWORD_RESET:"+email or "VERIFIED:PWD_RESET:"+email). Update
UserService.passwordReset to read redisUtil.getData for the new password-reset
key and throw UserException(UserErrorCode.USER_EMAIL_NOT_VERIFIED) when that key
is missing/invalid; also update EmailService where password-reset emails are
generated (currently setting the signup key around line 109) to set the new
password-reset Redis key with appropriate TTL instead of the signup key. Ensure
all references to "VERIFIED:"+email remain for signup flows only and document
the new key name in the email-reset logic.
| //비밀번호 재설정을 위한 인증코드 이메일 발송 로직 (이미 회원가입 된 상태에서 비밀번호 재설정) | ||
| public EmailSentResponse sendEmailForPwd(String toEmail) { | ||
| if (userRepository.existsByEmail(toEmail)) { //이미 회원가입 되어있는 것이 확인되면 | ||
| return emailSendTemplate(toEmail); //정상적으로 이메일 발송 | ||
| } else { //만약 회원가입 되어있지 않다면 | ||
| throw new UserException(UserErrorCode.USER_NOT_FOUND); //예외발생 | ||
| } |
There was a problem hiding this comment.
계정 존재 여부가 노출될 수 있어요.
현재는 미가입 이메일에 대해 예외를 반환해 계정 열거(Enumeration)가 가능합니다. 예: 공격자가 이메일 리스트를 넣어보고 USER_NOT_FOUND로 가입 여부를 추측할 수 있어요. 존재 여부는 확인하되, 응답은 동일하게 주는 방식이 안전합니다.
🔒 개선 예시 (응답 통일, 미가입은 메일 미발송)
public EmailSentResponse sendEmailForPwd(String toEmail) {
- if (userRepository.existsByEmail(toEmail)) { //이미 회원가입 되어있는 것이 확인되면
- return emailSendTemplate(toEmail); //정상적으로 이메일 발송
- } else { //만약 회원가입 되어있지 않다면
- throw new UserException(UserErrorCode.USER_NOT_FOUND); //예외발생
- }
+ if (!userRepository.existsByEmail(toEmail)) {
+ // 존재 여부는 체크하되, 응답은 동일하게 반환 (계정 열거 방지)
+ return new EmailSentResponse("인증코드를 이메일로 전송했습니다.", toEmail, 300L);
+ }
+ return emailSendTemplate(toEmail);
}🤖 Prompt for AI Agents
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/EmailService.java`
around lines 40 - 46, The sendEmailForPwd method leaks account existence by
throwing UserException when userRepository.existsByEmail(toEmail) is false;
change it so it always returns the same EmailSentResponse regardless of
existence: call emailSendTemplate(toEmail) only when exists, but when not exists
do not throw—return the identical success response and optionally log the
non-existent attempt internally. Update sendEmailForPwd to remove the
UserException path, keep userRepository.existsByEmail(toEmail) check to decide
whether to send the email, and ensure the public behavior and response shape
remain identical in both branches.
| //이미 회원가입 된 회원의 비밀번호 재설정 메서드 | ||
| public void passwordReset(String email, String password) { | ||
| //이메일 인증이 되어있는지 확인 | ||
| String isEmailVerified = redisUtil.getData("VERIFIED:" + email); | ||
|
|
||
| //인증이 안되었다면, | ||
| if (isEmailVerified == null || !isEmailVerified.equals("TRUE")) { | ||
| throw new UserException(UserErrorCode.USER_EMAIL_NOT_VERIFIED); //예외 발생 | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "VERIFIED:" src/main/java/com/whereyouad/WhereYouAd/domains/user -g '*.java' -B 2 -A 2Repository: WhereYouAd/WhereYouAd-Backend
Length of output: 2574
🏁 Script executed:
# UserService.java 파일 전체 읽기 (크기 확인)
wc -l src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/UserService.javaRepository: WhereYouAd/WhereYouAd-Backend
Length of output: 160
🏁 Script executed:
# 비밀번호 재설정 관련 메서드 전체 확인 (61-95 줄 범위)
sed -n '55,95p' src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/UserService.javaRepository: WhereYouAd/WhereYouAd-Backend
Length of output: 1255
🏁 Script executed:
# 회원가입/이메일 인증 로직에서 VERIFIED 키 설정하는 부분 찾기
rg -n "VERIFIED:" src/main/java/com/whereyouad/WhereYouAd/domains -g '*.java' -B 3 -A 3Repository: WhereYouAd/WhereYouAd-Backend
Length of output: 3508
비밀번호 재설정용 인증 키를 분리하세요 (보안 문제)
현재 코드의 심각한 보안 문제를 발견했습니다. VERIFIED:{email} 키가 회원가입과 비밀번호 재설정 두 가지 용도로 공유되고 있습니다.
공격 시나리오:
- 공격자가 피해자 이메일로 회원가입 시작 → 이메일 인증 완료 →
VERIFIED:user@example.com설정 (1시간 TTL) - 같은 시간 내에
passwordReset(user@example.com, "새_악의적_비밀번호")호출 - 별도 비밀번호 재설정 이메일 인증 없이 비밀번호 변경 성공
수정 방법:
비밀번호 재설정용 접두어를 분리하세요. 예를 들어:
- 이메일 인증:
VERIFIED:{email} - 비밀번호 재설정:
VERIFIED:PWD_RESET:{email}또는PASSWORD_RESET:{email}
EmailService에서 이메일 인증 설정 부분(line 109)은 회원가입 인증용으로만 유지하고, 비밀번호 재설정이 필요할 때는 별도의 Redis 키를 사용해야 합니다.
🔧 변경 예시
// UserService.java - passwordReset 메서드
- String isEmailVerified = redisUtil.getData("VERIFIED:" + email);
+ String isEmailVerified = redisUtil.getData("VERIFIED:PWD_RESET:" + email);
...
- redisUtil.deleteData("VERIFIED:" + email);
+ redisUtil.deleteData("VERIFIED:PWD_RESET:" + email);🤖 Prompt for AI Agents
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/UserService.java`
around lines 61 - 69, The passwordReset method currently relies on the same
Redis key ("VERIFIED:"+email) used for signup verification; separate the
concerns by checking a dedicated password-reset verification key instead (e.g.,
"PASSWORD_RESET:"+email or "VERIFIED:PWD_RESET:"+email). Update
UserService.passwordReset to read redisUtil.getData for the new password-reset
key and throw UserException(UserErrorCode.USER_EMAIL_NOT_VERIFIED) when that key
is missing/invalid; also update EmailService where password-reset emails are
generated (currently setting the signup key around line 109) to set the new
password-reset Redis key with appropriate TTL instead of the signup key. Ensure
all references to "VERIFIED:"+email remain for signup flows only and document
the new key name in the email-reset logic.
src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/EmailService.java
Show resolved
Hide resolved
src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/EmailService.java
Show resolved
Hide resolved
src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/EmailService.java
Show resolved
Hide resolved
src/main/java/com/whereyouad/WhereYouAd/domains/user/exception/handler/UserHandler.java
Show resolved
Hide resolved
| //무작위 인증코드 값 생성 | ||
| private String createCode() { | ||
| return String.valueOf((int)(Math.random() * (900000)) + 100000); | ||
| } |
There was a problem hiding this comment.
P3: 저도 SMS 인증 서비스(SmsService) 만들 때 이런 인증 코드 생성 로직을 구현해놨는데, 이건 공통 로직이니 global.utils 패키지에 CodeGenerator 같은 유틸 클래스를 하나 만들어서 추후에 공통 메서드 처리 하는 것도 괜찮을 것 같습니다!
There was a problem hiding this comment.
이 부분은 지금 제 PR 이랑 지민님 PR 다하고 나서 하는것도 좋을 것 같아요! 일단 Resolve 안하고 남겨놓을게요
|
P4: 말씀해주신 비밀번호 재사용 로직과 이메일 인증 API 반복해서 사용하는 것 모두 좋은 것 같습니다! |
src/main/java/com/whereyouad/WhereYouAd/domains/user/presentation/UserController.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/EmailService.java (1)
74-81:⚠️ Potential issue | 🟡 Minor비밀번호 재설정 메일 제목이 회원가입으로 고정되어 있어요.
현재 제목이 “회원가입 인증번호”로 고정돼 재설정 메일에서도 동일하게 보입니다.type을 제목에도 반영해 주세요.✉️ 수정 예시
- message.setSubject("whereyouad 회원가입 인증번호"); + message.setSubject("whereyouad " + type + " 인증번호");
이렇게 수정 진행했습니다! 머지 진행하겠습니다 |
📌 관련 이슈
🚀 개요
비밀번호를 재설정 하려는 계정에 대해 이메일 인증 진행 후 비밀번호 재설정하는 API 추가
📄 작업 내용
📸 스크린샷 / 테스트 결과 (선택)
===예외 처리===
1-1) redis 비어있는 상태 -> 진행된 이메일 인증 없음
1-2) 이메일 인증 없이 비밀번호 재설정 요청 시 예외처리

2-1) 이메일 test@example.com, 비밀번호 password 로 회원가입한 상태
2-2) 동일한 "password" 로 비밀번호 재설정 시도 시 예외처리

✅ 체크리스트
🔍 리뷰 포인트 (Review Points)
템플릿 느낌으로 메서드를 빼서 만들어봤는데 오류의 여지가 있을까요? (EmailService 내부 sendEmail(), sendEmailForPwd() 메서드)
이대로 해도 괜찮을까요 아니면 따로 추가 API 를 만드는 게 나을까요?
Summary by CodeRabbit
새 기능
버그 수정 / 유효성 검사
개선
✏️ Tip: You can customize this high-level summary in your review settings.