Desktop OIDC: full page nav to localhost, meta redirect back
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled

SPA navigates to 127.0.0.1:18901/oidc/start, passing ret URL.
Go opens browser, waits for callback, saves token, returns HTML with
<meta refresh> back to Wails app with ?desktop=1&token=TOKEN.
main.ts extracts token from URL on reload.
This commit is contained in:
2026-07-14 00:13:01 +02:00
parent 8b3fe02a10
commit 2bd7de355b
5 changed files with 72 additions and 101 deletions

View File

@@ -142,80 +142,65 @@ func startOIDCServer() *http.Server {
})
}
h("/oidc/open", func(w http.ResponseWriter, r *http.Request) {
h("/oidc/start", func(w http.ResponseWriter, r *http.Request) {
apiUrl := strings.TrimRight(r.URL.Query().Get("apiUrl"), "/")
returnURL := r.URL.Query().Get("ret")
if apiUrl == "" {
http.Error(w, "apiUrl required", http.StatusBadRequest)
return
}
sessionID := randomString(16)
go func() {
oidcCfg, err := fetchOIDCConfig(apiUrl)
if err != nil {
return
}
verifier, challenge, _ := pkceParams()
state := randomString(32)
redirectURI := fmt.Sprintf("http://127.0.0.1:%d/oidc/callback", oidcCallbackPort)
ch := make(chan string, 1)
oidcSessionsMu.Lock()
oidcSessions[sessionID] = &oidcSession{apiUrl: apiUrl, verifier: verifier, state: state, ch: ch}
oidcSessionsMu.Unlock()
authURL := fmt.Sprintf("%s?%s",
oidcCfg.AuthorizationEndpoint,
url.Values{
"response_type": {"code"},
"client_id": {oidcCfg.ClientID},
"redirect_uri": {redirectURI},
"code_challenge": {challenge},
"code_challenge_method": {"S256"},
"state": {state},
"scope": {"openid profile email"},
}.Encode(),
)
exec.Command("open", authURL).Start()
select {
case token := <-ch:
if token != "" {
c := &ConfigService{}
c.SaveConfig(apiUrl, token)
}
case <-time.After(5 * time.Minute):
}
}()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"id": sessionID})
})
h("/oidc/result", func(w http.ResponseWriter, r *http.Request) {
sessionID := r.URL.Query().Get("id")
var token string
oidcSessionsMu.Lock()
session, ok := oidcSessions[sessionID]
if ok {
select {
case t := <-session.ch:
token = t
session.ch <- t // put it back for other pollers
default:
}
if returnURL == "" {
returnURL = "/?desktop=1"
}
oidcCfg, err := fetchOIDCConfig(apiUrl)
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
verifier, challenge, _ := pkceParams()
state := randomString(32)
redirectURI := fmt.Sprintf("http://127.0.0.1:%d/oidc/callback", oidcCallbackPort)
ch := make(chan string, 1)
oidcSessionsMu.Lock()
sessionID := randomString(16)
oidcSessions[sessionID] = &oidcSession{apiUrl: apiUrl, verifier: verifier, state: state, ch: ch}
oidcSessionsMu.Unlock()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"token": token,
"pending": fmt.Sprintf("%t", !ok || (ok && token == "")),
})
authURL := fmt.Sprintf("%s?%s",
oidcCfg.AuthorizationEndpoint,
url.Values{
"response_type": {"code"},
"client_id": {oidcCfg.ClientID},
"redirect_uri": {redirectURI},
"code_challenge": {challenge},
"code_challenge_method": {"S256"},
"state": {state},
"scope": {"openid profile email"},
}.Encode(),
)
exec.Command("open", authURL).Start()
select {
case token := <-ch:
if token != "" {
c := &ConfigService{}
c.SaveConfig(apiUrl, token)
returnURL += "&token=" + url.QueryEscape(token)
}
case <-time.After(5 * time.Minute):
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Oikos</title>
<meta http-equiv="refresh" content="0;url=%s">
<style>body{font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#0a0a0a;color:#e0e0e0;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0}
.card{background:#1a1a1a;border:1px solid #2a2a2a;border-radius:12px;padding:32px;max-width:400px;text-align:center}
h1{font-size:18px;margin-bottom:8px}.ok{color:#22c55e;font-size:14px}</style>
</head><body><div class="card"><h1>Connected</h1><p class="ok">Redirecting back to Oikos…</p></div></body></html>`, returnURL)
})
h("/oidc/callback", func(w http.ResponseWriter, r *http.Request) {