# -*- coding: utf-8 -*- """카카오 OAuth 클라이언트 (castad app/user/services/kakao.py 이식·축약). 흐름: get_authorization_url() → 사용자 카카오 로그인 → 인가코드(code) → get_access_token(code) → get_user_info(token) → (id, nickname, email) """ from fastapi import HTTPException import aiohttp from .config import settings AUTH_URL = "https://kauth.kakao.com/oauth/authorize" TOKEN_URL = "https://kauth.kakao.com/oauth/token" USER_INFO_URL = "https://kapi.kakao.com/v2/user/me" def get_authorization_url() -> str: if not settings.KAKAO_CLIENT_ID: raise HTTPException(501, "카카오 로그인 미설정 — KAKAO_CLIENT_ID 필요") return ( f"{AUTH_URL}?client_id={settings.KAKAO_CLIENT_ID}" f"&redirect_uri={settings.KAKAO_REDIRECT_URI}&response_type=code" ) async def get_access_token(code: str) -> str: data = { "grant_type": "authorization_code", "client_id": settings.KAKAO_CLIENT_ID, "redirect_uri": settings.KAKAO_REDIRECT_URI, "code": code, } if settings.KAKAO_CLIENT_SECRET: data["client_secret"] = settings.KAKAO_CLIENT_SECRET async with aiohttp.ClientSession() as s: async with s.post(TOKEN_URL, data=data) as r: res = await r.json() if "error" in res: raise HTTPException(400, f"카카오 토큰 발급 실패: {res.get('error_description', res.get('error'))}") return res["access_token"] async def get_user_info(access_token: str) -> dict: """카카오 사용자 → {kakao_id:int, nickname:str|None, email:str|None, profile_image:str|None}.""" async with aiohttp.ClientSession() as s: async with s.get(USER_INFO_URL, headers={"Authorization": f"Bearer {access_token}"}) as r: if r.status != 200: raise HTTPException(500, "카카오 사용자 정보 조회 실패") info = await r.json() acc = info.get("kakao_account") or {} prof = acc.get("profile") or {} return { "kakao_id": int(info["id"]), "nickname": prof.get("nickname"), "email": acc.get("email"), "profile_image": prof.get("profile_image_url"), }