Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth/social login #24

Merged
merged 5 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions instaclone/app/auth/store.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from sqlalchemy.future import select
from datetime import datetime
from instaclone.app.user.models import User
from instaclone.database.connection import SESSION
from sqlalchemy.exc import SQLAlchemyError
from instaclone.common.errors import CommentServerError

async def get_or_create_user_from_google(user_info: dict):
try:
# User DB - email 기반 사용자 정보 조회
async with SESSION() as session:
stmt = select(User).filter_by(email=user_info["email"])
result = await session.execute(stmt)
user = result.scalars().first()

# 유저가 새로 생성된 경우
if not user:
user = User(
username=user_info.get("name"),
email=user_info.get("email"),
full_name=user_info.get("name"),
password="default",
phone_number=99999999999,
creation_date=datetime.today().date(),
profile_image=user_info.get("picture"),
social=True
)
session.add(user)
await session.commit()
return {
"user": user,
"is_created": True
}

# 유저가 이미 있는 경우
return {
"user": user,
"is_created": False
}

except SQLAlchemyError as e:
# 예외가 발생하면 롤백 처리하고, 커스텀 예외 던지기
await SESSION.rollback()
raise CommentServerError(f"Failed to create or retrieve user: {str(e)}", e) from e
66 changes: 66 additions & 0 deletions instaclone/app/auth/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import os
import httpx
from fastapi import FastAPI, Depends, APIRouter, HTTPException
from fastapi.responses import RedirectResponse
from fastapi import Query
from instaclone.database.google_settings import GOOGLE_SETTINGS
from instaclone.app.auth.store import get_or_create_user_from_google

GOOGLE_CLIENT_ID = GOOGLE_SETTINGS.client_id
GOOGLE_CLIENT_SECRET = GOOGLE_SETTINGS.client_secret
GOOGLE_REDIRECT_URI = "http://localhost:8000/auth/callback" # 설정한 리디렉션 URI

google_oauth_router = APIRouter()

@google_oauth_router.get("/login")
async def login():
# Google OAuth 인증 URL 생성
google_auth_url = f"https://accounts.google.com/o/oauth2/v2/auth?client_id={GOOGLE_CLIENT_ID}&redirect_uri={GOOGLE_REDIRECT_URI}&response_type=code&scope=email%20profile"
return RedirectResponse(google_auth_url)



@google_oauth_router.get("/callback")
async def callback(code: str = Query(..., description="Google Authorization Code")):
# Google API에 액세스 토큰 요청
token_url = "https://oauth2.googleapis.com/token"
data = {
"code": code,
"client_id": GOOGLE_CLIENT_ID,
"client_secret": GOOGLE_CLIENT_SECRET,
"redirect_uri": GOOGLE_REDIRECT_URI,
"grant_type": "authorization_code"
}

async with httpx.AsyncClient() as client:
response = await client.post(token_url, data=data)
if response.status_code != 200:
raise HTTPException(status_code=400, detail="Failed to retrieve access token")

tokens = response.json()
access_token = tokens.get("access_token")
id_token = tokens.get("id_token")

# Access token을 사용해 Google 사용자 정보 요청
user_info_url = "https://www.googleapis.com/oauth2/v3/userinfo"
headers = {"Authorization": f"Bearer {access_token}"}

async with httpx.AsyncClient() as client:
user_info_response = await client.get(user_info_url, headers=headers)
if user_info_response.status_code != 200:
raise HTTPException(status_code=400, detail="Failed to retrieve user info")

user_info = user_info_response.json()

response = await get_or_create_user_from_google(user_info)

# user와 is_created를 각각 받음
user = response['user']
is_created = response['is_created']
return {
"user_info": user_info,
"user_id": user.user_id,
"username": user.username,
"user_password": user.password,
"is_created": is_created
}
6 changes: 4 additions & 2 deletions instaclone/app/user/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import TYPE_CHECKING, List
from pydantic import EmailStr
from sqlalchemy import String, BigInteger, Date, ForeignKey
from sqlalchemy import String, BigInteger, Date, Boolean
from sqlalchemy.orm import Mapped, mapped_column, relationship
from instaclone.database.common import Base

