Skip to content

Conversation

@minjee2758
Copy link
Collaborator

@minjee2758 minjee2758 commented Jun 30, 2025

작업 내용

1. 소셜로그인 RedirectURI 전달 값 변경

기본 api
→ GET : /api/oauth2/authorize/{provider}?c={redirect_uri}
{provider} : oauth2 인증할 곳 이름 (github/google)
{redirect_url} : 인증 성공 후 사용자에게 보여줄 화면의 주소

보안 상 허용된 uri만 가능하게 하기 위해서 현재 백엔드에 아래 두가지 경로만 허용되어있습니다

- 테스트용
"http://localhost:8080", //백엔드
"http://localhost:3000", //프론트엔드

- 배포된 메인페이지 경로
"https://ezcode.my"

결과는 다음과같이 옵니다

http://도메인명/callback?
  accessToken=eyJhbGci...&
  refreshToken=eyJhbGci...

2. 프로필 이미지 등록, 삭제, 업데이트

1. 프로필 이미지 등록 & 업데이트

PUT : /api/users/profile

  • 프로필 이미지가 없을 때
    • 등록한 사진이 aws로 업데이트되며, DB에는 해당 URL이 저장됩니다.
  • 프로필 이미지가 이미 있을 때
    • 기존 이미지는 삭제됩니다
    • 등록한 사진이 새로 DB에 저장됩니다.
  • 프로필 이미지는 1장만 등록 가능합니다.
  • 만약 DB의 프로필 이미지가 null이라면 기본 이미지(피그마에 있는 기본 프로필 이미지)로 설정됩니다.

2. 프로필 이미지 삭제 (기본 이미지로 설정)

DELETE : /api/users/profile
삭제 api를 불러오면 db에 저장되어있던 이미지url은 삭제되고, 기본 이미지로 대체

3. 어드민 권한 부여 API

: ADMIN 권한을 가진 유저는 다른 유저에게 ADMIN 권한을 부여할 수 있습니다.

4. 어드민 경로 제한

: /api/admin으로 시작하는 경로는 어드민 권한을 가진 유저만 접근 가능하도록 변경 (기존에 앞에 api를 빼먹어서 적용이 안됐었음)


참고 사항

  • 테스트코드 작성 필요

코드 리뷰 전 확인 체크리스트

  • 불필요한 콘솔 로그, 주석 제거
  • 커밋 메시지 컨벤션 준수 (type : )
  • 기능 정상 동작 확인

Summary by CodeRabbit

  • 신규 기능

    • 사용자가 프로필 이미지를 업로드, 교체, 삭제할 수 있는 기능이 추가되었습니다.
    • 관리자가 다른 사용자에게 관리자 권한을 부여할 수 있는 기능이 추가되었습니다.
  • 버그 수정 및 개선

    • 비밀번호 재설정 요청 시 필수 입력값 검증 메시지가 추가되었습니다.
    • 관리자 전용 API 경로가 "/api/admin/**"로 변경되었습니다.
    • OAuth2 리디렉션 URI에 대한 유효성 검사가 강화되었습니다.
    • 인증 관련 예외 코드 및 메시지가 추가되었습니다.
    • 보안 경로에 "/oauth/**"가 공개 경로로 포함되었습니다.
  • 기타

    • 일부 내부 로직 및 예외 처리 방식이 개선되었습니다.
    • API 응답 메시지용 DTO가 추가되었습니다.

@minjee2758 minjee2758 self-assigned this Jun 30, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jun 30, 2025

