Skip to content

feat(auth): 네이버 로그인 Supabase Admin 브리지 구현 - #59

Merged
Siul49 merged 4 commits into
devfrom
fix/58-supabase-oauth-providers
Jun 11, 2026
Merged

Siul49 merged 4 commits into
devfrom
fix/58-supabase-oauth-providers

Conversation

@kokkumong

@kokkumong kokkumong commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

🚀 작업 내용 (What)

  • 네이버 로그인을 Supabase Admin 브리지 방식으로 구현 ([FEAT] 인증 시스템 Supabase Auth로 통합 (자체 JWT+Prisma 마이그레이션) #45 마이그레이션 이후 naver_not_implemented 스텁이던 것을 활성화).
  • src/lib/auth/naver.ts: 네이버 OAuth 헬퍼(authorize URL/토큰/프로필) 복원 — 자체 JWT 의존 제거, fetch-with-timeout 기반.
  • naver/login: authorize 리다이렉트 + state 쿠키.
  • naver/callback: 네이버 프로필 수신 → Supabase Admin(service_role)으로 유저 생성/조회 → magiclink 토큰을 verifyOtp로 교환해 일반 Supabase 세션 쿠키 발급.
  • 코드래빗 리뷰 반영: profiles.naver_id(컬럼+트리거)로 안정적 식별, 빈 닉네임 fallback 보강.

📣 핵심 변경 이유 (Why)

  • 네이버는 Supabase가 기본 지원하지 않아 카카오/구글/애플(signInWithOAuth)과 같은 네이티브 흐름을 쓸 수 없음.
  • OAuth로 사용자 정보만 받은 뒤 Admin으로 세션을 발급해, 4개 제공자 모두 supabase.auth.getUser() 세션으로 일원화.

📸 스크린샷 (Visuals, 선택)

  • UI 변경 없음 (서버 라우트).

⚠️ 체크리스트 (Checklist)

  • 브랜치 컨벤션(feature/00-name)을 지켰나요?
  • 커밋 컨벤션(feat:, fix: 등)을 지켰나요?
  • 작업 전에 관련 이슈를 생성하고 연결했나요?
  • 내 코드가 팀의 기존 코드를 망가뜨리지 않았는지 확인했나요?

⚙️ 배포 전 필요한 설정 (Deploy notes)

  • Vercel 환경변수: NAVER_CLIENT_ID, NAVER_CLIENT_SECRET, NAVER_REDIRECT_URI=https://moim-app-eosin.vercel.app/api/auth/naver/callback, SUPABASE_SERVICE_ROLE_KEY
  • 네이버 개발자센터 Callback URL = 앱 주소 https://moim-app-eosin.vercel.app/api/auth/naver/callback (Supabase 콜백 아님)
  • 마이그레이션 20260611000000_profiles_naver_id.sql 적용 필요 (naver_id 컬럼/트리거)
  • Supabase Authentication → Email provider 활성화 필요 (magiclink/verifyOtp 의존)
  • ⚠️ 카카오/구글/애플 provider 활성화는 본 PR 범위 밖(설정 작업) — #58에서 추적

🔗 관련 이슈 (Issue)

Refs #46
Part of #58

🤖 Generated with Claude Code

네이버는 Supabase가 기본 지원하지 않는 OAuth 제공자라, #45 마이그레이션
이후 /api/auth/naver/* 가 naver_not_implemented 스텁으로 비활성 상태였다.

- 네이버 OAuth 헬퍼(getNaverAuthUrl/getNaverToken/getNaverUser) 복원
  (자체 JWT 의존 제거, fetch-with-timeout 기반)
- naver/login: authorize 리다이렉트 + state 쿠키 복원
- naver/callback: 네이버 프로필 수신 → Supabase Admin(service_role)으로
  유저 생성/조회 → magiclink 토큰을 verifyOtp로 교환해 일반 Supabase
  세션 쿠키 발급. 카카오/구글/애플과 동일하게 getUser() 세션으로 일원화.
- 이메일 미제공 시 결정적 placeholder로 동일 사용자 식별, 닉네임 unique
  충돌 시 naverId 기반으로 대체.

Refs #46
Part of #58

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
moim-app Ready Ready Preview, Comment Jun 11, 2026 9:38am

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kokkumong, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 12 minutes and 4 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7b1dc5e7-96d2-47af-8393-7c641372f7b4

📥 Commits

Reviewing files that changed from the base of the PR and between 3334d46 and f726ef0.

📒 Files selected for processing (2)
  • src/app/api/auth/naver/callback/route.ts
  • supabase/migrations/20260611000000_profiles_naver_id.sql

Walkthrough

Naver OAuth 라이브러리와 로그인/콜백 라우트를 추가해 state 쿠키 기반 인증 흐름을 구현하고, Supabase Admin으로 프로필 조회/생성 및 magiclink로 세션을 발급하며 profiles.naver_id 컬럼을 마이그레이션합니다.

Changes

Naver OAuth 인증 흐름

Layer / File(s) Summary
Naver OAuth 라이브러리 계약 및 구현
src/lib/auth/naver.ts
타입 정의: NaverUser, NaverTokenResponse. 함수: getNaverAuthUrl()은 state/환경변수 검증 후 Naver 인가 URL 반환. getNaverToken()은 code/state로 POST 요청해 access_token 응답 파싱. getNaverUser()는 Bearer 토큰으로 사용자 정보 엔드포인트 호출하고 resultcoderesponse.id 검증. extractNaverUserInfo()는 nickname→name→naver_{id} 순으로 nickname 결정하고 naverId, email, nickname 반환.
Naver 로그인 진입점
src/app/api/auth/naver/login/route.ts
STATE_COOKIE, STATE_MAX_AGE 상수 추가. GET 핸들러: crypto.randomUUID()로 state 생성 → getNaverAuthUrl(state)로 Naver 인가 URL 구성 → httpOnly 쿠키로 state 저장하고 URL로 리다이렉트. 실패 시 console.error 후 /login?error=naver_login_failed 리다이렉트.
콜백 초기화 및 헬퍼
src/app/api/auth/naver/callback/route.ts
Supabase Admin/Server 클라이언트 의존성 추가, state 쿠키 정리 헬퍼(redirectWithStateCleanup)와 placeholder 이메일 생성 헬퍼(resolveEmail/getPlaceholderEmail) 도입.
콜백 파라미터·state 검증
src/app/api/auth/naver/callback/route.ts
쿼리 code/state/errornaver_oauth_state 쿠키 정합성 검증 및 실패 시 state 쿠키 정리 후 /login?error=naver_login_failed로 리다이렉트.
토큰·사용자 조회 및 프로필 매핑/생성
src/app/api/auth/naver/callback/route.ts
getNaverToken()/getNaverUser()로 정보 획득 → naverId/email/nickname 결정 → Supabase Admin으로 profiles를 naver_id 우선 조회 → 이메일 기준 재조회 및 naver_id backfill → 프로필 없으면 닉네임 충돌 회피 후 auth.users 생성. 동시성으로 이미 존재하는 경우 에러 메시지 패턴으로 무시하고 신규/프로필완료 여부 세팅.
Magiclink 생성 및 세션 확정
src/app/api/auth/naver/callback/route.ts
Admin에서 magiclink 토큰을 발급받아 hashed_token으로 서버 클라이언트 verifyOtp()를 호출해 세션을 확정.
리다이렉트·쿠키 정리 및 오류 처리
src/app/api/auth/naver/callback/route.ts
isNewUser/profileComplete에 따라 /signup/additional-info?provider=naver 또는 /schedule/create로 리다이렉트. last_login_provider=naver 쿠키 설정 및 naver_oauth_state 쿠키 삭제. 예외 시 로깅 및 state 쿠키 정리 후 /login?error=naver_login_failed 리다이렉트.
DB 마이그레이션: profiles.naver_id
supabase/migrations/20260611000000_profiles_naver_id.sql
public.profilesnaver_id 컬럼 추가, naver_id IS NOT NULL일 때 유니크 인덱스 생성, handle_new_user() 트리거의 insert 매핑에 naver_id 저장 추가.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as 사용자 브라우저
  participant LoginRoute as /api/auth/naver/login
  participant NaverAuth as Naver 인가 서버
  participant CallbackRoute as /api/auth/naver/callback
  participant NaverAPI as Naver API (토큰/프로필)
  participant SupabaseAdmin as Supabase Admin
  participant SupabaseAuth as Supabase Auth (verifyOtp)

  Browser->>LoginRoute: GET /api/auth/naver/login
  LoginRoute->>Browser: Set-Cookie(state) + Redirect(네이버 인가 URL)
  Browser->>NaverAuth: 사용자 인증/동의
  NaverAuth->>Browser: Redirect 백엔드 콜백?code&state
  Browser->>CallbackRoute: GET /api/auth/naver/callback?code&state
  CallbackRoute->>NaverAPI: getNaverToken(code,state)
  NaverAPI-->>CallbackRoute: access_token
  CallbackRoute->>NaverAPI: getNaverUser(access_token)
  NaverAPI-->>CallbackRoute: user profile
  CallbackRoute->>SupabaseAdmin: 프로필 조회/생성 (naver_id/email 기준)
  SupabaseAdmin-->>CallbackRoute: 프로필/사용자 생성 결과
  CallbackRoute->>SupabaseAuth: generate magiclink -> verifyOtp(hashed_token)
  SupabaseAuth-->>CallbackRoute: 세션 확정
  CallbackRoute->>Browser: Redirect(final) + Set-Cookie(last_login_provider)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45분

Possibly related issues

  • Siul49/moim#46: Naver → Supabase Admin 브릿지 구현(콜백 처리, profiles.naver_id backfill, auth.users 생성 및 세션 발급)과 목적이 일치함.

Suggested labels

feature


리뷰 주의사항

아래 항목들은 반드시 수정/테스트 포함되어야 함. 이유(Why) + 해결안(How) + 코드 스니펫(권장 적용 위치)을 제시한다.

  1. State 검증 미흡 → CSRF 취약점
    Why: 현재 구현은 쿠키 값만 비교하거나 파라미터 누락을 충분히 방어하지 않음. 공격자는 쿼리 파라미터를 위조해 CSRF를 유발할 수 있음.
    How: 쿼리의 state 존재 여부를 먼저 확인하고, 쿼리 state와 쿠키 state를 엄격 비교한다.
    코드:
// callback/route.ts (검증부)
const stateFromQuery = req.nextUrl.searchParams.get('state');
const stateFromCookie = req.cookies.get(STATE_COOKIE)?.value;
if (!stateFromQuery || !stateFromCookie || stateFromQuery !== stateFromCookie) {
  return redirectWithStateCleanup('/login?error=naver_login_failed', req);
}
  1. error 쿼리 파라미터 무시 → 실패 원인 상실
    Why: Naver가 전달한 error(예: access_denied)를 로깅·구분하지 않으면 사용자·운영상 원인 파악 불가.
    How: error가 존재하면 세부 로그를 남기고 사용자에게 구체적 에러 코드 또는 분기된 에러 쿼리 전달.
    코드:
const error = req.nextUrl.searchParams.get('error');
if (error) {
  console.warn(`Naver OAuth error: ${error}`);
  return redirectWithStateCleanup(`/login?error=naver_oauth_${encodeURIComponent(error)}`, req);
}
  1. Placeholder 이메일 정책의 충돌 가능성
    Why: placeholder 이메일이 DB의 유니크 제약과 충돌하거나 사용자가 나중에 실제 이메일을 등록할 때 충돌 유발 가능.
    How: DB 수준에서 profiles.naver_id 유니크 제약을 유지하되 placeholder는 도메인을 명확히 분리하고, 사용자 등록 시 placeholder 교체 절차·검증을 테스트한다. 가능하면 이메일이 필수인 경로에서는 소유 검증을 요구.
    코드(placeholder 생성 예):
const getPlaceholderEmail = (naverId: string) => `naver+${naverId}`@placeholder.local``;
  1. 닉네임 충돌 회피 로직 방어적 강화 필요
    Why: 닉네임 중복 처리 로직이 불완전하면 auth.users 생성 실패를 유발하거나 UX가 깨짐.
    How: 닉네임 충돌 시 deterministic suffix(예: _naver{shortId})를 붙이고, 충돌 재시도 루프와 최대 시도 횟수, 트랜잭션 실패 패턴을 검사한다.
    코드 스니펫:
async function ensureUniqueNickname(base: string) {
  for (let i = 0; i < 5; i++) {
    const cand = i === 0 ? base : `${base}_naver${crypto.randomUUID().slice(0,4)}`;
    const { data } = await adminClient.from('profiles').select('id').eq('nickname', cand).maybeSingle();
    if (!data) return cand;
  }
  return `naver_${Date.now()}`;
}
  1. Magiclink 토큰 즉시 검증의 타임윈도우 위험성
    Why: magiclink 토큰 생성·검증 흐름은 타이밍·유효기간 이슈가 있음(생성 후 바로 사용 불가 사례 등).
    How: generateLink 응답의 유효성 체크(hashed_token 존재·유효기간) 후 즉시 verifyOtp 호출; 실패 시 재시도 로직 또는 대체 세션 발급 루틴을 마련한다. 테스트를 추가하여 race condition을 확인.
    코드(검증 예):
if (!magicLinkData?.properties?.hashed_token) throw new Error('missing magiclink token');
try {
  await supabaseAuth.auth.verifyOtp({ email: email, token: magicLinkData.properties.hashed_token, type: 'magiclink' });
} catch (e) {
  // 재시도 또는 실패 분기
}
  1. 테스트 미비 — 반드시 커버할 시나리오
    Why: 콜백 로직에 분기( state 실패, naver API 실패, 기존 사용자, 신규 사용자, 프로필 미완성 )가 많아 자동화 검증 필수.
    How: unit 테스트/통합 테스트 작성 — state 유효성, error 파라미터 처리, placeholder 이메일, nickname 충돌 회피, magiclink 생성·verify 성공/실패. Mock Naver API와 Supabase Admin을 사용.

  2. 운영용 로깅/트래킹 필요
    Why: 현재 console.error만 사용하면 운영에서 문제 원인 탐지 불가.
    How: Sentry/Datadog 등의 에러 추적 연동이나 structured logging 적용. 최소한 중요한 catch 블록에서 error 메시지와 사용자 식별자(nav id/email)를 포함해 전송.


타협 없음: 위 7가지는 머지 전 반드시 코드·테스트·마이그레이션 문서로 보완할 것.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 'feat:' 컨벤션을 따르며 PR의 핵심 변경(네이버 로그인 Supabase Admin 브리지 구현)을 명확히 요약함.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed PR 설명이 템플릿의 모든 필수 섹션(What, Why, Checklist, Issue)을 완성하고 배포 전 필요한 설정까지 상세히 기술했다.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/58-supabase-oauth-providers

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/api/auth/naver/callback/route.ts`:
- Around line 56-66: The current lookup uses only profiles.email (authEmail)
which can split the same Naver account; change the lookup logic in the callback
flow (around extractNaverUserInfo/resolveEmail/createAdminClient usage) to first
query profiles by the stable naverId (user_metadata.naver_id) and only fall back
to profiles.email (authEmail) for migration/backfill; if the profiles table has
no naver_id column, create or use an identity mapping table to map naverId →
profile_id and query that first, then update the profile with email/backfill as
needed.

In `@src/lib/auth/naver.ts`:
- Around line 143-145: The nickname fallback currently uses nullish coalescing
on naverUser.nickname which treats an empty string as a valid value and allows
empty nicknames to persist; change the nickname assignment (the line setting
const nickname using naverUser.nickname, naverUser.name and naverId) to
explicitly treat empty/whitespace-only strings as missing by trimming and
checking truthiness, then fall back to naverUser.name (also trimmed/checked) and
finally to the generated `naver_${naverId}` default.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dd5042a1-9675-4468-a11e-2c9c4dbbe714

📥 Commits

Reviewing files that changed from the base of the PR and between 2f9a53e and d7e4160.

📒 Files selected for processing (3)
  • src/app/api/auth/naver/callback/route.ts
  • src/app/api/auth/naver/login/route.ts
  • src/lib/auth/naver.ts

Comment thread src/app/api/auth/naver/callback/route.ts
Comment thread src/lib/auth/naver.ts Outdated
- naver_id를 profiles 1차 식별자로 사용 (이메일 단독 조회 시 같은 네이버
  계정이 갈라지는 문제 해결). profiles.naver_id 컬럼+unique 인덱스 추가하고
  handle_new_user 트리거가 user_metadata.naver_id를 채우도록 갱신.
- 콜백: naver_id로 먼저 조회, 미존재 시 이메일로 매칭 후 naver_id 백필.
- extractNaverUserInfo: 빈 문자열/공백 닉네임·이메일을 "값 없음"으로 취급해
  name → naver_<id> fallback이 정상 동작하도록 수정.

Refs #46
Part of #58

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kokkumong

Copy link
Copy Markdown
Collaborator Author

@coderabbitai 리뷰 반영했습니다 (3334d46).

  • 계정 분기(Major): profiles.naver_id 컬럼 + unique 인덱스 추가, handle_new_user 트리거가 user_metadata.naver_id를 채우도록 갱신. 콜백은 naver_id로 1차 조회하고, 미존재 시 이메일로 매칭 후 naver_id를 백필합니다. (마이그레이션: 20260611000000_profiles_naver_id.sql — 배포 시 적용 필요)
  • 빈 닉네임(Minor): extractNaverUserInfo에서 빈 문자열/공백을 '값 없음'으로 취급해 name → naver_<id> fallback이 동작하도록 수정.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the migration file and updated source files
echo "=== Migration file ==="
fd "20260611000000_profiles_naver_id" --exec cat {}

echo ""
echo "=== naver.ts - extractNaverUserInfo ==="
fd "naver.ts" --search-path src/lib --exec cat {}

echo ""
echo "=== callback route ==="
fd "route.ts" --search-path src/app/api/auth/naver/callback --exec cat {}

echo ""
echo "=== handle_new_user trigger (any SQL migration files) ==="
fd "handle_new_user" --exec cat {}

echo ""
echo "=== All migration files ==="
fd "\.sql$" --exec ls -la {}

Length of output: 12139



반영 확인했습니다. 전체적으로 핵심 피드백이 잘 구현됐고, 두 가지만 추가로 짚겠습니다.


1. 백필 update — naver_id IS NULL guard 누락 (Minor)

// 현재
const { error: backfillError } = await admin
  .from("profiles")
  .update({ naver_id: naverId })
  .eq("id", byEmail.id);

프로필에 이미 다른 naver_id가 세팅돼 있다면(수동 수정, 동시 요청 등) 이 update가 overwrite합니다. profiles_naver_id_key unique index 때문에 최악엔 23505 constraint violation이 발생합니다.

const { error: backfillError } = await admin
  .from("profiles")
  .update({ naver_id: naverId })
  .eq("id", byEmail.id)
  .is("naver_id", null); // 이미 채워진 경우 덮어쓰지 않음

2. concurrent 생성 race — 기존 유저가 /signup/additional-info로 잘못 이동 (Minor)

// createError가 "already exists" 계열이면
isNewUser = !createError; // → false
profileComplete = false;  // ← 기존 유저인데도 false 그대로

동시 요청으로 두 번째 createUser가 "already exists" 오류를 내면, existingProfilenull이고 profileCompletefalse여서 기존 유저를 추가 정보 입력 페이지로 보냅니다.

if (createError) {
  // 이미 존재 → DB에서 재조회해 profileComplete 판정
  if (/already|registered|exists/i.test(createError.message)) {
    const { data: raceProfile } = await admin
      .from("profiles")
      .select("phone_number, terms_agreed_at")
      .eq("email", authEmail)
      .maybeSingle();
    profileComplete =
      !!raceProfile?.phone_number && !!raceProfile?.terms_agreed_at;
  } else {
    throw createError;
  }
}

두 항목 모두 실제 발생 빈도는 낮지만, 1번은 unique index와 충돌 가능성이 있어 선 적용을 권장합니다.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/api/auth/naver/callback/route.ts`:
- Around line 75-87: The current email-fallback backfill unconditionally updates
profiles.naver_id and can overwrite an existing naver linkage; change the logic
in the callback handler that queries profiles (the admin.from("profiles") call
producing byEmail) to also select the naver_id field, then only perform the
update (the admin.from("profiles").update... using naverId that produces
backfillError) if byEmail.naver_id is null/empty/undefined; otherwise skip the
update and do not throw—use the existing variables byEmail, backfillError,
naverId and authEmail to implement this guard.

In `@supabase/migrations/20260611000000_profiles_naver_id.sql`:
- Around line 15-17: Normalize empty/whitespace naver_id to NULL at write and
make the partial unique index use the same normalization: in the TS callback
that writes profiles.naver_id (src/app/api/auth/naver/callback/route.ts around
the code that reads new.raw_user_meta_data->>'naver_id') trim and convert empty
strings to null before saving (e.g., apply btrim/trim and treat '' as null), and
in the migration change the index definition (profiles_naver_id_key) to use the
same expression in its predicate (e.g., WHERE nullif(btrim(naver_id), '') IS NOT
NULL or create the index on the normalized expression) so both insert/update
logic and the partial-unique index use identical nullif(btrim(...), '')
normalization.
- Around line 20-56: The security-definer function public.handle_new_user lacks
a fixed search_path; update its declaration to include "SET search_path =
public" so name resolution is locked to the public schema (i.e., change the
function header/footer options for public.handle_new_user to LANGUAGE plpgsql
SECURITY DEFINER SET search_path = public) to prevent external search_path
influencing object/function lookup when the after insert on auth.users trigger
runs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: acec9617-8234-4fd9-8c32-861f9d46d594

📥 Commits

Reviewing files that changed from the base of the PR and between d7e4160 and 3334d46.

📒 Files selected for processing (3)
  • src/app/api/auth/naver/callback/route.ts
  • src/lib/auth/naver.ts
  • supabase/migrations/20260611000000_profiles_naver_id.sql

Comment thread src/app/api/auth/naver/callback/route.ts Outdated
Comment thread supabase/migrations/20260611000000_profiles_naver_id.sql Outdated
Comment thread supabase/migrations/20260611000000_profiles_naver_id.sql Outdated
- 콜백: 이메일 매칭 백필 시 byEmail.naver_id가 비어있을 때만 갱신해
  기존 네이버 연결을 덮어쓰지 않도록 가드.
- 마이그레이션: naver_id 빈 문자열/공백을 NULL로 정규화(트리거
  nullif(btrim(...))), unique 인덱스 predicate도 동일 정규화로 일치.
- handle_new_user에 SET search_path = public 추가
  (SECURITY DEFINER 함수의 search_path 고정).

Refs #46
Part of #58

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kokkumong

Copy link
Copy Markdown
Collaborator Author

@coderabbitai 2차 리뷰 3건 반영했습니다 (382e5f2).

  1. 백필 덮어쓰기(Major): 이메일 매칭 백필을 byEmail.naver_id가 비어있을 때만 수행하도록 가드. 이미 다른 naver_id가 연결돼 있으면 덮어쓰지 않습니다.
  2. naver_id 공백 정규화(Major): 트리거에서 nullif(btrim(...), '')로 빈 문자열/공백을 NULL 처리, unique 인덱스 predicate도 동일 정규화로 일치시킴.
  3. search_path(Major): handle_new_userSET search_path = public 추가.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== callback route.ts ==="
fd "route.ts" --search-path src/app/api/auth/naver/callback --exec cat {}

echo ""
echo "=== migration file ==="
fd "20260611000000_profiles_naver_id" --exec cat {}

Length of output: 7649



3건 모두 코드에서 확인했습니다.


✅ 반영 확인

항목 확인
백필 가드 if (!byEmail.naver_id)
트리거 nullif(btrim(...), '')
unique index predicate nullif(btrim(naver_id), '') is not null ✅ (트리거 정규화와 일치)
set search_path = public

⚠️ 미반영 — concurrent race profileComplete 재조회

이전 리뷰 2번 항목이 아직 코드에 남아있습니다.

// 현재: race로 createError가 "already exists"이면
isNewUser = !createError; // → false
profileComplete = false;  // ← 기존 유저인데 false 고정

발생 빈도는 낮지만 UX 결함(기존 유저 → 추가 정보 페이지)이므로 별도 이슈로 트래킹을 권장합니다. 지금 이슈를 열어 드릴까요?

@kokkumong

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

createUser가 "already exists"로 실패하는 동시요청 race에서는 기존
사용자인데도 profileComplete를 false로 고정해, 추가정보 입력 페이지로
잘못 보내던 문제를 수정. race 감지 시 naver_id(없으면 이메일)로 실제
프로필 상태를 재조회해 isNewUser=false + 정확한 profileComplete로 분기.

Refs #46
Part of #58

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kokkumong

Copy link
Copy Markdown
Collaborator Author

@coderabbitai 별도 이슈 대신 이 PR에서 바로 수정했습니다 (f726ef0).

동시요청 race로 createUser가 "already exists"가 되면, 기존 사용자로 보고 naver_id(없으면 이메일)로 프로필을 재조회해 isNewUser=false + 실제 profileComplete 값으로 분기합니다. 신규로 오판해 추가정보 페이지로 보내지 않습니다.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== callback route.ts (current) ==="
fd "route.ts" --search-path src/app/api/auth/naver/callback --exec cat {}

Length of output: 6471



확인했습니다. race 처리 블록이 정확히 구현됐습니다.

// createError → "already exists" 분기
const { data: raced } = await admin
  .from("profiles")
  .select("phone_number, terms_agreed_at")
  .eq("naver_id", naverId)
  .maybeSingle();
const racedProfile =
  raced ??
  (await admin.from("profiles").select(...).eq("email", authEmail).maybeSingle()).data;
isNewUser = false;
profileComplete = !!racedProfile?.phone_number && !!racedProfile?.terms_agreed_at;
  • naver_id 우선 조회 → 없으면 email fallback 체인 ✅
  • isNewUser = false 강제 ✅
  • profileComplete를 실제 DB 값 기준으로 판정 ✅

이전 리뷰에서 제기한 모든 항목이 반영됐습니다. PR approve 진행합니다.

[approve]

@Siul49
Siul49 self-requested a review June 11, 2026 13:10
@Siul49

Siul49 commented Jun 11, 2026

Copy link
Copy Markdown
Owner

good

@Siul49
Siul49 merged commit b78b938 into dev Jun 11, 2026
5 checks passed
Siul49 added a commit that referenced this pull request Jun 11, 2026
* refactor: 코드레빗 리뷰 반영 — 터치 드래그 스크롤 방지 개선 및 E2E 안정화

* test(e2e): 호스트 플로우 내 고정 대기 시간 제거 및 요소 가시성 검사 보강

* feat(auth): 네이버 로그인 Supabase Admin 브리지 구현 (#59)

* feat(auth): 네이버 로그인 Supabase Admin 브리지 구현

네이버는 Supabase가 기본 지원하지 않는 OAuth 제공자라, #45 마이그레이션
이후 /api/auth/naver/* 가 naver_not_implemented 스텁으로 비활성 상태였다.

- 네이버 OAuth 헬퍼(getNaverAuthUrl/getNaverToken/getNaverUser) 복원
  (자체 JWT 의존 제거, fetch-with-timeout 기반)
- naver/login: authorize 리다이렉트 + state 쿠키 복원
- naver/callback: 네이버 프로필 수신 → Supabase Admin(service_role)으로
  유저 생성/조회 → magiclink 토큰을 verifyOtp로 교환해 일반 Supabase
  세션 쿠키 발급. 카카오/구글/애플과 동일하게 getUser() 세션으로 일원화.
- 이메일 미제공 시 결정적 placeholder로 동일 사용자 식별, 닉네임 unique
  충돌 시 naverId 기반으로 대체.

Refs #46
Part of #58

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(auth): 코드래빗 리뷰 반영 — 네이버 식별자/닉네임 보강

- naver_id를 profiles 1차 식별자로 사용 (이메일 단독 조회 시 같은 네이버
  계정이 갈라지는 문제 해결). profiles.naver_id 컬럼+unique 인덱스 추가하고
  handle_new_user 트리거가 user_metadata.naver_id를 채우도록 갱신.
- 콜백: naver_id로 먼저 조회, 미존재 시 이메일로 매칭 후 naver_id 백필.
- extractNaverUserInfo: 빈 문자열/공백 닉네임·이메일을 "값 없음"으로 취급해
  name → naver_<id> fallback이 정상 동작하도록 수정.

Refs #46
Part of #58

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(auth): 코드래빗 2차 리뷰 반영 — naver_id 백필 가드/정규화/보안

- 콜백: 이메일 매칭 백필 시 byEmail.naver_id가 비어있을 때만 갱신해
  기존 네이버 연결을 덮어쓰지 않도록 가드.
- 마이그레이션: naver_id 빈 문자열/공백을 NULL로 정규화(트리거
  nullif(btrim(...))), unique 인덱스 predicate도 동일 정규화로 일치.
- handle_new_user에 SET search_path = public 추가
  (SECURITY DEFINER 함수의 search_path 고정).

Refs #46
Part of #58

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(auth): 네이버 동시요청 race 시 기존 사용자 추가정보 오라우팅 수정

createUser가 "already exists"로 실패하는 동시요청 race에서는 기존
사용자인데도 profileComplete를 false로 고정해, 추가정보 입력 페이지로
잘못 보내던 문제를 수정. race 감지 시 naver_id(없으면 이메일)로 실제
프로필 상태를 재조회해 isNewUser=false + 정확한 profileComplete로 분기.

Refs #46
Part of #58

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: kokkumong <102169091+kokkumong@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Siul49 added a commit that referenced this pull request Jun 12, 2026
* feat(auth): 네이버 로그인 Supabase Admin 브리지 구현 (#59)

* feat(auth): 네이버 로그인 Supabase Admin 브리지 구현

네이버는 Supabase가 기본 지원하지 않는 OAuth 제공자라, #45 마이그레이션
이후 /api/auth/naver/* 가 naver_not_implemented 스텁으로 비활성 상태였다.

- 네이버 OAuth 헬퍼(getNaverAuthUrl/getNaverToken/getNaverUser) 복원
  (자체 JWT 의존 제거, fetch-with-timeout 기반)
- naver/login: authorize 리다이렉트 + state 쿠키 복원
- naver/callback: 네이버 프로필 수신 → Supabase Admin(service_role)으로
  유저 생성/조회 → magiclink 토큰을 verifyOtp로 교환해 일반 Supabase
  세션 쿠키 발급. 카카오/구글/애플과 동일하게 getUser() 세션으로 일원화.
- 이메일 미제공 시 결정적 placeholder로 동일 사용자 식별, 닉네임 unique
  충돌 시 naverId 기반으로 대체.

Refs #46
Part of #58

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(auth): 코드래빗 리뷰 반영 — 네이버 식별자/닉네임 보강

- naver_id를 profiles 1차 식별자로 사용 (이메일 단독 조회 시 같은 네이버
  계정이 갈라지는 문제 해결). profiles.naver_id 컬럼+unique 인덱스 추가하고
  handle_new_user 트리거가 user_metadata.naver_id를 채우도록 갱신.
- 콜백: naver_id로 먼저 조회, 미존재 시 이메일로 매칭 후 naver_id 백필.
- extractNaverUserInfo: 빈 문자열/공백 닉네임·이메일을 "값 없음"으로 취급해
  name → naver_<id> fallback이 정상 동작하도록 수정.

Refs #46
Part of #58

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(auth): 코드래빗 2차 리뷰 반영 — naver_id 백필 가드/정규화/보안

- 콜백: 이메일 매칭 백필 시 byEmail.naver_id가 비어있을 때만 갱신해
  기존 네이버 연결을 덮어쓰지 않도록 가드.
- 마이그레이션: naver_id 빈 문자열/공백을 NULL로 정규화(트리거
  nullif(btrim(...))), unique 인덱스 predicate도 동일 정규화로 일치.
- handle_new_user에 SET search_path = public 추가
  (SECURITY DEFINER 함수의 search_path 고정).

Refs #46
Part of #58

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(auth): 네이버 동시요청 race 시 기존 사용자 추가정보 오라우팅 수정

createUser가 "already exists"로 실패하는 동시요청 race에서는 기존
사용자인데도 profileComplete를 false로 고정해, 추가정보 입력 페이지로
잘못 보내던 문제를 수정. race 감지 시 naver_id(없으면 이메일)로 실제
프로필 상태를 재조회해 isNewUser=false + 정확한 profileComplete로 분기.

Refs #46
Part of #58

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(auth): 네이버 로그인 및 E2E 테스트 관련 코드레빗 리뷰 반영

* refactor: 캘린더 어댑터 상속 구조화 및 에러 핸들링 계층 구조 구축

* chore: PR #61 코드레빗 리뷰 피드백 반영

---------

Co-authored-by: kokkumong <102169091+kokkumong@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

2 participants