Conversation
왜: Supabase OAuth를 기존 JWT 인증 흐름에 통합할 필요가 있음 무엇: Supabase 사용자 조회/검증, 사용자 매핑, 토큰 교환 API 및 유저 OAuth 필드 추가
WalkthroughSupabase 기반 OAuth 인증을 통한 사용자 로그인 기능을 추가했습니다. 새로운 DTO, 컨트롤러 엔드포인트, 서비스 로직, 저장소 쿼리 메서드, Supabase 클라이언트, 토큰 관리 메서드, 데이터베이스 마이그레이션을 포함합니다. Changes
Sequence DiagramsequenceDiagram
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/>로그인 결과 반환
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Important Action Needed: IP Allowlist UpdateIf your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:
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. Comment |
There was a problem hiding this comment.
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 제약 + 재시도 전략으로 보완을 권장합니다.
| @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) { |
There was a problem hiding this comment.
🧩 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"
fiRepository: 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"
fiRepository: Const-Project/MainBE
Length of output: 1666
SupabaseLoginRequest에 검증 애노테이션과 @Valid 추가 필요
SupabaseLoginRequest에 검증 애노테이션이 없어서 빈 accessToken이 서비스로 직접 전달될 수 있습니다. 불필요한 Supabase 호출과 오류를 방지하려면 다음 두 가지를 수정하세요:
- DTO에 검증 애노테이션 추가:
@NotBlank(message = "accessToken은 필수입니다") - 컨트롤러 메서드에
@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.
| @Transactional | ||
| public AnonymousRegistrationResponse loginWithSupabase(String accessToken, String deviceId) { | ||
| SupabaseUserResponse supabaseUser = supabaseAuthClient.fetchUser(accessToken); | ||
|
|
There was a problem hiding this comment.
🧩 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
fiRepository: 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"
fiRepository: 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 -50Repository: 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.
| User newUser = | ||
| User.builder() | ||
| .uuid(newUuid) | ||
| .nickname(nickname) | ||
| .email(supabaseUser.getEmail()) | ||
| .profileImageUrl(extractProfileImageUrl(supabaseUser)) | ||
| .oauthProvider(oauthProvider) | ||
| .oauthSubject(oauthSubject) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find User entity file
fd -a 'User\.java' --type f | head -5Repository: Const-Project/MainBE
Length of output: 155
🏁 Script executed:
# Check User entity definition
find . -name 'User.java' -type f | head -1 | xargs wc -lRepository: Const-Project/MainBE
Length of output: 142
🏁 Script executed:
# Find migration files
fd -a 'V.*\.sql' --type f | head -20Repository: 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 -30Repository: 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.javaRepository: Const-Project/MainBE
Length of output: 6225
🏁 Script executed:
cat /home/jailuser/git/src/main/resources/db/migration/V4__add_user_oauth_fields.sqlRepository: 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 1Repository: 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 15Repository: 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 -lhRepository: 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 25Repository: 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 15Repository: 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 10Repository: Const-Project/MainBE
Length of output: 46
🏁 Script executed:
# Check all files in resources
find src/main/resources -type f | head -20Repository: Const-Project/MainBE
Length of output: 327
🏁 Script executed:
# Look for schema files or flyway initialization
fd '(schema|initial|ddl)' src/main/resources/ -iRepository: 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/nullRepository: 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.javaRepository: 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 -50Repository: 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 20Repository: Const-Project/MainBE
Length of output: 46
🏁 Script executed:
# Check new-schema.sql as well
grep -n "CREATE TABLE.*users" new-schema.sql -A 20Repository: 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 -80Repository: 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.javaRepository: Const-Project/MainBE
Length of output: 46
🏁 Script executed:
# Read entity-schema.sql directly
head -50 entity-schema.sqlRepository: 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 -30Repository: Const-Project/MainBE
Length of output: 46
🏁 Script executed:
# Check new-schema.sql
head -50 new-schema.sqlRepository: 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 2Repository: 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 2Repository: 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 10Repository: 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 30Repository: 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.javaRepository: 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.javaRepository: 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.javaRepository: 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 5Repository: 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/ -iRepository: 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 v1Repository: 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}")
EOFRepository: 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.
| 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(); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "SupabaseAuthClient.java" | head -5Repository: Const-Project/MainBE
Length of output: 142
🏁 Script executed:
fd "SupabaseAuthClient.java" --type fRepository: Const-Project/MainBE
Length of output: 140
🏁 Script executed:
rg "SupabaseAuthClient" --type java -lRepository: Const-Project/MainBE
Length of output: 221
🏁 Script executed:
cat -n src/main/java/com/example/cp_main_be/global/supabase/SupabaseAuthClient.javaRepository: Const-Project/MainBE
Length of output: 1861
🏁 Script executed:
head -50 src/main/java/com/example/cp_main_be/global/supabase/SupabaseAuthClient.javaRepository: Const-Project/MainBE
Length of output: 1553
🏁 Script executed:
rg "\.block\(" src/main/java --type java -B 3 -A 1Repository: Const-Project/MainBE
Length of output: 1951
🏁 Script executed:
rg "Duration\." src/main/java --type java | head -20Repository: Const-Project/MainBE
Length of output: 1162
🏁 Script executed:
rg "timeout\|TimeoutException" src/main/java --type java -i | head -20Repository: 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.
| ALTER TABLE users | ||
| ADD COLUMN IF NOT EXISTS oauth_provider varchar(50), | ||
| ADD COLUMN IF NOT EXISTS oauth_subject varchar(255); |
There was a problem hiding this comment.
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.
왜: Supabase OAuth를 기존 JWT 인증 흐름에 통합할 필요가 있음
무엇: Supabase 사용자 조회/검증, 사용자 매핑, 토큰 교환 API 및 유저 OAuth 필드 추가
Summary by CodeRabbit
릴리스 노트