134 lines
5.2 KiB
Python
134 lines
5.2 KiB
Python
import uuid
|
|
from typing import Dict, Optional
|
|
from datetime import datetime, timezone
|
|
from fastapi import HTTPException
|
|
from google_auth_oauthlib.flow import Flow
|
|
from googleapiclient.discovery import build
|
|
from .utils import load_client_config
|
|
from .redis import RedisOAuthStorage, get_oauth_storage
|
|
|
|
class OAuthService:
|
|
def __init__(self, oauth_storage: RedisOAuthStorage):
|
|
self.oauth_storage = oauth_storage
|
|
self.scopes = [
|
|
"https://www.googleapis.com/auth/youtube.upload",
|
|
"https://www.googleapis.com/auth/youtube.readonly",
|
|
"https://www.googleapis.com/auth/userinfo.email",
|
|
"https://www.googleapis.com/auth/userinfo.profile",
|
|
"openid"
|
|
]
|
|
self.client_secret = "client_secret.json"
|
|
self.redirect_uri = "http://localhost:8000/social/google/callback"
|
|
|
|
async def login_url(self, return_url: Optional[str] = None):
|
|
'''Google OAuth 로그인 URL 생성'''
|
|
try:
|
|
client_config = load_client_config(self.client_secret)
|
|
|
|
flow = Flow.from_client_config(
|
|
client_config,
|
|
scopes=self.scopes,
|
|
redirect_uri=self.redirect_uri
|
|
)
|
|
|
|
# 고유한 state 생성 ( CSRF 공격 방지 )
|
|
state = str(uuid.uuid4())
|
|
|
|
# Redis에 flow 데이터 저장 (return_url 포함)
|
|
flow_data = {
|
|
"client_config": client_config,
|
|
"scopes": self.scopes,
|
|
"redirect_uri": self.redirect_uri,
|
|
"return_url": return_url,
|
|
"created_at": datetime.now(timezone.utc).isoformat()
|
|
}
|
|
await self.oauth_storage.store_oauth_state(state, flow_data)
|
|
|
|
# 인증 URL 생성
|
|
auth_url, _ = flow.authorization_url(
|
|
access_type="offline",
|
|
include_granted_scopes="true",
|
|
state=state,
|
|
prompt='consent'
|
|
)
|
|
return {
|
|
"auth_url": auth_url,
|
|
"state": state,
|
|
"return_url": return_url,
|
|
"message": "Google 로그인 URL이 생성되었습니다."
|
|
}
|
|
except Exception as e:
|
|
print(f"Google OAuth URL 생성 오류: {e}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"Google OAuth URL 생성 오류: {str(e)}"
|
|
)
|
|
|
|
async def callback(self, state: str, code: str):
|
|
'''Google OAuth 콜백 처리'''
|
|
|
|
# Redis에서 flow 데이터 조회
|
|
flow_data = await self.oauth_storage.get_oauth_state(state)
|
|
if not flow_data:
|
|
raise HTTPException(400, "유효하지 않은 state 토큰입니다.")
|
|
|
|
try:
|
|
# Flow 재생성
|
|
flow = Flow.from_client_config(
|
|
flow_data["client_config"],
|
|
scopes=flow_data["scopes"],
|
|
redirect_uri=flow_data["redirect_uri"]
|
|
)
|
|
|
|
# 인증 코드로 토큰 교환
|
|
flow.fetch_token(code=code)
|
|
credentials = flow.credentials
|
|
|
|
# 사용자 정보 가져오기
|
|
user_info = self._get_user_info(credentials)
|
|
|
|
# 토큰 데이터 준비
|
|
token_data = {
|
|
"access_token": credentials.token,
|
|
"refresh_token": credentials.refresh_token,
|
|
"expires_at": credentials.expiry.isoformat() if credentials.expiry else None,
|
|
"scopes": credentials.scopes,
|
|
"user_info": user_info,
|
|
"return_url": flow_data.get("return_url")
|
|
}
|
|
|
|
# 임시 토큰 ID 생성하여 저장
|
|
temp_token_id = str(uuid.uuid4())
|
|
await self.oauth_storage.store_temp_token(temp_token_id, token_data)
|
|
|
|
return {
|
|
"message": "Google 로그인 성공",
|
|
"temp_token_id": temp_token_id,
|
|
"return_url": flow_data.get("return_url"),
|
|
"user_info": user_info,
|
|
"credentials": {
|
|
"access_token": credentials.token,
|
|
"refresh_token": credentials.refresh_token,
|
|
"expires_at": credentials.expiry.isoformat() if credentials.expiry else None,
|
|
"scopes": credentials.scopes
|
|
}
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=400, detail=f"Google 인증 처리 오류: {str(e)}")
|
|
|
|
def _get_user_info(self, credentials):
|
|
'''Google 사용자 정보 조회'''
|
|
try:
|
|
service = build('oauth2', 'v2', credentials=credentials)
|
|
user_info = service.userinfo().get().execute()
|
|
return user_info
|
|
except Exception as e:
|
|
print(f"사용자 정보 조회 실패: {e}")
|
|
return {"error": "사용자 정보 조회 실패"}
|
|
|
|
async def get_token_by_temp_id(self, temp_token_id: str):
|
|
'''임시 토큰 ID로 실제 토큰 정보 조회'''
|
|
token_data = await self.oauth_storage.get_temp_token(temp_token_id)
|
|
if not token_data:
|
|
raise HTTPException(400, "유효하지 않은 토큰 ID입니다.")
|
|
return token_data |