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.
This commit is contained in:
@@ -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"), "/")
|
apiUrl := strings.TrimRight(r.URL.Query().Get("apiUrl"), "/")
|
||||||
if apiUrl == "" {
|
if apiUrl == "" {
|
||||||
http.Error(w, "apiUrl required", http.StatusBadRequest)
|
http.Error(w, "apiUrl required", http.StatusBadRequest)
|
||||||
@@ -151,7 +151,7 @@ func startOIDCServer() *http.Server {
|
|||||||
|
|
||||||
oidcCfg, err := fetchOIDCConfig(apiUrl)
|
oidcCfg, err := fetchOIDCConfig(apiUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("OIDC config: %v", err), http.StatusServiceUnavailable)
|
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,13 +185,12 @@ func startOIDCServer() *http.Server {
|
|||||||
if token != "" {
|
if token != "" {
|
||||||
c := &ConfigService{}
|
c := &ConfigService{}
|
||||||
c.SaveConfig(apiUrl, token)
|
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):
|
case <-time.After(5 * time.Minute):
|
||||||
w.Header().Set("Content-Type", "application/json")
|
http.Redirect(w, r, "/?desktop=1&error=timeout", http.StatusFound)
|
||||||
w.WriteHeader(http.StatusRequestTimeout)
|
|
||||||
json.NewEncoder(w).Encode(map[string]string{"error": "login timed out"})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -101,14 +101,13 @@ export async function startLogin(): Promise<void> {
|
|||||||
scope: 'openid profile email'
|
scope: 'openid profile email'
|
||||||
})
|
})
|
||||||
|
|
||||||
const authURL = `${cfg.authorization_endpoint.replace(/\/$/, '')}/?${params}`
|
|
||||||
|
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
window.open(authURL, '_blank', 'width=800,height=700')
|
const apiUrl = getConfig().apiUrl || location.protocol + '//' + location.host
|
||||||
throw new Error('Login opened in your browser. After authenticating, copy the token and paste it into the Token tab.')
|
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<boolean> {
|
export async function handleCallback(code: string, returnedState: string): Promise<boolean> {
|
||||||
|
|||||||
@@ -1,25 +1,28 @@
|
|||||||
import { mount } from 'svelte'
|
import { mount } from 'svelte'
|
||||||
import App from './App.svelte'
|
import App from './App.svelte'
|
||||||
import './app.css'
|
import './app.css'
|
||||||
import { initConfig, setConfig } from '$lib/config'
|
import { initConfig, setConfig, getConfig } from '$lib/config'
|
||||||
|
|
||||||
async function loadDesktopConfig() {
|
function handleDesktopToken() {
|
||||||
const wails = (window as any).wails
|
const params = new URLSearchParams(location.search)
|
||||||
if (!wails?.Call?.ByName) return
|
const token = params.get('token')
|
||||||
|
if (token) {
|
||||||
try {
|
const apiUrl = getConfig().apiUrl || params.get('apiUrl') || ''
|
||||||
const cfg = await wails.Call.ByName('GetStoredConfig')
|
setConfig({ apiUrl, token, isDesktop: true })
|
||||||
if (cfg?.apiUrl && cfg?.token) {
|
initConfig({ apiUrl, token, isDesktop: true })
|
||||||
setConfig({ apiUrl: cfg.apiUrl, token: cfg.token, isDesktop: true })
|
// clean the URL
|
||||||
}
|
params.delete('token')
|
||||||
} catch {
|
params.delete('apiUrl')
|
||||||
// no stored config — Config page will handle it
|
let q = params.toString()
|
||||||
|
history.replaceState(null, '', location.pathname + (q ? '?' + q : ''))
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
async function start() {
|
function start() {
|
||||||
initConfig()
|
initConfig()
|
||||||
await loadDesktopConfig()
|
handleDesktopToken()
|
||||||
|
|
||||||
requestAnimationFrame(() => import('./lib/renderers'))
|
requestAnimationFrame(() => import('./lib/renderers'))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user