"""

Walkthrough

S3 파일 삭제 메서드의 시그니처가 디렉터리명을 추가로 받도록 변경되었으며, 이에 따라 관련 서비스에서 호출 방식이 수정되었습니다. 사용자 프로필 이미지 관리 및 관리자 권한 부여 기능이 추가되었고, 이에 따른 DTO, 예외 코드, 엔드포인트가 신설되었습니다. 보안 경로와 인증 관련 일부 로직도 개선되었습니다.

Changes

파일/그룹 변경 요약
.../application/problem/service/ProblemService.java, .../infrastructure/s3/S3Uploader.java S3Uploader의 delete 메서드가 디렉터리명을 인자로 받도록 변경, 관련 호출부 수정 및 프로필/문제 이미지 경로 구분 로직 추가
.../usermanagement/user/dto/response/GrantAdminRoleResponse.java, .../usermanagement/user/dto/response/UserProfileImageResponse.java 관리자 권한 부여 및 프로필 이미지 응답용 DTO 클래스 신규 추가
.../usermanagement/user/dto/request/ResetPasswordRequest.java 비밀번호 재설정 요청 DTO에 NotBlank 검증 어노테이션 추가
.../usermanagement/user/service/UserService.java, .../usermanagement/user/model/entity/User.java 사용자 프로필 이미지 업로드/삭제, 관리자 권한 부여 메서드 및 User 엔티티에 관련 필드 수정 메서드 추가
.../usermanagement/user/model/entity/AuthUser.java getAuthorities 메서드에서 GrantedAuthority 생성 방식을 SimpleGrantedAuthority로 변경
.../usermanagement/user/exception/code/UserExceptionCode.java 본인에게 관리자 권한 부여 및 이미 관리자일 때의 예외 코드 추가
.../usermanagement/user/exception/code/AuthExceptionCode.java 관리자만 접근 가능 예외 코드 추가
.../presentation/usermanagement/UserController.java 프로필 이미지 업로드/삭제, 관리자 권한 부여 엔드포인트 추가
.../presentation/usermanagement/OAuth2Controller.java redirect_uri 검증 및 허용 도메인 체크 로직 추가, 문서 예시 수정
.../common/security/config/SecurityConfig.java, .../common/security/util/SecurityPath.java 관리자 API 경로를 "/api/admin/"로 변경, 공개 경로에 "/oauth/" 추가
.../common/security/hander/CustomSuccessHandler.java OAuth 인증 성공 시 토큰을 쿼리로 리다이렉트하도록 변경
.../common/security/util/JwtUtil.java createEmailToken에서 userId null 체크 추가
.../common/security/util/JwtFilter.java 코드 가독성을 위한 공백 추가 (로직 변경 없음)

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant UserController
    participant UserService
    participant S3Uploader
    participant User

    Client->>UserController: PUT /users/profile (image)
    UserController->>UserService: uploadUserProfileImage(authUser, image)
    UserService->>User: 사용자 조회 및 기존 이미지 삭제
    UserService->>S3Uploader: delete(기존이미지URL, "profile")
    UserService->>S3Uploader: upload(image, "profile")
    S3Uploader-->>UserService: 새 이미지 URL 반환
    UserService->>User: 프로필 이미지 URL 수정
    UserService-->>UserController: UserProfileImageResponse 반환
    UserController-->>Client: 응답 반환
Loading
sequenceDiagram
    participant Admin
    participant UserController
    participant UserService
    participant User

    Admin->>UserController: POST /admin/users/{userId}/grant-admin
    UserController->>UserService: grantAdminRole(authUser, userId)
    UserService->>User: 대상 사용자 조회 및 권한 체크
    UserService->>User: 권한 수정 (ADMIN)
    UserService-->>UserController: GrantAdminRoleResponse 반환
    UserController-->>Admin: 응답 반환
Loading

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • NCookies
  • pokerbearkr
  • Kimminu7
  • thezz9

Poem

🐰
새로운 권한, 프로필 빛나
S3에 폴더명도 추가하나
관리자는 권한을 부여하고
이미지는 새롭게 바꾸고
토큰과 경로, 검증도 빼먹지 않아
코드가 쑥쑥 자라나는
오늘도 깡총깡총 EZCode의 봄!

"""


📜 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 0c233fb and 3287ddd.

📒 Files selected for processing (2)
  • src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java (4 hunks)
  • src/main/java/org/ezcode/codetest/common/security/hander/CustomSuccessHandler.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/org/ezcode/codetest/common/security/hander/CustomSuccessHandler.java
  • src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
