Skip to content

feat: supabase 소셜 로그인 토큰 교환#176

Merged
lejuho merged 1 commit intodevelopfrom
feat/oauth-login-supabase-clean
Feb 4, 2026
Merged

feat: supabase 소셜 로그인 토큰 교환#176
lejuho merged 1 commit intodevelopfrom
feat/oauth-login-supabase-clean

Conversation

@lejuho
Copy link
Collaborator

@lejuho lejuho commented Feb 4, 2026

왜: Supabase OAuth를 기존 JWT 인증 흐름에 통합할 필요가 있음

무엇: Supabase 사용자 조회/검증, 사용자 매핑, 토큰 교환 API 및 유저 OAuth 필드 추가

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능
    • Supabase를 통한 OAuth 기반 로그인 기능 추가
    • 로그인 시 자동으로 사용자 프로필 정보 및 닉네임 생성
    • 토큰 관리 기능 강화: 토큰 폐기 및 만료된 토큰 자동 정리 기능 추가

왜: Supabase OAuth를 기존 JWT 인증 흐름에 통합할 필요가 있음

무엇: Supabase 사용자 조회/검증, 사용자 매핑, 토큰 교환 API 및 유저 OAuth 필드 추가
@coderabbitai
Copy link

coderabbitai bot commented Feb 4, 2026

Walkthrough

Supabase 기반 OAuth 인증을 통한 사용자 로그인 기능을 추가했습니다. 새로운 DTO, 컨트롤러 엔드포인트, 서비스 로직, 저장소 쿼리 메서드, Supabase 클라이언트, 토큰 관리 메서드, 데이터베이스 마이그레이션을 포함합니다.

Changes

Cohort / File(s) Summary
Supabase 요청 및 응답 DTO
src/main/java/.../auth/dto/request/SupabaseLoginRequest.java, src/main/java/.../global/supabase/SupabaseUserResponse.java
Supabase 로그인 요청과 사용자 응답을 위한 Lombok 기반 DTO 클래스 추가. SupabaseLoginRequest는 accessToken, SupabaseUserResponse는 id, email, app_metadata, user_metadata 필드 포함.
API 엔드포인트
src/main/java/.../auth/presentation/AuthController.java
새로운 /api/v1/auth/supabase POST 엔드포인트 추가. SupabaseLoginRequest를 받아 AuthService의 loginWithSupabase 메서드 호출.
인증 서비스 핵심 로직
src/main/java/.../auth/service/AuthService.java
Supabase 로그인 메서드(loginWithSupabase), 토큰 회수 메서드(revokeRefreshToken, revokeAllByUser), 토큰 정리 메서드(purgeExpiredTokens) 추가. 닉네임 생성, 프로필 업데이트, 토큰 발급 로직 포함.
사용자 엔티티 및 저장소
src/main/java/.../user/domain/User.java, src/main/java/.../user/domain/repository/UserRepository.java
User 엔티티에 oauthProvider, oauthSubject 필드 추가. UserRepository에 OAuth 기반 사용자 조회 메서드(findByOauthProviderAndOauthSubject) 추가.
Supabase 클라이언트
src/main/java/.../global/supabase/SupabaseAuthClient.java
WebClient를 사용하여 Supabase 사용자 엔드포인트와 통신하는 Spring 컴포넌트. fetchUser 메서드에서 accessToken 검증 및 오류 처리(401/403을 INVALID_TOKEN으로 매핑).
보안 설정
src/main/java/.../global/config/SecurityConfig.java
/api/v1/auth/supabase 엔드포인트를 인증 불필요 목록에 추가.
데이터베이스 마이그레이션
src/main/resources/db/migration/V4__add_user_oauth_fields.sql
users 테이블에 oauth_provider(varchar 50), oauth_subject(varchar 255) 컬럼 추가.

Sequence Diagram

sequenceDiagram
    participant Client
    participant AuthController
    participant AuthService
    participant SupabaseAuthClient
    participant UserRepository
    participant Database

    Client->>AuthController: POST /api/v1/auth/supabase<br/>{accessToken}
    AuthController->>AuthService: loginWithSupabase(accessToken, deviceId)
    
    AuthService->>SupabaseAuthClient: fetchUser(accessToken)
    SupabaseAuthClient->>SupabaseAuthClient: 헤더 설정 및<br/>토큰 검증
    SupabaseAuthClient-->>AuthService: SupabaseUserResponse<br/>(id, email, metadata)
    
    AuthService->>UserRepository: findByOauthProviderAndOauthSubject<br/>("supabase", userId)
    UserRepository->>Database: 쿼리 실행
    Database-->>UserRepository: User 또는 null
    UserRepository-->>AuthService: Optional<User>
    
    alt 신규 사용자
        AuthService->>AuthService: 닉네임 생성<br/>프로필 이미지 추출
        AuthService->>UserRepository: save(User)
        UserRepository->>Database: INSERT
        Database-->>UserRepository: 저장된 User
    else 기존 사용자
        AuthService->>AuthService: 프로필 업데이트 적용
        AuthService->>UserRepository: save(User)
        UserRepository->>Database: UPDATE
    end
    
    AuthService->>AuthService: issueTokens(User)<br/>accessToken, refreshToken 생성
    AuthService-->>AuthController: AnonymousRegistrationResponse<br/>(userId, nickname, isNewUser, tokens)
    AuthController-->>Client: ApiResponse<br/>로그인 결과 반환
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Refactor/lejuhooo #78: AnonymousRegistrationResponse에 userId 필드 추가 및 AuthService의 응답 흐름 업데이트로 직접 연관됨.

Poem

🐰 토끼가 Supabase 길을 뛰어가며
OAuth 토큰 들고 기뻐하네
사용자를 찾고, 닉네임을 지어주고
토큰을 쓱쓱 발급하면
새로운 친구들이 반갑게 인사해요! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 Supabase OAuth 토큰 교환 기능이라는 주요 변경사항을 명확하게 요약하고 있으며, 간결하고 구체적입니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feat/oauth-login-supabase-clean

Important

Action Needed: IP Allowlist Update

If your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:

  • 136.113.208.247/32 (new)
  • 34.170.211.100/32
  • 35.222.179.152/32

Reviews will stop working after February 8, 2026 if the new IP is not added to your allowlist.


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.

@lejuho lejuho merged commit 3ffeb59 into develop Feb 4, 2026
10 of 11 checks passed
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

🤖 Fix all issues with AI agents
In
`@src/main/java/com/example/cp_main_be/domain/member/auth/presentation/AuthController.java`:
- Around line 44-48: Add validation to the SupabaseLoginRequest DTO by
annotating the accessToken field with `@NotBlank`(message = "accessToken은 필수입니다")
(ensure you use the javax/jakarta.validation import), and update the
AuthController.loginWithSupabase method signature to accept a validated request
by adding `@Valid` to the `@RequestBody` parameter (i.e., change to `@Valid`
`@RequestBody` SupabaseLoginRequest request); this enforces non-empty accessToken
and prevents invalid requests from reaching the service.

In
`@src/main/java/com/example/cp_main_be/domain/member/auth/service/AuthService.java`:
- Around line 99-102: loginWithSupabase currently performs the external HTTP
call supabaseAuthClient.fetchUser(accessToken) inside a method annotated with
`@Transactional`, which keeps the DB transaction open during network I/O; extract
the Supabase call into a separate non-transactional helper (e.g.,
fetchSupabaseUser(...) or make fetchUser call before entering the transactional
scope) so that loginWithSupabase only performs database work inside the
transactional block; keep `@Transactional` on the DB-updating portion (the method
doing member creation/updates) and call the external fetchUser outside that
transaction to prevent connection pool exhaustion.
- Around line 124-131: buildUniqueNickname() can produce nicknames exceeding the
DB varchar(255) and doesn't handle null Supabase emails; modify
buildUniqueNickname() to sanitize input, truncate the base nickname to 250
characters before appending the 6-char suffix (ensuring final length ≤ 256 or
better truncate to 249+6 to be safe for 255), and enforce a min-safe fallback
when metadata is empty. Also update the code that reads supabaseUser.getEmail()
(used in User.builder()) to validate null emails and either supply a safe
default (e.g., "no-email@local") or throw a clear validation exception before
constructing the User; reference the buildUniqueNickname() method and the
User.builder() usage so the nickname and email constraints are applied
consistently.

In
`@src/main/java/com/example/cp_main_be/global/supabase/SupabaseAuthClient.java`:
- Around line 24-42: The fetchUser method in SupabaseAuthClient uses
WebClient.block() with no timeout; update the call in fetchUser(String
accessToken) to use a bounded wait such as .block(Duration.ofSeconds(5)) and add
the required import (java.time.Duration) to prevent thread starvation when
Supabase is slow or unresponsive; keep existing onStatus handlers and return
type unchanged.

In `@src/main/resources/db/migration/V4__add_user_oauth_fields.sql`:
- Around line 1-3: The migration adds oauth_provider and oauth_subject to users
but lacks a uniqueness constraint, allowing duplicate OAuth accounts; update the
migration to add a unique constraint on the (oauth_provider, oauth_subject) pair
for the users table (e.g., via ALTER TABLE users ADD CONSTRAINT ... UNIQUE on
oauth_provider and oauth_subject) so that duplicate OAuth mappings cannot be
created and lookups in authentication logic that rely on this pair remain
deterministic.
🧹 Nitpick comments (5)
src/main/java/com/example/cp_main_be/global/config/SecurityConfig.java (1)

37-40: 공개 엔드포인트를 POST로 제한 권장

Line 37-40에서 /api/v1/auth/supabase가 모든 HTTP 메서드에 대해 공개됩니다. 토큰 교환이 POST 전용이라면 메서드 제한으로 공격 표면을 줄이는 게 좋습니다.

✏️ 제안 변경
+import org.springframework.http.HttpMethod;
 ...
-                    .requestMatchers(
-                        "/api/v1/auth/signup",
-                        "/api/v1/auth/supabase",
-                        "/api/v1/auth/refresh",
+                    .requestMatchers(HttpMethod.POST, "/api/v1/auth/supabase")
+                    .permitAll()
+                    .requestMatchers(
+                        "/api/v1/auth/signup",
+                        "/api/v1/auth/refresh",
                         "/api/v1/policy",
                         "/swagger-ui/**", // Swagger UI 페이지
                         "/v3/api-docs/**", // OpenAPI 명세서
src/main/java/com/example/cp_main_be/domain/member/auth/dto/request/SupabaseLoginRequest.java (1)

5-7: accessToken 유효성 검증 추가 권장

Line 5-7에 입력 검증이 없어서 컨트롤러 단계에서 명확한 400 응답을 만들기 어렵습니다. @NotBlank 추가와 함께 컨트롤러에서 @Valid 적용을 권장합니다.

✅ 제안 변경
+import jakarta.validation.constraints.NotBlank;
 ...
 `@Getter`
 public class SupabaseLoginRequest {
-  private String accessToken;
+  `@NotBlank`
+  private String accessToken;
 }
src/main/java/com/example/cp_main_be/domain/member/user/domain/User.java (1)

56-60: 컬럼 길이 스키마와 정합성 맞추기

Line 56-60의 컬럼 길이가 DB 스키마(varchar(50), varchar(255))와 맞지 않습니다. 엔티티에 length를 명시하면 스키마 드리프트를 줄일 수 있습니다.

🧩 제안 변경
-  `@Column`(name = "oauth_provider")
+  `@Column`(name = "oauth_provider", length = 50)
   private String oauthProvider;

-  `@Column`(name = "oauth_subject")
+  `@Column`(name = "oauth_subject", length = 255)
   private String oauthSubject;
src/main/java/com/example/cp_main_be/global/supabase/SupabaseUserResponse.java (1)

8-19: Supabase 응답 확장 대비를 위해 unknown 필드 무시 권장

Line 8-19 DTO에 @JsonIgnoreProperties(ignoreUnknown = true)를 추가하면 Supabase가 필드를 추가해도 역직렬화 실패를 방지할 수 있습니다.

🔧 제안 변경
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 ...
 `@Getter`
 `@NoArgsConstructor`
+@JsonIgnoreProperties(ignoreUnknown = true)
 public class SupabaseUserResponse {
src/main/java/com/example/cp_main_be/domain/member/auth/service/AuthService.java (1)

249-277: 닉네임 중복 레이스 대비 권장

existsByNickname 선검사는 동시 로그인 시 충돌 가능성이 있습니다. 저장 실패 시 재시도하거나, DB unique 제약 + 재시도 전략으로 보완을 권장합니다.

Comment on lines +44 to +48
@Operation(summary = "Supabase 소셜 로그인", description = "Supabase OAuth 토큰을 우리 서비스 토큰으로 교환합니다.")
@PostMapping("/supabase")
public ResponseEntity<ApiResponse<AnonymousRegistrationResponse>> loginWithSupabase(
@RequestBody SupabaseLoginRequest request,
@RequestHeader(value = "X-Client-Device-Id", required = false) String deviceId) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find and display SupabaseLoginRequest
echo "=== SupabaseLoginRequest.java ===" 
file=$(fd -a 'SupabaseLoginRequest.java' 2>/dev/null | head -n 1)
if [ -n "$file" ]; then
  cat -n "$file"
else
  echo "File not found"
fi

Repository: Const-Project/MainBE

Length of output: 316


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find and display AuthController around the loginWithSupabase method
echo "=== AuthController.java (lines 40-55) ===" 
file=$(fd -a 'AuthController.java' 2>/dev/null | head -n 1)
if [ -n "$file" ]; then
  sed -n '40,55p' "$file" | cat -n
  echo ""
  echo "=== Checking imports ===" 
  head -n 30 "$file" | grep -i "import"
else
  echo "File not found"
fi

Repository: Const-Project/MainBE

Length of output: 1666


SupabaseLoginRequest에 검증 애노테이션과 @Valid 추가 필요

SupabaseLoginRequest에 검증 애노테이션이 없어서 빈 accessToken이 서비스로 직접 전달될 수 있습니다. 불필요한 Supabase 호출과 오류를 방지하려면 다음 두 가지를 수정하세요:

  1. DTO에 검증 애노테이션 추가: @NotBlank(message = "accessToken은 필수입니다")
  2. 컨트롤러 메서드에 @Valid 추가
✅ 수정 제안

SupabaseLoginRequest.java:

 package com.example.cp_main_be.domain.member.auth.dto.request;

 import lombok.Getter;
+import jakarta.validation.constraints.NotBlank;

 `@Getter`
 public class SupabaseLoginRequest {
+  `@NotBlank`(message = "accessToken은 필수입니다")
   private String accessToken;
 }

AuthController.java:

 public ResponseEntity<ApiResponse<AnonymousRegistrationResponse>> loginWithSupabase(
-    `@RequestBody` SupabaseLoginRequest request,
+    `@RequestBody` `@Valid` SupabaseLoginRequest request,
     `@RequestHeader`(value = "X-Client-Device-Id", required = false) String deviceId) {
🤖 Prompt for AI Agents
In
`@src/main/java/com/example/cp_main_be/domain/member/auth/presentation/AuthController.java`
around lines 44 - 48, Add validation to the SupabaseLoginRequest DTO by
annotating the accessToken field with `@NotBlank`(message = "accessToken은 필수입니다")
(ensure you use the javax/jakarta.validation import), and update the
AuthController.loginWithSupabase method signature to accept a validated request
by adding `@Valid` to the `@RequestBody` parameter (i.e., change to `@Valid`
`@RequestBody` SupabaseLoginRequest request); this enforces non-empty accessToken
and prevents invalid requests from reaching the service.

Comment on lines +99 to +102
@Transactional
public AnonymousRegistrationResponse loginWithSupabase(String accessToken, String deviceId) {
SupabaseUserResponse supabaseUser = supabaseAuthClient.fetchUser(accessToken);

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find SupabaseAuthClient.java
file=$(fd -a 'SupabaseAuthClient.java' 2>/dev/null | head -n 1)

if [ -n "$file" ]; then
  echo "=== SupabaseAuthClient.java ==="
  wc -l "$file"
  cat -n "$file"
else
  echo "SupabaseAuthClient.java not found, searching for similar files..."
  fd -a '.*Supabase.*' -t f | head -20
fi

Repository: Const-Project/MainBE

Length of output: 1992


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find and examine AuthService.java
file=$(fd -a 'AuthService.java' 2>/dev/null | grep -E 'auth/service' | head -n 1)

if [ -n "$file" ]; then
  echo "=== AuthService.java (around lines 99-102) ==="
  sed -n '85,120p' "$file"
  echo ""
  echo "=== Full method context (lines 90-150) ==="
  sed -n '90,150p' "$file"
else
  echo "AuthService.java not found"
fi

Repository: Const-Project/MainBE

Length of output: 3366


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for external HTTP client usage patterns in the codebase
echo "=== Searching for HTTP client patterns ==="
rg '(RestTemplate|WebClient|HttpClient|OkHttpClient|CloseableHttpClient)' -t java --max-count 20

echo ""
echo "=== Searching for Supabase client usage ==="
rg 'supabaseAuthClient' -t java -B 2 -A 2 | head -50

Repository: Const-Project/MainBE

Length of output: 2725


외부 Supabase 호출을 트랜잭션 밖으로 분리하세요

loginWithSupabase() 메서드에서 @Transactional 내부에서 supabaseAuthClient.fetchUser(accessToken)로 외부 HTTP 호출(WebClient)을 수행하고 있습니다. 이로 인해 DB 트랜잭션이 네트워크 요청 완료까지 열린 상태로 유지되어 커넥션 풀 고갈과 성능 저하를 초래할 수 있습니다. Supabase 호출을 별도 메서드로 분리하고 DB 작업만 트랜잭션으로 묶으시기 바랍니다.

✅ 분리 예시
-  `@Transactional`
-  public AnonymousRegistrationResponse loginWithSupabase(String accessToken, String deviceId) {
-    SupabaseUserResponse supabaseUser = supabaseAuthClient.fetchUser(accessToken);
-    ...
-  }
+  public AnonymousRegistrationResponse loginWithSupabase(String accessToken, String deviceId) {
+    SupabaseUserResponse supabaseUser = supabaseAuthClient.fetchUser(accessToken);
+    return loginWithSupabaseInternal(supabaseUser, deviceId);
+  }
+
+  `@Transactional`
+  protected AnonymousRegistrationResponse loginWithSupabaseInternal(
+      SupabaseUserResponse supabaseUser, String deviceId) {
+    // 기존 조회/저장/토큰발급 로직 이동
+  }
🤖 Prompt for AI Agents
In
`@src/main/java/com/example/cp_main_be/domain/member/auth/service/AuthService.java`
around lines 99 - 102, loginWithSupabase currently performs the external HTTP
call supabaseAuthClient.fetchUser(accessToken) inside a method annotated with
`@Transactional`, which keeps the DB transaction open during network I/O; extract
the Supabase call into a separate non-transactional helper (e.g.,
fetchSupabaseUser(...) or make fetchUser call before entering the transactional
scope) so that loginWithSupabase only performs database work inside the
transactional block; keep `@Transactional` on the DB-updating portion (the method
doing member creation/updates) and call the external fetchUser outside that
transaction to prevent connection pool exhaustion.

Comment on lines +124 to +131
User newUser =
User.builder()
.uuid(newUuid)
.nickname(nickname)
.email(supabaseUser.getEmail())
.profileImageUrl(extractProfileImageUrl(supabaseUser))
.oauthProvider(oauthProvider)
.oauthSubject(oauthSubject)
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

🏁 Script executed:

# Find User entity file
fd -a 'User\.java' --type f | head -5

Repository: Const-Project/MainBE

Length of output: 155


🏁 Script executed:

# Check User entity definition
find . -name 'User.java' -type f | head -1 | xargs wc -l

Repository: Const-Project/MainBE

Length of output: 142


🏁 Script executed:

# Find migration files
fd -a 'V.*\.sql' --type f | head -20

Repository: Const-Project/MainBE

Length of output: 318


🏁 Script executed:

# Search for email/nickname in migration files
rg -n "nickname|email" --type sql -g '*.sql' | head -30

Repository: Const-Project/MainBE

Length of output: 788


🏁 Script executed:

cat -n /home/jailuser/git/src/main/java/com/example/cp_main_be/domain/member/user/domain/User.java

Repository: Const-Project/MainBE

Length of output: 6225


🏁 Script executed:

cat /home/jailuser/git/src/main/resources/db/migration/V4__add_user_oauth_fields.sql

Repository: Const-Project/MainBE

Length of output: 195


🏁 Script executed:

# Check for null email handling in AuthService
rg -n "email|nickname" src/main/java/com/example/cp_main_be/domain/member/auth/service/AuthService.java -A 3 -B 1

Repository: Const-Project/MainBE

Length of output: 1156


🏁 Script executed:

rg -n "buildUniqueNickname" src/main/java/com/example/cp_main_be/domain/member/auth/service/AuthService.java -A 15

Repository: Const-Project/MainBE

Length of output: 1289


🏁 Script executed:

# Check all migration files to see actual nickname column definitions
cat /home/jailuser/git/src/main/resources/db/migration/V1__initial_schema.sql 2>/dev/null | grep -A 2 -B 2 "nickname\|email"

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Find the initial schema or all migration files content
fd -a '\.sql$' src/main/resources/db/migration/ | xargs ls -lh

Repository: Const-Project/MainBE

Length of output: 462


🏁 Script executed:

rg -n "buildUniqueNickname" src/main/java/com/example/cp_main_be/domain/member/auth/service/AuthService.java -A 25

Repository: Const-Project/MainBE

Length of output: 1955


🏁 Script executed:

# Search for the initial schema definition in migration files
rg -n "CREATE TABLE.*users" src/main/resources/db/migration/ -A 15

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Check if there's a schema.sql or init script
fd -a '\.(sql|ddl)$' src/main/resources/

Repository: Const-Project/MainBE

Length of output: 318


🏁 Script executed:

# Search more broadly for users table creation
rg -i "create.*table" src/main/resources/db/migration/ -A 10

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Check all files in resources
find src/main/resources -type f | head -20

Repository: Const-Project/MainBE

Length of output: 327


🏁 Script executed:

# Look for schema files or flyway initialization
fd '(schema|initial|ddl)' src/main/resources/ -i

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Check application.yml for Hibernate DDL generation settings
cat src/main/resources/application.yml | grep -i "hibernate\|jpa\|ddl"

Repository: Const-Project/MainBE

Length of output: 390


🏁 Script executed:

# Look for entity-schema.sql or similar that was shown in earlier results
find . -name "*.sql" -type f 2>/dev/null

Repository: Const-Project/MainBE

Length of output: 304


🏁 Script executed:

# Check if nickname length constraint exists in User entity or any validation annotations
rg -n "@(Column|Size|Length)" src/main/java/com/example/cp_main_be/domain/member/user/domain/User.java

Repository: Const-Project/MainBE

Length of output: 367


🏁 Script executed:

# Get the complete buildUniqueNickname method to understand length constraints
rg -n "buildUniqueNickname|randomSuffix" src/main/java/com/example/cp_main_be/domain/member/auth/service/AuthService.java -A 30 | head -50

Repository: Const-Project/MainBE

Length of output: 1840


🏁 Script executed:

# Check the generated entity-schema.sql for actual nickname column definition
grep -n "CREATE TABLE.*users" entity-schema.sql -A 20

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Check new-schema.sql as well
grep -n "CREATE TABLE.*users" new-schema.sql -A 20

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Get the complete buildUniqueNickname method with randomSuffix
rg -n "private String buildUniqueNickname|private.*randomSuffix" src/main/java/com/example/cp_main_be/domain/member/auth/service/AuthService.java -A 40 | tail -80

Repository: Const-Project/MainBE

Length of output: 1542


🏁 Script executed:

# Check for any validation of email being null
rg -n "getEmail\|email.*null" src/main/java/com/example/cp_main_be/domain/member/auth/service/AuthService.java

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Read entity-schema.sql directly
head -50 entity-schema.sql

Repository: Const-Project/MainBE

Length of output: 1550


🏁 Script executed:

# Look specifically for users table in entity-schema.sql
sed -n '/CREATE TABLE.*users/,/);/p' entity-schema.sql | head -30

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Check new-schema.sql
head -50 new-schema.sql

Repository: Const-Project/MainBE

Length of output: 2272


🏁 Script executed:

# Search for where avatar nickname is set
rg -n "avatar.*nickname\|Avatar.*nickname" src/main/java/com/example/cp_main_be/domain/ -B 2 -A 2

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Check if avatar creation also uses buildUniqueNickname
rg -n "buildUniqueNickname\|new Avatar" src/main/java/com/example/cp_main_be/ -B 2 -A 2

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Verify the exact length limit - check if buildUniqueNickname can produce strings over 255 chars
rg -n "class Avatar" src/main/java/com/example/cp_main_be/domain/ -A 30 | grep -i "nickname"

Repository: Const-Project/MainBE

Length of output: 855


🏁 Script executed:

# Check if there's any null check or validation for email before saving
rg -n "saveAndInitializeUser" src/main/java/com/example/cp_main_be/domain/member/auth/service/AuthService.java -A 10

Repository: Const-Project/MainBE

Length of output: 1606


🏁 Script executed:

# Check if there's any length constraint in SupabaseUserResponse
rg -n "class SupabaseUserResponse" src/main/java/com/example/cp_main_be/ -A 30

Repository: Const-Project/MainBE

Length of output: 1203


🏁 Script executed:

# Verify if buildUniqueNickname has any length truncation logic
rg -n "substring\|length\|truncate" src/main/java/com/example/cp_main_be/domain/member/auth/service/AuthService.java

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Check if there are any validators on User entity
rg -n "@(NotNull|NotBlank|Size|Length)" src/main/java/com/example/cp_main_be/domain/member/user/domain/User.java

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Check what happens when email is null - any null coalescing or default values
rg -n "email.*\?" src/main/java/com/example/cp_main_be/domain/member/auth/service/AuthService.java

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Verify if buildUniqueNickname handles extremely long input from userMetadata
rg -n "userMetadata.get\|firstNonBlank" src/main/java/com/example/cp_main_be/domain/member/auth/service/AuthService.java -B 2 -A 5

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Let's check if there are any tests that show how email null is handled
rg -n "loginWithSupabase\|email.*null" src/test/ -i

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Final verification: check actual limit by calculating max nickname length
# buildUniqueNickname: sanitized (no length limit) + randomSuffix (6 chars)
# Let's see if there's any actual limit check anywhere
rg -n "nickname.*length\|nickname.*size\|nickname.*\d+" src/main/java/com/example/cp_main_be/domain/member/

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Check if V1 migration exists or if Hibernate ddl-auto is creating tables
ls -la src/main/resources/db/migration/ | grep -i v1

Repository: Const-Project/MainBE

Length of output: 46


🏁 Script executed:

# Let me verify the exact issue - check if there's any string length limit in buildUniqueNickname
python3 << 'EOF'
# Simulate buildUniqueNickname behavior
def simulate_nickname(metadata_value):
    base = metadata_value if metadata_value else "user"
    sanitized = base.replace(" ", "")
    # Remove special chars (keep only alphanumeric, ._-)
    sanitized = ''.join(c for c in sanitized if c.isalnum() or c in '._-')
    if not sanitized:
        sanitized = "user"
    # Add suffix
    suffix = "ABCDEF"  # Simulating 6-char random suffix
    candidate = sanitized + suffix
    return candidate

# Test cases
test_cases = [
    "user",
    "john_doe",
    "a" * 250,  # Very long nickname
    "한글이름테스트",  # Korean characters (would be filtered)
]

for test in test_cases:
    result = simulate_nickname(test)
    print(f"Input: {test[:30]:<30} | Output length: {len(result):<5} | DB limit (varchar(255)): {len(result) <= 255}")
EOF

Repository: Const-Project/MainBE

Length of output: 436


🏁 Script executed:

# Check database constraints one more time
cat new-schema.sql | grep -A 5 "create table users"

Repository: Const-Project/MainBE

Length of output: 346


buildUniqueNickname()에서 닉네임이 255자를 초과할 수 있습니다. 길이 제한 및 null 이메일 처리 로직을 추가하세요.

User 엔티티의 nickname 필드에는 길이 제약이 없으며, buildUniqueNickname() 메서드가 사용자 메타데이터에서 매우 긴 문자열을 받을 경우 sanitize 후 6자 suffix 추가로 인해 varchar(255) 제한을 초과할 수 있습니다. 또한 Supabase에서 email이 null일 수 있는데 현재 코드는 이를 검증하지 않습니다. nickname을 최대 250자로 제한하고, email이 null인 경우 기본값 처리 또는 검증을 추가해주세요.

🤖 Prompt for AI Agents
In
`@src/main/java/com/example/cp_main_be/domain/member/auth/service/AuthService.java`
around lines 124 - 131, buildUniqueNickname() can produce nicknames exceeding
the DB varchar(255) and doesn't handle null Supabase emails; modify
buildUniqueNickname() to sanitize input, truncate the base nickname to 250
characters before appending the 6-char suffix (ensuring final length ≤ 256 or
better truncate to 249+6 to be safe for 255), and enforce a min-safe fallback
when metadata is empty. Also update the code that reads supabaseUser.getEmail()
(used in User.builder()) to validate null emails and either supply a safe
default (e.g., "no-email@local") or throw a clear validation exception before
constructing the User; reference the buildUniqueNickname() method and the
User.builder() usage so the nickname and email constraints are applied
consistently.

Comment on lines +24 to +42
public SupabaseUserResponse fetchUser(String accessToken) {
if (accessToken == null || accessToken.isBlank()) {
throw new CustomApiException(ErrorCode.INVALID_REQUEST);
}

return webClient
.get()
.uri(supabaseUrl + "/auth/v1/user")
.header("apikey", supabaseServiceKey)
.header("Authorization", "Bearer " + accessToken)
.retrieve()
.onStatus(
status -> status.value() == 401 || status.value() == 403,
response -> Mono.error(new CustomApiException(ErrorCode.INVALID_TOKEN)))
.onStatus(
HttpStatusCode::isError,
response -> Mono.error(new CustomApiException(ErrorCode.INVALID_REQUEST)))
.bodyToMono(SupabaseUserResponse.class)
.block();
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

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "SupabaseAuthClient.java" | head -5

Repository: Const-Project/MainBE

Length of output: 142


🏁 Script executed:

fd "SupabaseAuthClient.java" --type f

Repository: Const-Project/MainBE

Length of output: 140


🏁 Script executed:

rg "SupabaseAuthClient" --type java -l

Repository: Const-Project/MainBE

Length of output: 221


🏁 Script executed:

cat -n src/main/java/com/example/cp_main_be/global/supabase/SupabaseAuthClient.java

Repository: Const-Project/MainBE

Length of output: 1861


🏁 Script executed:

head -50 src/main/java/com/example/cp_main_be/global/supabase/SupabaseAuthClient.java

Repository: Const-Project/MainBE

Length of output: 1553


🏁 Script executed:

rg "\.block\(" src/main/java --type java -B 3 -A 1

Repository: Const-Project/MainBE

Length of output: 1951


🏁 Script executed:

rg "Duration\." src/main/java --type java | head -20

Repository: Const-Project/MainBE

Length of output: 1162


🏁 Script executed:

rg "timeout\|TimeoutException" src/main/java --type java -i | head -20

Repository: Const-Project/MainBE

Length of output: 46


WebClient.block() 타임아웃 누락으로 인한 스레드 고갈 위험

Line 42의 .block()에 타임아웃이 설정되어 있지 않아 Supabase 응답 지연 또는 장애 시 요청 스레드가 무한정 대기하며 고갈될 수 있습니다. Duration.ofSeconds(5)를 추가하고 필요한 import를 포함하세요.

제안 변경
+import java.time.Duration;
 import com.example.cp_main_be.global.common.CustomApiException;
 import com.example.cp_main_be.global.common.ErrorCode;
 import lombok.RequiredArgsConstructor;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpStatusCode;
 import org.springframework.stereotype.Component;
 import org.springframework.web.reactive.function.client.WebClient;
 import reactor.core.publisher.Mono;
         .bodyToMono(SupabaseUserResponse.class)
-        .block();
+        .block(Duration.ofSeconds(5));
🤖 Prompt for AI Agents
In `@src/main/java/com/example/cp_main_be/global/supabase/SupabaseAuthClient.java`
around lines 24 - 42, The fetchUser method in SupabaseAuthClient uses
WebClient.block() with no timeout; update the call in fetchUser(String
accessToken) to use a bounded wait such as .block(Duration.ofSeconds(5)) and add
the required import (java.time.Duration) to prevent thread starvation when
Supabase is slow or unresponsive; keep existing onStatus handlers and return
type unchanged.

Comment on lines +1 to +3
ALTER TABLE users
ADD COLUMN IF NOT EXISTS oauth_provider varchar(50),
ADD COLUMN IF NOT EXISTS oauth_subject varchar(255);
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

OAuth 사용자 중복 방지를 위한 유니크 제약 필요

Line 1-3에 컬럼은 추가되었지만 (oauth_provider, oauth_subject)에 대한 유니크 제약이 없어 동일 OAuth 계정이 중복 생성될 수 있습니다. 로그인 매핑이 모호해질 수 있으므로 제약을 권장합니다.

🛠️ 제안 변경
 ALTER TABLE users
     ADD COLUMN IF NOT EXISTS oauth_provider varchar(50),
-    ADD COLUMN IF NOT EXISTS oauth_subject varchar(255);
+    ADD COLUMN IF NOT EXISTS oauth_subject varchar(255),
+    ADD CONSTRAINT uq_users_oauth UNIQUE (oauth_provider, oauth_subject);
🤖 Prompt for AI Agents
In `@src/main/resources/db/migration/V4__add_user_oauth_fields.sql` around lines 1
- 3, The migration adds oauth_provider and oauth_subject to users but lacks a
uniqueness constraint, allowing duplicate OAuth accounts; update the migration
to add a unique constraint on the (oauth_provider, oauth_subject) pair for the
users table (e.g., via ALTER TABLE users ADD CONSTRAINT ... UNIQUE on
oauth_provider and oauth_subject) so that duplicate OAuth mappings cannot be
created and lookups in authentication logic that rely on this pair remain
deterministic.

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.

1 participant