Expand All @@ -27,11 +27,13 @@ class User(Base):
# email
email: Mapped[EmailStr] = mapped_column(String(100), unique=True)
# phone_number : 010XXXXXXXX
phone_number: Mapped[str] = mapped_column(String(11), unique=True)
phone_number: Mapped[str] = mapped_column(String(11), unique=True, nullable=True)
# creation_date : YYYY-MM-DD
creation_date: Mapped[Date] = mapped_column(Date)
# profile_image : file path string
profile_image: Mapped[str] = mapped_column(String(100))
# social : bool
social: Mapped[bool] = mapped_column(Boolean, default=False, nullable=True)

# gender
gender: Mapped[str] = mapped_column(String(10), nullable=True)
Expand Down
10 changes: 8 additions & 2 deletions instaclone/common/errors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from fastapi import HTTPException
from starlette.status import HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED
from starlette.status import HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_500_INTERNAL_SERVER_ERROR


class InstacloneHttpException(HTTPException):
Expand All @@ -26,4 +26,10 @@ def __init__(self) -> None:

class BlockedTokenError(HTTPException):
def __init__(self) -> None:
super().__init__(HTTP_401_UNAUTHORIZED, "The token has been blocked.")
super().__init__(HTTP_401_UNAUTHORIZED, "The token has been blocked.")

class CommentServerError(HTTPException):
def __init__(self, message: str, original_exception=None) -> None:
# HTTPException 초기화
super().__init__(HTTP_500_INTERNAL_SERVER_ERROR, message)
self.original_exception = original_exception
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""DB initialized

Revision ID: d9846c7e8030
Revision ID: c7b13a1ab89d
Revises:
Create Date: 2025-01-17 13:23:06.067010
Create Date: 2025-01-19 17:17:18.641285

"""
from typing import Sequence, Union
Expand All @@ -12,7 +12,7 @@


# revision identifiers, used by Alembic.
revision: str = 'd9846c7e8030'
revision: str = 'c7b13a1ab89d'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
Expand All @@ -31,9 +31,10 @@ def upgrade() -> None:
sa.Column('password', sa.String(length=128), nullable=False),
sa.Column('full_name', sa.String(length=30), nullable=False),
sa.Column('email', sa.String(length=100), nullable=False),
sa.Column('phone_number', sa.String(length=11), nullable=False),
sa.Column('phone_number', sa.String(length=11), nullable=True),
sa.Column('creation_date', sa.Date(), nullable=False),
sa.Column('profile_image', sa.String(length=100), nullable=False),
sa.Column('social', sa.Boolean(), nullable=True),
sa.Column('gender', sa.String(length=10), nullable=True),
sa.Column('birthday', sa.Date(), nullable=True),
sa.Column('introduce', sa.String(length=100), nullable=True),
Expand Down
16 changes: 16 additions & 0 deletions instaclone/database/google_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# instaclone/settings/google_settings.py
from pydantic_settings import BaseSettings
from instaclone.settings import SETTINGS

class GoogleSettings(BaseSettings):
client_id: str = ""
client_secret: str = ""

class Config:
case_sensitive = False
env_prefix = "GOOGLE_"
env_file = SETTINGS.env_file
extra = "allow" # extra 필드 허용

# GoogleSettings 인스턴스를 따로 초기화
GOOGLE_SETTINGS = GoogleSettings()
1 change: 1 addition & 0 deletions instaclone/database/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def url(self) -> str:
case_sensitive=False,
env_prefix="DB_",
env_file=SETTINGS.env_file,
extra = "allow"
)


Expand Down
5 changes: 4 additions & 1 deletion instaclone/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
from fastapi.staticfiles import StaticFiles

from instaclone.api import api_router
from instaclone.app.auth.views import google_oauth_router

app = FastAPI()

app.include_router(api_router, prefix="/api")
app.include_router(google_oauth_router, prefix='/auth')

origins = [
"https://d3l72zsyuz0duc.cloudfront.net",
"http://localhost:5173"
"http://localhost:5173",
"http://localhost:8000"
]

app.add_middleware(
Expand Down