Skip to content

Conversation

@dungbik
Copy link
Contributor

@dungbik dungbik commented Jul 6, 2025

📝 변경 내용


✅ 체크리스트

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

💬 기타 참고 사항

Summary by CodeRabbit

  • 신규 기능

    • 이메일 인증 코드 발송 및 확인 기능이 추가되었습니다.
    • 이메일 인증 메일이 HTML 템플릿으로 발송됩니다.
    • 사용자 등록 시 이메일 인증이 필수로 적용됩니다.
    • 전화번호 필드가 AES 방식으로 암호화되어 저장됩니다.
    • 비밀번호 및 휴대전화 번호 입력값에 대한 유효성 검증이 강화되었습니다.
    • 비동기 이메일 전송 및 재시도 기능이 도입되었습니다.
    • Redis 기반 이메일 인증 코드 저장 및 검증 기능이 추가되었습니다.
    • Resend 이메일 서비스 연동 및 Thymeleaf 템플릿 적용이 이루어졌습니다.
    • 비밀번호 및 휴대전화 번호에 대한 커스텀 검증 어노테이션이 추가되었습니다.
  • 버그 수정

    • 회원가입 시 중복 이메일, 중복 휴대전화, 미인증 이메일에 대한 오류 메시지가 개선되었습니다.
  • 구성 및 환경

    • Redis, Thymeleaf, Resend 이메일 서비스 등 다양한 외부 서비스 연동이 추가되었습니다.
    • 신규 Docker Compose 파일 및 테스트/운영 환경 설정이 업데이트되었습니다.
    • 비동기 처리 및 스레드풀 설정이 도입되었습니다.
    • AES 암호화 키 및 JWT, Resend API 키 등 보안 관련 설정이 환경변수로 관리되도록 변경되었습니다.
  • 테스트

    • 이메일 인증, 비밀번호/휴대전화 유효성 검사, 사용자 등록에 대한 단위 테스트가 추가되었습니다.
  • 기타

    • 코드 및 설정 파일의 구조가 개선되고, 일부 불필요한 클래스 및 필드가 정리되었습니다.
    • 사용자 등록 관련 DTO가 새롭게 정의되고 기존 클래스가 제거되었습니다.
    • 보안 설정에서 이메일 인증 관련 엔드포인트 접근 권한이 추가되었습니다.

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

coderabbitai bot commented Jul 6, 2025

Walkthrough

이 변경사항은 이메일 인증 기능, AES 암호화, 비밀번호/휴대전화 유효성 검사, Redis 연동, Resend 이메일 API 연동, 비동기 및 재시도 처리, 신규 DTO 및 테스트 코드 추가 등 인증 및 회원가입 전반을 대대적으로 확장합니다. 환경설정, 시큐리티, 엔티티, 컨트롤러, 서비스, 예외, 테스트 코드 등 프로젝트 전반에 걸쳐 파일이 추가·수정·삭제되었습니다.

Changes

파일/경로 요약 변경 내용 요약
build.gradle, application.yml, application-test.yml, docker-compose.local.yml 등 설정 Redis, Resend, Thymeleaf, AOP, spring-retry 등 의존성 및 환경설정 추가/수정, 환경변수 적용, 테스트용 설정 파일 신설
auth/controller, auth/model, auth/constants, auth/exception, auth/event, auth/listener, auth/repository, auth/service 이메일 인증 요청/확인 API, Redis 연동 인증코드 저장/검증, 이벤트 발행 및 비동기 이메일 전송, 인증 관련 예외 코드 추가, DTO 및 상수, 유효성 검증 로직 추가
infra/config, infra/email Resend API 연동 설정, 이메일 인증코드 발송 서비스 및 인터페이스 추가, Thymeleaf 템플릿 기반 이메일 본문 생성
common/config, common/constants, common/crypto, common/exception, common/response, common/security, common/validation Redis, AES 암호화, 비동기/재시도, 커스텀 예외 및 응답, 시큐리티 허용 경로 확장, 비밀번호/휴대전화 유효성 검사 어노테이션 및 Validator 추가
user/model, user/service, user/controller, user/entity 회원가입 DTO(UserRegisterDto) 삭제, 신규 DTO(UserRegisterRequest/Response) 도입, AES 암호화 적용, 이메일 인증 체크 및 검증, 서비스/컨트롤러 시그니처 및 로직 변경
templates/email/email-verification.html 이메일 인증번호 발송용 HTML 템플릿 추가(Thymeleaf)
테스트 코드(src/test/...) AuthService, UserService, 비밀번호/휴대전화 Validator에 대한 단위 테스트 신규 작성

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant AuthController
    participant AuthService
    participant EmailVerificationRedisRepository
    participant ApplicationEventPublisher
    participant EmailVerificationEventListener
    participant EmailService

    Client->>AuthController: POST /v1/auth/email (이메일 인증코드 요청)
    AuthController->>AuthService: sendEmailVerificationCode(req)
    AuthService->>EmailVerificationRedisRepository: 이메일 중복/코드 발급 여부 확인 및 저장
    AuthService->>ApplicationEventPublisher: EmailVerificationSendEvent 발행
    ApplicationEventPublisher->>EmailVerificationEventListener: handleEmailVerificationSendEvent
    EmailVerificationEventListener->>EmailService: sendEmailVerificationCode(to, code, ttl)
    EmailService-->>EmailVerificationEventListener: 이메일 발송 (Resend API)

    Client->>AuthController: POST /v1/auth/email/confirm (인증코드 확인)
    AuthController->>AuthService: confirmEmailVerificationCode(req)
    AuthService->>EmailVerificationRedisRepository: 인증코드 조회 및 검증, 코드 삭제, 이메일 verified 마킹
Loading
sequenceDiagram
    participant Client
    participant UserController
    participant UserService
    participant AuthService
    participant UserRepository

    Client->>UserController: POST /v1/users (회원가입)
    UserController->>UserService: register(req)
    UserService->>UserRepository: 이메일/휴대전화 중복 체크
    UserService->>AuthService: validateEmail(email)
    UserService->>UserRepository: User 저장 (휴대전화 AES 암호화)
    UserService->>AuthService: deleteVerifiedEmail(email)
    UserService-->>UserController: UserRegisterResponse 반환
Loading

Poem

🐇
이메일 인증 hoppity-hop,
Redis에 코드 저장,
Resend로 번쩍 발송,
AES로 휴대폰 꽁꽁,
비밀번호도 깐깐히 검사!
테스트도 빵빵하게 추가,
더 안전해진 가입의 나라!
🥕✨


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 98b9df0 and cf3a53e.

📒 Files selected for processing (1)
  • src/main/java/project/flipnote/common/crypto/AesCryptoProperties.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/project/flipnote/common/crypto/AesCryptoProperties.java
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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.

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: 12

🔭 Outside diff range comments (1)
src/main/java/project/flipnote/user/service/UserService.java (1)

52-54: 전화번호 검증 메서드도 정리된 전화번호를 사용해야 합니다

중복 검증 시 정리된 전화번호를 사용하도록 수정이 필요합니다.

 private void validatePhoneDuplicate(String phone) {
-    if (userRepository.existsByPhone(phone)) {
+    String cleanedPhone = phone == null ? null : phone.replaceAll("-", "");
+    if (userRepository.existsByPhone(cleanedPhone)) {
         throw new BizException(UserErrorCode.DUPLICATE_PHONE);
     }
 }
🧹 Nitpick comments (14)
src/main/java/project/flipnote/infra/email/EmailService.java (1)

3-6: 인터페이스 문서화 개선 제안

이메일 서비스 인터페이스가 잘 설계되었습니다. 다음과 같은 개선을 고려해보세요:

+/**
+ * 이메일 서비스 인터페이스
+ */
 public interface EmailService {
 
+	/**
+	 * 이메일 인증 코드를 전송합니다.
+	 * @param to 수신자 이메일 주소
+	 * @param code 인증 코드
+	 * @param ttl 인증 코드 유효시간 (초)
+	 * @throws EmailSendException 이메일 전송 실패 시
+	 */
 	void sendEmailVerificationCode(String to, String code, int ttl);
 }
docker-compose.local.yml (1)

1-17: Docker Compose 설정 개선 제안

기본적인 설정은 잘 구성되어 있습니다. 다음과 같은 개선을 고려해보세요:

  1. 버전 고정: latest 태그 대신 특정 버전 사용
  2. 데이터 지속성: 볼륨 마운트 추가
  3. 보안: 환경 변수 외부화
 services:
   flipnote-mysql:
-    image: mysql:latest
+    image: mysql:8.0
     container_name: flipnote-mysql
     ports:
       - "3306:3306"
+    volumes:
+      - flipnote-mysql-data:/var/lib/mysql
     environment:
       MYSQL_USERNAME: root
       MYSQL_ROOT_PASSWORD: root
       MYSQL_DATABASE: flipnote
       TZ: Asia/Seoul

   flipnote-redis:
-    image: redis:latest
+    image: redis:7-alpine
     container_name: flipnote-redis
     ports:
       - "6379:6379"
+    volumes:
+      - flipnote-redis-data:/data
+
+volumes:
+  flipnote-mysql-data:
+  flipnote-redis-data:
src/main/java/project/flipnote/common/exception/EmailSendException.java (1)

3-7: 예외 클래스 구현 승인 및 확장 제안

예외 클래스가 올바르게 구현되어 있습니다. 더 유연한 사용을 위해 추가 생성자를 고려해보세요:

 public class EmailSendException extends RuntimeException {
+	public EmailSendException(String message) {
+		super(message);
+	}
+
+	public EmailSendException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
 	public EmailSendException(Throwable cause) {
 		super(cause);
 	}
 }
src/main/java/project/flipnote/auth/event/EmailVerificationSendEvent.java (1)

4-5: 필드 검증 및 문서화를 고려해보세요.

이메일 주소와 인증 코드에 대한 기본적인 검증이나 JavaDoc 문서화를 추가하는 것을 고려해보세요.

+/**
+ * 이메일 인증 코드 발송 이벤트
+ * @param to 수신자 이메일 주소
+ * @param code 인증 코드
+ */
 public record EmailVerificationSendEvent(
+	@Email @NotBlank
 	String to,
+	@NotBlank
 	String code
 ) {
src/main/java/project/flipnote/common/validator/PhoneConstraintValidator.java (1)

11-11: 성능 최적화를 위해 Pattern 미리 컴파일을 고려해보세요.

PasswordConstraintValidator처럼 Pattern을 미리 컴파일하여 성능을 개선할 수 있습니다.

+	private static final Pattern PHONE_PATTERN = Pattern.compile("^010-\\d{4}-\\d{4}$");

-	private static final String PHONE_PATTERN = "^010-\\d{4}-\\d{4}$";

그리고 isValid 메소드에서:

-		return phone.matches(PHONE_PATTERN);
+		return PHONE_PATTERN.matcher(phone).matches();
src/test/java/project/flipnote/common/validator/PasswordConstraintValidatorTest.java (1)

22-28: 유효한 비밀번호 테스트 케이스 추가 확인

테스트 케이스가 잘 구성되어 있습니다. 허용되는 특수문자 목록을 주석으로 명시하면 테스트의 가독성이 더 좋아질 것입니다.

 @Test
 @DisplayName("유효한 비밀번호는 true를 반환한다")
 void validPassword() {
+	// 허용되는 특수문자: ! @ # $ ^ & * ( ) - _ + = [ ] { } | \ : ; " ' < > , . /
 	assertThat(validator.isValid("Abc12345!", context)).isTrue();
 	assertThat(validator.isValid("A1b2c3d4@", context)).isTrue();
 	assertThat(validator.isValid("aB1!abcd", context)).isTrue();
 }
src/main/java/project/flipnote/infra/config/ResendProperties.java (1)

9-16: 설정 프로퍼티 클래스가 올바르게 구현되었습니다.

Spring Boot ConfigurationProperties 패턴을 올바르게 따르고 있으며, 기존 AsyncProperties와 일관된 구조입니다.

API 키는 민감 정보이므로 프로덕션 환경에서는 환경 변수나 암호화된 설정 파일로 관리하는 것을 권장합니다.

src/main/java/project/flipnote/auth/repository/EmailVerificationRedisRepository.java (2)

49-49: 매직 스트링 대신 상수를 사용하세요

"1"과 같은 매직 스트링보다는 의미 있는 상수를 사용하는 것이 좋습니다.

+private static final String VERIFIED_MARKER = "verified";

 public void markAsVerified(String email) {
     String key = AuthRedisKey.EMAIL_VERIFIED.key(email);
     Duration ttl = AuthRedisKey.EMAIL_VERIFIED.getTtl();
 
-    emailRedisTemplate.opsForValue().set(key, "1", ttl);
+    emailRedisTemplate.opsForValue().set(key, VERIFIED_MARKER, ttl);
 }

25-29: hasKey() 결과에 대한 null 안전성 개선

hasKey() 메서드는 null을 반환할 수 있으므로 Boolean.TRUE.equals()를 사용하는 것이 더 안전합니다.

 public boolean existCode(String email) {
     String key = AuthRedisKey.EMAIL_CODE.key(email);
 
-    return emailRedisTemplate.hasKey(key);
+    return Boolean.TRUE.equals(emailRedisTemplate.hasKey(key));
 }

 public boolean isVerified(String email) {
     String key = AuthRedisKey.EMAIL_VERIFIED.key(email);
 
-    return emailRedisTemplate.hasKey(key);
+    return Boolean.TRUE.equals(emailRedisTemplate.hasKey(key));
 }

Also applies to: 52-56

src/main/java/project/flipnote/auth/service/AuthService.java (2)

89-93: 인증 코드 생성 로직 개선 필요

현재 구현은 큰 length 값에서 정수 오버플로우가 발생할 수 있습니다. 또한 더 간단한 방법으로 구현할 수 있습니다.

 private String generateVerificationCode(int length) {
-    int origin = (int)Math.pow(10, length - 1);
-    int bound = (int)Math.pow(10, length);
-    return String.valueOf(random.nextInt(origin, bound));
+    if (length <= 0 || length > 9) {
+        throw new IllegalArgumentException("Code length must be between 1 and 9");
+    }
+    StringBuilder code = new StringBuilder(length);
+    for (int i = 0; i < length; i++) {
+        code.append(RANDOM.nextInt(10));
+    }
+    // 첫 자리가 0이 아니도록 보장
+    if (code.charAt(0) == '0') {
+        code.setCharAt(0, (char)('1' + RANDOM.nextInt(9)));
+    }
+    return code.toString();
 }

71-71: 보안: 타이밍 공격 방지를 위한 안전한 문자열 비교

인증 코드 비교 시 타이밍 공격을 방지하기 위해 상수 시간 비교를 사용하는 것이 좋습니다.

+import java.security.MessageDigest;
+
-if (!Objects.equals(req.code(), code)) {
+if (!MessageDigest.isEqual(req.code().getBytes(), code.getBytes())) {
     throw new BizException(AuthErrorCode.INVALID_VERIFICATION_CODE);
 }
src/test/java/project/flipnote/user/service/UserServiceTest.java (3)

3-4: 정적 임포트 정리 필요

AssertionsForClassTypes와 Assertions를 모두 임포트하고 있습니다. 하나로 통일하는 것이 좋습니다.

-import static org.assertj.core.api.AssertionsForClassTypes.*;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.assertj.core.api.Assertions.*;

66-68: 테스트 데이터 상수 추출 제안

중복되는 테스트 데이터를 상수로 추출하면 유지보수가 더 쉬워집니다.

클래스 상단에 다음 상수들을 추가하세요:

private static final String TEST_EMAIL = "test@test.com";
private static final String TEST_PASSWORD = "testPass";
private static final String TEST_NAME = "테스트";
private static final String TEST_NICKNAME = "테스트";
private static final String TEST_PHONE = "010-1234-5678";

Also applies to: 86-88, 102-104, 118-120


75-81: 테스트 개선: 실제 인자 검증

any(String.class) 대신 실제 이메일 값을 검증하는 것이 더 정확한 테스트입니다.

-verify(authService, times(1)).validateEmail(any(String.class));
-verify(authService, times(1)).deleteVerifiedEmail(any(String.class));
+verify(authService, times(1)).validateEmail(req.email());
+verify(authService, times(1)).deleteVerifiedEmail(req.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 62cbcbc and e8f4654.

📒 Files selected for processing (40)
  • build.gradle (1 hunks)
  • docker-compose.local.yml (1 hunks)
  • src/main/java/project/flipnote/auth/constants/AuthRedisKey.java (1 hunks)
  • src/main/java/project/flipnote/auth/constants/VerificationConstants.java (1 hunks)
  • src/main/java/project/flipnote/auth/controller/AuthController.java (2 hunks)
  • src/main/java/project/flipnote/auth/event/EmailVerificationSendEvent.java (1 hunks)
  • src/main/java/project/flipnote/auth/exception/AuthErrorCode.java (1 hunks)
  • src/main/java/project/flipnote/auth/listener/EmailVerificationEventListener.java (1 hunks)
  • src/main/java/project/flipnote/auth/model/EmailVerificationConfirmDto.java (1 hunks)
  • src/main/java/project/flipnote/auth/model/EmailVerificationDto.java (1 hunks)
  • src/main/java/project/flipnote/auth/model/UserLoginDto.java (1 hunks)
  • src/main/java/project/flipnote/auth/repository/EmailVerificationRedisRepository.java (1 hunks)
  • src/main/java/project/flipnote/auth/service/AuthService.java (3 hunks)
  • src/main/java/project/flipnote/common/annotation/ValidPassword.java (1 hunks)
  • src/main/java/project/flipnote/common/annotation/ValidPhone.java (1 hunks)
  • src/main/java/project/flipnote/common/config/AsyncConfig.java (1 hunks)
  • src/main/java/project/flipnote/common/config/AsyncProperties.java (1 hunks)
  • src/main/java/project/flipnote/common/config/RedisConfig.java (1 hunks)
  • src/main/java/project/flipnote/common/constants/RedisKeys.java (1 hunks)
  • src/main/java/project/flipnote/common/exception/EmailSendException.java (1 hunks)
  • src/main/java/project/flipnote/common/exception/GlobalExceptionHandler.java (1 hunks)
  • src/main/java/project/flipnote/common/response/ApiResponse.java (1 hunks)
  • src/main/java/project/flipnote/common/security/config/SecurityConfig.java (1 hunks)
  • src/main/java/project/flipnote/common/security/filter/ExceptionHandlerFilter.java (1 hunks)
  • src/main/java/project/flipnote/common/util/CookieUtil.java (1 hunks)
  • src/main/java/project/flipnote/common/validator/PasswordConstraintValidator.java (1 hunks)
  • src/main/java/project/flipnote/common/validator/PhoneConstraintValidator.java (1 hunks)
  • src/main/java/project/flipnote/infra/config/ResendConfig.java (1 hunks)
  • src/main/java/project/flipnote/infra/config/ResendProperties.java (1 hunks)
  • src/main/java/project/flipnote/infra/email/EmailService.java (1 hunks)
  • src/main/java/project/flipnote/infra/email/ResendEmailService.java (1 hunks)
  • src/main/java/project/flipnote/user/model/UserRegisterDto.java (1 hunks)
  • src/main/java/project/flipnote/user/service/UserService.java (2 hunks)
  • src/main/resources/application-local.yml (1 hunks)
  • src/main/resources/application-test.yml (1 hunks)
  • src/main/resources/application.yml (2 hunks)
  • src/main/resources/templates/email/email-verification.html (1 hunks)
  • src/test/java/project/flipnote/common/validator/PasswordConstraintValidatorTest.java (1 hunks)
  • src/test/java/project/flipnote/common/validator/PhoneConstraintValidatorTest.java (1 hunks)
  • src/test/java/project/flipnote/user/service/UserServiceTest.java (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (10)
src/main/java/project/flipnote/common/config/AsyncProperties.java (1)
src/main/java/project/flipnote/infra/config/ResendProperties.java (1)
  • Getter (9-16)
src/main/java/project/flipnote/common/annotation/ValidPhone.java (1)
src/main/java/project/flipnote/common/validator/PhoneConstraintValidator.java (1)
  • PhoneConstraintValidator (9-21)
src/main/java/project/flipnote/common/annotation/ValidPassword.java (1)
src/main/java/project/flipnote/common/validator/PasswordConstraintValidator.java (1)
  • PasswordConstraintValidator (10-24)
src/main/java/project/flipnote/infra/email/ResendEmailService.java (4)
src/main/java/project/flipnote/auth/service/AuthService.java (1)
  • RequiredArgsConstructor (24-94)
src/main/java/project/flipnote/infra/config/ResendConfig.java (1)
  • RequiredArgsConstructor (10-20)
src/main/java/project/flipnote/auth/listener/EmailVerificationEventListener.java (1)
  • Slf4j (17-38)
src/main/java/project/flipnote/common/exception/EmailSendException.java (1)
  • EmailSendException (3-7)
src/main/java/project/flipnote/infra/config/ResendProperties.java (1)
src/main/java/project/flipnote/common/config/AsyncProperties.java (1)
  • Getter (9-17)
src/main/java/project/flipnote/auth/controller/AuthController.java (2)
src/main/java/project/flipnote/auth/model/EmailVerificationConfirmDto.java (1)
  • EmailVerificationConfirmDto (8-21)
src/main/java/project/flipnote/auth/model/EmailVerificationDto.java (1)
  • EmailVerificationDto (6-14)
src/main/java/project/flipnote/common/config/AsyncConfig.java (5)
src/main/java/project/flipnote/common/config/RedisConfig.java (1)
  • Configuration (9-20)
src/main/java/project/flipnote/auth/service/AuthService.java (1)
  • RequiredArgsConstructor (24-94)
src/main/java/project/flipnote/auth/repository/EmailVerificationRedisRepository.java (1)
  • RequiredArgsConstructor (12-63)
src/main/java/project/flipnote/common/security/config/SecurityConfig.java (1)
  • RequiredArgsConstructor (33-106)
src/main/java/project/flipnote/infra/config/ResendConfig.java (1)
  • RequiredArgsConstructor (10-20)
src/main/java/project/flipnote/auth/listener/EmailVerificationEventListener.java (4)
src/main/java/project/flipnote/auth/service/AuthService.java (1)
  • RequiredArgsConstructor (24-94)
src/main/java/project/flipnote/common/config/AsyncConfig.java (1)
  • RequiredArgsConstructor (12-29)
src/main/java/project/flipnote/infra/email/ResendEmailService.java (1)
  • Slf4j (16-47)
src/main/java/project/flipnote/common/exception/EmailSendException.java (1)
  • EmailSendException (3-7)
src/main/java/project/flipnote/user/service/UserService.java (1)
src/main/java/project/flipnote/user/model/UserRegisterDto.java (1)
  • UserRegisterDto (9-47)
src/test/java/project/flipnote/user/service/UserServiceTest.java (1)
src/main/java/project/flipnote/user/model/UserRegisterDto.java (1)
  • UserRegisterDto (9-47)
🪛 Gitleaks (8.26.0)
src/main/resources/application-local.yml

23-23: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (43)
src/main/java/project/flipnote/common/security/filter/ExceptionHandlerFilter.java (1)

44-44: 파일 끝 개행 문자 추가 승인

파일 끝에 개행 문자를 추가하는 것은 좋은 코딩 관례입니다.

src/main/java/project/flipnote/common/response/ApiResponse.java (1)

11-11: Import 문 순서 정리 승인

프로젝트 내부 import를 외부 라이브러리 import 아래에 배치하는 것은 좋은 코딩 관례입니다.

src/main/java/project/flipnote/auth/event/EmailVerificationSendEvent.java (1)

3-7: 깔끔한 이벤트 레코드 구현입니다.

이메일 인증 이벤트를 위한 간단하고 명확한 레코드 구현입니다. 불변성과 간결성을 잘 유지했습니다.

src/main/resources/application-test.yml (2)

18-21: Redis 설정이 적절하게 추가되었습니다.

테스트 환경에서 사용할 Redis 설정이 올바르게 구성되어 있습니다. localhost와 기본 포트 6379 사용이 적절합니다.


26-27: 테스트용 더미 API 키 설정이 적절합니다.

Resend API 연동을 위한 더미 키 설정이 테스트 환경에 적합합니다.

src/main/java/project/flipnote/common/util/CookieUtil.java (1)

11-18: 가독성 개선을 위한 좋은 포맷팅 변경입니다.

메소드 파라미터를 여러 줄로 분할하여 가독성이 향상되었습니다. 많은 파라미터가 있을 때 적절한 포맷팅입니다.

src/main/java/project/flipnote/auth/model/UserLoginDto.java (1)

10-14: 검증 강화를 위한 우수한 개선사항입니다.

@NotEmpty에서 @NotBlank로 변경하여 공백 문자만 있는 입력도 방지할 수 있게 되었습니다. 로그인 보안 강화에 도움이 됩니다.

build.gradle (2)

31-38: 새로운 기능 지원을 위한 적절한 의존성 추가입니다.

이메일 인증 기능 구현에 필요한 의존성들이 올바르게 추가되었습니다:

  • Redis: 인증 코드 저장
  • Thymeleaf: 이메일 템플릿
  • AOP/Spring Retry: 재시도 로직
  • Resend Java: 이메일 서비스 연동

38-38: com.resend:resend-java 의존성 버전 및 보안 검증 필요

  • build.gradle 38행:
    implementation 'com.resend:resend-java:4.1.1'
    → 해당 버전이 최신 릴리즈인지, 공개된 보안 취약점(CVE 등)은 없는지 확인해주세요.
src/main/java/project/flipnote/common/config/RedisConfig.java (1)

9-20: Redis 설정이 적절하게 구현되었습니다.

이메일 인증을 위한 Redis 설정이 올바르게 구현되었습니다. StringRedisSerializer를 사용하여 키와 값을 모두 문자열로 처리하는 것이 이메일 인증 코드 저장 용도에 적합합니다.

src/main/java/project/flipnote/common/exception/GlobalExceptionHandler.java (1)

42-44: 코드 가독성 개선.

메서드 시그니처를 여러 줄로 포맷팅하여 가독성이 향상되었습니다.

src/main/java/project/flipnote/auth/model/EmailVerificationDto.java (1)

6-14: 이메일 검증 DTO가 적절하게 구현되었습니다.

Record를 사용한 구조와 @Email, @NotBlank 검증 어노테이션이 적절하게 적용되어 있습니다. 이메일 인증 요청을 위한 간단하고 명확한 구조입니다.

src/main/java/project/flipnote/auth/constants/AuthRedisKey.java (1)

9-15: Redis 키 상수 정의가 적절합니다.

이메일 인증 코드와 인증 상태를 위한 Redis 키 패턴이 잘 정의되어 있습니다. TTL 값도 적절하게 설정되었습니다 (인증 코드는 더 짧은 시간, 인증 상태는 더 긴 시간).

src/main/resources/application-local.yml (2)

25-26: 더미 API 키는 적절합니다.

로컬 개발환경용 더미 API 키로 적절하게 설정되어 있습니다. 실제 운영 환경에서는 올바른 API 키로 교체되어야 합니다.


1-21: 로컬 개발 환경 설정이 적절합니다.

데이터베이스, Redis, JPA 설정이 로컬 개발 환경에 적합하게 구성되어 있습니다. Hibernate의 SQL 로깅 설정도 개발 시 유용할 것입니다.

src/main/java/project/flipnote/common/security/config/SecurityConfig.java (1)

60-63: 이메일 인증 엔드포인트 보안 설정이 올바르게 구성되었습니다.

회원가입 플로우에서 이메일 인증 기능을 위해 인증 없이 접근할 수 있도록 설정된 것이 적절합니다.

src/main/java/project/flipnote/common/annotation/ValidPassword.java (1)

1-19: 비밀번호 유효성 검증 어노테이션이 올바르게 구현되었습니다.

표준 Jakarta Bean Validation 패턴을 따르고 있으며, 필드, 메소드, 어노테이션 타입에 적용할 수 있도록 적절히 설정되었습니다.

src/main/java/project/flipnote/common/annotation/ValidPhone.java (1)

1-19: 휴대전화 번호 유효성 검증 어노테이션이 올바르게 구현되었습니다.

ValidPassword와 일관성 있는 구조로 작성되었으며, 표준 validation 어노테이션 패턴을 따르고 있습니다.

src/main/java/project/flipnote/common/validator/PasswordConstraintValidator.java (1)

10-24: 비밀번호 유효성 검증 로직이 잘 구현되었습니다.

정규표현식을 사용하여 영문자, 숫자, 특수문자를 포함한 8-16자 길이의 비밀번호를 요구하고 있으며, Pattern을 미리 컴파일하여 성능을 최적화한 점이 좋습니다.

src/main/java/project/flipnote/common/validator/PhoneConstraintValidator.java (1)

14-17: 전화번호 null 처리 동작을 명확히 검토해주세요.

현재 null 값에 대해 true를 반환하여 전화번호를 선택적 필드로 처리하고 있습니다. 이것이 의도된 동작인지 확인하고, 필요시 @NotNull 어노테이션과 함께 사용하는 것을 고려해보세요.

src/main/java/project/flipnote/auth/constants/VerificationConstants.java (1)

6-10: 상수 정의가 적절합니다

유틸리티 클래스 패턴을 올바르게 구현했습니다. 인증 코드 길이 6자리와 TTL 5분은 보안성과 사용성의 균형을 잘 맞춘 설정입니다.

src/main/resources/templates/email/email-verification.html (1)

1-72: 이메일 템플릿이 잘 구성되어 있습니다.

HTML 구조가 깔끔하고 이메일 클라이언트 호환성을 고려한 임베디드 CSS 스타일링이 적절합니다. Thymeleaf 표현식도 올바르게 사용되었습니다.

src/test/java/project/flipnote/common/validator/PhoneConstraintValidatorTest.java (1)

13-70: 휴대폰 번호 유효성 검증 테스트가 포괄적입니다.

다양한 유효/무효 케이스를 모두 다루고 있으며, 한국어 디스플레이 이름으로 테스트 의도가 명확합니다. 테스트 커버리지도 우수합니다.

src/main/java/project/flipnote/user/model/UserRegisterDto.java (1)

13-36: 유효성 검증 어노테이션 업데이트가 적절합니다.

@NotEmpty에서 @NotBlank로 변경하여 공백 문자열도 검증하도록 개선되었고, 커스텀 유효성 검증 어노테이션 사용으로 더 엄격한 검증이 가능합니다. getCleanedPhone() 메서드 추가도 좋은 개선사항입니다.

src/main/java/project/flipnote/common/config/AsyncConfig.java (2)

1-11: import 구문이 잘 정리되어 있습니다.

필요한 Spring 비동기 설정 관련 클래스들이 적절히 import되어 있습니다.


12-17: 비동기 설정 클래스가 적절히 구현되었습니다.

@Configuration, @EnableAsync, @RequiredArgsConstructor 어노테이션이 올바르게 사용되었고, AsyncConfigurer 인터페이스를 구현하여 커스텀 비동기 실행기를 제공합니다.

src/main/java/project/flipnote/auth/model/EmailVerificationConfirmDto.java (2)

1-6: 필요한 validation 관련 import가 적절히 구성되어 있습니다.

Jakarta validation과 VerificationConstants 클래스가 올바르게 import되어 있습니다.


8-21: 검증 어노테이션이 적절히 적용되었습니다.

@Email, @NotBlank, @Size 어노테이션을 사용하여 이메일과 인증 코드의 유효성을 검증하고 있습니다. 특히 인증 코드의 길이를 VerificationConstants.CODE_LENGTH로 제한하는 것이 적절합니다.

src/main/java/project/flipnote/auth/listener/EmailVerificationEventListener.java (3)

1-15: 필요한 Spring 관련 import가 적절히 구성되어 있습니다.

이벤트 리스너, 재시도, 비동기 처리에 필요한 모든 클래스들이 올바르게 import되어 있습니다.


17-22: 이벤트 리스너 클래스가 적절히 구성되었습니다.

@Component, @RequiredArgsConstructor, @Slf4j 어노테이션이 올바르게 사용되었고, EmailService 의존성이 주입되었습니다.


34-37: 복구 메서드가 적절히 구현되었습니다.

재시도 실패 시 로그를 남기는 복구 메서드가 올바르게 구현되어 있습니다.

src/main/resources/application.yml (7)

7-9: 데이터소스 설정이 환경 변수로 외부화되었습니다.

설정 값들이 비워져 있어 환경 변수나 외부 설정 파일에서 주입받는 것으로 보입니다. 이는 보안 측면에서 적절한 접근입니다.


14-14: JPA ddl-auto 설정이 validate로 변경되었습니다.

프로덕션 환경에 적합한 validate 모드로 변경되어 안전성이 향상되었습니다.


20-25: Thymeleaf 설정이 적절히 구성되었습니다.

이메일 템플릿 처리를 위한 Thymeleaf 설정이 올바르게 추가되었습니다.


27-31: Redis 설정이 환경 변수로 외부화되었습니다.

Redis 연결 정보가 외부 설정으로 관리되어 보안성이 확보되었습니다.


41-41: JWT 시크릿이 환경 변수로 외부화되었습니다.

보안에 중요한 JWT 시크릿이 외부 설정으로 관리되어 적절합니다.


45-48: 비동기 설정이 적절히 구성되었습니다.

스레드 풀 설정이 합리적인 값으로 구성되어 있습니다.


50-52: Resend 설정이 환경 변수로 외부화되었습니다.

API 키가 외부 설정으로 관리되어 보안성이 확보되었습니다.

src/main/java/project/flipnote/auth/controller/AuthController.java (3)

12-13: 새로운 DTO 클래스가 적절히 import되었습니다.

이메일 인증 기능을 위한 DTO 클래스들이 올바르게 import되었습니다.


46-51: 이메일 인증 코드 발송 엔드포인트가 적절히 구현되었습니다.

@Valid 어노테이션을 사용하여 요청 데이터의 유효성 검증을 수행하고, 적절한 HTTP 상태 코드를 반환합니다.


53-60: 이메일 인증 코드 확인 엔드포인트가 적절히 구현되었습니다.

요청 데이터 검증과 적절한 HTTP 응답이 구현되어 있습니다.

src/main/java/project/flipnote/auth/exception/AuthErrorCode.java (1)

13-17: 에러 코드 추가가 적절합니다!

이메일 인증 기능에 필요한 에러 코드들이 명확한 HTTP 상태 코드와 설명적인 메시지와 함께 잘 정의되었습니다.

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

34-34: SecureRandom 인스턴스는 final로 선언해야 합니다

스레드 안전성과 불변성을 보장하기 위해 static 필드는 final로 선언하는 것이 좋습니다.

-private static final SecureRandom random = new SecureRandom();
+private static final SecureRandom RANDOM = new SecureRandom();

메서드에서도 해당 상수 사용:

-return String.valueOf(random.nextInt(origin, bound));
+return String.valueOf(RANDOM.nextInt(origin, bound));

Likely an incorrect or invalid review comment.

Comment on lines +38 to +43
@Test
@DisplayName("허용되지 않은 특수문자 포함 시 false")
void invalidPassword_wrongSpecialChar() {
assertThat(validator.isValid("Abc12345%", context)).isFalse();
assertThat(validator.isValid("Abc12345?", context)).isFalse();
}
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

허용되지 않은 특수문자 테스트 완성도 확인

허용되지 않은 특수문자에 대한 테스트가 있지만, 실제 검증기에서 허용하는 특수문자 목록을 확인해야 합니다.

다음 스크립트로 실제 검증기의 허용 특수문자 목록을 확인해주세요:


🏁 Script executed:

#!/bin/bash
# PasswordConstraintValidator에서 허용되는 특수문자 패턴 확인
rg -A 10 "class PasswordConstraintValidator" --type java

Length of output: 2630


PasswordConstraintValidator 허용 특수문자 패턴 확인 및 테스트 보완

PasswordConstraintValidator.java에서 허용하는 특수문자 목록이 아래와 같음을 확인했습니다.
^(?=.[A-Za-z])(?=.\d)(?=.[!@#$^()-])[A-Za-z\d!@#$^*()-]{8,16}$
→ 허용 특수문자: ! @ # $ ^ * ( ) _ -
현재 테스트에서 %?가 올바르게 차단됨을 확인했으나, 완성도를 위해 허용 특수문자 각각에 대한 성공 케이스 테스트를 추가할 것을 권장드립니다.

• src/main/java/project/flipnote/common/validator/PasswordConstraintValidator.java
– 허용 특수문자 패턴: !@#$^*()_-
• src/test/java/project/flipnote/common/validator/PasswordConstraintValidatorTest.java
– 각 허용 특수문자(!, @, #, $, ^, *, (, ), _, -)를 포함하는 비밀번호에 대해 validator.isValid(..., context).isTrue() 테스트 추가

🤖 Prompt for AI Agents
In
src/test/java/project/flipnote/common/validator/PasswordConstraintValidatorTest.java
around lines 38 to 43, the test currently verifies that disallowed special
characters '%' and '?' cause validation to fail. To improve test coverage, add
separate test cases that assert validator.isValid returns true for passwords
containing each allowed special character (!, @, #, $, ^, *, (, ), _, -). This
ensures that all permitted special characters are explicitly tested for
successful validation.

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/infra/config/ResendProperties.java (1)

14-21: 클래스 문서화를 추가하는 것을 권장합니다.

코드의 가독성과 유지보수성을 향상시키기 위해 클래스와 필드에 대한 JavaDoc 문서를 추가하는 것이 좋겠습니다.

다음과 같이 문서화를 추가할 수 있습니다:

+/**
+ * Resend 이메일 서비스 구성 속성
+ * 
+ * @author FlipNote Team
+ */
 @Getter
 @Setter
 @ConfigurationProperties("resend")
 @Component
 public class ResendProperties {

+    /**
+     * 이메일 발신자 주소
+     */
     @NotEmpty
     private String fromEmail;

+    /**
+     * Resend API 키
+     */
     @NotEmpty
     private String apiKey;
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 65d4d39 and 8a47f7c.

📒 Files selected for processing (1)
  • src/main/java/project/flipnote/infra/config/ResendProperties.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (1)
src/main/java/project/flipnote/infra/config/ResendProperties.java (1)

10-21: 클래스 구조와 구성이 적절합니다.

Spring Boot 구성 속성 클래스의 베스트 프랙티스를 잘 따르고 있습니다. @ConfigurationProperties@Component 어노테이션의 사용이 적절하고, @NotEmpty 검증 어노테이션으로 필수 속성들을 올바르게 검증하고 있습니다.

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: 2

♻️ Duplicate comments (1)
src/main/java/project/flipnote/common/config/AsyncProperties.java (1)

18-25: 기본값 설정 및 추가 검증 로직이 필요합니다.

@positive 제약 조건이 추가되어 이전 리뷰 의견의 일부가 해결되었지만, 여전히 기본값이 없어 설정이 누락된 경우 시작 시 검증 오류가 발생할 수 있습니다. 또한 스레드 풀 설정의 논리적 일관성 검증도 필요합니다.

다음과 같이 개선하는 것을 권장합니다:

 @Getter
 @Setter
 @Validated
 @ConfigurationProperties(prefix = "async")
 @Component
 public class AsyncProperties {

 	@Positive
-	private int corePoolSize;
+	private int corePoolSize = 2;

 	@Positive
-	private int maxPoolSize;
+	private int maxPoolSize = 10;

 	@Positive
-	private int queueCapacity;
+	private int queueCapacity = 100;
+
+	@AssertTrue(message = "Max pool size must be greater than or equal to core pool size")
+	private boolean isMaxPoolSizeValid() {
+		return maxPoolSize >= corePoolSize;
+	}
 }

추가로 jakarta.validation.constraints.AssertTrue import가 필요합니다.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4a533d0 and 51a9f8a.

📒 Files selected for processing (3)
  • src/main/java/project/flipnote/common/config/AsyncProperties.java (1 hunks)
  • src/main/java/project/flipnote/infra/config/ResendProperties.java (1 hunks)
  • src/main/resources/application-test.yml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/project/flipnote/infra/config/ResendProperties.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/project/flipnote/common/config/AsyncProperties.java (1)
src/main/java/project/flipnote/infra/config/ResendProperties.java (1)
  • Getter (11-23)
🪛 Gitleaks (8.26.0)
src/main/resources/application-test.yml

24-24: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (3)
src/main/java/project/flipnote/common/config/AsyncProperties.java (2)

11-16: 클래스 선언 및 어노테이션이 올바릅니다.

Spring Boot 설정 프로퍼티 클래스의 모범 사례를 잘 따르고 있습니다. @validated 어노테이션이 추가되어 필드 유효성 검증이 활성화됩니다.


1-27: 전체적인 클래스 구조가 프로젝트 패턴과 일치합니다.

ResendProperties 클래스와 동일한 구조와 어노테이션 패턴을 사용하여 프로젝트 내 일관성을 유지하고 있습니다. Lombok 사용으로 보일러플레이트 코드도 적절히 줄였습니다.

src/main/resources/application-test.yml (1)

26-27: Resend API 키도 하드코딩되어 있습니다 – 환경 분리 필요
dummy_api_key 라고 명시돼 있지만, 실환경 배포 시 ENV 로 주입되지 않으면 메일 발송 실패가 발생합니다. resend.api-key=${RESEND_API_KEY:dummy} 형태로 바꿔 두는 것이 안전합니다. 필요 시 CI 환경 변수를 확인해 주세요.

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)

102-116: 유틸리티 메서드들 및 코드 생성 로직 검토

코드 생성 로직에서 개선이 필요합니다:

-private String generateVerificationCode(int length) {
-	int origin = (int)Math.pow(10, length - 1);
-	int bound = (int)Math.pow(10, length);
-	return String.valueOf(random.nextInt(origin, bound));
-}
+private String generateVerificationCode(int length) {
+	int origin = (int)Math.pow(10, length - 1);
+	int bound = (int)Math.pow(10, length);
+	return String.format("%0" + length + "d", random.nextInt(origin, bound));
+}

현재 구현에서는 앞자리가 0인 코드가 생성될 수 있지만, 실제로는 Math.pow 계산으로 인해 항상 지정된 길이가 보장됩니다. 하지만 String.format을 사용하면 더 명확하고 안전합니다.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 461b512 and 4d2eeba.

📒 Files selected for processing (1)
  • src/main/java/project/flipnote/auth/service/AuthService.java (3 hunks)
🔇 Additional comments (6)
src/main/java/project/flipnote/auth/service/AuthService.java (6)

3-4: 새로운 import 추가 승인

SecureRandom과 Objects 클래스 import가 적절하게 추가되었습니다. 보안을 위한 SecureRandom 사용은 좋은 선택입니다.


6-18: 이메일 인증 관련 의존성 추가 승인

이메일 인증 기능을 위한 새로운 의존성들이 적절하게 추가되었습니다:

  • ApplicationEventPublisher: 이벤트 기반 이메일 발송을 위한 좋은 패턴
  • 각종 DTO와 Repository: 기능 구현에 필요한 컴포넌트들

31-34: 의존성 주입 및 SecureRandom 인스턴스 승인

새로운 의존성들이 적절하게 주입되었고, static final SecureRandom 인스턴스 사용은 보안 측면에서 적절한 선택입니다.


39-53: 비밀번호 검증 로직 추출 승인

비밀번호 검증 로직을 별도 메서드로 추출한 것은 코드의 가독성과 재사용성을 향상시키는 좋은 리팩토링입니다.


55-78: 이메일 인증 코드 발송 로직 검토

이메일 인증 코드 발송 로직이 잘 구현되어 있습니다:

  • 이메일 사용 가능성 검증
  • 기존 인증 코드 존재 여부 확인
  • 보안 코드 생성 및 Redis 저장
  • 이벤트 발행을 통한 비동기 이메일 발송

전체 플로우가 논리적으로 올바르게 구성되어 있습니다.


80-100: 이메일 인증 코드 확인 로직 승인

인증 코드 확인 로직이 안전하게 구현되어 있습니다:

  • 적절한 예외 처리
  • Objects.equals()를 사용한 안전한 문자열 비교
  • 인증 성공 시 코드 삭제 및 검증 상태 저장

보안과 기능성 측면에서 잘 구현되었습니다.

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 (2)
src/test/java/project/flipnote/auth/service/AuthServiceTest.java (2)

104-105: 매직 넘버 사용을 개선해보세요.

하드코딩된 이메일 주소와 인증번호 대신 상수나 테스트 데이터 클래스를 사용하는 것을 고려해보세요.

+ private static final String TEST_EMAIL = "test@test.com";
+ private static final String VALID_CODE = "123456";
+ private static final String INVALID_CODE = "654321";

  EmailVerificationConfirmDto.Request req
-   = new EmailVerificationConfirmDto.Request("test@test.com", "123456");
+   = new EmailVerificationConfirmDto.Request(TEST_EMAIL, VALID_CODE);

119-120: 테스트 데이터 중복을 줄여보세요.

동일한 이메일 주소와 인증번호가 여러 테스트에서 반복 사용됩니다. 테스트 데이터를 상수로 추출하거나 @beforeeach에서 초기화하는 것을 고려해보세요.

+ private static final String TEST_EMAIL = "test@test.com";
+ private static final String VALID_CODE = "123456";
+ private static final String INVALID_CODE = "654321";

  EmailVerificationConfirmDto.Request req
-   = new EmailVerificationConfirmDto.Request("test@test.com", "123456");
+   = new EmailVerificationConfirmDto.Request(TEST_EMAIL, VALID_CODE);

Also applies to: 137-138

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 19fc7fe and db82650.

📒 Files selected for processing (2)
  • src/test/java/project/flipnote/auth/service/AuthServiceTest.java (1 hunks)
  • src/test/java/project/flipnote/user/service/UserServiceTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/java/project/flipnote/user/service/UserServiceTest.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/test/java/project/flipnote/auth/service/AuthServiceTest.java (2)
src/main/java/project/flipnote/auth/model/EmailVerificationConfirmDto.java (1)
  • EmailVerificationConfirmDto (8-21)
src/main/java/project/flipnote/auth/model/EmailVerificationDto.java (1)
  • EmailVerificationDto (6-14)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (9)
src/test/java/project/flipnote/auth/service/AuthServiceTest.java (9)

1-26: 임포트 구조가 잘 정리되어 있습니다.

정적 임포트와 일반 임포트가 적절히 사용되었고, 필요한 모든 의존성이 올바르게 임포트되어 있습니다.


27-42: 테스트 클래스 구조가 적절합니다.

@DisplayName 어노테이션으로 한국어 설명을 제공하고, 필요한 모든 의존성을 @mock으로 모킹하여 단위 테스트 환경을 올바르게 구성했습니다.


49-65: 인증번호 생성 검증 로직이 우수합니다.

정규표현식을 사용한 인증번호 형식 검증(argThat(code -> code.length() == codeLength && code.matches("\\d{%s}".formatted(codeLength))))이 특히 인상적입니다. 길이와 숫자 형식을 모두 확인하여 생성된 인증번호의 품질을 보장합니다.


68-79: 예외 처리 테스트가 완벽합니다.

예외 타입과 에러 코드를 모두 검증하고, never() 검증으로 부작용이 없음을 확인하는 것이 좋습니다.


82-94: 중복 인증번호 발급 방지 테스트가 적절합니다.

Redis에 이미 존재하는 인증번호에 대한 중복 발급 방지 로직을 올바르게 테스트하고 있습니다.


107-108: 모킹 설정이 명확합니다.

given-willReturn 패턴을 사용하여 Redis 리포지토리의 동작을 명확하게 모킹했습니다.


112-113: 메서드 호출 검증이 정확합니다.

deleteCodemarkAsVerified 메서드가 정확히 한 번씩 호출되는지 검증하여 서비스 로직의 정확성을 보장합니다.


140-141: 잘못된 인증번호 시나리오가 명확합니다.

저장된 인증번호("654321")와 요청된 인증번호("123456")가 다른 경우를 명확하게 테스트하고 있습니다.


143-147: 예외 처리와 에러 코드 검증이 일관성 있습니다.

모든 실패 케이스에서 BizException 타입과 해당 AuthErrorCode를 일관성 있게 검증하고 있습니다.

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/user/model/UserRegisterRequest.java (1)

31-33: 전화번호 정리 메서드의 방어적 프로그래밍을 개선할 수 있습니다.

현재 구현은 기본적인 null 체크와 하이픈 제거 기능을 제공하지만, 더 안전한 구현을 위해 개선할 수 있습니다.

다음과 같이 개선하는 것을 고려해보세요:

public String getCleanedPhone() {
-    return phone == null ? null : phone.replaceAll("-", "");
+    return phone == null ? null : phone.replaceAll("[^0-9]", "");
}

이렇게 하면 하이픈뿐만 아니라 다른 특수문자도 제거할 수 있어 더 안전한 전화번호 처리가 가능합니다.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between db82650 and 5503f7b.

📒 Files selected for processing (6)
  • src/main/java/project/flipnote/user/controller/UserController.java (2 hunks)
  • src/main/java/project/flipnote/user/model/UserRegisterDto.java (0 hunks)
  • src/main/java/project/flipnote/user/model/UserRegisterRequest.java (1 hunks)
  • src/main/java/project/flipnote/user/model/UserRegisterResponse.java (1 hunks)
  • src/main/java/project/flipnote/user/service/UserService.java (2 hunks)
  • src/test/java/project/flipnote/user/service/UserServiceTest.java (1 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/project/flipnote/user/model/UserRegisterDto.java
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/project/flipnote/user/model/UserRegisterResponse.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/test/java/project/flipnote/user/service/UserServiceTest.java
  • src/main/java/project/flipnote/user/service/UserService.java
🔇 Additional comments (3)
src/main/java/project/flipnote/user/model/UserRegisterRequest.java (1)

9-29: 레코드 구조와 유효성 검증이 적절하게 구현되었습니다.

사용자 등록에 필요한 필드들이 적절한 유효성 검증 어노테이션과 함께 잘 정의되어 있습니다. 커스텀 유효성 검증 어노테이션(@ValidPassword, @ValidPhone)을 사용하여 비즈니스 로직에 맞는 검증을 수행하고 있습니다.

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

12-13: 새로운 요청/응답 타입으로의 전환이 적절하게 이루어졌습니다.

UserRegisterRequestUserRegisterResponse 타입으로의 임포트 변경이 깔끔하게 처리되었습니다.


24-26: 서비스 계층 register 메서드 시그니처 일치 확인
src/main/java/project/flipnote/user/service/UserService.java 28행:

public UserRegisterResponse register(UserRegisterRequest req) {

컨트롤러와 요청/응답 타입이 일치하므로 추가 작업이 필요 없습니다.

.nickname(req.nickname())
.smsAgree(req.smsAgree())
.phone(req.phone())
.phone(phone)
Copy link
Member

Choose a reason for hiding this comment

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

P3: 번호 같은 경우 개인정보라 암호화를 하는게 좋지 않을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

깜빡했네요 반영하겠습니다!

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: 2

🧹 Nitpick comments (4)
src/main/java/project/flipnote/common/validation/validator/PasswordConstraintValidator.java (1)

12-14: 비밀번호 정규식 패턴의 특수문자 범위를 검토해주세요.

현재 허용되는 특수문자가 !@#$^*()_-로 제한되어 있습니다. 일반적으로 사용되는 다른 특수문자들(예: %, &, +, =, ? 등)도 고려해보시기 바랍니다.

현재 패턴을 더 포괄적으로 변경하는 것을 고려해보세요:

private static final String PASSWORD_PATTERN =
-	"^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$^*()_\\-])[A-Za-z\\d!@#$^*()_\\-]{8,16}$";
+	"^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_\\-+=?])[A-Za-z\\d!@#$%^&*()_\\-+=?]{8,16}$";
src/main/java/project/flipnote/common/validation/validator/PhoneConstraintValidator.java (1)

11-20: 성능 최적화를 위해 Pattern을 컴파일하는 것을 고려해보세요.

PasswordConstraintValidator와 달리 매번 String.matches()를 호출하고 있어 성능 일관성이 떨어집니다.

다음과 같이 개선할 수 있습니다:

+import java.util.regex.Pattern;
+
 public class PhoneConstraintValidator implements ConstraintValidator<ValidPhone, String> {
 
-	private static final String PHONE_PATTERN = "^010-\\d{4}-\\d{4}$";
+	private static final String PHONE_PATTERN = "^010-\\d{4}-\\d{4}$";
+	private static final Pattern pattern = Pattern.compile(PHONE_PATTERN);
 
 	@Override
 	public boolean isValid(String phone, ConstraintValidatorContext context) {
 		if (Objects.isNull(phone)) {
 			return true;
 		}
 
-		return phone.matches(PHONE_PATTERN);
+		return pattern.matcher(phone).matches();
 	}
src/main/java/project/flipnote/common/crypto/AesCryptoProperties.java (1)

12-25: 설정 파일에서 암호화 키를 안전하게 관리하는 방법을 고려해주세요.

현재 구현은 application.yml에서 평문으로 키를 관리하게 됩니다. 프로덕션 환경에서는 다음과 같은 방법을 고려해보세요:

  1. 환경변수를 통한 키 주입
  2. Spring Cloud Config Server 사용
  3. AWS Systems Manager Parameter Store 등 외부 키 관리 시스템 활용
src/main/java/project/flipnote/common/crypto/AesCryptoConverter.java (1)

28-52: 암호화 로직이 올바르게 구현되었습니다.

랜덤 IV 생성, 데이터 결합, Base64 인코딩이 모두 적절하게 처리되었습니다. 다만 성능 최적화를 위해 SecureRandom 인스턴스를 재사용하는 것을 고려해보세요.

성능 개선을 위한 SecureRandom 최적화 제안:

+    private static final SecureRandom SECURE_RANDOM = new SecureRandom();
+
     @Override
     public String convertToDatabaseColumn(String attribute) {
         if (!StringUtils.hasText(attribute)) {
             return attribute;
         }
 
         try {
             Cipher cipher = Cipher.getInstance(ALGORITHM);
 
             byte[] iv = new byte[IV_SIZE_BYTES];
-            new SecureRandom().nextBytes(iv);
+            SECURE_RANDOM.nextBytes(iv);
             IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9caf7d6 and 98b9df0.

📒 Files selected for processing (17)
  • src/main/java/project/flipnote/auth/service/AuthService.java (2 hunks)
  • src/main/java/project/flipnote/common/config/AsyncProperties.java (1 hunks)
  • src/main/java/project/flipnote/common/crypto/AesCryptoConfig.java (1 hunks)
  • src/main/java/project/flipnote/common/crypto/AesCryptoConverter.java (1 hunks)
  • src/main/java/project/flipnote/common/crypto/AesCryptoProperties.java (1 hunks)
  • src/main/java/project/flipnote/common/security/jwt/JwtProperties.java (1 hunks)
  • src/main/java/project/flipnote/common/validation/annotation/ValidPassword.java (1 hunks)
  • src/main/java/project/flipnote/common/validation/annotation/ValidPhone.java (1 hunks)
  • src/main/java/project/flipnote/common/validation/validator/PasswordConstraintValidator.java (1 hunks)
  • src/main/java/project/flipnote/common/validation/validator/PhoneConstraintValidator.java (1 hunks)
  • src/main/java/project/flipnote/infra/config/ResendProperties.java (1 hunks)
  • src/main/java/project/flipnote/user/entity/User.java (2 hunks)
  • src/main/java/project/flipnote/user/model/UserRegisterRequest.java (1 hunks)
  • src/main/resources/application.yml (1 hunks)
  • src/test/java/project/flipnote/common/validator/PasswordConstraintValidatorTest.java (1 hunks)
  • src/test/java/project/flipnote/common/validator/PhoneConstraintValidatorTest.java (1 hunks)
  • src/test/resources/application-test.yml (1 hunks)
✅ Files skipped from review due to trivial changes (4)
  • src/main/java/project/flipnote/common/security/jwt/JwtProperties.java
  • src/test/resources/application-test.yml
  • src/main/java/project/flipnote/user/entity/User.java
  • src/main/java/project/flipnote/common/crypto/AesCryptoConfig.java
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/test/java/project/flipnote/common/validator/PasswordConstraintValidatorTest.java
  • src/test/java/project/flipnote/common/validator/PhoneConstraintValidatorTest.java
  • src/main/java/project/flipnote/user/model/UserRegisterRequest.java
  • src/main/resources/application.yml
  • src/main/java/project/flipnote/common/config/AsyncProperties.java
  • src/main/java/project/flipnote/infra/config/ResendProperties.java
  • src/main/java/project/flipnote/auth/service/AuthService.java
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/main/java/project/flipnote/common/validation/annotation/ValidPhone.java (1)
src/main/java/project/flipnote/common/validation/validator/PhoneConstraintValidator.java (1)
  • PhoneConstraintValidator (9-21)
src/main/java/project/flipnote/common/validation/annotation/ValidPassword.java (1)
src/main/java/project/flipnote/common/validation/validator/PasswordConstraintValidator.java (1)
  • PasswordConstraintValidator (10-24)
src/main/java/project/flipnote/common/crypto/AesCryptoProperties.java (3)
src/main/java/project/flipnote/common/config/AsyncProperties.java (1)
  • Getter (11-26)
src/main/java/project/flipnote/common/security/jwt/JwtProperties.java (1)
  • Getter (13-31)
src/main/java/project/flipnote/infra/config/ResendProperties.java (1)
  • Getter (11-23)
src/main/java/project/flipnote/common/crypto/AesCryptoConverter.java (1)
src/main/java/project/flipnote/common/crypto/AesCryptoConfig.java (1)
  • RequiredArgsConstructor (12-22)
🪛 ast-grep (0.38.1)
src/main/java/project/flipnote/common/crypto/AesCryptoConverter.java

[warning] 34-34: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: Cipher.getInstance(ALGORITHM)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html

(use-of-aes-ecb-java)


[warning] 60-60: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: Cipher.getInstance(ALGORITHM)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html

(use-of-aes-ecb-java)


[warning] 34-34: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Cipher.getInstance(ALGORITHM)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA

(desede-is-deprecated-java)


[warning] 60-60: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Cipher.getInstance(ALGORITHM)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA

(desede-is-deprecated-java)

🔇 Additional comments (6)
src/main/java/project/flipnote/common/validation/annotation/ValidPassword.java (1)

1-19: 비밀번호 검증 어노테이션이 올바르게 구현되었습니다.

Bean Validation 표준을 잘 따르고 있으며, 필요한 모든 메타데이터가 포함되어 있습니다.

src/main/java/project/flipnote/common/validation/validator/PasswordConstraintValidator.java (1)

17-23: null 값 처리와 검증 로직이 올바르게 구현되었습니다.

컴파일된 Pattern 사용으로 성능도 잘 최적화되어 있습니다.

src/main/java/project/flipnote/common/validation/annotation/ValidPhone.java (1)

1-19: 휴대전화 번호 검증 어노테이션이 올바르게 구현되었습니다.

ValidPassword와 일관된 구조를 가지고 있어 코드 일관성이 좋습니다.

src/main/java/project/flipnote/common/validation/validator/PhoneConstraintValidator.java (1)

15-17: 휴대폰 번호 필드의 null 허용 여부 확인

UserRegisterRequest의 phone 필드에는 @ValidPhone만 적용되어 있어 null도 유효 처리됩니다.

  • 선택적 입력이라면 현재 로직(Objects.isNull → true 반환)을 그대로 유지해도 무방합니다.
  • 필수 입력이라면 @NotNull을 추가로 적용해주세요.

위치

  • src/main/java/project/flipnote/user/model/UserRegisterRequest.java:25-26
src/main/java/project/flipnote/common/crypto/AesCryptoConverter.java (2)

18-26: 클래스 구조와 암호화 알고리즘 선택이 적절합니다.

JPA 컨버터 애노테이션과 의존성 주입이 올바르게 구현되었으며, AES/CBC/PKCS5Padding 알고리즘 선택과 16바이트 IV 크기 설정이 암호화 보안 모범 사례에 부합합니다.


49-51: 예외 처리가 적절하게 구현되었습니다.

JPA 컨버터에서 체크 예외를 런타임 예외로 변환하는 방식이 올바르며, 원본 예외를 보존하고 한국어 오류 메시지를 제공하는 것이 프로젝트와 일관성이 있습니다.

Also applies to: 76-78

Comment on lines +54 to +79
@Override
public String convertToEntityAttribute(String dbData) {
if (!StringUtils.hasText(dbData)) {
return dbData;
}

try {
Cipher cipher = Cipher.getInstance(ALGORITHM);

byte[] combined = Base64.getDecoder().decode(dbData);

byte[] iv = new byte[IV_SIZE_BYTES];
System.arraycopy(combined, 0, iv, 0, iv.length);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

byte[] encrypted = new byte[combined.length - iv.length];
System.arraycopy(combined, iv.length, encrypted, 0, encrypted.length);

cipher.init(Cipher.DECRYPT_MODE, aesSecretKeySpec, ivParameterSpec);
byte[] decrypted = cipher.doFinal(encrypted);

return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new IllegalStateException("데이터 복호화에 실패했습니다.", e);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

복호화 로직이 적절하게 구현되었으나 입력 검증을 강화해야 합니다.

IV 추출과 복호화 과정이 올바르게 구현되었지만, Base64 디코딩 후 데이터 길이 검증이 누락되어 있습니다. 잘못된 데이터가 입력될 경우 예외가 발생할 수 있습니다.

입력 데이터 검증 강화를 위한 개선 사항:

     @Override
     public String convertToEntityAttribute(String dbData) {
         if (!StringUtils.hasText(dbData)) {
             return dbData;
         }
 
         try {
             Cipher cipher = Cipher.getInstance(ALGORITHM);
 
             byte[] combined = Base64.getDecoder().decode(dbData);
+            
+            if (combined.length < IV_SIZE_BYTES) {
+                throw new IllegalArgumentException("암호화된 데이터의 길이가 올바르지 않습니다.");
+            }
 
             byte[] iv = new byte[IV_SIZE_BYTES];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public String convertToEntityAttribute(String dbData) {
if (!StringUtils.hasText(dbData)) {
return dbData;
}
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
byte[] combined = Base64.getDecoder().decode(dbData);
byte[] iv = new byte[IV_SIZE_BYTES];
System.arraycopy(combined, 0, iv, 0, iv.length);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
byte[] encrypted = new byte[combined.length - iv.length];
System.arraycopy(combined, iv.length, encrypted, 0, encrypted.length);
cipher.init(Cipher.DECRYPT_MODE, aesSecretKeySpec, ivParameterSpec);
byte[] decrypted = cipher.doFinal(encrypted);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new IllegalStateException("데이터 복호화에 실패했습니다.", e);
}
}
@Override
public String convertToEntityAttribute(String dbData) {
if (!StringUtils.hasText(dbData)) {
return dbData;
}
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
byte[] combined = Base64.getDecoder().decode(dbData);
if (combined.length < IV_SIZE_BYTES) {
throw new IllegalArgumentException("암호화된 데이터의 길이가 올바르지 않습니다.");
}
byte[] iv = new byte[IV_SIZE_BYTES];
System.arraycopy(combined, 0, iv, 0, iv.length);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
byte[] encrypted = new byte[combined.length - iv.length];
System.arraycopy(combined, iv.length, encrypted, 0, encrypted.length);
cipher.init(Cipher.DECRYPT_MODE, aesSecretKeySpec, ivParameterSpec);
byte[] decrypted = cipher.doFinal(encrypted);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new IllegalStateException("데이터 복호화에 실패했습니다.", e);
}
}
🧰 Tools
🪛 ast-grep (0.38.1)

[warning] 60-60: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: Cipher.getInstance(ALGORITHM)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html

(use-of-aes-ecb-java)


[warning] 60-60: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Cipher.getInstance(ALGORITHM)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA

(desede-is-deprecated-java)

🤖 Prompt for AI Agents
In src/main/java/project/flipnote/common/crypto/AesCryptoConverter.java around
lines 54 to 79, the method convertToEntityAttribute lacks validation of the
decoded Base64 data length before extracting the IV and encrypted bytes. To fix
this, add a check after Base64 decoding to ensure the combined byte array length
is at least the size of the IV plus some minimum expected length. If the length
is insufficient, throw an appropriate exception to prevent processing invalid
data and avoid runtime errors during array copying.

@dungbik dungbik changed the title Feat: (FN-4) 회원가입 Feat: [FN-4] 회원가입 Jul 11, 2025
@dungbik dungbik merged commit 17af2ba into develop Jul 11, 2025
3 checks passed
@dungbik dungbik deleted the feat/register branch July 11, 2025 02:36
@coderabbitai coderabbitai bot mentioned this pull request Sep 24, 2025
4 tasks
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.

3 participants