Fix broker reconnect dashboard redirect

This commit is contained in:
Thigazhezhilan J 2026-04-14 09:54:19 +05:30
parent b5b759c5ed
commit a90603f4f6
2 changed files with 125 additions and 1 deletions

View File

@ -1,5 +1,6 @@
import os import os
from datetime import datetime, timedelta from datetime import datetime, timedelta
from urllib.parse import urlsplit, urlunsplit
from fastapi import APIRouter, HTTPException, Query, Request from fastapi import APIRouter, HTTPException, Query, Request
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
@ -53,6 +54,7 @@ from app.services.zerodha_storage import (
) )
router = APIRouter(prefix="/api/broker") router = APIRouter(prefix="/api/broker")
DEFAULT_PRODUCTION_DASHBOARD_URL = "https://app.quantfortune.com/dashboard?armed=false"
def _require_user(request: Request): def _require_user(request: Request):
@ -72,6 +74,31 @@ def _require_session_id(request: Request) -> str:
return session_id return session_id
def _build_broker_dashboard_url(request: Request) -> str:
configured_dashboard = (os.getenv("BROKER_DASHBOARD_URL") or "").strip()
if configured_dashboard:
return configured_dashboard
app_base_url = (os.getenv("APP_BASE_URL") or "").strip()
if app_base_url:
return f"{app_base_url.rstrip('/')}/dashboard?armed=false"
base_url = str(request.base_url).rstrip("/")
parsed = urlsplit(base_url)
hostname = (parsed.hostname or "").strip().lower()
scheme = parsed.scheme or "https"
if hostname in {"localhost", "127.0.0.1"}:
return f"{scheme}://{hostname}:5173/dashboard?armed=false"
if hostname.startswith("api.") and len(hostname) > 4:
scheme = "https"
frontend_netloc = parsed.netloc.replace(hostname, f"app.{hostname[4:]}", 1)
return urlunsplit((scheme, frontend_netloc, "/dashboard", "armed=false", ""))
return DEFAULT_PRODUCTION_DASHBOARD_URL
def _first_number(*values, default: float = 0.0) -> float: def _first_number(*values, default: float = 0.0) -> float:
for value in values: for value in values:
try: try:
@ -654,7 +681,7 @@ async def broker_callback(request: Request, request_token: str = "", state: str
broker_user_id=session_data.get("user_id"), broker_user_id=session_data.get("user_id"),
auth_state="VALID", auth_state="VALID",
) )
target_url = os.getenv("BROKER_DASHBOARD_URL") or "/dashboard?armed=false" target_url = _build_broker_dashboard_url(request)
return RedirectResponse(target_url) return RedirectResponse(target_url)

View File

@ -173,6 +173,103 @@ def test_wrong_or_expired_broker_callback_state_fails(monkeypatch):
assert response.json() == {"detail": "Invalid or expired broker callback state"} assert response.json() == {"detail": "Invalid or expired broker callback state"}
def test_reconnect_callback_redirects_to_app_dashboard_by_default(monkeypatch):
import app.main as app_main
import app.routers.broker as broker_router
monkeypatch.setenv("APP_ENV", "test")
monkeypatch.setenv("DISABLE_STARTUP_TASKS", "1")
monkeypatch.setenv("CORS_ORIGINS", "http://localhost:3000")
monkeypatch.delenv("BROKER_DASHBOARD_URL", raising=False)
monkeypatch.delenv("APP_BASE_URL", raising=False)
importlib.reload(app_main)
app = app_main.create_app()
client = TestClient(app)
monkeypatch.setattr(broker_router, "get_user_for_session", lambda _sid: {"id": "user-1", "username": "user@example.com"})
monkeypatch.setattr(
broker_router,
"consume_broker_callback_state",
lambda **kwargs: {"id": "state-1", "expires_at": datetime.now(timezone.utc).isoformat()},
)
monkeypatch.setattr(
broker_router,
"get_broker_credentials",
lambda _user_id: {"api_key": "kite-key", "api_secret": "kite-secret"},
)
monkeypatch.setattr(
broker_router,
"exchange_request_token",
lambda api_key, api_secret, token: {
"access_token": "access-token",
"request_token": token,
"user_name": "Trader",
"user_id": "Z123",
},
)
monkeypatch.setattr(broker_router, "set_zerodha_session", lambda user_id, payload: None)
monkeypatch.setattr(broker_router, "set_connected_broker", lambda user_id, broker, token, **kwargs: None)
response = client.get(
"/api/broker/callback",
params={"request_token": "request-token", "state": "valid-state"},
cookies={"session_id": "session-1"},
follow_redirects=False,
headers={"host": "api.quantfortune.com"},
)
assert response.status_code == 307
assert response.headers["location"] == "https://app.quantfortune.com/dashboard?armed=false"
def test_reconnect_callback_uses_configured_dashboard_url(monkeypatch):
import app.main as app_main
import app.routers.broker as broker_router
monkeypatch.setenv("APP_ENV", "test")
monkeypatch.setenv("DISABLE_STARTUP_TASKS", "1")
monkeypatch.setenv("CORS_ORIGINS", "http://localhost:3000")
monkeypatch.setenv("BROKER_DASHBOARD_URL", "https://app.quantfortune.com/dashboard?armed=false")
importlib.reload(app_main)
app = app_main.create_app()
client = TestClient(app)
monkeypatch.setattr(broker_router, "get_user_for_session", lambda _sid: {"id": "user-1", "username": "user@example.com"})
monkeypatch.setattr(
broker_router,
"consume_broker_callback_state",
lambda **kwargs: {"id": "state-1", "expires_at": datetime.now(timezone.utc).isoformat()},
)
monkeypatch.setattr(
broker_router,
"get_broker_credentials",
lambda _user_id: {"api_key": "kite-key", "api_secret": "kite-secret"},
)
monkeypatch.setattr(
broker_router,
"exchange_request_token",
lambda api_key, api_secret, token: {
"access_token": "access-token",
"request_token": token,
"user_name": "Trader",
"user_id": "Z123",
},
)
monkeypatch.setattr(broker_router, "set_zerodha_session", lambda user_id, payload: None)
monkeypatch.setattr(broker_router, "set_connected_broker", lambda user_id, broker, token, **kwargs: None)
response = client.get(
"/api/broker/callback",
params={"request_token": "request-token", "state": "valid-state"},
cookies={"session_id": "session-1"},
follow_redirects=False,
headers={"host": "api.quantfortune.com"},
)
assert response.status_code == 307
assert response.headers["location"] == "https://app.quantfortune.com/dashboard?armed=false"
def test_broker_callback_state_service_rejects_wrong_user_and_expired_state(monkeypatch): def test_broker_callback_state_service_rejects_wrong_user_and_expired_state(monkeypatch):
import app.services.broker_callback_state as callback_state import app.services.broker_callback_state as callback_state