feat: OMS(orderlist)와 SSO 호환되도록 세션 계약 정렬
- 세션 쿠키 이름을 SESSION_COOKIE_NAME env 로 OMS 와 통일 - 로그인 성공 시 top-level user_email / user_name 키도 함께 저장 - get_user() 가 OMS 가 만든 SSO 세션(top-level email만 있는 경우)도 인정 - /login 에 ?next= 지원 (open redirect 방지 위해 같은 호스트 절대경로만 허용)
This commit is contained in:
@@ -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
|
||||
|
||||
+38
-4
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user