✨ 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.

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

🔭 Outside diff range comments (1)
src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java (1)

64-79: 디렉터리별 S3 삭제 로직에 잠재적 문제 존재

현재 구현에서 dirName이 "problem" 또는 "profile"과 일치하지 않으면 fileName이 빈 문자열로 남아있어 예상치 못한 동작을 할 수 있습니다.

다음과 같이 수정하여 방어적 프로그래밍을 적용하세요:

 public void delete(String fileUrl, String dirName) {
     try {
         String fileName = "";
         if (dirName.equalsIgnoreCase("problem")) {
             fileName = extractKeyFromProblemUrl(fileUrl);
-        }
-        if (dirName.equalsIgnoreCase("profile")) {
+        } else if (dirName.equalsIgnoreCase("profile")) {
             fileName = extractKeyFromProfileUrl(fileUrl);
+        } else {
+            throw new S3Exception(S3ExceptionCode.S3_DELETE_FAILED);
         }
🧹 Nitpick comments (2)
src/main/java/org/ezcode/codetest/common/security/hander/CustomSuccessHandler.java (1)

121-122: 사용되지 않는 코드 제거 필요

OAuthResponse 객체가 생성되지만 실제로 사용되지 않습니다. 불필요한 코드를 제거해주세요.

-		//JSON 문자열로 바꿔서 클라이언트에게 응답 본문으로 전달
-		OAuthResponse oAuthResponse = new OAuthResponse(accessToken, refreshToken);
src/main/java/org/ezcode/codetest/presentation/usermanagement/OAuth2Controller.java (1)

4-4: OAuth2 리다이렉트 URI 보안 검증 추가 완료

오픈 리다이렉트 취약점을 방지하기 위한 보안 검증이 적절히 구현되었습니다. 허용된 도메인 목록을 통한 화이트리스트 방식이 안전합니다.

보안성을 유지하면서 유지보수성을 향상시키기 위해 허용된 도메인을 설정 파일로 분리하는 것을 고려해보세요:

+@Value("${oauth.redirect.allowed-domains}")
+private List<String> allowedDomains;

 private boolean isValidRedirectUri(String uri) {
-    List<String> allowedDomains = List.of("http://localhost:8080", "http://localhost:3000","https://ezcode.my");
     return allowedDomains.stream().anyMatch(uri::startsWith);
 }

Also applies to: 43-45, 50-53

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between ce44244 and 0c233fb.

📒 Files selected for processing (17)
  • src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java (2 hunks)
  • src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/request/ResetPasswordRequest.java (1 hunks)
  • src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/GrantAdminRoleResponse.java (1 hunks)
  • src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/UserProfileImageResponse.java (1 hunks)
  • src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java (4 hunks)
  • src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java (1 hunks)
  • src/main/java/org/ezcode/codetest/common/security/hander/CustomSuccessHandler.java (1 hunks)
  • src/main/java/org/ezcode/codetest/common/security/util/JwtFilter.java (2 hunks)
  • src/main/java/org/ezcode/codetest/common/security/util/JwtUtil.java (1 hunks)
  • src/main/java/org/ezcode/codetest/common/security/util/SecurityPath.java (1 hunks)
  • src/main/java/org/ezcode/codetest/domain/user/exception/code/AuthExceptionCode.java (1 hunks)
  • src/main/java/org/ezcode/codetest/domain/user/exception/code/UserExceptionCode.java (1 hunks)
  • src/main/java/org/ezcode/codetest/domain/user/model/entity/AuthUser.java (2 hunks)
  • src/main/java/org/ezcode/codetest/domain/user/model/entity/User.java (2 hunks)
  • src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java (2 hunks)
  • src/main/java/org/ezcode/codetest/presentation/usermanagement/OAuth2Controller.java (3 hunks)
  • src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java (4 hunks)
🧰 Additional context used
🧠 Learnings (8)
src/main/java/org/ezcode/codetest/common/security/util/JwtFilter.java (1)
Learnt from: NCookies
PR: ezcode-my/backend#17
File: src/main/java/org/ezcode/codetest/presentation/problemmanagement/ProblemAdminController.java:25-25
Timestamp: 2025-06-02T06:57:37.929Z
Learning: 이 프로젝트에서는 아직 Spring Security가 적용되지 않아서 @PreAuthorize 어노테이션이 주석 처리되어 있음. Spring Security 도입 전까지는 권한 검증 어노테이션들이 비활성화 상태임.
src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java (1)
Learnt from: NCookies
PR: ezcode-my/backend#17
File: src/main/java/org/ezcode/codetest/presentation/problemmanagement/ProblemAdminController.java:25-25
Timestamp: 2025-06-02T06:57:37.929Z
Learning: 이 프로젝트에서는 아직 Spring Security가 적용되지 않아서 @PreAuthorize 어노테이션이 주석 처리되어 있음. Spring Security 도입 전까지는 권한 검증 어노테이션들이 비활성화 상태임.
src/main/java/org/ezcode/codetest/domain/user/exception/code/UserExceptionCode.java (2)
Learnt from: minjee2758
PR: ezcode-my/backend#110
File: src/main/java/org/ezcode/codetest/domain/user/service/CustomOAuth2UserService.java:102-112
Timestamp: 2025-06-27T06:03:05.687Z
Learning: `CustomOAuth2UserService`의 `updateGithubUrl` 메서드는 새로운 유저 생성 시나 기존 유저가 새로운 AuthType을 추가할 때만 호출되므로, `UserGithubInfo` 중복 생성 가능성이 없다. 별도의 중복 체크 로직이 불필요하다.
Learnt from: thezz9
PR: ezcode-my/backend#75
File: src/main/java/org/ezcode/codetest/application/submission/service/SubmissionService.java:142-147
Timestamp: 2025-06-19T07:00:53.895Z
Learning: SubmissionService.getCodeReview() 메서드에서 토큰 차감을 리뷰 요청 전에 수행하는 이유: OpenAI 서버 장애보다 유저 토큰 부족 상황이 더 빈번하므로, 확률이 높은 실패 케이스를 먼저 체크하여 불필요한 처리를 방지함. OpenAI 서버 장애 시에는 OpenAIClient에서 트랜잭션 롤백으로 토큰 복원이 처리됨.
src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/request/ResetPasswordRequest.java (1)
Learnt from: minjee2758
PR: ezcode-my/backend#9
File: src/main/java/org/ezcode/codetest/domain/user/service/UserDomainService.java:32-39
Timestamp: 2025-05-31T11:26:06.360Z
Learning: AuthService.signin() 메서드에서 userPasswordCheck 호출 시 signinRequest.getPassword() (원본 비밀번호)를 전달해야 하는데, loginUser.getPassword() (해시된 비밀번호)를 전달하는 버그가 있었음.
src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java (1)
Learnt from: chat26666
PR: ezcode-my/backend#31
File: src/main/java/org/ezcode/codetest/domain/problem/service/ProblemDomainService.java:25-27
Timestamp: 2025-06-04T15:11:19.343Z
Learning: In ProblemDomainService, when saving problems to both database and Elasticsearch, maintain transactional consistency by allowing rollback if either save operation fails. Data integrity between DB and ES is prioritized over availability - if Elasticsearch save fails, the entire transaction should roll back to prevent data inconsistency.
src/main/java/org/ezcode/codetest/presentation/usermanagement/OAuth2Controller.java (1)
Learnt from: NCookies
PR: ezcode-my/backend#17
File: src/main/java/org/ezcode/codetest/presentation/problemmanagement/ProblemAdminController.java:25-25
Timestamp: 2025-06-02T06:57:37.929Z
Learning: 이 프로젝트에서는 아직 Spring Security가 적용되지 않아서 @PreAuthorize 어노테이션이 주석 처리되어 있음. Spring Security 도입 전까지는 권한 검증 어노테이션들이 비활성화 상태임.
src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java (1)
Learnt from: NCookies
PR: ezcode-my/backend#17
File: src/main/java/org/ezcode/codetest/presentation/problemmanagement/ProblemAdminController.java:25-25
Timestamp: 2025-06-02T06:57:37.929Z
Learning: 이 프로젝트에서는 아직 Spring Security가 적용되지 않아서 @PreAuthorize 어노테이션이 주석 처리되어 있음. Spring Security 도입 전까지는 권한 검증 어노테이션들이 비활성화 상태임.
src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java (2)
Learnt from: NCookies
PR: ezcode-my/backend#17
File: src/main/java/org/ezcode/codetest/presentation/problemmanagement/ProblemAdminController.java:25-25
Timestamp: 2025-06-02T06:57:37.929Z
Learning: 이 프로젝트에서는 아직 Spring Security가 적용되지 않아서 @PreAuthorize 어노테이션이 주석 처리되어 있음. Spring Security 도입 전까지는 권한 검증 어노테이션들이 비활성화 상태임.
Learnt from: minjee2758
PR: ezcode-my/backend#43
File: src/main/java/org/ezcode/codetest/domain/user/model/entity/User.java:77-89
Timestamp: 2025-06-10T02:13:33.890Z
Learning: 이 프로젝트에서는 입력 검증을 DTO 레벨에서 수행하고 있으며, User 엔티티의 팩토리 메서드에서는 별도의 입력 검증을 하지 않는다.
🧬 Code Graph Analysis (1)
src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/GrantAdminRoleResponse.java (2)
src/main/java/org/ezcode/codetest/common/security/util/JwtFilter.java (1)
  • AllArgsConstructor (28-103)
src/main/java/org/ezcode/codetest/domain/user/model/entity/AuthUser.java (1)
  • Getter (14-40)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (28)
src/main/java/org/ezcode/codetest/common/security/util/SecurityPath.java (2)

11-11: OAuth 경로 추가 승인, 중복 패턴 검토 필요

OAuth 플로우를 위한 공개 경로 추가는 적절하지만, 기존의 /oauth2/**/login/oauth2/** 패턴과 중복될 수 있습니다. 새로운 /oauth/** 패턴이 기존 패턴들과 어떻게 구분되는지 확인해주세요.


11-11: OAuth 경로 추가 승인

OAuth 인증 플로우를 위한 공개 경로 추가가 적절합니다. 이는 PR 목표에서 언급된 OAuth 리다이렉트 기능과 일치합니다.

src/main/java/org/ezcode/codetest/domain/user/model/entity/AuthUser.java (3)

9-9: Import 추가 승인

SimpleGrantedAuthority를 명시적으로 사용하기 위한 import 추가입니다.


33-35: 권한 생성 방식 개선 승인

람다 표현식에서 명시적인 SimpleGrantedAuthority 생성으로 변경하여 코드 가독성과 명확성이 향상되었습니다. 기능적으로는 동일하며 Spring Security 모범 사례를 따릅니다.


33-35: 권한 생성 방식 개선 승인

람다 표현식에서 명시적인 SimpleGrantedAuthority 인스턴스 생성으로 변경한 것이 코드 가독성과 명확성을 향상시킵니다.

src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java (2)

76-76: 관리자 경로 패턴 수정 승인

관리자 경로를 /admin/**에서 /api/admin/**로 변경하여 API 접두사 누락 문제를 해결했습니다. 이는 REST API 관례를 따르며 보안 제한은 동일하게 유지됩니다.


76-76: 관리자 API 경로 보안 설정 승인

관리자 경로 패턴을 /api/admin/**로 변경한 것이 REST API 규칙에 부합하며, PR 목표에서 언급된 관리자 경로 제한 기능과 일치합니다.

src/main/java/org/ezcode/codetest/common/security/util/JwtFilter.java (2)

73-73: 코드 가독성 개선 승인

논리적 블록을 구분하는 빈 줄 추가로 코드 가독성이 향상되었습니다.

Also applies to: 82-82


73-73: 코드 가독성 개선 승인

논리적 블록을 분리하는 빈 줄 추가로 코드 가독성이 향상되었습니다.

Also applies to: 82-82

src/main/java/org/ezcode/codetest/common/security/util/JwtUtil.java (1)

153-153: 보안 개선 승인

이메일 토큰 생성 시 userId 매개변수에 대한 null 체크가 추가되어 입력 검증이 강화되었습니다. 이는 보안상 필요한 개선사항입니다.

src/main/java/org/ezcode/codetest/domain/user/exception/code/AuthExceptionCode.java (1)

23-24: 새로운 예외 코드 승인

OAuth 리다이렉트 URI 검증과 관리자 권한 제한을 위한 새로운 예외 코드가 적절히 추가되었습니다. HTTP 상태 코드와 메시지가 적절합니다.

src/main/java/org/ezcode/codetest/domain/user/exception/code/UserExceptionCode.java (1)

16-18: 관리자 권한 관리 예외 코드 승인

관리자 권한 부여 기능을 위한 새로운 예외 코드들이 적절히 추가되었습니다:

  • 본인에게 관리자 권한 부여 방지
  • 이미 관리자인 사용자에 대한 중복 권한 부여 방지
  • GitHub 저장소 검증

비즈니스 로직의 보안성과 일관성을 보장하는 적절한 예외 처리입니다.

src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/UserProfileImageResponse.java (1)

1-11: 프로필 이미지 응답 DTO 승인

프로필 이미지 관리 기능을 위한 새로운 응답 DTO가 적절히 구현되었습니다. 단순한 메시지 응답 구조로 프로젝트의 패턴을 따르고 있습니다.

src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java (2)

137-137: S3 삭제 메서드 API 변경 승인

S3Uploader의 delete 메서드 시그니처 변경에 맞춰 "problem" 디렉터리 매개변수가 적절히 추가되었습니다.


166-166: S3 삭제 메서드 API 변경 승인

이미지 업데이트 시에도 S3 삭제 메서드에 "problem" 디렉터리 매개변수가 일관되게 적용되었습니다.

src/main/java/org/ezcode/codetest/domain/user/model/entity/User.java (2)

202-204: 새로운 프로필 이미지 수정 메서드 추가 완료

메서드 구현이 간단하고 명확합니다. 기존의 다른 수정 메서드들과 일관된 패턴을 따르고 있습니다.


206-208: 사용자 역할 수정 메서드 추가 완료

메서드 구현이 적절합니다. 하지만 역할 변경은 민감한 작업이므로 서비스 레이어에서 적절한 권한 검증이 이루어지는지 확인하세요.

src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/request/ResetPasswordRequest.java (1)

4-4: 입력 검증 어노테이션 추가 완료

모든 필수 필드에 @NotBlank 검증이 적절히 추가되었습니다. 한국어 오류 메시지도 명확하고 사용자 친화적입니다.

Also applies to: 8-8, 12-12, 16-16

src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java (1)

87-90: 새로운 프로필 이미지 URL 추출 메서드 추가 완료

기존 문제 이미지 URL 추출 메서드와 동일한 패턴을 따라 일관성이 유지되었습니다.

src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/GrantAdminRoleResponse.java (1)

1-13: 관리자 권한 부여 응답 DTO 구현 완료

간단하고 명확한 DTO 구조로 잘 구현되었습니다. Lombok 어노테이션과 Swagger 문서화도 적절히 적용되었습니다.

src/main/java/org/ezcode/codetest/presentation/usermanagement/OAuth2Controller.java (1)

31-31: API 문서화 예시 업데이트 완료

Google OAuth2 예시로 업데이트되어 명확성이 향상되었습니다.

src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java (3)

6-8: 새로운 import 구문들이 적절히 추가되었습니다.

프로필 이미지 관리와 관리자 권한 부여 기능을 위한 필요한 import들이 올바르게 추가되었습니다.

Also applies to: 17-19, 23-24, 26-26


70-78: 프로필 이미지 삭제 엔드포인트가 올바르게 구현되었습니다.

RESTful 설계 원칙에 따라 DELETE 메서드를 사용하고, 구현이 간결하고 명확합니다.


97-104: 승인: 관리자 권한 부여 엔드포인트 보호 확인 완료

  • /api/admin/users/{userId}/grant-admin 엔드포인트는 @RequestMapping("/api") + @PostMapping("/admin/...")/api/admin/** 경로에 매핑됩니다.
  • SecurityConfig.java에서
    .requestMatchers("/api/admin/**").hasRole("ADMIN")
    설정을 통해 ADMIN 권한이 필요한 경로로 올바르게 보호되고 있습니다.
src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java (3)

7-8: 새로운 의존성들이 적절히 추가되었습니다.

프로필 이미지 관리와 관리자 권한 부여 기능을 위한 필요한 import들과 S3Uploader 의존성이 올바르게 추가되었습니다.

Also applies to: 18-18, 20-20, 24-24, 27-30, 35-35, 48-48


152-159: 프로필 이미지 업로드 헬퍼 메서드가 잘 구현되었습니다.

S3Directory enum 사용으로 일관성을 유지하고, 적절한 예외 처리와 로깅이 구현되어 있습니다.


168-180: 관리자 권한 부여 메서드가 올바르게 구현되었습니다.

자기 자신에게 권한 부여 방지, 중복 권한 부여 방지 등 필요한 비즈니스 검증 로직이 적절히 구현되어 있습니다. 예외 처리도 도메인별 예외를 사용하여 일관성 있게 처리되었습니다.

src/main/java/org/ezcode/codetest/common/security/hander/CustomSuccessHandler.java (1)

123-123: OAuth 리다이렉트 방식 변경 승인

JSON 응답에서 쿼리 파라미터를 포함한 리다이렉트 방식으로 변경한 것이 PR 목표에서 설명한 OAuth 플로우와 일치합니다.

Comment on lines 121 to 124
OAuthResponse oAuthResponse = new OAuthResponse(accessToken, refreshToken);

response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(oAuthResponse));
// response.sendRedirect(targetUri);
response.sendRedirect(targetUri);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

보안 우려: URL 쿼리 파라미터로 토큰 전달

액세스 토큰과 리프레시 토큰을 URL 쿼리 파라미터로 전달하는 것은 보안상 위험할 수 있습니다:

  1. 브라우저 히스토리에 토큰이 노출됩니다
  2. 서버 로그에 토큰이 기록될 수 있습니다
  3. HTTP 리퍼러 헤더를 통해 토큰이 노출될 수 있습니다

더 안전한 대안을 고려해보세요:

  • URL 프래그먼트(#) 사용: #accessToken=...&refreshToken=...
  • POST 요청으로 토큰 전달
  • 임시 인증 코드 방식 사용
-		String targetUri = UriComponentsBuilder.fromUriString(redirectUri)
-			.queryParam("accessToken", accessToken)
-			.queryParam("refreshToken", refreshToken)
-			.build().toUriString();
+		String targetUri = UriComponentsBuilder.fromUriString(redirectUri)
+			.fragment("accessToken=" + accessToken + "&refreshToken=" + refreshToken)
+			.build().toUriString();

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

🤖 Prompt for AI Agents
In
src/main/java/org/ezcode/codetest/common/security/hander/CustomSuccessHandler.java
around lines 121 to 124, the access and refresh tokens are being sent via URL
query parameters, which poses security risks such as exposure in browser
history, server logs, and HTTP referrer headers. To fix this, avoid including
tokens in the URL query string; instead, use a safer method like sending tokens
in the URL fragment (#accessToken=...&refreshToken=...), or deliver them via a
POST request, or implement a temporary authorization code flow to securely
transmit tokens.

Comment on lines +140 to +150
@Transactional
public UserProfileImageResponse uploadUserProfileImage(AuthUser authUser, MultipartFile image) {
User user = userDomainService.getUserById(authUser.getId());
if (user.getProfileImageUrl()!=null) {
s3Uploader.delete(user.getProfileImageUrl(), "profile");
}
String profileImageUrl = uploadProfileImage(image);

user.modifyProfileImage(profileImageUrl);
return new UserProfileImageResponse(profileImageUrl);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

프로필 이미지 업로드 로직을 개선하세요.

현재 구현에서 몇 가지 개선할 점이 있습니다:

  1. MultipartFile이 null이거나 비어있을 때의 처리 로직이 명확하지 않습니다.
  2. S3 삭제 실패 시에도 새 이미지 업로드가 진행됩니다.

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

 @Transactional
 public UserProfileImageResponse uploadUserProfileImage(AuthUser authUser, MultipartFile image) {
+    if (image == null || image.isEmpty()) {
+        throw new S3Exception(S3ExceptionCode.EMPTY_FILE);
+    }
+    
     User user = userDomainService.getUserById(authUser.getId());
+    String oldImageUrl = user.getProfileImageUrl();
+    
+    // 새 이미지를 먼저 업로드
+    String profileImageUrl = uploadProfileImage(image);
+    
+    // 기존 이미지가 있다면 삭제 (새 이미지 업로드 성공 후)
-    if (user.getProfileImageUrl()!=null) {
-        s3Uploader.delete(user.getProfileImageUrl(), "profile");
+    if (oldImageUrl != null) {
+        try {
+            s3Uploader.delete(oldImageUrl, "profile");
+        } catch (Exception e) {
+            log.warn("기존 프로필 이미지 삭제 실패 - url: {}", oldImageUrl, e);
+            // 기존 이미지 삭제 실패해도 새 이미지 업로드는 진행
+        }
     }
-    String profileImageUrl = uploadProfileImage(image);

     user.modifyProfileImage(profileImageUrl);
     return new UserProfileImageResponse(profileImageUrl);
 }
📝 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
@Transactional
public UserProfileImageResponse uploadUserProfileImage(AuthUser authUser, MultipartFile image) {
User user = userDomainService.getUserById(authUser.getId());
if (user.getProfileImageUrl()!=null) {
s3Uploader.delete(user.getProfileImageUrl(), "profile");
}
String profileImageUrl = uploadProfileImage(image);
user.modifyProfileImage(profileImageUrl);
return new UserProfileImageResponse(profileImageUrl);
}
@Transactional
public UserProfileImageResponse uploadUserProfileImage(AuthUser authUser, MultipartFile image) {
if (image == null || image.isEmpty()) {
throw new S3Exception(S3ExceptionCode.EMPTY_FILE);
}
User user = userDomainService.getUserById(authUser.getId());
String oldImageUrl = user.getProfileImageUrl();
// 새 이미지를 먼저 업로드
String profileImageUrl = uploadProfileImage(image);
// 기존 이미지가 있다면 삭제 (새 이미지 업로드 성공 후)
if (oldImageUrl != null) {
try {
s3Uploader.delete(oldImageUrl, "profile");
} catch (Exception e) {
log.warn("기존 프로필 이미지 삭제 실패 - url: {}", oldImageUrl, e);
// 기존 이미지 삭제 실패해도 새 이미지 업로드는 진행
}
}
user.modifyProfileImage(profileImageUrl);
return new UserProfileImageResponse(profileImageUrl);
}
🤖 Prompt for AI Agents
In
src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java
around lines 140 to 150, the uploadUserProfileImage method lacks checks for null
or empty MultipartFile and proceeds with new image upload even if S3 deletion
fails. Fix this by first validating that the MultipartFile is not null and not
empty, returning an appropriate response or throwing an exception if it is.
Then, modify the S3 deletion call to handle potential failures gracefully, such
as by catching exceptions and preventing the new image upload if deletion fails,
ensuring consistent state and error handling.

@pokerbearkr pokerbearkr merged commit f73dc3f into dev Jul 1, 2025
2 checks passed
@pokerbearkr pokerbearkr deleted the refactor/oauthRedirecting branch July 1, 2025 01:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants