"""Schwab OAuth callback. Schwab requires an HTTPS callback URL. The usual answer is ``https://127.0.0.1:8182`` with a self-signed certificate, which means clicking through a browser warning on every re-authentication — and the refresh token expires weekly. There are also reports of Schwab refusing to register apps whose callback is a loopback address. This app already terminates real HTTPS, so it can receive the redirect itself. Deliberately unauthenticated: Schwab redirects a browser here and cannot attach the chart token. Nothing is stored — the page only echoes the query string of the request that produced it, which the caller already has in their address bar. Storing the code would mean a later, unauthenticated visitor could read it. The path is deliberately unrevealing. That is not a security control — the endpoint's safety is that it is inert — it simply avoids advertising which brokerage this host talks to. Treat it as fixed: changing a registered callback URL means editing the Schwab app, which can send it back through approval. """ from fastapi import APIRouter, Request from fastapi.responses import HTMLResponse, RedirectResponse router = APIRouter(prefix="/api") def begin_login(settings) -> object: from schwab.auth import get_auth_context return get_auth_context(settings.schwab_api_key, settings.schwab_callback_url) def complete_login(settings, context, redirect_url: str) -> None: from schwab import auth from schwab.auth import client_from_received_url path = settings.schwab_token_path path.parent.mkdir(parents=True, exist_ok=True) write_token = getattr(auth, "__make_update_token_func")(str(path)) client_from_received_url( settings.schwab_api_key, settings.schwab_app_secret, context, redirect_url, write_token, ) PAGE = """
Paste this entire URL into the waiting login prompt:
Single use, and it expires within minutes. Do not share it.
Nothing was stored on the server. This page shows only the URL you just arrived with.
""" IDLE = """OAuth callback endpoint. Register this exact URL with the provider:
{url}
Arriving here directly is expected and harmless — the useful version of this page is the one you are redirected to.
""" FAILED = """The authorisation code could not be exchanged. Start again from the chart.
Nothing was stored.
""" @router.get("/qt") def callback(request: Request): runtime = getattr(request.app.state, "runtime", None) if runtime is not None and runtime.schwab_login is not None and request.query_params.get("code"): try: runtime.finish_schwab_login(str(request.url)) except Exception: page = PAGE.format(heading="Authorisation failed", body=FAILED) return HTMLResponse(page, headers={"Cache-Control": "no-store"}, status_code=400) return RedirectResponse("/", headers={"Cache-Control": "no-store"}) if request.query_params.get("code"): page = PAGE.format( heading="Authorisation received", body=RECEIVED.format(url=str(request.url)), ) else: page = PAGE.format( heading="Callback endpoint", body=IDLE.format(url=str(request.url).split("?")[0]), ) # Never cached: it carries a single-use authorisation code. return HTMLResponse(page, headers={"Cache-Control": "no-store"})