"""인증 API — 카카오 로그인·토큰 갱신·로그아웃·내 정보 (castad 이식). 콜백은 백엔드가 받아 JWT 발급 후 프론트로 `#access_token=..&refresh_token=..` 해시 프래그먼트로 리다이렉트한다 (쿼리스트링이 아니라 해시 — 서버 로그·Referer 에 토큰이 남지 않게 castad 방식에서 한 단계 보강). """ from __future__ import annotations from typing import Optional from fastapi import APIRouter, Depends, Header, Request, status from fastapi.responses import RedirectResponse, Response from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession from ..config import settings from ..database import get_db from ..models import User from ..services import auth_kakao router = APIRouter(prefix="/api/auth", tags=["auth"]) class RefreshIn(BaseModel): refreshToken: str def _client_ip(request: Request) -> str | None: fwd = request.headers.get("X-Forwarded-For") if fwd: return fwd.split(",")[0].strip() return request.client.host if request.client else None @router.get("/kakao/login") async def kakao_login_url() -> dict: """카카오 인증 페이지 URL — 프론트가 이 URL 로 이동시킨다.""" return {"authUrl": auth_kakao.kakao_authorization_url()} @router.get("/kakao/callback") async def kakao_callback( request: Request, code: str, db: AsyncSession = Depends(get_db), user_agent: Optional[str] = Header(None, alias="User-Agent"), ) -> RedirectResponse: """카카오 콜백 — 인가 코드로 JWT 발급 후 프론트로 리다이렉트.""" result = await auth_kakao.kakao_login( db, code, user_agent=user_agent, ip_address=_client_ip(request) ) redirect = ( f"{settings.public_origin}/" f"#access_token={result['access_token']}" f"&refresh_token={result['refresh_token']}" ) return RedirectResponse(url=redirect, status_code=302) @router.post("/refresh") async def refresh(body: RefreshIn, db: AsyncSession = Depends(get_db)) -> dict: """리프레시 토큰 회전 — 새 access/refresh 발급, 기존 refresh 폐기.""" return await auth_kakao.refresh_tokens(db, body.refreshToken) @router.post("/logout", status_code=status.HTTP_204_NO_CONTENT) async def logout(body: RefreshIn, db: AsyncSession = Depends(get_db)) -> Response: """리프레시 토큰 폐기. access 만료 후엔 재갱신 불가.""" await auth_kakao.logout(db, body.refreshToken) return Response(status_code=status.HTTP_204_NO_CONTENT) @router.get("/me") async def me(user: User = Depends(auth_kakao.get_current_user)) -> dict: """현재 로그인 사용자 정보.""" return auth_kakao.user_out(user)