Skip to content

refactor(IN-314): 유튜브 로그인, 개발서버 로그인 구현 및 로그인 로직 수정#136

Merged
kimYunHyeong merged 10 commits into
developfrom
IN-314
May 31, 2026
Merged

refactor(IN-314): 유튜브 로그인, 개발서버 로그인 구현 및 로그인 로직 수정#136
kimYunHyeong merged 10 commits into
developfrom
IN-314

Conversation

@kimYunHyeong

@kimYunHyeong kimYunHyeong commented May 29, 2026

Copy link
Copy Markdown
Collaborator

📌 작업 개요

  • 화면 랜더링 시 최상단이 아닌 하단으로 내려가서 랜더링 되는 문제가 있음. auth여부와 채널 연동 여부 레이아웃에 children을 통해 랜더링하여 해결
  • 401 에러 발생 시 /로 리다이렉트 되는 것이 아니라 로그인 모달이 랜더링 되도록 코드 수정
  • 개발 서버 로그인: app\auth\callback\route.ts에 개발서버 라우팅 분기 처리
  • 유튜브 로그인: app\auth\youtube\route.ts 생성

🗂 작업 유형

해당하는 항목에 x를 채워 주세요.

  • [] 기능 추가 (feat)
  • 버그 수정 (fix)
  • 리팩토링 (refactor)
  • 스타일 / UI 수정 (style)
  • 성능 개선 (perf)
  • 테스트 (test)
  • 기타 (chore, docs 등)

✏️ 작업 내용

✅ 셀프 체크리스트

머지 전 직접 확인해 주세요.

  • 로컬에서 정상 동작 확인
  • 불필요한 콘솔 로그, 주석, 디버그 코드 제거
  • 타입 에러 없음

💬 리뷰어에게

Summary by CodeRabbit

  • New Features

    • YouTube 소셜 로그인 추가
    • 로그인 모달 기반 인증 플로우 전환
  • Improvements

    • 인증 필요 메뉴 표시(네비게이션 항목에 인증 요구 반영)
    • OAuth 팝업 모니터링 및 리셋 동작 강화
    • 인증 초기화/모달 연동으로 라우팅 및 UX 개선
    • 스크롤 복원(restoration) 수동 설정 추가
  • Bug Fixes

    • 레이아웃 스타일(flex) 오타 수정 및 가시성 처리 개선

- 개발 서버 로그인: app\auth\callback\route.ts에 개발서버 라우팅 분기 처리
- 유튜브 로그인: app\auth\youtube\route.ts 생성
401 에러 발생 시 /로 리다이렉트 되는 것이 아니라 로그인 모달이 랜더링 되도록 코드 수정
화면 랜더링 시 최상단이 아닌 하단으로 내려가서 랜더링 되는 문제가 있음. auth여부와 채널 연동 여부 레이아웃에 children을 통해 랜더링하여 해결
@vercel

vercel Bot commented May 29, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
inflace Error Error May 31, 2026 2:28pm

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

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

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

⌛ 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2c64d678-39ab-4977-85b5-2ee25b40fe0a

📥 Commits

Reviewing files that changed from the base of the PR and between 19b8ccb and 860364d.

📒 Files selected for processing (7)
  • src/features/auth/model/useAuthInit.ts
  • src/shared/api/axiosInstance.ts
  • src/shared/api/index.ts
  • src/shared/api/msw/handlers/authHandlers.ts
  • src/shared/api/userApi.ts
  • src/widgets/auth/ui/LoginModal.tsx
  • src/widgets/layout/sidebar/model/sidebarStore.ts

워크스루

Google과 YouTube OAuth를 제공자 접두사(provider:uuid)로 구분하고 YouTube 시작 엔드포인트를 추가했습니다. 콜백에서 state를 파싱해 동적 provider로 백엔드에 로그인 요청을 보내며, 보호된 경로 접근은 라우트 리다이렉트 대신 로그인 모달로 유도합니다. 팝업 폴링 및 모달 리셋도 도입했습니다.

변경 사항

다중 제공자 OAuth 및 모달 기반 인증 플로우

Layer / File(s) Summary
다중 제공자 OAuth 상태 형식 및 YouTube 엔드포인트
app/auth/google/route.ts, app/auth/google/route.test.ts, app/auth/youtube/route.ts, app/auth/callback/route.ts
stateprovider:uuid 접두사를 추가해 제공자를 식별하고, YouTube OAuth 시작 라우트와 콜백의 동적 provider 파싱·백엔드 호출 경로 분기를 구현했습니다.
모달 기반 인증 가드 및 초기화
src/features/auth/model/useRequireAuth.ts, src/features/auth/model/useRequireAuth.test.ts, src/app/layouts/AuthGuardLayout.tsx, src/pages/home/ui/HomePage.tsx
useRequireAuth는 라우터 리다이렉트 대신 로그인 모달을 열고, AuthGuardLayout과 HomePage는 모달 상태를 반영해 조건부 리다이렉트/모달 오픈을 처리합니다.
인증 실패 시 모달 열기 및 팝업 상태 관리
src/shared/api/axiosInstance.ts, src/features/auth/model/usePopupOAuth.ts, src/widgets/auth/ui/LoginModal.tsx
401 refresh 실패 시 로그인 모달을 열고, 팝업 닫힘 감지를 위한 폴링과 팝업 상태 리셋(reset)을 추가하며, 모달 닫힘 시 Google/YouTube 팝업을 리셋합니다.
네비게이션 항목별 인증 요구 표시 및 검증
src/features/navigation/model/types.ts, src/features/navigation/model/navItems.ts, src/features/navigation/ui/NavMenuItem.tsx
NavItemrequiresAuth 필드를 추가하고 일부 항목에 적용해 미인증 사용자의 클릭을 막고 로그인 모달을 엽니다.
레이아웃 안정성 및 UX 개선
src/features/auth/model/useAuthInit.ts, src/app/layouts/ChannelLinkedLayout.tsx, src/app/styles/globals.css, src/pages/main/ui/ChannelProfilePage.tsx
마운트 시 scrollRestoration을 manual로 설정하고, 채널 연동 미존재 시 null 대신 invisible div 반환, CSS 블록 종료 위치 및 클래스 오타를 수정했습니다.

예상 코드 리뷰 난이도

🎯 3 (중간) | ⏱️ ~20 분

제안된 리뷰어

  • juuhye
  • shunn2

🐰 OAuth에 접두사 하나 더해
유튜브 길을 열고,
모달로 로그인 부르는 밤,
팝업은 폴링으로 지키고,
클릭은 모달로 멈추네 ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 PR 제목이 변경 사항의 주요 내용을 명확하게 반영하고 있습니다. YouTube 로그인, 개발 서버 로그인 구현 및 로그인 로직 수정이라는 핵심 변경사항을 정확히 설명합니다.
Description check ✅ Passed PR 설명이 작업 개요, 작업 유형, 셀프 체크리스트를 모두 포함하고 있으며, 주요 변경사항(렌더링 위치 수정, 401 에러 처리, 개발 서버 로그인, YouTube 로그인)을 구체적으로 설명합니다.
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.

✏️ 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 IN-314

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/app/layouts/ChannelLinkedLayout.tsx (1)

32-37: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

인증/연동 미충족 상태에서 children을 마운트하지 마세요.

Line 37처럼 children을 그대로 렌더링하면 router.replace('/') 이전에 보호된 하위 트리가 실행되어 불필요한 API 호출/상태 갱신이 발생할 수 있습니다. 가드 실패 시에는 placeholder만 렌더링하고 children 마운트는 막는 쪽이 안전합니다.

수정 예시
   if (
     isInitializing ||
     !user?.userChannelDetails?.youtubeChannelName ||
     !user?.userChannelDetails?.youtubeChannelId
   )
-    return <div className='invisible'>{children}</div>
+    return <div className='invisible min-h-screen' aria-hidden='true' />
🤖 Prompt for 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.

In `@src/app/layouts/ChannelLinkedLayout.tsx` around lines 32 - 37, The current
guard in ChannelLinkedLayout.tsx mounts children even when the auth/channel
checks fail (the if checking isInitializing ||
!user?.userChannelDetails?.youtubeChannelName ||
!user?.userChannelDetails?.youtubeChannelId), which causes protected subtree
effects before router.replace('/'). Change the early return so it does not
render or mount children (e.g., return a placeholder like <div
className='invisible' /> or null) instead of returning <div
className='invisible'>{children}</div>, ensuring children are only mounted after
the guard passes.
app/auth/callback/route.ts (1)

59-59: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

민감 정보(accessToken·사용자 PII) 로깅을 제거하세요.

JSON.stringify(data)에는 accessTokenuserDetails 등 PII가 포함되어 서버 로그에 평문으로 남습니다. PR 설명에서는 디버그 로그를 제거했다고 했으나 이 라인이 남아 있습니다.

🔒️ 제안 수정
     const data: LoginResponse = await backendResponse.json()
-    console.log('[auth/callback] backend data:', JSON.stringify(data))
🤖 Prompt for 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.

In `@app/auth/callback/route.ts` at line 59, Remove the plaintext
console.log('[auth/callback] backend data:', JSON.stringify(data)) in
app/auth/callback/route.ts; either delete the call or replace it with a
sanitized/log-safe message that does not include data.accessToken,
data.userDetails, or any PII (only log non-sensitive status or an opaque request
id). Locate the statement that references "data" (the console.log with
'[auth/callback]') and ensure any logging uses redaction or explicit
whitelisting of safe fields before emitting to logs.
🧹 Nitpick comments (1)
app/auth/youtube/route.ts (1)

4-36: ⚡ Quick win

app/auth/google/route.ts와 거의 동일한 로직 중복.

state 접두사(youtube: vs google:)만 다르고 쿠키 설정·URLSearchParams·리다이렉트 구성이 완전히 동일합니다. 제공자가 추가될수록 복사·붙여넣기가 늘어나므로 공통 헬퍼로 추출하는 것을 권장합니다.

♻️ 제안: 공통 헬퍼 추출
// 예: app/auth/shared/startOAuth.ts
import { NextResponse } from 'next/server'
import { cookies } from 'next/headers'

export async function startOAuth(provider: 'google' | 'youtube') {
  if (process.env.NEXT_PUBLIC_MOCK_ENABLED === 'true') {
    return NextResponse.redirect(
      `${process.env.NEXT_PUBLIC_APP_URL}/auth/mock-callback`
    )
  }

  const state = `${provider}:${crypto.randomUUID()}`
  const cookieStore = await cookies()
  cookieStore.set('oauth_state', state, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
    path: '/',
    maxAge: 60 * 10,
  })

  const params = new URLSearchParams({
    client_id: process.env.GOOGLE_CLIENT_ID!,
    redirect_uri: `${process.env.NEXT_PUBLIC_APP_URL}/auth/callback`,
    response_type: 'code',
    scope:
      'openid email profile https://www.googleapis.com/auth/youtube.readonly',
    state,
    access_type: 'offline',
    prompt: 'consent',
  })

  return NextResponse.redirect(
    `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`
  )
}

각 라우트는 export const GET = () => startOAuth('youtube') 형태로 단순화됩니다.

🤖 Prompt for 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.

In `@app/auth/youtube/route.ts` around lines 4 - 36, There is duplicated
OAuth-start logic in the GET handlers (only the state prefix differs); extract a
shared startOAuth(provider: string) helper that encapsulates the mock redirect,
cookie creation via cookies().set('oauth_state', ...), URLSearchParams
construction, and NextResponse.redirect call; update the existing GET exports
(e.g., the functions named GET in both provider routes) to simply return
startOAuth('youtube') or startOAuth('google'), and make the helper accept a
provider-specific state prefix and optional scope override so provider-specific
scopes (like YouTube’s) can be passed in.
🤖 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/layouts/AuthGuardLayout.tsx`:
- Around line 21-22: The current AuthGuardLayout returns <div
className='invisible'>{children}</div> when isInitializing or not isLoggedIn,
which keeps children mounted and can trigger unwanted side effects; change the
guard so children are not mounted in that branch (e.g., return null or a
container without rendering children) and only render children when
isInitializing is false and isLoggedIn is true; update the logic inside
AuthGuardLayout (referencing isInitializing, isLoggedIn, and children) to avoid
mounting protected content while unauthenticated or initializing.

In `@src/features/auth/model/usePopupOAuth.ts`:
- Around line 26-31: The reset function currently nulls popupRef.current without
closing the window, leaving an open OAuth popup; update reset (the useCallback
named reset that calls stopPolling, manipulates popupRef, setIsLoading,
setError) to check if popupRef.current exists and is not closed (e.g.,
!popupRef.current.closed), call popupRef.current.close() inside a try/catch to
safely close the popup, then set popupRef.current = null and continue to call
stopPolling(), setIsLoading(false), and setError(null).

---

Outside diff comments:
In `@app/auth/callback/route.ts`:
- Line 59: Remove the plaintext console.log('[auth/callback] backend data:',
JSON.stringify(data)) in app/auth/callback/route.ts; either delete the call or
replace it with a sanitized/log-safe message that does not include
data.accessToken, data.userDetails, or any PII (only log non-sensitive status or
an opaque request id). Locate the statement that references "data" (the
console.log with '[auth/callback]') and ensure any logging uses redaction or
explicit whitelisting of safe fields before emitting to logs.

In `@src/app/layouts/ChannelLinkedLayout.tsx`:
- Around line 32-37: The current guard in ChannelLinkedLayout.tsx mounts
children even when the auth/channel checks fail (the if checking isInitializing
|| !user?.userChannelDetails?.youtubeChannelName ||
!user?.userChannelDetails?.youtubeChannelId), which causes protected subtree
effects before router.replace('/'). Change the early return so it does not
render or mount children (e.g., return a placeholder like <div
className='invisible' /> or null) instead of returning <div
className='invisible'>{children}</div>, ensuring children are only mounted after
the guard passes.

---

Nitpick comments:
In `@app/auth/youtube/route.ts`:
- Around line 4-36: There is duplicated OAuth-start logic in the GET handlers
(only the state prefix differs); extract a shared startOAuth(provider: string)
helper that encapsulates the mock redirect, cookie creation via
cookies().set('oauth_state', ...), URLSearchParams construction, and
NextResponse.redirect call; update the existing GET exports (e.g., the functions
named GET in both provider routes) to simply return startOAuth('youtube') or
startOAuth('google'), and make the helper accept a provider-specific state
prefix and optional scope override so provider-specific scopes (like YouTube’s)
can be passed in.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c26ce192-8173-4850-8a88-4b652f3ed69e

📥 Commits

Reviewing files that changed from the base of the PR and between 1a08fb7 and fabb966.

📒 Files selected for processing (18)
  • app/auth/callback/route.ts
  • app/auth/google/route.test.ts
  • app/auth/google/route.ts
  • app/auth/youtube/route.ts
  • src/app/layouts/AuthGuardLayout.tsx
  • src/app/layouts/ChannelLinkedLayout.tsx
  • src/app/styles/globals.css
  • src/features/auth/model/useAuthInit.ts
  • src/features/auth/model/usePopupOAuth.ts
  • src/features/auth/model/useRequireAuth.test.ts
  • src/features/auth/model/useRequireAuth.ts
  • src/features/navigation/model/navItems.ts
  • src/features/navigation/model/types.ts
  • src/features/navigation/ui/NavMenuItem.tsx
  • src/pages/home/ui/HomePage.tsx
  • src/pages/main/ui/ChannelProfilePage.tsx
  • src/shared/api/axiosInstance.ts
  • src/widgets/auth/ui/LoginModal.tsx
💤 Files with no reviewable changes (1)
  • src/app/styles/globals.css

Comment thread src/app/layouts/AuthGuardLayout.tsx Outdated
Comment thread src/features/auth/model/usePopupOAuth.ts
children이 마운트되면 그 안의 컴포넌트들이 즉시 실행되어 불필요한 호출이 발생하는 문제를 수정
reset은 로그인 취소용인데 기존 코드는 참조만 null로 비워서 OAuth 팝업 창이 화면에 그대로 남는 문제가 있었습니다.
eset 함수 내 popupRef.current = null 앞에 팝업 닫기 로직을 추가했습니다.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/layouts/AuthGuardLayout.tsx (1)

15-19: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

모달 오픈과 동시에 redirect 레이스 가능—redirect 조건을 모달-오픈 이력 기반으로 보강 필요
useRequireAuth!isInitializing && !isLoggedIn일 때 useEffect에서 open()으로 로그인 모달을 여는데(src/features/auth/model/useRequireAuth.ts:12-17), AuthGuardLayout은 같은 커밋의 redirect useEffect에서 렌더 캡처된 isModalOpen === false 기준으로 즉시 router.replace('/')를 실행할 수 있어 모달 기반 인증 플로우가 건너뛰어질 수 있습니다(src/app/layouts/AuthGuardLayout.tsx:15-19).

가능한 수정 예시
-import { useEffect } from 'react'
+import { useEffect, useRef } from 'react'
@@
   const { isLoggedIn, isInitializing } = useRequireAuth()
   const isModalOpen = useLoginModal((s) => s.isOpen)
   const router = useRouter()
+  const hasOpenedLoginModal = useRef(false)
+
+  useEffect(() => {
+    if (isModalOpen) hasOpenedLoginModal.current = true
+  }, [isModalOpen])
@@
   useEffect(() => {
-    if (!isInitializing && !isLoggedIn && !isModalOpen) {
+    if (!isInitializing && !isLoggedIn && hasOpenedLoginModal.current && !isModalOpen) {
       router.replace('/')
     }
   }, [isInitializing, isLoggedIn, isModalOpen, router])
🤖 Prompt for 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.

In `@src/app/layouts/AuthGuardLayout.tsx` around lines 15 - 19, The redirect race
happens because AuthGuardLayout's effect uses the rendered isModalOpen (false)
and immediately calls router.replace('/') while useRequireAuth may be about to
open the login modal; fix by making the redirect guard aware of the modal-open
request lifecycle: expose a flag from useRequireAuth (e.g.,
hasAttemptedToOpenModal or isModalOpenRequested) that is set when open() is
invoked in useRequireAuth, then update AuthGuardLayout's effect to require
!hasAttemptedToOpenModal (or !isModalOpenRequested) in addition to
!isInitializing and !isLoggedIn and !isModalOpen before calling router.replace;
use the existing symbols useRequireAuth, isInitializing, isLoggedIn, isModalOpen
and router.replace to locate where to add the flag and change the effect
condition.
🤖 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.

Outside diff comments:
In `@src/app/layouts/AuthGuardLayout.tsx`:
- Around line 15-19: The redirect race happens because AuthGuardLayout's effect
uses the rendered isModalOpen (false) and immediately calls router.replace('/')
while useRequireAuth may be about to open the login modal; fix by making the
redirect guard aware of the modal-open request lifecycle: expose a flag from
useRequireAuth (e.g., hasAttemptedToOpenModal or isModalOpenRequested) that is
set when open() is invoked in useRequireAuth, then update AuthGuardLayout's
effect to require !hasAttemptedToOpenModal (or !isModalOpenRequested) in
addition to !isInitializing and !isLoggedIn and !isModalOpen before calling
router.replace; use the existing symbols useRequireAuth, isInitializing,
isLoggedIn, isModalOpen and router.replace to locate where to add the flag and
change the effect condition.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3617416a-f357-4e06-ab36-b3a4846ea098

📥 Commits

Reviewing files that changed from the base of the PR and between fabb966 and 19b8ccb.

📒 Files selected for processing (2)
  • src/app/layouts/AuthGuardLayout.tsx
  • src/features/auth/model/usePopupOAuth.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/features/auth/model/usePopupOAuth.ts

토큰 리프레시 성공 후 유저 정보를 반환하는 API 요청을 보내도록(/user/me)해 유저 상태를 업데이트 하도록 수정
@kimYunHyeong kimYunHyeong merged commit 5226751 into develop May 31, 2026
2 of 3 checks passed
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