Skip to content

[Fix/signup token] - 회원가입 로직 수정#54

Merged
ekgns33 merged 11 commits intomainfrom
fix/signup-token
Apr 15, 2025
Merged

[Fix/signup token] - 회원가입 로직 수정#54
ekgns33 merged 11 commits intomainfrom
fix/signup-token

Conversation

@ekgns33
Copy link
Copy Markdown
Contributor

@ekgns33 ekgns33 commented Apr 14, 2025

작업 내역

#53 이슈에서 논의한 바를 적용했습니다.
Screenshot 2025-04-15 at 3 23 42 PM

1. 회원 미가입으로 인한 실패 시, 회원가입을 위한 소셜 데이터를 데이터베이스에 임시저장

  • UUID를 생성하여 식별자로 사용.
  • provider_id , provider 를 공통으로 저장
  • 애플로그인은 refresh_token 까지 저장

2. 미가입 시 404 에러와, 회원가입에 사용되는 임시 토큰을 발급

  • Controller에서 분기 처리하여 응답을 반환하도록 구현
  • Service에서는 성공 / 실패 여부에 따른 AuthResult객체를 반환

3. 회원 가입 시, 임시로 저장했던 데이터를 활용

  • 클라이언트가 보낸 토큰에 대한 검증
  • 10분 이내에 저장된 임시 데이터를 UUID로 조회
  • 애플로그인refresh_tokenapple_user_token 테이블에 저장.

추후 작업할 내용

  • 스케줄링 또는 배치로 쌓여있는 임시 토큰 데이터 삭제
    • 현재는 JWT token에 10분 expired_time과 데이터베이스의 created_at 을 활용하여 검증하고 있습니다.
    • 쌓이는 데이터를 삭제하는 로직은 회의 후 주기를 정하여 개발예정입니다. ex) 1h 3h ...

Summary by CodeRabbit

  • New Features

    • Introduced unified authentication responses that clearly signal successful logins or indicate when signup is required.
    • Added temporary signup token generation to enhance new user registration through social logins.
    • Added validation for required fields in user registration commands.
  • Refactor

    • Streamlined the social login flows with improved error handling and clearer token payloads.
    • Renamed token-related methods to clarify their purposes and improve overall maintainability.
  • Chores

    • Updated the database schema for secure token management.
    • Enhanced test setups to integrate new signup token functionalities.

@ekgns33 ekgns33 added bug Something isn't working enhancement New feature or request labels Apr 14, 2025
@ekgns33 ekgns33 requested a review from jeeheaG April 14, 2025 15:50
@ekgns33 ekgns33 self-assigned this Apr 14, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2025

Walkthrough

This pull request standardizes and enhances the authentication and signup flows. Login methods in controllers and handler classes are updated to return unified response types (using Response and the new AuthResult record) instead of the old AuthResponse. New entities, records, and repositories (such as SignupToken and SignupTokenRepository) have been introduced for managing signup tokens. JWT handling methods have been renamed and updated to reflect a "signup" context. Additionally, error handling and encryption mechanisms have been refined; database schema updates and test modifications ensure the new token management functionality is integrated.

Changes

File(s) Change Summary
src/main/java/org/runimo/.../AuthController.java, OidcService.java, KakaoLoginHandler.java, AppleLoginHandler.java Updated login methods to return Response or AuthResult instead of AuthResponse; added helper methods (e.g., buildAuthResponse, createTemporalSignupToken) for centralized response building and token creation.
src/main/java/org/runimo/.../AuthResponse.java (deleted from dtos), AuthResult.java, AuthStatus.java Removed deprecated AuthResponse record; introduced new AuthResult record and AuthStatus enum to encapsulate authentication outcomes.
src/main/java/org/runimo/.../JwtResolver.java, JwtTokenFactory.java, SignupTokenPayload.java Renamed methods and updated payload structures from "register" to "signup"; added new parameters (e.g., key) and included additional JWT data.
src/main/java/org/runimo/.../SignupToken.java, SignupTokenRepository.java Introduced new SignupToken entity with JPA annotations and corresponding repository interface for handling signup tokens in the database.
src/main/java/org/runimo/.../EncryptUtil.java Modified the encrypt method to remove checked exceptions; switched to internal try-catch handling and rethrowing as a RuntimeException.
src/main/java/org/runimo/.../SignUpUsecaseImpl.java Added @Transactional annotation; introduced methods findUnExpiredSignupToken and createAppleUserToken to validate tokens and handle Apple user token creation during registration.
src/main/java/org/runimo/.../AppleTokenVerifier.java Renamed methods to getAppleTokenPairFromAuthCode and verifyTokenAndExtractUserInfo to better describe functionality.
src/main/java/org/runimo/.../CreatedAuditEntity.java Added a new abstract base class with a createdAt field for JPA entities.
src/main/java/org/runimo/.../GlobalExceptionHandler.java, RegisterErrorResponse.java Removed the handler for UnRegisteredUserException; updated RegisterErrorResponse to extend Response and use a CustomResponseCode for error details.
src/main/resources/sql/schema.sql, src/test/resources/sql/schema.sql Added new SQL definitions for the signup_token table (with DROP statement) and redefined the apple_user_token table.
src/test/java/org/runimo/.../AuthControllerTest.java, RewardTest.java, UserItemAcceptanceTest.java Updated tests to reflect new token generation methods and response types, including the use of UUIDs for signup tokens.

Possibly related PRs

  • [Feat/apple user withdraw] #52: Modifications in AppleLoginHandler’s authentication response structure and token management that align with this PR’s changes in AuthController and overall authentication flow.

Suggested reviewers

  • jeeheaG

Poem

I'm a hopping rabbit in the code garden green,
Skipping through tokens where changes are seen.
New responses and flows make me twitch with delight,
As error handling and JWTs now shine so bright.
With each line of code, I hop and sing,
Celebrating refresh tokens and every new thing!
🐇✨

✨ 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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 resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @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
Copy Markdown

@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

🔭 Outside diff range comments (3)
src/main/java/org/runimo/runimo/auth/jwt/JwtTokenFactory.java (1)

52-65: ⚠️ Potential issue

Inconsistency between method name and token type claim

The method has been renamed from generateRegisterTemporalToken to generateSignupTemporalToken and now accepts a key parameter for the JWT subject. However, on line 63, the "tokenType" claim still uses "register" instead of "signup", which is inconsistent with the method name change.

-            .withClaim("tokenType", "register")
+            .withClaim("tokenType", "signup")
src/main/java/org/runimo/runimo/auth/service/apple/AppleTokenVerifier.java (2)

92-116: 🛠️ Refactor suggestion

Validate HTTP status before parsing the response.

In getAppleTokenPairFromAuthCode, consider checking if the response is a successful (2xx) status code before attempting to parse the JSON body. This prevents potential null-pointer or parsing issues when Apple’s endpoint returns non-2xx responses (such as 4xx or 5xx) with different payloads.

 ResponseEntity<String> response = restTemplate.postForEntity(
     "https://appleid.apple.com/auth/token", request, String.class);

