Altinity / Altinity/altinity-sql-browser
Local runner: desktop/native OAuth via loopback redirect (RFC 8252) instead of a registered web redirect URI
Nadie ha tomado este issue todavía.
- Lenguaje dominante
- TypeScript
- Estrellas
- 8
- Forks
- 2
- Merge medio
- 1 h 34 min
- PR fusionados (30 d)
- 6
Descripción
Today
npm run local (build/local.py) serves the SPA + config.json on 127.0.0.1:8900 and does no auth — the browser runs PKCE with redirect_uri = http://localhost:8900/sql (src/ui/app.js, src/main.js). So a local user must:
- register the exact
http://localhost:8900/sqlas a redirect URI on a Web-type IdP client (and re-register wheneverPORTchanges), and - for some IdPs (e.g. a Google "Web application" client) ship a
client_secretin the browser-servedconfig.json.
Proposal
Give the local runner a desktop/native OAuth flow (RFC 8252 §7.3, loopback interface redirection). The user registers a Desktop/Native IdP client; build/local.py then:
- generates a PKCE
verifier/challenge+state(stdlib:secrets,hashlib,base64), - binds a loopback HTTP server on
http://127.0.0.1:<port>and opens the system browser (webbrowser) to the authorize URL with that loopbackredirect_uri, - catches the redirect
GET /callback?code&state, validatesstate, then POSTs the code + PKCEverifierto the IdP token endpoint (urllib, noclient_secret), - hands the resulting bearer to the SPA (e.g. a localhost-only endpoint the page reads, or injected into the served page), after which the SPA talks to ClickHouse exactly as today.
Stdlib-only (keeps local.py's zero-dependency property); the .well-known discovery, PKCE, and token-exchange shapes mirror the existing JS (src/core/pkce.js, src/net/oauth.js, src/net/oauth-config.js). Scope: authenticate the local app only — queries still run from the browser SPA.
Why this matters vs the current web client
- Loopback redirects need no per-URL registration. A Web client must register the exact
http://localhost:8900/sql; change the port → re-register; every developer repeats the setup. IdPs grant Desktop/Native clients loopback redirects on anyhttp://127.0.0.1:<port>(RFC 8252), so the runner can use an OS-assigned ephemeral port with zero IdP changes. This is the biggest friction removed. - Plain-HTTP localhost is first-class only for native clients. "Web application" client types often reject
http://redirects or special-case localhost; the Desktop/Native type is the IdP-sanctioned path for an app catching a loopback redirect — using the right client type stops fighting the provider. - Secret-free, and correctly so. A Google "Web application" client can require a
client_secreteven with PKCE (this repo documents that, and the antalya deploy ships the Google secret inline as "public-by-design"). A Desktop/Native client is a true public client — PKCE only, no secret — which fits the repo's "no secrets inconfig.json" rule and removes the awkward shipped-secret for local use. - Refresh tokens / log-in-once. Native clients are designed to receive refresh tokens (offline access) for installed apps, so the local runner can persist a session across restarts; the pure-browser SPA flow (esp. Google) re-prompts each session and can't safely hold a refresh token. (Follow-on benefit once token storage is added.)
- The runner owns the callback. Catching the code on a dedicated loopback endpoint (instead of depending on the redirect landing on the exact served SPA URL) is more robust, allows an auto-closing "you can return to the app" tab, and lays the groundwork for a headless Python query runner later.
Implementation notes
build/local.py: add the loopback OAuth broker — PKCE generation, a/callbackhandler on the loopback server,urllib-based token POST (no secret), and a path to surface the bearer to the SPA. Keep it stdlib-only. Prefer an ephemeral port (RFC-recommended) with a fixed-port fallback.- Config:
local.pycan default to loopback for OAuth IdPs; optionally add a smallconfig.jsonopt-in (e.g. a redirect/modehint) so the SPA knows the runner brokered the token. Noclient_secretrequired →config.jsonstays secret-free. - Reuse vs reimplement: the JS PKCE/discovery/exchange in
src/core/pkce.js+src/net/oauth*.jsis the reference; Python re-implements the same ~30 lines in stdlib (can't import the JS). Keep the JS browser flow unchanged for deployed clusters. - Docs: update README "run it locally" + "Configuring OAuth" and
docs/local-app.html— register a Desktop client, no fixed redirect URI, no secret. - Tests:
build/local.pyisn't under the JS coverage gate, but factor the PKCE/exchange/url-building into testable pure Python functions with unit tests; anysrc/change keeps the per-file 100% gate.
Acceptance criteria
- With a Desktop IdP client configured,
npm run localopens the browser, the user signs in, and the redirect is caught on a127.0.0.1loopback server — no fixed web redirect URI and noclient_secretregistered. - Works on a non-default
PORTwith no IdP re-registration. - The SPA receives the bearer and queries ClickHouse as today (bearer / basic modes unchanged).
-
build/local.pystays stdlib-only (no new Python deps);npm testgreen at the per-file gate. - README +
docs/local-app.htmlupdated for the Desktop-client setup.
Out of scope / follow-on
- Headless Python query runner (using the token to run SQL from Python) — natural next step, not here.
- Deployed-cluster OAuth and the production SPA flow — unchanged.
Open questions
- Ephemeral loopback port vs a fixed default (ephemeral = RFC-recommended; fixed = simpler docs).
- How the bearer reaches the SPA (a localhost-only endpoint the page fetches vs injection) and where it's stored (in-memory vs OS keyring for "log in once").
- Add a
config.jsonfield to opt an IdP into desktop/loopback mode, or infer it inlocal.py?
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Línea de trabajo
Empieza por build/local.py y compara las estructuras de PKCE, discovery e intercambio de tokens en src/core/pkce.js, src/net/oauth.js y src/net/oauth-config.js. Extrae helpers de Python comprobables y añade pruebas unitarias; después actualiza README y docs/local-app.html para la configuración del Desktop-client. Se considera terminado cuando el runner local detecta un callback de 127.0.0.1 sin un secret, proporciona el bearer a la SPA, admite un puerto cambiado y npm test sigue pasando.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- clickhouse, python, typescript
- Área
- authentication, backend, documentation, frontend, testing
- Tipo de issue
- Nueva funcionalidad
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Estado de actividad
- Tranquilo
- Claridad
- Bastante claro
- Aptitud para principiantes
- 35/100