From f67e8f901af8916436fc18cd38500696a94a7170 Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Thu, 9 Oct 2025 09:39:08 +0200 Subject: [PATCH] Fix provider authorization ui error handling (#24250) --- assets/js/components/Top/Navigation.vue | 23 ++++++++++--- i18n/de.json | 1 + i18n/en.json | 1 + server/providerauth/handler.go | 46 +++++++++++++++---------- vite.config.ts | 1 + 5 files changed, 48 insertions(+), 24 deletions(-) diff --git a/assets/js/components/Top/Navigation.vue b/assets/js/components/Top/Navigation.vue index c528834ac..022989ff7 100644 --- a/assets/js/components/Top/Navigation.vue +++ b/assets/js/components/Top/Navigation.vue @@ -219,19 +219,32 @@ export default defineComponent({ const { title, authenticated, loginPath, logoutPath } = provider; if (!authenticated) { try { - const response = await baseAPI.get(loginPath); - window.location.href = response.data.loginUri; + const response = await baseAPI.get(loginPath, { + validateStatus: (code) => [200, 400].includes(code), + }); + if (response.status === 200) { + window.open(response.data?.loginUri, "_blank"); + } else { + alert(`Failed to login: ${response.data?.error}`); + } } catch (error: any) { console.error(error); - alert(`Failed to login: ${error.response?.data}`); + alert("Unexpected login error: " + error.message); } } else { if (window.confirm(this.$t("header.authProviders.confirmLogout", { title }))) { try { - await baseAPI.get(logoutPath); + const response = await baseAPI.get(logoutPath, { + validateStatus: (code) => [200, 400, 500].includes(code), + }); + if (response.status === 200) { + alert(this.$t("header.authProviders.loggedOut")); + } else { + alert(`Failed to logout: ${response.data?.error}`); + } } catch (error: any) { console.error(error); - alert(`Failed to logout: ${error.response?.data}`); + alert(`Unexpected logout error: ${error.response?.data}`); } } } diff --git a/i18n/de.json b/i18n/de.json index fa2e26ca1..54f1566d9 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -658,6 +658,7 @@ "about": "Über", "authProviders": { "confirmLogout": "Sicher, dass du {title} trennen möchtest?", + "loggedOut": "Erfolgreich abgemeldet", "title": "Autorisierungsstatus" }, "blog": "Blog", diff --git a/i18n/en.json b/i18n/en.json index ef599199b..ce804ff0f 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -658,6 +658,7 @@ "about": "About", "authProviders": { "confirmLogout": "Are you sure you want to disconnect {title}?", + "loggedOut": "Successfully logged out", "title": "Authorization Status" }, "blog": "Blog", diff --git a/server/providerauth/handler.go b/server/providerauth/handler.go index 01adcadff..539757efb 100644 --- a/server/providerauth/handler.go +++ b/server/providerauth/handler.go @@ -13,6 +13,26 @@ import ( "github.com/evcc-io/evcc/util" ) +type errorResponse struct { + Error string `json:"error"` +} + +type loginResponse struct { + LoginUri string `json:"loginUri"` +} + +// jsonWrite writes a JSON response +func jsonWrite(w http.ResponseWriter, data interface{}) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(data) +} + +// jsonError writes an error response +func jsonError(w http.ResponseWriter, status int, message string) { + w.WriteHeader(status) + jsonWrite(w, errorResponse{Error: message}) +} + // Handler manages a dynamic map of routes for handling the redirect during // OAuth authentication. When a route is registered a token OAuth state is returned. // On GET request the generic handler identifies route and target handler @@ -70,8 +90,7 @@ func (a *Handler) handleLogin(w http.ResponseWriter, r *http.Request) { provider, ok := a.providers[id] if !ok { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintln(w, "invalid id") + jsonError(w, http.StatusBadRequest, "invalid id") return } @@ -89,23 +108,11 @@ func (a *Handler) handleLogin(w http.ResponseWriter, r *http.Request) { uri, err := provider.Login(encryptedState) if err != nil { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, "error: %v", err) + jsonError(w, http.StatusBadRequest, err.Error()) return } - // return authorization URL - res := struct { - LoginUri string `json:"loginUri"` - }{ - LoginUri: uri, - } - - if err := json.NewEncoder(w).Encode(res); err != nil { - a.log.ERROR.Printf("failed to encode login URI response: %v", err) - } - - w.WriteHeader(http.StatusFound) + jsonWrite(w, loginResponse{LoginUri: uri}) } func (a *Handler) handleLogout(w http.ResponseWriter, r *http.Request) { @@ -117,17 +124,18 @@ func (a *Handler) handleLogout(w http.ResponseWriter, r *http.Request) { provider, ok := a.providers[id] if !ok { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintln(w, "invalid id") + jsonError(w, http.StatusBadRequest, "invalid id") return } // Handle logout if err := provider.Logout(); err != nil { a.log.ERROR.Printf("logout for provider %s failed: %v", id, err) + jsonError(w, http.StatusInternalServerError, "logout failed") + return } - http.Redirect(w, r, "/", http.StatusFound) + jsonWrite(w, "OK") } func (a *Handler) handleCallback(w http.ResponseWriter, r *http.Request) { diff --git a/vite.config.ts b/vite.config.ts index f384e9427..2890c2b97 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -24,6 +24,7 @@ export default defineConfig({ proxy: { "/api": "http://localhost:7070", "/i18n": "http://localhost:7070", + "/providerauth": "http://localhost:7070", "/ws": { target: "ws://localhost:7070", ws: true }, }, },