126 lines
3.9 KiB
Python
126 lines
3.9 KiB
Python
"""
|
|
카카오 로그인 API 라우터
|
|
"""
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from fastapi.responses import RedirectResponse
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.auth.dependencies import get_current_user_optional
|
|
from app.auth.models import User
|
|
from app.auth.schemas import AuthStatusResponse, TokenResponse, UserResponse
|
|
from app.auth.services.jwt import create_access_token
|
|
from app.auth.services.kakao import kakao_client
|
|
from app.database.session import get_session
|
|
|
|
router = APIRouter(tags=["Auth"])
|
|
|
|
|
|
@router.get("/auth/kakao/login")
|
|
async def kakao_login():
|
|
"""
|
|
카카오 로그인 페이지로 리다이렉트
|
|
|
|
프론트엔드에서 이 URL을 호출하면 카카오 로그인 페이지로 이동합니다.
|
|
"""
|
|
auth_url = kakao_client.get_authorization_url()
|
|
return RedirectResponse(url=auth_url)
|
|
|
|
|
|
@router.get("/kakao/callback", response_model=TokenResponse)
|
|
async def kakao_callback(
|
|
code: str,
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
"""
|
|
카카오 로그인 콜백
|
|
|
|
카카오 로그인 성공 후 인가 코드를 받아 JWT 토큰을 발급합니다.
|
|
"""
|
|
# 1. 인가 코드로 액세스 토큰 획득
|
|
token_data = await kakao_client.get_access_token(code)
|
|
|
|
if "error" in token_data:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"카카오 토큰 발급 실패: {token_data.get('error_description', token_data.get('error'))}",
|
|
)
|
|
|
|
access_token = token_data.get("access_token")
|
|
if not access_token:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="액세스 토큰을 받지 못했습니다",
|
|
)
|
|
|
|
# 2. 액세스 토큰으로 사용자 정보 조회
|
|
user_info = await kakao_client.get_user_info(access_token)
|
|
|
|
if "id" not in user_info:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="사용자 정보를 가져오지 못했습니다",
|
|
)
|
|
|
|
kakao_id = str(user_info["id"])
|
|
kakao_account = user_info.get("kakao_account", {})
|
|
profile = kakao_account.get("profile", {})
|
|
|
|
nickname = profile.get("nickname")
|
|
email = kakao_account.get("email")
|
|
profile_image = profile.get("profile_image_url")
|
|
|
|
# 3. 기존 회원 확인 또는 신규 가입
|
|
result = await session.execute(select(User).where(User.kakao_id == kakao_id))
|
|
user = result.scalar_one_or_none()
|
|
|
|
if user is None:
|
|
# 신규 가입
|
|
user = User(
|
|
kakao_id=kakao_id,
|
|
nickname=nickname,
|
|
email=email,
|
|
profile_image=profile_image,
|
|
)
|
|
session.add(user)
|
|
await session.commit()
|
|
await session.refresh(user)
|
|
else:
|
|
# 기존 회원 - 마지막 로그인 시간 및 정보 업데이트
|
|
user.nickname = nickname
|
|
user.email = email
|
|
user.profile_image = profile_image
|
|
user.last_login_at = datetime.now(timezone.utc)
|
|
await session.commit()
|
|
await session.refresh(user)
|
|
|
|
# 4. JWT 토큰 발급
|
|
jwt_token = create_access_token({"sub": str(user.id)})
|
|
|
|
return TokenResponse(
|
|
access_token=jwt_token,
|
|
user=UserResponse.model_validate(user),
|
|
)
|
|
|
|
|
|
@router.get("/auth/me", response_model=AuthStatusResponse)
|
|
async def get_auth_status(
|
|
current_user: User | None = Depends(get_current_user_optional),
|
|
):
|
|
"""
|
|
현재 인증 상태 확인
|
|
|
|
프론트엔드에서 로그인 상태를 확인할 때 사용합니다.
|
|
토큰이 유효하면 사용자 정보를, 아니면 is_authenticated=False를 반환합니다.
|
|
"""
|
|
if current_user is None:
|
|
return AuthStatusResponse(is_authenticated=False)
|
|
|
|
return AuthStatusResponse(
|
|
is_authenticated=True,
|
|
user=UserResponse.model_validate(current_user),
|
|
)
|