From d7197c195277a2b914aae8867039d369d468b321 Mon Sep 17 00:00:00 2001 From: dtoro Date: Tue, 14 Jul 2026 00:04:49 +0200 Subject: [PATCH] Desktop OIDC: redirect webview to local server, Go opens browser The webview navigates to http://127.0.0.1:18901/oidc/open?apiUrl=... The Go server opens the system browser to Authentik, waits for callback, exchanges code for token, saves to keychain, then redirects the webview back with ?desktop=1&token=TOKEN. main.ts extracts the token from URL. --- cmd/desktop/main.go | 13 ++++++------- web/src/lib/oidc.ts | 9 ++++----- web/src/main.ts | 31 +++++++++++++++++-------------- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/cmd/desktop/main.go b/cmd/desktop/main.go index fb87686..beebbaa 100644 --- a/cmd/desktop/main.go +++ b/cmd/desktop/main.go @@ -142,7 +142,7 @@ func startOIDCServer() *http.Server { }) } - h("/oidc/login", func(w http.ResponseWriter, r *http.Request) { + h("/oidc/open", func(w http.ResponseWriter, r *http.Request) { apiUrl := strings.TrimRight(r.URL.Query().Get("apiUrl"), "/") if apiUrl == "" { http.Error(w, "apiUrl required", http.StatusBadRequest) @@ -151,7 +151,7 @@ func startOIDCServer() *http.Server { oidcCfg, err := fetchOIDCConfig(apiUrl) if err != nil { - http.Error(w, fmt.Sprintf("OIDC config: %v", err), http.StatusServiceUnavailable) + http.Error(w, err.Error(), http.StatusServiceUnavailable) return } @@ -185,13 +185,12 @@ func startOIDCServer() *http.Server { if token != "" { c := &ConfigService{} c.SaveConfig(apiUrl, token) + http.Redirect(w, r, "/?desktop=1&token="+url.QueryEscape(token), http.StatusFound) + } else { + http.Redirect(w, r, "/?desktop=1&error=login_failed", http.StatusFound) } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]string{"token": token}) case <-time.After(5 * time.Minute): - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusRequestTimeout) - json.NewEncoder(w).Encode(map[string]string{"error": "login timed out"}) + http.Redirect(w, r, "/?desktop=1&error=timeout", http.StatusFound) } }) diff --git a/web/src/lib/oidc.ts b/web/src/lib/oidc.ts index 86b4fa9..941e196 100644 --- a/web/src/lib/oidc.ts +++ b/web/src/lib/oidc.ts @@ -101,14 +101,13 @@ export async function startLogin(): Promise { scope: 'openid profile email' }) - const authURL = `${cfg.authorization_endpoint.replace(/\/$/, '')}/?${params}` - if (isDesktop) { - window.open(authURL, '_blank', 'width=800,height=700') - throw new Error('Login opened in your browser. After authenticating, copy the token and paste it into the Token tab.') + const apiUrl = getConfig().apiUrl || location.protocol + '//' + location.host + location.href = `http://127.0.0.1:18901/oidc/open?apiUrl=${encodeURIComponent(apiUrl)}` + throw new Error('Redirecting to login...') } - location.href = authURL + location.href = `${cfg.authorization_endpoint.replace(/\/$/, '')}/?${params}` } export async function handleCallback(code: string, returnedState: string): Promise { diff --git a/web/src/main.ts b/web/src/main.ts index a0d6da3..2aefcdd 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -1,25 +1,28 @@ import { mount } from 'svelte' import App from './App.svelte' import './app.css' -import { initConfig, setConfig } from '$lib/config' +import { initConfig, setConfig, getConfig } from '$lib/config' -async function loadDesktopConfig() { - const wails = (window as any).wails - if (!wails?.Call?.ByName) return - - try { - const cfg = await wails.Call.ByName('GetStoredConfig') - if (cfg?.apiUrl && cfg?.token) { - setConfig({ apiUrl: cfg.apiUrl, token: cfg.token, isDesktop: true }) - } - } catch { - // no stored config — Config page will handle it +function handleDesktopToken() { + const params = new URLSearchParams(location.search) + const token = params.get('token') + if (token) { + const apiUrl = getConfig().apiUrl || params.get('apiUrl') || '' + setConfig({ apiUrl, token, isDesktop: true }) + initConfig({ apiUrl, token, isDesktop: true }) + // clean the URL + params.delete('token') + params.delete('apiUrl') + let q = params.toString() + history.replaceState(null, '', location.pathname + (q ? '?' + q : '')) + return true } + return false } -async function start() { +function start() { initConfig() - await loadDesktopConfig() + handleDesktopToken() requestAnimationFrame(() => import('./lib/renderers'))