eigent/server/app/domains/oauth/api/oauth_controller.py
Tong Chen 712f20a8fa
Some checks are pending
Pre-commit / pre-commit (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Test / Run Python Tests (push) Waiting to run
Feat: Server refactor v1 (#1509)
2026-03-24 18:05:52 +08:00

97 lines
4.1 KiB
Python

# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
# STATUS: full-rewrite (uses OAuthService, H12 code/state XSS validation)
import json
from typing import Optional
from urllib.parse import quote
from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
from app.core.oauth_adapter import OauthCallbackPayload
from app.domains.oauth.schema import OAuthAuthorizeReq, OAuthTokenReq
from app.domains.oauth.service.oauth_service import OAuthService
router = APIRouter(prefix="/oauth", tags=["Oauth Servers"])
_OAUTH_CODE_MAX_LEN = 2048
_OAUTH_STATE_MAX_LEN = 512
_OAUTH_SAFE_CHARS = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.")
def _validate_oauth_param(value: Optional[str], name: str, max_len: int) -> str:
"""Validate and sanitize OAuth callback params to prevent XSS injection (H12)."""
if value is None:
return ""
s = str(value).strip()
if len(s) > max_len:
raise HTTPException(status_code=400, detail=f"Invalid {name}: too long")
if s and not all(c in _OAUTH_SAFE_CHARS or c in " /+=" for c in s):
raise HTTPException(status_code=400, detail=f"Invalid {name}: invalid characters")
return s
@router.get("/{app}/login", name="OAuth Login Redirect")
def oauth_login(app: str, request: Request, state: Optional[str] = None):
callback_url = str(request.url_for("OAuth Callback", app=app))
if callback_url.startswith("http://"):
callback_url = "https://" + callback_url[len("http://"):]
result = OAuthService.get_authorize_url(
OAuthAuthorizeReq(provider=app, redirect_uri=callback_url, state=state)
)
if not result.success:
raise HTTPException(status_code=400, detail="Failed to generate authorization URL")
return RedirectResponse(str(result.authorize_url))
@router.get("/{app}/callback", name="OAuth Callback")
def oauth_callback(app: str, request: Request, code: Optional[str] = None, state: Optional[str] = None):
if not code:
raise HTTPException(status_code=400, detail="Missing code parameter")
safe_code = _validate_oauth_param(code, "code", _OAUTH_CODE_MAX_LEN)
safe_state = _validate_oauth_param(state, "state", _OAUTH_STATE_MAX_LEN)
safe_app = _validate_oauth_param(app, "provider", 64) or ""
query = f"provider={quote(safe_app, safe='')}&code={quote(safe_code, safe='')}&state={quote(safe_state, safe='')}"
redirect_url = f"eigent://callback/oauth?{query}"
html_content = f"""
<html>
<head>
<title>OAuth Callback</title>
</head>
<body>
<script type='text/javascript'>
window.location.href = {json.dumps(redirect_url)};
</script>
<p>Redirecting, please wait...</p>
<button onclick='window.close()'>Close this window</button>
</body>
</html>
"""
return HTMLResponse(content=html_content)
@router.post("/{app}/token", name="OAuth Fetch Token")
def fetch_token(app: str, request: Request, data: OauthCallbackPayload):
callback_url = str(request.url_for("OAuth Callback", app=app))
if callback_url.startswith("http://"):
callback_url = "https://" + callback_url[len("http://"):]
result = OAuthService.exchange_token(
OAuthTokenReq(provider=app, code=data.code, redirect_uri=callback_url)
)
if not result.success:
raise HTTPException(status_code=500, detail="Token exchange failed")
return JSONResponse(result.token_data)