Skip to content

Conversation

@dungbik
Copy link
Contributor

@dungbik dungbik commented Jul 22, 2025

📝 변경 내용


✅ 체크리스트

  • 코드가 정상적으로 동작함
  • 테스트 코드 통과함
  • 문서(README 등)를 최신화함
  • 코드 스타일 가이드 준수

💬 기타 참고 사항

Summary by CodeRabbit

  • 신규 기능

    • 사용자가 자신의 비밀번호를 변경할 수 있는 기능이 추가되었습니다. (API 경로: /v1/users/me/password)
    • 비밀번호 변경 시 현재 비밀번호 검증 및 새로운 비밀번호 유효성 검사가 적용됩니다.
    • 이메일 인증 상태 검증 기능이 별도 서비스로 도입되어, 인증되지 않은 이메일 사용 시 접근이 제한됩니다.
    • 토큰 버전 관리 기능이 강화되어, 비밀번호 변경 및 회원 탈퇴 시 토큰 버전이 자동으로 갱신됩니다.
  • 버그 수정

    • 기존 회원 탈퇴 테스트가 내부 로직 변경에 맞게 수정되었습니다.
  • 테스트

    • 비밀번호 변경 기능에 대한 성공 및 실패 케이스 테스트가 추가되었습니다.

@dungbik dungbik requested a review from stoneTiger0912 July 22, 2025 07:58
@dungbik dungbik self-assigned this Jul 22, 2025
@dungbik dungbik added the enhancement New feature or request label Jul 22, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jul 22, 2025

"""

Walkthrough

비밀번호 변경 기능이 추가되었습니다. 사용자 컨트롤러에 비밀번호 변경 엔드포인트가 도입되었고, 관련 서비스와 엔티티, 요청 모델, 저장소, 테스트 코드가 확장되었습니다. 토큰 버전 관리 로직이 추가되어 비밀번호 변경 시 토큰 버전이 증가하도록 구현되었습니다.

Changes

파일/경로 요약 변경 내용 요약
.../auth/service/AuthService.java validatePasswordMatch 메서드가 private에서 public으로 변경되어 상단에 위치함.
.../auth/service/TokenVersionService.java incrementTokenVersion(long userId) 메서드 추가: 사용자 토큰 버전 증가 및 캐시 삭제 기능 도입.
.../auth/service/EmailVerificationService.java EmailVerificationService 클래스 추가: 이메일 인증 상태 검증 및 예외 처리 기능 포함.
.../auth/exception/AuthErrorCode.java UNVERIFIED_EMAIL 에러 코드 추가: 인증되지 않은 이메일 관련 예외 처리용.
.../user/controller/UserController.java /v1/users/me/password로 비밀번호 변경 PATCH 엔드포인트 추가.
.../user/entity/User.java changePassword(String password) 메서드 추가: 사용자 비밀번호 필드 갱신.
.../user/model/ChangePasswordRequest.java ChangePasswordRequest 레코드 신설: currentPassword, newPassword 필드와 유효성 검사 어노테이션 적용.
.../user/repository/UserRepository.java incrementTokenVersion(Long userId) 메서드 추가: JPQL로 토큰 버전 1 증가 쿼리 구현.
.../user/service/UserService.java changePassword(Long userId, ChangePasswordRequest req) 메서드 추가 및 이메일 인증 서비스, 토큰 버전 서비스 도입.
.../user/exception/UserErrorCode.java UNVERIFIED_EMAIL 에러 코드 제거: 이메일 인증 관련 에러 코드가 auth 패키지로 이동됨.
.../user/service/UserServiceTest.java 비밀번호 변경 관련 테스트 및 기존 unregister 테스트 수정, 의존성 모킹 및 예외 처리 테스트 추가.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UserController
    participant UserService
    participant AuthService
    participant TokenVersionService
    participant UserRepository

    User->>UserController: PATCH /v1/users/me/password (ChangePasswordRequest)
    UserController->>UserService: changePassword(userId, req)
    UserService->>UserRepository: findActiveUserById(userId)
    UserService->>AuthService: validatePasswordMatch(req.currentPassword, user.password)
    AuthService-->>UserService: (성공/실패)
    UserService->>User: changePassword(암호화된 newPassword)
    UserService->>TokenVersionService: incrementTokenVersion(userId)
    TokenVersionService->>UserRepository: incrementTokenVersion(userId)
    TokenVersionService->>TokenVersionRedisRepository: delete(userId)
    UserService-->>UserController: 완료(204 No Content)
    UserController-->>User: 응답 반환
Loading

Estimated code review effort

3 (~40 minutes)

Suggested reviewers

  • stoneTiger0912

Poem

🐇
비밀번호 바꿔요, 살랑살랑
새 토큰 버전, 깡충깡충
안전한 노트, 달빛 아래
테스트도 꼼꼼히,
FlipNote에 봄바람이!
🥕
"""

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@dungbik dungbik changed the title Feat/change password Feat: [FN-67] 내 비밀번호 재설정 Jul 22, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/main/java/project/flipnote/auth/service/AuthService.java (1)

88-92: 비밀번호 검증 메서드의 public 변경이 적절합니다.

비밀번호 변경 기능에서 재사용하기 위해 public으로 변경한 것은 합리적입니다. passwordEncoder.matches()를 사용하여 안전하게 구현되었습니다.

보안상 더 나은 접근을 위해 별도의 패스워드 검증 서비스를 고려해볼 수 있지만, 현재 구현도 충분히 안전합니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9c9cd0 and d877eb0.

📒 Files selected for processing (8)
  • src/main/java/project/flipnote/auth/service/AuthService.java (1 hunks)
  • src/main/java/project/flipnote/auth/service/TokenVersionService.java (1 hunks)
  • src/main/java/project/flipnote/user/controller/UserController.java (3 hunks)
  • src/main/java/project/flipnote/user/entity/User.java (1 hunks)
  • src/main/java/project/flipnote/user/model/ChangePasswordRequest.java (1 hunks)
  • src/main/java/project/flipnote/user/repository/UserRepository.java (2 hunks)
  • src/main/java/project/flipnote/user/service/UserService.java (3 hunks)
  • src/test/java/project/flipnote/user/service/UserServiceTest.java (4 hunks)
🔇 Additional comments (12)
src/main/java/project/flipnote/user/entity/User.java (1)

102-104: 비밀번호 변경 메서드가 적절히 구현되었습니다.

엔티티 메서드가 단순하게 필드만 업데이트하는 것은 좋은 설계입니다. 비밀번호 인코딩은 서비스 계층에서 처리되어야 합니다.

src/main/java/project/flipnote/auth/service/TokenVersionService.java (1)

29-32: 토큰 버전 증가 로직이 올바르게 구현되었습니다.

데이터베이스 업데이트 후 Redis 캐시를 무효화하는 패턴이 적절합니다. 비밀번호 변경 시 기존 토큰들을 무효화하는 보안 요구사항을 잘 충족합니다.

src/main/java/project/flipnote/user/repository/UserRepository.java (2)

6-6: @Modifying 어노테이션 임포트가 적절합니다.

JPQL 업데이트 쿼리에 필요한 어노테이션입니다.


26-28: 트랜잭션 컨텍스트 내 호출 확인 완료
토큰 버전 증가 쿼리가 효율적으로 구현되었습니다. 해당 incrementTokenVersion 메서드는 UserService.changePassword에 적용된 @Transactional(기본 readOnly 해제) 내에서 호출되어 트랜잭션 컨텍스트가 보장됩니다.
추가 조치가 필요 없으므로 이대로 머지해도 좋습니다.

src/main/java/project/flipnote/user/controller/UserController.java (2)

8-8: 필요한 임포트가 적절히 추가되었습니다.

@PatchMapping과 ChangePasswordRequest 임포트가 정확합니다.

Also applies to: 20-20


72-79: 비밀번호 변경 엔드포인트 검토 및 유효성 검증 확인 완료
비밀번호 변경 기능이 RESTful 원칙에 맞게 잘 구현되었습니다.
ChangePasswordRequest 모델의 currentPassword, newPassword 필드에 @ValidPassword 어노테이션이 적용되어 있어 입력값 유효성 검증이 정상적으로 동작합니다.

추가 보안 강화를 위해 다음 사항을 고려해 보세요:

  • 비밀번호 변경 요청에 대한 Rate Limiting 적용
  • 성공적인 비밀번호 변경 시 사용자에게 알림 이메일 발송
  • 비밀번호 변경 이력 및 시도 로그 기록
src/main/java/project/flipnote/user/model/ChangePasswordRequest.java (1)

5-12: 구현이 잘 되었습니다

레코드 클래스를 DTO로 사용하는 것이 적절하며, 두 비밀번호 필드 모두에 @ValidPassword 검증 어노테이션을 적용한 것이 보안상 좋습니다.

src/main/java/project/flipnote/user/service/UserService.java (2)

12-13: 새로운 의존성 추가가 적절합니다

비밀번호 변경 기능을 위해 AuthServiceTokenVersionService를 추가한 것이 적절하며, 기존 코드 스타일과 일치합니다.

Also applies to: 19-19, 36-37


101-110: 비밀번호 변경 로직이 보안적으로 잘 구현되었습니다

구현이 우수합니다:

  • 트랜잭션 범위 적용
  • 현재 비밀번호 검증 후 변경 허용
  • 새 비밀번호 암호화 저장
  • 토큰 버전 증가로 기존 토큰 무효화

보안 모범 사례를 잘 따르고 있습니다.

src/test/java/project/flipnote/user/service/UserServiceTest.java (3)

18-18: 새로운 Mock 객체 추가가 적절합니다

비밀번호 변경 기능 테스트를 위해 AuthServiceTokenVersionService Mock을 추가한 것이 적절합니다.

Also applies to: 21-22, 29-29, 56-61


167-168: Spy 객체를 사용한 검증 방식이 개선되었습니다

상태 검증 대신 행위 검증을 사용하는 것이 더 나은 테스트 방식입니다.


329-386: 비밀번호 변경 테스트가 포괄적으로 잘 작성되었습니다

테스트 커버리지가 우수합니다:

  • 성공 시나리오: 모든 상호작용 검증
  • 사용자 없음: 예외 처리 및 부작용 없음 확인
  • 잘못된 현재 비밀번호: 검증 실패 처리 및 부작용 없음 확인

각 테스트가 명확하고 적절한 Mock과 검증 패턴을 사용하고 있습니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/main/java/project/flipnote/auth/service/EmailVerificationService.java (2)

16-20: 메소드명과 동작의 일관성을 확인해 주세요.

validateVerified 메소드는 검증이 실패할 때 예외를 던지는 동작을 합니다. 메소드명이 boolean 반환을 암시할 수 있으므로, ensureVerified 또는 checkVerified와 같은 명명을 고려해보시기 바랍니다.

