유튜브 인증 로직 수정
parent
ec3e0159e8
commit
f47dd423c5
|
|
@ -62,6 +62,7 @@ def _build_redirect_url(is_success: bool, params: dict) -> str:
|
|||
async def start_connect(
|
||||
platform: SocialPlatform,
|
||||
current_user: User = Depends(get_current_user),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> SocialConnectResponse:
|
||||
"""
|
||||
소셜 계정 연동 시작
|
||||
|
|
@ -77,6 +78,7 @@ async def start_connect(
|
|||
return await social_account_service.start_connect(
|
||||
user_uuid=current_user.user_uuid,
|
||||
platform=platform,
|
||||
session=session,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -24,12 +24,13 @@ class BaseOAuthClient(ABC):
|
|||
platform: SocialPlatform
|
||||
|
||||
@abstractmethod
|
||||
def get_authorization_url(self, state: str) -> str:
|
||||
def get_authorization_url(self, state: str, force_consent: bool = False) -> str:
|
||||
"""
|
||||
OAuth 인증 URL 생성
|
||||
|
||||
Args:
|
||||
state: CSRF 방지용 state 토큰
|
||||
force_consent: True면 동의 화면 강제 표시 (refresh_token 재발급 필요 시)
|
||||
|
||||
Returns:
|
||||
str: OAuth 인증 페이지 URL
|
||||
|
|
|
|||
|
|
@ -43,12 +43,13 @@ class YouTubeOAuthClient(BaseOAuthClient):
|
|||
self.client_secret = social_oauth_settings.YOUTUBE_CLIENT_SECRET
|
||||
self.redirect_uri = social_oauth_settings.YOUTUBE_REDIRECT_URI
|
||||
|
||||
def get_authorization_url(self, state: str) -> str:
|
||||
def get_authorization_url(self, state: str, force_consent: bool = False) -> str:
|
||||
"""
|
||||
Google OAuth 인증 URL 생성
|
||||
|
||||
Args:
|
||||
state: CSRF 방지용 state 토큰
|
||||
force_consent: True면 동의 화면 강제 표시하여 refresh_token 재발급
|
||||
|
||||
Returns:
|
||||
str: Google OAuth 인증 페이지 URL
|
||||
|
|
@ -58,12 +59,12 @@ class YouTubeOAuthClient(BaseOAuthClient):
|
|||
"redirect_uri": self.redirect_uri,
|
||||
"response_type": "code",
|
||||
"scope": " ".join(YOUTUBE_SCOPES),
|
||||
"access_type": "offline", # refresh_token 받기 위해 필요
|
||||
"prompt": "select_account", # 계정 선택만 표시 (동의 화면은 최초 1회만)
|
||||
"access_type": "offline",
|
||||
"prompt": "consent" if force_consent else "select_account",
|
||||
"state": state,
|
||||
}
|
||||
url = f"{self.AUTHORIZATION_URL}?{urlencode(params)}"
|
||||
logger.debug(f"[YOUTUBE_OAUTH] 인증 URL 생성: {url[:100]}...")
|
||||
logger.debug(f"[YOUTUBE_OAUTH] 인증 URL 생성 - force_consent: {force_consent}, url: {url[:100]}...")
|
||||
return url
|
||||
|
||||
async def exchange_code(self, code: str) -> OAuthTokenResponse:
|
||||
|
|
|
|||
|
|
@ -59,15 +59,18 @@ class SocialAccountService:
|
|||
self,
|
||||
user_uuid: str,
|
||||
platform: SocialPlatform,
|
||||
session: AsyncSession,
|
||||
) -> SocialConnectResponse:
|
||||
"""
|
||||
소셜 계정 연동 시작
|
||||
|
||||
OAuth 인증 URL을 생성하고 state 토큰을 저장합니다.
|
||||
기존 연동 계정에 refresh_token이 없으면 동의 화면을 강제 표시합니다.
|
||||
|
||||
Args:
|
||||
user_uuid: 사용자 UUID
|
||||
platform: 연동할 플랫폼
|
||||
session: DB 세션
|
||||
|
||||
Returns:
|
||||
SocialConnectResponse: OAuth 인증 URL 및 state 토큰
|
||||
|
|
@ -77,10 +80,19 @@ class SocialAccountService:
|
|||
f"user_uuid: {user_uuid}, platform: {platform.value}"
|
||||
)
|
||||
|
||||
# 1. state 토큰 생성 (CSRF 방지)
|
||||
# 1. 기존 계정의 refresh_token 존재 여부 확인
|
||||
existing_account = await self.get_account_by_platform(user_uuid, platform, session)
|
||||
force_consent = not (existing_account and existing_account.refresh_token)
|
||||
logger.debug(
|
||||
f"[SOCIAL] OAuth prompt 결정 - force_consent: {force_consent}, "
|
||||
f"has_account: {existing_account is not None}, "
|
||||
f"has_refresh_token: {bool(existing_account and existing_account.refresh_token)}"
|
||||
)
|
||||
|
||||
# 2. state 토큰 생성 (CSRF 방지)
|
||||
state = secrets.token_urlsafe(32)
|
||||
|
||||
# 2. state를 Redis에 저장 (user_uuid 포함)
|
||||
# 3. state를 Redis에 저장 (user_uuid 포함)
|
||||
state_key = f"{self.STATE_KEY_PREFIX}{state}"
|
||||
state_data = {
|
||||
"user_uuid": user_uuid,
|
||||
|
|
@ -89,13 +101,13 @@ class SocialAccountService:
|
|||
await redis_client.setex(
|
||||
state_key,
|
||||
social_oauth_settings.OAUTH_STATE_TTL_SECONDS,
|
||||
json.dumps(state_data), # JSON으로 직렬화
|
||||
json.dumps(state_data),
|
||||
)
|
||||
logger.debug(f"[SOCIAL] OAuth state 저장 - key: {state_key}")
|
||||
|
||||
# 3. OAuth 클라이언트에서 인증 URL 생성
|
||||
# 4. OAuth 클라이언트에서 인증 URL 생성
|
||||
oauth_client = get_oauth_client(platform)
|
||||
auth_url = oauth_client.get_authorization_url(state)
|
||||
auth_url = oauth_client.get_authorization_url(state, force_consent=force_consent)
|
||||
|
||||
logger.info(f"[SOCIAL] OAuth URL 생성 완료 - platform: {platform.value}")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue