From e33cc8686bcfb368f45610e39f62d44e81fd4a7d Mon Sep 17 00:00:00 2001 From: king Date: Sun, 24 May 2026 05:38:43 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20OMS(orderlist)=EC=99=80=20SSO=20?= =?UTF-8?q?=ED=98=B8=ED=99=98=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=84=B8?= =?UTF-8?q?=EC=85=98=20=EA=B3=84=EC=95=BD=20=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 세션 쿠키 이름을 SESSION_COOKIE_NAME env 로 OMS 와 통일 - 로그인 성공 시 top-level user_email / user_name 키도 함께 저장 - get_user() 가 OMS 가 만든 SSO 세션(top-level email만 있는 경우)도 인정 - /login 에 ?next= 지원 (open redirect 방지 위해 같은 호스트 절대경로만 허용) --- .env.example | 2 ++ app/main.py | 42 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index e0ab2f0..430234a 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,8 @@ GOOGLE_CLIENT_ID=your-google-oauth-client-id.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=your-google-oauth-client-secret +# OMS(orderlist) 와 SSO 하려면 양쪽 .env 에 동일한 값으로 설정 SESSION_SECRET_KEY=replace-with-a-long-random-secret +SESSION_COOKIE_NAME=session SESSION_COOKIE_SECURE=true PUBLIC_BASE_URL=https://dbx.no1king.freeddns.org CS_ORDER_URL=https://cs.example.com diff --git a/app/main.py b/app/main.py index 4f4ee2a..b981044 100644 --- a/app/main.py +++ b/app/main.py @@ -11,6 +11,9 @@ from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from starlette.middleware.sessions import SessionMiddleware +# OMS(orderlist) 와 공유하는 세션 키. SessionMiddleware 의 session_cookie 도 동일 이름. +SESSION_COOKIE_DEFAULT = "session" + load_dotenv() @@ -45,9 +48,11 @@ app = FastAPI(title="DBX 메인 페이지") app.add_middleware( SessionMiddleware, secret_key=env("SESSION_SECRET_KEY", "change-this-session-secret"), + session_cookie=env("SESSION_COOKIE_NAME", SESSION_COOKIE_DEFAULT), https_only=env("SESSION_COOKIE_SECURE", "true").lower() == "true", same_site="lax", max_age=60 * 60 * 8, + path="/", ) app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static") @@ -64,7 +69,26 @@ def public_url_for(request: Request, route_name: str) -> str: def get_user(request: Request) -> dict[str, Any] | None: user = request.session.get("user") - return user if isinstance(user, dict) else None + if isinstance(user, dict): + return user + # OMS 가 top-level user_email 만 채워놓은 SSO 세션도 인정 + email = request.session.get("user_email") + if email: + return { + "email": str(email), + "name": request.session.get("user_name") or str(email), + "picture": "", + } + return None + + +def safe_next(raw: str | None) -> str: + """Open redirect 방지: 같은 호스트의 절대 경로만 허용.""" + if not raw: + return "/" + if not raw.startswith("/") or raw.startswith("//") or raw.startswith("/\\"): + return "/" + return raw def render_template( @@ -140,6 +164,9 @@ async def login(request: Request): status_code=500, ) + # SSO: ?next= 로 들어온 목적지를 OAuth 콜백 뒤에 사용하기 위해 세션에 임시 보관 + request.session["_post_login_next"] = safe_next(request.query_params.get("next")) + redirect_uri = public_url_for(request, "auth_google") return await oauth.google.authorize_redirect( request, @@ -174,12 +201,19 @@ async def auth_google(request: Request): status_code=403, ) + email = str(userinfo.get("email", "")).lower().strip() + name = userinfo.get("name") or email + # OMS 와 공유하는 top-level 키 (SSO 계약) + request.session["user_email"] = email + request.session["user_name"] = name + # 기존 코드/템플릿 호환용 dict request.session["user"] = { - "email": str(userinfo.get("email", "")).lower().strip(), - "name": userinfo.get("name") or userinfo.get("email"), + "email": email, + "name": name, "picture": userinfo.get("picture", ""), } - return RedirectResponse(url="/", status_code=303) + next_url = safe_next(request.session.pop("_post_login_next", "/")) + return RedirectResponse(url=next_url, status_code=303) @app.get("/logout")