-	public void validateVerified(String email) {
+	public void ensureVerified(String email) {

17-18: 입력값 검증 추가를 고려해 주세요.

email 파라미터에 대한 null 체크나 빈 문자열 검증을 추가하는 것을 권장합니다.

	public void validateVerified(String email) {
+		if (email == null || email.trim().isEmpty()) {
+			throw new BizException(AuthErrorCode.INVALID_EMAIL);
+		}
		if (!emailVerificationRedisRepository.isVerified(email)) {
			throw new BizException(AuthErrorCode.UNVERIFIED_EMAIL);
		}
	}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d877eb0 and c5bced9.

📒 Files selected for processing (4)
  • src/main/java/project/flipnote/auth/exception/AuthErrorCode.java (1 hunks)
  • src/main/java/project/flipnote/auth/service/EmailVerificationService.java (1 hunks)
  • src/main/java/project/flipnote/user/exception/UserErrorCode.java (1 hunks)
  • src/main/java/project/flipnote/user/service/UserService.java (5 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/project/flipnote/auth/exception/AuthErrorCode.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/project/flipnote/user/service/UserService.java
🔇 Additional comments (2)
src/main/java/project/flipnote/auth/service/EmailVerificationService.java (1)

12-21: 이메일 검증 서비스 구현이 잘 설계되었습니다.

단일 책임 원칙을 잘 따르고 있으며, 인증 도메인으로 로직을 분리한 것이 적절합니다. 코드가 간결하고 이해하기 쉽습니다.

src/main/java/project/flipnote/user/exception/UserErrorCode.java (1)

11-15: 도메인 분리 리팩토링이 적절합니다.

UNVERIFIED_EMAIL 오류 코드를 AuthErrorCode로 이동시킨 것이 도메인 책임 분리 관점에서 올바른 접근입니다. 남은 오류 코드들은 사용자 도메인에 적합합니다.

Comment on lines 13 to +15
DUPLICATE_EMAIL(HttpStatus.CONFLICT, "USER_001", "이미 사용 중인 이메일입니다."),
DUPLICATE_PHONE(HttpStatus.CONFLICT, "USER_002", "이미 사용 중인 휴대전화 번호입니다."),
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER_003", "회원이 존재하지 않습니다."),
UNVERIFIED_EMAIL(HttpStatus.FORBIDDEN, "USER_004", "인증되지 않은 이메일입니다. 이메일 인증을 완료해 주세요.");

USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER_003", "회원이 존재하지 않습니다.");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

기존 코드에서 UNVERIFIED_EMAIL 사용 여부를 확인해 주세요.

UserErrorCode.UNVERIFIED_EMAIL을 사용하던 기존 코드가 모두 AuthErrorCode.UNVERIFIED_EMAIL로 업데이트되었는지 확인이 필요합니다.

다음 스크립트로 기존 사용처를 확인해 주세요:


🏁 Script executed:

#!/bin/bash
# Description: UserErrorCode.UNVERIFIED_EMAIL 사용처 확인

# UserErrorCode.UNVERIFIED_EMAIL 사용처 검색
rg -A 3 -B 3 "UserErrorCode\.UNVERIFIED_EMAIL"

# AuthErrorCode.UNVERIFIED_EMAIL로 변경된 부분 확인
rg -A 3 -B 3 "AuthErrorCode\.UNVERIFIED_EMAIL"

Length of output: 1574


테스트 코드에서 UserErrorCode.UNVERIFIED_EMAIL 참조 수정 필요

UserServiceTest에서 여전히 UserErrorCode.UNVERIFIED_EMAIL을 사용하고 있으므로, AuthErrorCode.UNVERIFIED_EMAIL로 변경해 주세요.

  • src/test/java/project/flipnote/user/service/UserServiceTest.java
    - assertThat(exception.getErrorCode()).isEqualTo(UserErrorCode.UNVERIFIED_EMAIL);
    + assertThat(exception.getErrorCode()).isEqualTo(AuthErrorCode.UNVERIFIED_EMAIL);
  • 필요한 경우 import 구문도 함께 수정하세요.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/test/java/project/flipnote/user/service/UserServiceTest.java, update all
references of UserErrorCode.UNVERIFIED_EMAIL to AuthErrorCode.UNVERIFIED_EMAIL
to reflect the correct error code usage. Also, modify the import statements
accordingly to import AuthErrorCode instead of UserErrorCode where necessary.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5bced9 and ef91f12.

📒 Files selected for processing (1)
  • src/test/java/project/flipnote/user/service/UserServiceTest.java (5 hunks)
🔇 Additional comments (6)
src/test/java/project/flipnote/user/service/UserServiceTest.java (6)

18-23: 새로운 서비스 계층 의존성 추가가 적절합니다.

서비스 계층 추상화로의 리팩토링을 반영하여 AuthService, EmailVerificationService, TokenVersionService를 추가한 것이 좋습니다. 이는 테스트가 구현 세부사항보다는 비즈니스 로직에 집중할 수 있게 해줍니다.


30-32: 새로운 모델 임포트가 적절합니다.

비밀번호 변경 기능을 위한 ChangePasswordRequest 모델 임포트가 추가되었습니다.


57-64: 서비스 계층 Mock 객체 추가가 적절합니다.

리팩토링된 아키텍처에 맞춰 서비스 계층의 Mock 객체들을 추가한 것이 좋습니다. 이는 테스트의 관심사 분리를 개선합니다.


146-151: 이메일 검증 테스트 로직 개선이 적절합니다.

직접적인 repository mocking에서 service layer의 예외 처리로 변경된 것이 좋습니다. 또한 에러 코드가 AuthErrorCode.UNVERIFIED_EMAIL로 이동된 것도 적절한 도메인 분리를 반영합니다.


170-171: 회원 탈퇴 테스트 개선이 적절합니다.

엔티티의 unregister() 메서드 호출과 토큰 버전 서비스의 incrementTokenVersion() 호출을 검증하는 것이 좋습니다. 이는 비즈니스 로직의 정확한 실행을 보장합니다.


331-388: 비밀번호 변경 테스트 구현이 우수합니다.

새로운 ChangePassword 테스트 클래스가 다음과 같은 우수한 특징을 보여줍니다:

  1. 포괄적인 시나리오 커버리지: 성공 케이스와 두 가지 실패 케이스를 모두 포함
  2. 적절한 Mock 활용: spy()를 사용하여 엔티티 메서드 호출 검증
  3. 서비스 계층 테스트: AuthService.validatePasswordMatch() 검증
  4. 부작용 방지 검증: 실패 시 불필요한 연산이 수행되지 않음을 확인

특히 라인 364-366과 385-386에서 실패 케이스에 대해 불필요한 연산이 수행되지 않음을 검증하는 것이 좋은 테스트 관행입니다.


verify(passwordEncoder, never()).matches(anyString(), anyString());
verify(passwordEncoder, never()).encode(anyString());
verify(tokenVersionRedisRepository, never()).deleteTokenVersion(anyLong());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

토큰 버전 서비스 사용으로 업데이트가 필요합니다.

테스트에서 tokenVersionRedisRepository.deleteTokenVersion() 대신 tokenVersionService.incrementTokenVersion()을 검증해야 합니다. 현재 코드가 서비스 계층을 사용하도록 리팩토링되었기 때문입니다.

다음과 같이 수정하세요:

-			verify(tokenVersionRedisRepository, never()).deleteTokenVersion(anyLong());
+			verify(tokenVersionService, never()).incrementTokenVersion(anyLong());

Also applies to: 386-386

🤖 Prompt for AI Agents
In src/test/java/project/flipnote/user/service/UserServiceTest.java at lines 366
and 386, the test incorrectly verifies
tokenVersionRedisRepository.deleteTokenVersion() calls, but the code now uses
tokenVersionService.incrementTokenVersion(). Update the verify statements to
check that tokenVersionService.incrementTokenVersion() is called or not called
as appropriate, replacing all instances of deleteTokenVersion verification with
incrementTokenVersion verification to reflect the service layer refactor.

@dungbik dungbik merged commit 5e1654c into develop Jul 25, 2025
3 checks passed
@dungbik dungbik deleted the feat/change-password branch July 25, 2025 05:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants