Skip to content

Conversation

@jaelyangChoi
Copy link
Collaborator

@jaelyangChoi jaelyangChoi commented Oct 21, 2025

📌 관련 이슈 (필수)

이 PR이 어떤 이슈를 해결하는지 작성해주세요. 예시: closes #123, resolves #456

📝 작업 내용 (필수)

이번 PR에서 작업한 내용을 간략히 설명해주세요.

💬 리뷰 참고 사항 (선택)

리뷰어가 참고할 만한 사항이나 리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요.

Summary by CodeRabbit

  • New Features

    • Account linking for social sign-in: requests/responses now include optional accountLinking and linkToken.
  • Bug Fixes / Cleanup

    • User withdrawal now also removes user tags during account deletion.
    • Social signup with an already-registered email returns HTTP 409 (Conflict) and indicates which provider was used.
  • Documentation

    • Social login docs updated to cover signup, account-linking fields, and conflict behavior.

@coderabbitai
Copy link

coderabbitai bot commented Oct 21, 2025

Walkthrough

Adds account-linking to social authentication: extends SocialLoginRequest with accountLinking and linkToken, updates controller and UserService to handle linking and link-token storage, introduces ALREADY_REGISTERED_EMAIL (HTTP 409) error/detail changes, and adds user-tag deletion on withdrawal plus related tests and docs.

Changes

Cohort / File(s) Summary
Docs
capturecat-core/src/docs/asciidoc/auth.adoc
Renames section to "소셜 로그인/회원가입"; documents HTTP 409 response when signing up with an already-registered email and includes which social provider was used.
Controller & DTO
capturecat-core/src/main/java/com/capturecat/core/api/auth/Oauth2AuthController.java, capturecat-core/src/main/java/com/capturecat/core/api/auth/dto/SocialLoginRequest.java
Adds accountLinking (boolean) and linkToken (String) to SocialLoginRequest; controller forwards new fields to UserService.upsertSocialUser; removes unused imports.
Service (user)
capturecat-core/src/main/java/com/capturecat/core/service/user/UserService.java
Expands upsertSocialUser signature to (OidcUserPayload payload, boolean accountLinking, String linkToken); introduces generateOrFetchUser and saveSocialAccount helpers; throws ALREADY_REGISTERED_EMAIL when duplicate email and linking disabled; deletes user tags during withdrawal.
Error infra
capturecat-core/src/main/java/com/capturecat/core/support/error/CoreException.java, capturecat-core/src/main/java/com/capturecat/core/support/error/ErrorCode.java, capturecat-core/src/main/java/com/capturecat/core/support/error/ErrorType.java, capturecat-core/src/main/java/com/capturecat/core/support/handler/CoreExceptionHandler.java, capturecat-core/src/main/java/com/capturecat/core/support/response/ApiResponse.java
Replaces rawResponseBody:String with detail:Object in CoreException and handlers; widens ApiResponse.error to accept Object detail; adds ALREADY_REGISTERED_EMAIL ErrorCode and ErrorType (HTTP 409).
Repository
capturecat-core/src/main/java/com/capturecat/core/domain/user/UserTagRepository.java
Adds void deleteAllByUserId(Long userId) annotated with @Modifying and @Query for bulk deletion of user tags.
Tests — controller / integration
capturecat-core/src/test/java/com/capturecat/core/api/auth/Oauth2AuthControllerSliceTest.java, capturecat-core/src/test/java/com/capturecat/core/api/auth/Oauth2AuthControllerTest.java, capturecat-core/src/test/java/com/capturecat/core/api/error/ErrorCodeControllerTest.java
Updates SocialLoginRequest usage to include new fields; updates mocks and expectations to pass (payload, accountLinking, linkToken); adds ALREADY_REGISTERED_EMAIL to documented error descriptors and response schema.
Tests — service
capturecat-core/src/test/java/com/capturecat/core/service/auth/SocialLoginLinkingTest.java, capturecat-core/src/test/java/com/capturecat/core/service/user/UserServiceTest.java
New tests covering linking scenarios (existing link, duplicate email with/without linking, new user); verifies userTagRepository.deleteAllByUserId(...) during withdrawal.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Controller as Oauth2AuthController
    participant Service as UserService
    participant Repo as Repositories

    Client->>Controller: POST /oauth2/login {idToken, nickname, authToken, accountLinking, linkToken}
    Controller->>Service: upsertSocialUser(payload, accountLinking, linkToken)

    alt Social account exists
        Service->>Repo: findSocialAccount(provider, socialId)
        Repo-->>Service: socialAccount -> user
        Service-->>Controller: return LoginUser (200)
        Controller-->>Client: 200 OK {user}
    else Email exists & linking disabled
        Service->>Repo: findUserByEmail(email)
        Repo-->>Service: user found
        Service-->>Controller: throw CoreException(ALREADY_REGISTERED_EMAIL, providerInfo)
        Controller-->>Client: 409 Conflict {detail: providerInfo}
    else Email exists & linking enabled
        Service->>Repo: findUserByEmail(email)
        Repo-->>Service: user found
        Service->>Repo: save UserSocialAccount (with linkToken)
        Repo-->>Service: saved social account
        Service-->>Controller: return LoginUser (200)
        Controller-->>Client: 200 OK {user}
    else New email
        Service->>Repo: save new User
        Repo-->>Service: user created
        Service->>Repo: save UserSocialAccount (with linkToken)
        Repo-->>Service: saved social account
        Service-->>Controller: return LoginUser (200)
        Controller-->>Client: 200 OK {user}
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • leeedohyun

Poem

🐰 I hopped through code to link each one,
Tokens in paw, I chased the sun.
If emails clash, a 409 will peep,
I point the provider so errors sleep.
Now socials and users tumble and run. 🎀

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: 소셜 로그인 연동" (social login integration/linking) accurately reflects the main objective of the changeset. The modifications across multiple files—including updates to the social login controller, expanded SocialLoginRequest and UserService.upsertSocialUser signatures to support account linking parameters, new error handling for duplicate emails with HTTP 409 responses, and comprehensive test coverage—are all cohesively focused on implementing social login linking functionality. The title is concise, specific, and clearly communicates the primary feature without misleading the reader.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#82-login-integration

📜 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 e35722e and 9118a80.

📒 Files selected for processing (2)
  • capturecat-core/src/test/java/com/capturecat/core/api/auth/Oauth2AuthControllerTest.java (3 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/api/error/ErrorCodeControllerTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • capturecat-core/src/test/java/com/capturecat/core/api/error/ErrorCodeControllerTest.java
  • capturecat-core/src/test/java/com/capturecat/core/api/auth/Oauth2AuthControllerTest.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: deploy-dev

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@jaelyangChoi jaelyangChoi changed the title feature/#82-login-integration feat: 소셜 로그인 연동 Oct 21, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aad4e48 and f6d45a0.

📒 Files selected for processing (13)
  • capturecat-core/src/docs/asciidoc/auth.adoc (1 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/api/auth/Oauth2AuthController.java (1 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/api/auth/dto/SocialLoginRequest.java (1 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/service/user/UserService.java (4 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/support/error/CoreException.java (1 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/support/error/ErrorCode.java (1 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/support/error/ErrorType.java (1 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/support/handler/CoreExceptionHandler.java (1 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/support/response/ApiResponse.java (1 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/api/auth/Oauth2AuthControllerSliceTest.java (7 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/api/auth/Oauth2AuthControllerTest.java (3 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/api/error/ErrorCodeControllerTest.java (1 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/service/auth/SocialLoginLinkingTest.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: deploy-dev
🔇 Additional comments (22)
capturecat-core/src/docs/asciidoc/auth.adoc (2)

5-5: LGTM! Clear documentation update.

The section title now accurately reflects that this endpoint handles both login and signup flows.


11-11: LGTM! Well-documented error behavior.

The documentation clearly explains the 409 Conflict response for duplicate email scenarios, which aligns with the account linking feature.

capturecat-core/src/test/java/com/capturecat/core/api/error/ErrorCodeControllerTest.java (1)

70-74: LGTM! Test coverage for new error code.

The test correctly includes ALREADY_REGISTERED_EMAIL in the social login error documentation.

capturecat-core/src/main/java/com/capturecat/core/support/error/ErrorCode.java (1)

45-46: LGTM! Proper error code addition.

The new ALREADY_REGISTERED_EMAIL error code is correctly added with a clear Korean message, consistent with the existing error codes in the enum.

capturecat-core/src/main/java/com/capturecat/core/api/auth/dto/SocialLoginRequest.java (1)

3-4: LGTM! Record extended for account linking.

The new accountLinking and linkToken fields are appropriately added. Note that linkToken can be null (handled correctly in UserService at line 193), which is expected when account linking is not being performed.

capturecat-core/src/main/java/com/capturecat/core/service/user/UserService.java (3)

71-71: LGTM! Method signature appropriately extended.

The method now accepts accountLinking and linkToken parameters to support the account linking feature.


187-196: LGTM! Well-encapsulated account persistence.

The method correctly handles the unlinkKey assignment, using linkToken when provided (for linking scenarios) and falling back to payload.unlinkKey() for initial signups.


235-236: LGTM! Clean error detail carrier.

The SocialLinkingInfo record provides a structured way to convey provider and link token information in error responses.

capturecat-core/src/main/java/com/capturecat/core/support/response/ApiResponse.java (1)

27-29: LGTM! Flexible error response with detail support.

The signature change from String to Object enables structured error details (like SocialLinkingInfo) to be returned in the response data field, improving error context for clients.

capturecat-core/src/main/java/com/capturecat/core/support/handler/CoreExceptionHandler.java (1)

26-30: LGTM! Consistent error detail handling.

The handler correctly logs and propagates the exception detail object, aligning with the refactor from rawResponseBody to detail.

capturecat-core/src/main/java/com/capturecat/core/support/error/ErrorType.java (1)

47-47: LGTM! Appropriate error type for duplicate email.

The new ALREADY_REGISTERED_EMAIL error type uses the correct HTTP 409 Conflict status and WARN log level for this client-side conflict scenario.

capturecat-core/src/main/java/com/capturecat/core/api/auth/Oauth2AuthController.java (1)

41-41: LGTM!

The controller correctly forwards the new account linking parameters to the service layer. The implementation is clean and aligns with the feature requirements.

capturecat-core/src/test/java/com/capturecat/core/service/auth/SocialLoginLinkingTest.java (4)

63-86: LGTM!

The test correctly verifies that existing linked accounts are returned without unnecessary database operations. Good use of never() to ensure no saves occur.


88-115: LGTM!

The test correctly validates that attempting to sign up with an existing email without account linking enabled throws ALREADY_REGISTERED_EMAIL exception. The test properly verifies that no database modifications occur in this error scenario.


117-148: LGTM!

The test comprehensively validates the account linking flow, correctly verifying that a new social account is linked to an existing user without creating a duplicate user. Good use of ArgumentCaptor to verify the saved entity's fields.


150-184: LGTM!

The test correctly validates the new user creation flow, ensuring both the user and social account are properly saved and linked. The test comprehensively verifies all required fields are set correctly.

capturecat-core/src/main/java/com/capturecat/core/support/error/CoreException.java (1)

11-21: LGTM!

The change from String rawResponseBody to Object detail provides better flexibility for carrying structured error information (like the SocialLinkingInfo record used in tests). The API surface change appears to be consistently handled across the codebase.

capturecat-core/src/test/java/com/capturecat/core/api/auth/Oauth2AuthControllerTest.java (1)

54-54: LGTM!

The test correctly constructs the request with the new account linking parameters and updates the mock to match the new service signature.

Also applies to: 64-64

capturecat-core/src/test/java/com/capturecat/core/api/auth/Oauth2AuthControllerSliceTest.java (4)

61-61: LGTM!

The existing tests are correctly updated to include the new accountLinking and linkToken parameters with appropriate default values.

Also applies to: 76-76, 100-100, 115-115, 139-139


153-179: LGTM!

The test comprehensively validates the conflict scenario when attempting to sign up with an already-registered email. It correctly expects HTTP 409 status and validates the structured error response including the provider information for account linking.


181-219: LGTM!

The test thoroughly validates the account linking flow, correctly setting accountLinking=true and providing a linkToken. It properly verifies that the login succeeds and returns the expected JWT tokens when account linking is enabled.


231-232: LGTM!

The SocialLinkingInfo record provides a clean, type-safe way to carry structured error details for the account linking conflict scenario. Good use of Java records for immutable test data.

Comment on lines +198 to +215
// 이메일로 가입된 계정을 불러오거나 신규 계정 생성
private User generateOrFetchUser(OidcUserPayload payload, boolean accountLinking) {
User user = null;
Optional<User> byUsername = userRepository.findByUsername(payload.email());
// 해당 이메일로 가입된 유저가 있을 경우
if (byUsername.isPresent()) {
user = byUsername.get();
// 연동이 아닐 경우 에러 응답
if (!accountLinking) {
String provider = userSocialAccountRepository.findByUser(user).getFirst().getProvider();
throw new CoreException(ErrorType.ALREADY_REGISTERED_EMAIL,
new SocialLinkingInfo(provider, payload.unlinkKey()));
}
} else { // 없을 경우 신규 생성
user = userRepository.save(buildUser(payload));
}
return user;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Risk: Potential NoSuchElementException on empty social accounts.

At line 207, getFirst() assumes the user has at least one associated UserSocialAccount. If a user exists in the database but has no social accounts (due to data inconsistency, migration issues, or manual operations), this will throw a NoSuchElementException.

Consider defensive handling:

 	private User generateOrFetchUser(OidcUserPayload payload, boolean accountLinking) {
 		User user = null;
 		Optional<User> byUsername = userRepository.findByUsername(payload.email());
 		// 해당 이메일로 가입된 유저가 있을 경우
 		if (byUsername.isPresent()) {
 			user = byUsername.get();
 			// 연동이 아닐 경우 에러 응답
 			if (!accountLinking) {
-				String provider = userSocialAccountRepository.findByUser(user).getFirst().getProvider();
+				List<UserSocialAccount> socialAccounts = userSocialAccountRepository.findByUser(user);
+				String provider = socialAccounts.isEmpty() 
+					? "UNKNOWN" 
+					: socialAccounts.getFirst().getProvider();
 				throw new CoreException(ErrorType.ALREADY_REGISTERED_EMAIL,
 					new SocialLinkingInfo(provider, payload.unlinkKey()));
 			}
 		} else { // 없을 경우 신규 생성
 			user = userRepository.save(buildUser(payload));
 		}
 		return user;
 	}

Alternatively, if users without social accounts represent invalid state, throw a more specific error explaining the data inconsistency.

📝 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
// 이메일로 가입된 계정을 불러오거나 신규 계정 생성
private User generateOrFetchUser(OidcUserPayload payload, boolean accountLinking) {
User user = null;
Optional<User> byUsername = userRepository.findByUsername(payload.email());
// 해당 이메일로 가입된 유저가 있을 경우
if (byUsername.isPresent()) {
user = byUsername.get();
// 연동이 아닐 경우 에러 응답
if (!accountLinking) {
String provider = userSocialAccountRepository.findByUser(user).getFirst().getProvider();
throw new CoreException(ErrorType.ALREADY_REGISTERED_EMAIL,
new SocialLinkingInfo(provider, payload.unlinkKey()));
}
} else { // 없을 경우 신규 생성
user = userRepository.save(buildUser(payload));
}
return user;
}
// 이메일로 가입된 계정을 불러오거나 신규 계정 생성
private User generateOrFetchUser(OidcUserPayload payload, boolean accountLinking) {
User user = null;
Optional<User> byUsername = userRepository.findByUsername(payload.email());
// 해당 이메일로 가입된 유저가 있을 경우
if (byUsername.isPresent()) {
user = byUsername.get();
// 연동이 아닐 경우 에러 응답
if (!accountLinking) {
List<UserSocialAccount> socialAccounts = userSocialAccountRepository.findByUser(user);
String provider = socialAccounts.isEmpty()
? "UNKNOWN"
: socialAccounts.getFirst().getProvider();
throw new CoreException(ErrorType.ALREADY_REGISTERED_EMAIL,
new SocialLinkingInfo(provider, payload.unlinkKey()));
}
} else { // 없을 경우 신규 생성
user = userRepository.save(buildUser(payload));
}
return user;
}
🤖 Prompt for AI Agents
In
capturecat-core/src/main/java/com/capturecat/core/service/user/UserService.java
around lines 198 to 215, the code calls findByUser(user).getFirst() which can
throw NoSuchElementException if the user has no UserSocialAccount entries;
defensively handle that case by retrieving the social account result safely
(check if present/empty) before calling getFirst(), and if empty throw a clear
CoreException indicating data inconsistency (or a specific error type) with
context (user id/email) or otherwise handle it explicitly so the code never
calls getFirst() on an empty collection.

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)
capturecat-core/src/main/java/com/capturecat/core/service/user/UserService.java (1)

204-221: Don’t call getFirst() on possibly empty list; handle missing social accounts.

Calling getFirst() can throw NoSuchElementException when the user exists but has no social accounts (data drift, migrations). Handle empty safely or throw a clear CoreException.

-                String provider = userSocialAccountRepository.findByUser(user).getFirst().getProvider();
-                throw new CoreException(ErrorType.ALREADY_REGISTERED_EMAIL,
-                    new SocialLinkingInfo(provider, payload.unlinkKey()));
+                List<UserSocialAccount> accounts = userSocialAccountRepository.findByUser(user);
+                String provider = accounts.isEmpty() ? "UNKNOWN" : accounts.get(0).getProvider();
+                throw new CoreException(
+                    ErrorType.ALREADY_REGISTERED_EMAIL,
+                    new SocialLinkingInfo(provider, /* ensure this is a safe, non-secret token */ payload.unlinkKey())
+                );

If users without social accounts are invalid state, throw a dedicated error describing the inconsistency.

🧹 Nitpick comments (3)
capturecat-core/src/main/java/com/capturecat/core/domain/user/UserTagRepository.java (1)

20-22: Bulk delete: clear/flush persistence context and return affected rows.

Add clearAutomatically/flushAutomatically to avoid stale state in the persistence context after JPQL delete, and return int for observability/tests.

-    @Modifying
-    @Query("delete from UserTag ut where ut.user.id = :userId")
-    void deleteAllByUserId(@Param("userId") Long userId);
+    @Modifying(flushAutomatically = true, clearAutomatically = true)
+    @Query("delete from UserTag ut where ut.user.id = :userId")
+    int deleteAllByUserId(@Param("userId") Long userId);

If this is ever called outside a service-level @transactional method, ensure transactional boundaries are present on the caller.

capturecat-core/src/test/java/com/capturecat/core/service/user/UserServiceTest.java (1)

125-127: Remove duplicate verification; optionally assert call order.

You verify userSettingsRepository.deleteById(savedUser.getId()) here and again on Line 132. The second verify is redundant; remove it or assert invocation count explicitly. Consider enforcing deletion order with InOrder.

@@
-        verify(userSettingsRepository).deleteById(savedUser.getId());
-        verify(userTagRepository).deleteAllByUserId(savedUser.getId());
+        verify(userSettingsRepository).deleteById(savedUser.getId());
+        verify(userTagRepository).deleteAllByUserId(savedUser.getId());
@@
-        verify(userSettingsRepository).deleteById(savedUser.getId());
+        // (optional) Already verified above; remove duplicate verify

Optional ordering check:

InOrder inOrder = inOrder(bookmarkRepository, imageTagRepository, imageRepository,
    userSettingsRepository, userTagRepository, userRepository);
inOrder.verify(bookmarkRepository).deleteByUserId(savedUser.getId());
inOrder.verify(imageTagRepository).deleteAllTagsByUserId(savedUser.getId());
inOrder.verify(imageRepository).deleteAllImagesByUserId(savedUser.getId());
inOrder.verify(userSettingsRepository).deleteById(savedUser.getId());
inOrder.verify(userTagRepository).deleteAllByUserId(savedUser.getId());
inOrder.verify(userRepository).deleteById(savedUser.getId());
capturecat-core/src/main/java/com/capturecat/core/service/user/UserService.java (1)

154-171: Deletion strategy: prefer model/DB cascade to avoid brittle test coupling.

The TODO calls it out: adding repositories per child entity scales poorly and forces test churn. Prefer:

  • DB FKs with ON DELETE CASCADE for join tables (e.g., user_tag).
  • JPA relationships with cascade + orphanRemoval where appropriate.
  • One top-level userRepository.deleteById(userId) with reliable cascades.

If bulk deletes remain, document ordering and keep them idempotent.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f6d45a0 and e35722e.

📒 Files selected for processing (3)
  • capturecat-core/src/main/java/com/capturecat/core/domain/user/UserTagRepository.java (2 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/service/user/UserService.java (7 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/service/user/UserServiceTest.java (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: deploy-dev
🔇 Additional comments (3)
capturecat-core/src/test/java/com/capturecat/core/service/user/UserServiceTest.java (1)

64-66: Added mock for UserTagRepository: LGTM.

Mock wiring is correct and enables verification of tag cleanup in withdraw.

capturecat-core/src/main/java/com/capturecat/core/service/user/UserService.java (2)

41-41: Wiring UserTagRepository: LGTM.

Field injection aligns with new cleanup path in withdraw.


73-86: upsert flow reads well; verify linking token usage matches unlink key semantics.

Overall control flow is clear. Ensure the token passed here isn’t later persisted as an unlinkKey. See comment on saveSocialAccount below.

Comment on lines +241 to +242
record SocialLinkingInfo(String provider, String linkToken) {
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Avoid exposing secrets in SocialLinkingInfo.

If the second field is currently carrying an unlinkKey, rename it and ensure it contains a safe, client-scoped link token instead. Never return a provider unlinkKey to clients.

-record SocialLinkingInfo(String provider, String linkToken) {
+record SocialLinkingInfo(String provider, String linkToken /* must NOT be unlinkKey */) {
 }

I'll verify this security concern by examining the codebase.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find the SocialLinkingInfo record definition and its usages
rg -n 'SocialLinkingInfo' capturecat-core/src/main/java/com/capturecat/core/service/user/UserService.java -B2 -A5

Length of output: 444


🏁 Script executed:

#!/bin/bash
# Search for where SocialLinkingInfo is instantiated
rg -n 'new SocialLinkingInfo|SocialLinkingInfo\s*\(' --type=java -B3 -A3

Length of output: 3287


🏁 Script executed:

#!/bin/bash
# Search for unlinkKey references in the codebase
rg -n 'unlinkKey' --type=java -B2 -A2

Length of output: 10812


🏁 Script executed:

#!/bin/bash
# Search for linkToken assignments and usage patterns
rg -n 'linkToken' --type=java -B2 -A2

Length of output: 7024


I need to verify how the exception data is handled and whether it's exposed to clients.


---

🏁 Script executed:

```shell
#!/bin/bash
# Check CoreException class and its handling
rg -n 'class CoreException' --type=java -A10

Length of output: 2907


🏁 Script executed:

#!/bin/bash
# Find exception handlers that might serialize SocialLinkingInfo
rg -n 'CoreException|@ExceptionHandler|@RestControllerAdvice' --type=java -B2 -A5

Length of output: 167251


🏁 Script executed:

#!/bin/bash
# Check if CoreException details are returned in responses
rg -n 'ErrorResponse|error.*response|exception.*response' --type=java -B2 -A5 | head -100

Length of output: 1953


🏁 Script executed:

#!/bin/bash
# Search for where the second parameter of CoreException is used
rg -n 'new CoreException\(' --type=java -B1 -A1

Length of output: 35423


Rename linkToken field or change the value—do not store unlinkKey here.

The SocialLinkingInfo.linkToken field receives payload.unlinkKey() at line 215, which is semantically incorrect. While this detail is currently logged server-side only (not exposed to clients), the field name contradicts its content and creates a code smell that could lead to accidental secret exposure if the error handling changes. Either rename the field to reflect it carries an unlinkKey, or ensure it carries only a safe, client-scoped link token as the current name implies.

@jaelyangChoi jaelyangChoi merged commit eb1fb45 into develop Dec 3, 2025
5 checks passed
@jaelyangChoi jaelyangChoi deleted the feature/#82-login-integration branch December 3, 2025 12:59
@prography prography deleted a comment from coderabbitai bot Dec 3, 2025
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.

유저 태그 수정 API 계정 관리 편의성을 높이기 위한 로그인 연동 기능

3 participants