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")