+ if (!response.getStatusCode().is2xxSuccessful()) {
+     log.error("Apple returned non-2xx status: {}", response.getStatusCode());
+     throw UserJwtException.of(UserHttpResponseCode.TOKEN_INVALID);
+ }

 try {
     JsonNode node = objectMapper.readTree(response.getBody());
     ...

143-166: 🛠️ Refactor suggestion

Address potential concurrency issues with publicKeys map.

The publicKeys map is refreshed by a scheduled task and is also accessed concurrently in verifyTokenAndExtractUserInfo. Using a plain HashMap can lead to race conditions or partial updates. Consider switching to a thread-safe structure (e.g., ConcurrentHashMap) or synchronizing the refresh logic to avoid potential concurrency errors.

-private final Map<String, RSAPublicKey> publicKeys = new HashMap<>();
+private final Map<String, RSAPublicKey> publicKeys = new ConcurrentHashMap<>();
🧹 Nitpick comments (11)
src/main/java/org/runimo/runimo/common/CreatedAuditEntity.java (1)

1-21: Good implementation of creation timestamp tracking

This audit entity class provides a clean way to track creation timestamps across entities. It's properly annotated with JPA and Lombok annotations to reduce boilerplate.

Consider specifying a timezone for the createdAt field to ensure consistent timestamp handling, especially since you'll be using this timestamp for the 10-minute token expiration window mentioned in the PR objectives. The GlobalConsts.TIME_ZONE_ID ("Asia/Seoul") could be used for consistency.

src/main/java/org/runimo/runimo/auth/service/EncryptUtil.java (1)

22-35: Improved error handling in encrypt method

The encryption method now properly handles exceptions internally instead of propagating them. This approach is more robust and prevents callers from having to handle checked exceptions.

Consider applying the same pattern to the decrypt method for consistency:

-    public String decrypt(String cipherText) throws Exception {
+    public String decrypt(String cipherText) {
+        try {
             Cipher cipher = Cipher.getInstance(CIPHER_TRANS);
             SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
             IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());

             cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
             byte[] decodedBytes = Base64.getDecoder().decode(cipherText);
             byte[] decrypted = cipher.doFinal(decodedBytes);

             return new String(decrypted);
+        } catch (Exception e) {
+            throw new RuntimeException("Error during decryption", e);
+        }
     }
🧰 Tools
🪛 ast-grep (0.31.1)

[warning] 23-23: 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(CIPHER_TRANS)
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] 23-23: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Cipher.getInstance(CIPHER_TRANS)
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)

src/main/resources/sql/schema.sql (1)

80-87: Consider adding indexes to the signup_token table

The signup_token table looks good, but consider adding indexes to columns that will be frequently used in WHERE clauses, especially for the provider_id and created_at fields since they'll likely be used for lookups and expiration checks.

CREATE TABLE `signup_token`
(
    `token`       VARCHAR(255) PRIMARY KEY NOT NULL UNIQUE ,
    `provider_id` VARCHAR(255) NOT NULL,
    `refresh_token` VARCHAR(255),
    `provider` VARCHAR(255),
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+    INDEX `idx_signup_token_provider_id` (`provider_id`),
+    INDEX `idx_signup_token_created_at` (`created_at`)
);
src/main/java/org/runimo/runimo/auth/service/apple/AppleTokenVerifier.java (1)

44-44: Consider externalizing the URL.

While defining REVOKE_URL as a static final is clear, you may want to externalize it (e.g., via application configuration) to facilitate environment-based overrides and future changes without code modifications.

src/main/java/org/runimo/runimo/auth/service/dtos/AuthResult.java (1)

1-42: Correct the spelling of the constructor parameter.

In the constructor on line 13, the parameter staus is likely a typo for status. Consider renaming it to maintain clarity and consistency.

- public AuthResult(final AuthStatus staus, final User user, final TokenPair tokenPair,
+ public AuthResult(final AuthStatus status, final User user, final TokenPair tokenPair,
src/main/java/org/runimo/runimo/auth/service/SignUpUsecaseImpl.java (2)

25-25: Consider externalizing the cutoff value.

The REGISTER_CUTOFF_MIN = 10 is a magic number. Consider moving it to configuration (e.g., application properties) so it can be adjusted without a code change.


50-55: Prefer throwing a more descriptive exception.

Currently, orElseThrow(IllegalAccessError::new); is used. IllegalAccessError typically indicates reflection-level access violations. Consider using a more semantically relevant runtime exception (e.g., NoSuchElementException or a custom business exception).

src/main/java/org/runimo/runimo/auth/controller/AuthController.java (2)

55-59: Consider validating the token parameter.
Although this login flow is presumably guarded by framework-level validations, adding a quick non-null or non-empty check for request.oidcToken() could help prevent potential misuse.


120-136: Re-examine using 404 for unregistered users.
While returning 404 can be valid if you treat an unregistered user as “resource not found,” you might also consider using 401 or 403 for better alignment with typical authentication semantics.

src/main/java/org/runimo/runimo/auth/service/apple/AppleLoginHandler.java (2)

43-59: Add optional logging or null checks.
The overall flow is sound, but consider adding log statements to help diagnose errors if the token verification or user lookup fails unexpectedly.


61-76: Consider hashing refresh tokens instead of encryption.
If the system only needs to match tokens rather than decrypt them, storing a hashed version can reduce risk in the event of a data breach.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 52cde81 and e4bd359.

📒 Files selected for processing (24)
  • src/main/java/org/runimo/runimo/auth/controller/AuthController.java (4 hunks)
  • src/main/java/org/runimo/runimo/auth/controller/AuthResponse.java (1 hunks)
  • src/main/java/org/runimo/runimo/auth/domain/SignupToken.java (1 hunks)
  • src/main/java/org/runimo/runimo/auth/jwt/JwtResolver.java (1 hunks)
  • src/main/java/org/runimo/runimo/auth/jwt/JwtTokenFactory.java (1 hunks)
  • src/main/java/org/runimo/runimo/auth/jwt/SignupTokenPayload.java (1 hunks)
  • src/main/java/org/runimo/runimo/auth/repository/SignupTokenRepository.java (1 hunks)
  • src/main/java/org/runimo/runimo/auth/service/EncryptUtil.java (1 hunks)
  • src/main/java/org/runimo/runimo/auth/service/OidcService.java (2 hunks)
  • src/main/java/org/runimo/runimo/auth/service/SignUpUsecaseImpl.java (1 hunks)
  • src/main/java/org/runimo/runimo/auth/service/apple/AppleLoginHandler.java (2 hunks)
  • src/main/java/org/runimo/runimo/auth/service/apple/AppleTokenVerifier.java (3 hunks)
  • src/main/java/org/runimo/runimo/auth/service/dtos/AuthResponse.java (0 hunks)
  • src/main/java/org/runimo/runimo/auth/service/dtos/AuthResult.java (1 hunks)
  • src/main/java/org/runimo/runimo/auth/service/dtos/AuthStatus.java (1 hunks)
  • src/main/java/org/runimo/runimo/auth/service/kakao/KakaoLoginHandler.java (2 hunks)
  • src/main/java/org/runimo/runimo/common/CreatedAuditEntity.java (1 hunks)
  • src/main/java/org/runimo/runimo/exceptions/GlobalExceptionHandler.java (0 hunks)
  • src/main/java/org/runimo/runimo/exceptions/RegisterErrorResponse.java (1 hunks)
  • src/main/resources/sql/schema.sql (2 hunks)
  • src/test/java/org/runimo/runimo/auth/controller/AuthControllerTest.java (4 hunks)
  • src/test/java/org/runimo/runimo/rewards/RewardTest.java (2 hunks)
  • src/test/java/org/runimo/runimo/user/api/UserItemAcceptanceTest.java (2 hunks)
  • src/test/resources/sql/schema.sql (2 hunks)
💤 Files with no reviewable changes (2)
  • src/main/java/org/runimo/runimo/exceptions/GlobalExceptionHandler.java
  • src/main/java/org/runimo/runimo/auth/service/dtos/AuthResponse.java
🧰 Additional context used
🧬 Code Graph Analysis (6)
src/main/java/org/runimo/runimo/auth/domain/SignupToken.java (1)
src/main/java/org/runimo/runimo/common/GlobalConsts.java (1)
  • NoArgsConstructor (8-21)
src/main/java/org/runimo/runimo/common/CreatedAuditEntity.java (1)
src/main/java/org/runimo/runimo/common/GlobalConsts.java (1)
  • NoArgsConstructor (8-21)
src/test/java/org/runimo/runimo/auth/controller/AuthControllerTest.java (1)
src/test/java/org/runimo/runimo/user/UserFixtures.java (1)
  • UserFixtures (6-27)
src/test/java/org/runimo/runimo/rewards/RewardTest.java (1)
src/main/java/org/runimo/runimo/auth/jwt/JwtTokenFactory.java (1)
  • JwtTokenFactory (10-72)
src/main/java/org/runimo/runimo/auth/controller/AuthController.java (1)
src/main/java/org/runimo/runimo/common/response/ErrorResponse.java (1)
  • ErrorResponse (5-22)
src/main/java/org/runimo/runimo/auth/service/SignUpUsecaseImpl.java (2)
src/main/java/org/runimo/runimo/auth/jwt/JwtResolver.java (1)
  • JwtResolver (11-44)
src/main/java/org/runimo/runimo/auth/jwt/JwtTokenFactory.java (1)
  • JwtTokenFactory (10-72)
🪛 ast-grep (0.31.1)
src/main/java/org/runimo/runimo/auth/service/EncryptUtil.java

[warning] 23-23: 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(CIPHER_TRANS)
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] 23-23: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Cipher.getInstance(CIPHER_TRANS)
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 (43)
src/main/java/org/runimo/runimo/auth/service/dtos/AuthStatus.java (1)

1-6: Clean enum implementation for authentication status

This enum provides a clear way to represent authentication outcomes. The two states (LOGIN_SUCCESS and SIGNUP_NEEDED) align well with the authentication flow changes described in the PR objectives, particularly for handling the scenario when users need to register.

src/main/java/org/runimo/runimo/auth/controller/AuthResponse.java (1)

1-12: Well-structured response record with clear OpenAPI documentation

The AuthResponse record effectively encapsulates all necessary information for authentication responses. Good use of the @Schema annotation to provide clear documentation for API consumers.

src/main/java/org/runimo/runimo/auth/jwt/SignupTokenPayload.java (1)

5-7: Appropriate renaming and token field addition

The record has been renamed from RegisterTokenPayload to SignupTokenPayload for consistency, and the new token field has been added to support the UUID-based identification described in the PR objectives. These changes align well with the temporary token storage mechanism being implemented.

src/main/java/org/runimo/runimo/auth/repository/SignupTokenRepository.java (1)

1-15: Well-structured repository implementation for token management.

This repository interface is well-designed for managing signup tokens with proper expiration handling through the custom query method. The implementation correctly extends JpaRepository for standard CRUD operations and adds a specialized query to find unexpired tokens, which aligns with the PR's objective of implementing temporary token storage with expiration checks.

src/test/java/org/runimo/runimo/user/api/UserItemAcceptanceTest.java (2)

18-18: Correctly added UUID import.

This import has been properly added to support the generation of random UUIDs for signup tokens.


139-141: Updated method call to match new implementation.

This test has been correctly updated to use the renamed method generateSignupTemporalToken with the additional UUID parameter, aligning with the changes in the authentication flow described in the PR objectives.

src/main/java/org/runimo/runimo/auth/domain/SignupToken.java (5)

1-14: Appropriate imports for entity class.

All necessary imports for JPA annotations, Lombok, and domain classes have been included correctly.


15-19: Well-designed entity class with proper annotations.

The entity is correctly annotated with JPA and Lombok annotations, extending the audit entity for creation timestamp tracking.


21-23: Appropriate ID field configuration.

The token field is correctly configured as the primary key with non-null and unique constraints, which is essential for secure token handling.


25-33: Well-structured entity fields with appropriate constraints.

The fields are properly annotated with appropriate nullability constraints. The provider_id is correctly marked as non-nullable while refresh_token is nullable (as it's only needed for Apple login). The social provider is appropriately stored as a string enum.


35-42: Builder pattern implementation for flexible object creation.

The builder pattern is appropriately implemented, making it easy to create instances with specific parameters. All fields are properly initialized in the constructor.

src/main/java/org/runimo/runimo/auth/jwt/JwtResolver.java (2)

29-30: Method renamed for consistency with signup flow.

The method has been renamed from getRegisterTokenPayload to getSignupTokenPayload for consistency with the overall signup token approach.


37-38: Updated return statement to include JWT subject as token identifier.

The return statement now correctly includes the JWT subject as the first parameter to the SignupTokenPayload constructor. This aligns with the PR objective of using a UUID for identification in the signup process.

src/test/resources/sql/schema.sql (2)

3-3: LGTM: Properly added drop statement for the new table

The addition of the DROP TABLE IF EXISTS signup_token; statement ensures clean test database initialization.


80-87: Approve new signup_token table schema

The new signup_token table has been properly defined with appropriate columns to support the temporary storage of social login data during the signup process.

src/main/java/org/runimo/runimo/auth/service/EncryptUtil.java (1)

15-16: LGTM: Good constant definitions

Extracting the cipher transformation and algorithm as constants improves maintainability.

src/main/resources/sql/schema.sql (2)

3-3: LGTM: Proper drop statement for the new table

Adding DROP TABLE IF EXISTS signup_token; ensures clean database initialization.


69-78: LGTM: Appropriate placement of apple_user_token table definition

The apple_user_token table definition is properly structured.

src/test/java/org/runimo/runimo/rewards/RewardTest.java (1)

8-9: Integration of SignupToken functionality looks good.

The implementation correctly follows the new signup flow by generating a UUID key, creating a temporary token using generateSignupTemporalToken, and storing the token details in the database through signupTokenRepository. This aligns with the PR objective of temporarily storing social data for the registration process.

I particularly like that you're using UUID.randomUUID() to generate unique keys for each test run, which ensures test isolation.

Also applies to: 13-14, 15-16, 58-59, 63-76

src/test/java/org/runimo/runimo/auth/controller/AuthControllerTest.java (3)

9-10: Clean integration of ObjectMapper and new DTOs.

The imports and autowired ObjectMapper support the updated authentication flow and JSON serialization needs.

Also applies to: 12-13, 18-21, 55-56


76-80: Great update to the unregistered user test case.

The test now correctly reflects the new authentication flow where a signup token is returned instead of throwing an exception. This aligns with the PR objective of issuing a temporary token when a user is not found.

Also applies to: 85-86


96-103: Updated success case matches new response structure.

Good job updating the test to use AuthResult.success with the proper token pair and user fixture. The expected values for nickname and image URL now match the UserFixtures implementation.

Also applies to: 111-113

src/main/java/org/runimo/runimo/exceptions/RegisterErrorResponse.java (1)

4-5: Good inheritance structure for RegisterErrorResponse.

The class now properly extends the base Response class and uses CustomResponseCode for standardized error handling. This is a good refactoring that improves code organization and maintains consistency across the application's response types.

Also applies to: 8-9, 12-14

src/main/java/org/runimo/runimo/auth/service/OidcService.java (1)

5-6: Consistent return type update to AuthResult.

Both kakaoLogin and appleLogin methods now return AuthResult instead of AuthResponse, which aligns with the standardized response structure mentioned in the PR objectives. This change ensures consistency across the authentication flow.

Also applies to: 16-17, 20-21

src/main/java/org/runimo/runimo/auth/service/SignUpUsecaseImpl.java (8)

3-5: No concerns for updated imports.


8-9: No concerns for newly added imports.


12-13: No concerns for newly added imports.


15-15: No concerns for newly added import.


19-19: Transactional annotation is appropriate.

Using @Transactional on the registration method ensures atomicity when creating users and tokens, preventing partial data persistence.


28-30: Fields injection check.

The addition of signupTokenRepository, appleUserTokenRepository, and jwtResolver fields looks good. Confirm that these beans are properly available and tested.


44-46: Apple-specific token logic is well-placed.

Conditionally invoking createAppleUserToken is a straightforward approach for Apple logins. This separation ensures each provider’s unique flows are handled in an isolated manner.


57-63: Consider checking for null refresh tokens.

In createAppleUserToken, signupToken.getRefreshToken() may be null if not properly set. You may want to validate or handle a null refresh token scenario to avoid unexpected database states.

src/main/java/org/runimo/runimo/auth/service/kakao/KakaoLoginHandler.java (5)

6-16: No concerns for added imports and new fields.


31-31: SignupTokenRepository field injection recognized.


33-55: Enhanced authentication flow.

The validateAndLogin method returning AuthResult is more expressive and aligns with the uniform response approach used across the codebase. Nice improvement in clarity and handling signup-needed vs. login-success states.


57-68: Temporary signup token creation logic is coherent.

Creating a random UUID for the DB entity and returning a signed JWT to the client is consistent. Ensure that the token is invalidated or removed if unused after 10 minutes to prevent buildup or possible misuse.


69-75: Improved error handling for JWT decoding.

Decoding and throwing a dedicated exception for an invalid JWT helps maintain consistent error reporting. This approach is a solid way to handle malformed tokens.

src/main/java/org/runimo/runimo/auth/controller/AuthController.java (3)

18-19: No issues with the new imports.


23-23: No concerns about adding the Response import.


73-77: Apple login flow is consistent with the new return type.
No major concerns here, as the logic matches the Kakao login approach.

src/main/java/org/runimo/runimo/auth/service/apple/AppleLoginHandler.java (3)

6-7: Added imports look good.


9-9: Repository introduction and DTO imports are consistent with the new signup token logic.

Also applies to: 12-12, 14-15, 32-32


78-82:

Details

❓ Verification inconclusive

Confirm actual signature verification outside this method.
This method only decodes the token without verifying its signature. It’s likely verified in appleTokenVerifier.verifyTokenAndExtractUserInfo, but let’s confirm proper usage.


🏁 Script executed:

#!/bin/bash
# Checking all occurrences of decodeJwtOrThrow to ensure a subsequent signature verification step:
rg -A 5 "decodeJwtOrThrow"

Length of output: 2916


Ensure JWT Signature Verification in AppleLoginHandler Is Confirmed

The decodeJwtOrThrow method is correctly limited to decoding the JWT. In the AppleLoginHandler, the decoded token is immediately passed to appleTokenVerifier.verifyTokenAndExtractUserInfo—mirroring the pattern seen in the Kakao handler—to handle signature verification. Please confirm that verifyTokenAndExtractUserInfo does perform complete JWT signature validation, and consider adding a brief in-line comment clarifying that responsibility.

Comment on lines +33 to 48
@Transactional
public SignupUserResponse register(UserSignupCommand command) {
RegisterTokenPayload payload = jwtResolver.getRegisterTokenPayload(command.registerToken());
SignupTokenPayload payload = jwtResolver.getSignupTokenPayload(command.registerToken());
SignupToken signupToken = findUnExpiredSignupToken(payload.token());
User savedUser = userRegisterService.registerUser(new UserRegisterCommand(
command.nickname(),
command.imgUrl(),
command.gender(),
payload.providerId(),
payload.socialProvider())
);
if (payload.socialProvider() == SocialProvider.APPLE) {
createAppleUserToken(savedUser.getId(), signupToken);
}
return new SignupUserResponse(savedUser, jwtTokenFactory.generateTokenPair(savedUser));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle missing or invalid tokens more clearly.

In the register method, if command.registerToken() is null or the token is missing specific claims, it will throw. Consider returning a more meaningful exception or custom error response to facilitate client-side handling.

Copy link
Copy Markdown

@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/org/runimo/runimo/auth/service/apple/AppleLoginHandler.java (2)

35-41: Comprehensive Javadoc but consider adding English translation.

The Javadoc is detailed but written in Korean. Consider adding English translation to improve accessibility for international contributors or adding a bilingual comment.

/**
 * 애플 로그인 검증을 처리하고 사용자를 로그인 또는 회원가입으로 안내합니다.
+ * Validates Apple login and guides users to either login or signup.
 *
 * @param authCode iOS 앱에서 받은 oidc 인증 코드.
+ * @param authCode OIDC authentication code received from iOS app.
 * @param verifier iOS 앱에서 받은 PKCE용 Verifier.
+ * @param verifier PKCE verifier received from iOS app.
 * @return 인증 결과를 담은 객체; 로그인 성공 시 사용자 정보와 토큰 쌍을 포함합니다. 실패시 회원가입 토큰을 반환합니다.
+ * @return AuthResult object containing authentication results; includes user info and token pair on success, or signup token on failure.
 */

66-76: Well-implemented temporary signup token creation.

This new method effectively implements the PR objective of temporarily storing social data in the database. It:

  1. Generates a UUID as required
  2. Properly encrypts the sensitive refresh token
  3. Saves provider_id and provider information
  4. Stores the token in the database via the repository

However, there's no explicit handling for token expiration or cleanup. According to the PR objectives, this will be addressed in future work, but consider adding a TODO comment to document this intention.

public SignupToken createTemporalSignupToken(AppleUserInfo appleUserInfo, TokenPair appleTokens) {
    String temporalTokenId = UUID.randomUUID().toString();
    String encryptedRefreshToken = encryptUtil.encrypt(appleTokens.refreshToken());
    SignupToken signupToken = new SignupToken(
        temporalTokenId,
        appleUserInfo.getProviderId(),
        encryptedRefreshToken,
        SocialProvider.APPLE
    );
+   // TODO: Implement scheduled cleanup of expired tokens (see PR #54)
    return signupTokenRepository.save(signupToken);
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e4bd359 and cce224b.

📒 Files selected for processing (2)
  • src/main/java/org/runimo/runimo/auth/service/apple/AppleLoginHandler.java (2 hunks)
  • src/main/java/org/runimo/runimo/user/service/dtos/UserRegisterCommand.java (1 hunks)
🔇 Additional comments (7)
src/main/java/org/runimo/runimo/user/service/dtos/UserRegisterCommand.java (2)

3-3: Added import for validation annotation

Adding the import for Jakarta validation's @NotNull constraint is necessary for implementing the field-level validations below.


11-12: Appropriate validation added to social authentication fields

Adding the @NotNull validation to providerId and socialProvider fields aligns with the PR objective of standardizing authentication flows. This ensures that these critical fields for social authentication cannot be null during user registration, which is essential since these values are retrieved from the temporary token storage.

Note that other fields (nickname, imgUrl, gender) remain without @NotNull annotations, which suggests they're optional during registration. This appears to be intentional, but you may want to confirm this is the desired behavior.

src/main/java/org/runimo/runimo/auth/service/apple/AppleLoginHandler.java (5)

6-7: Added imports for functional changes look appropriate.

The addition of Optional and UUID imports align with the new functionality for handling signup tokens and proper nullability management.


9-16: Imports support the new authentication flow correctly.

The new imports reflect the significant changes to the authentication flow, with SignupToken, SignupTokenRepository, and the new AuthResult/AuthStatus DTOs supporting the temporary token storage approach mentioned in the PR objectives.


32-32: Dependency injection for SignupTokenRepository correctly added.

This is necessary for the new functionality that stores temporary social login data.


62-64: Refactored successful login flow aligns with PR objectives.

This segment properly handles the successful login case when the user is already registered, generating a token pair and returning an appropriate AuthResult. The implementation correctly follows the design pattern established in the PR objectives.


78-84: Good error handling extraction improves code maintainability.

Extracting the JWT decoding logic into a separate method improves code readability and maintainability. The method properly handles JWTDecodeException and throws a more specific UserJwtException with an appropriate error code.

Copy link
Copy Markdown
Contributor

@jeeheaG jeeheaG left a comment

Choose a reason for hiding this comment

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

고생하셨습니다!

Comment thread src/main/resources/sql/schema.sql
@ekgns33 ekgns33 merged commit 3e87a9a into main Apr 15, 2025
5 checks passed
@ekgns33 ekgns33 deleted the fix/signup-token branch April 15, 2025 16:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants