from uuid import UUID from fastapi import APIRouter, Depends, Request, Response, HTTPException, Query from fastapi.responses import RedirectResponse from common.logger import LOG from common.models.gmodel import UserInfo from router.v1.validator.dependencies import IsValidAccessToken from services import social_account_service as service from services.external.social import SocialError router = APIRouter(prefix="/v1/social/oauth", tags=["Social"]) COOKIE = "social_oauth_browser" @router.post("/connect") async def connect(response: Response, user: UserInfo = Depends(IsValidAccessToken)): try: url, browser = service.begin(UUID(user.user_id), 2) except SocialError as ex: raise HTTPException(409, str(ex)) from ex response.set_cookie( COOKIE, browser, httponly=True, secure=True, samesite="lax", max_age=600, path="/v1/social/oauth", ) response.headers["Cache-Control"] = "no-store" return {"url": url} @router.get("/callback") async def oauth_callback( request: Request, state: str = Query("", max_length=2048), code: str = Query("", max_length=4096), error: str = Query("", max_length=200), ): ok = False if not error and code and state: try: await service.finish(state, request.cookies.get(COOKIE), code) ok = True except Exception as ex: # noqa: BLE001 # 화면에는 원문을 내보내지 않는다 — OAuth 응답·state 에는 자격증명이 들어 있다. LOG.w(f"[social] 계정 연결 실패: {type(ex).__name__}: {ex}") elif error: # 사장님이 Meta 화면에서 취소한 경우도 여기로 온다 — 고장과 구별되게 남긴다. LOG.i(f"[social] 계정 연결 중단(제공자 응답): {error[:80]}") response = RedirectResponse( "/sites?social=" + ("connected" if ok else "failed"), status_code=303 ) response.delete_cookie( COOKIE, path="/v1/social/oauth", secure=True, httponly=True, samesite="lax" ) response.headers["Cache-Control"] = "no-store" response.headers["Referrer-Policy"] = "no-referrer" return response @router.post("/disconnect") async def disconnect(user: UserInfo = Depends(IsValidAccessToken)): await service.disconnect(UUID(user.user_id), 2) return {"disconnected": True}