From d8a590eb7914b271e61b4c9e19d377b9644b2375 Mon Sep 17 00:00:00 2001 From: agent-company Date: Mon, 20 Apr 2026 15:13:04 +0000 Subject: [PATCH] feat: redirect to /settings with error banner when Gitea API token is expired Add isTokenError() helper that detects HTTP 401/403 responses from the Gitea API, and redirectOnTokenError() that redirects to /settings with an error=token_expired query parameter. Update Dashboard, ListIssues, and ListPulls handlers to check for token errors. The settings page now displays an error banner explaining the token needs to be refreshed. Closes leeworks-agents/gitea-mobile#192 Co-Authored-By: Claude Opus 4.6 (1M context) --- internal/handlers/handlers.go | 34 ++++++++++++++++++++++++++++++++++ internal/handlers/settings.go | 7 +++++++ 2 files changed, 41 insertions(+) diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 6067a12..1134c72 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -78,6 +78,31 @@ func getToken(r *http.Request) string { return middleware.TokenFromContext(r.Context()) } +// isTokenError returns true if the error indicates an expired or revoked API token. +func isTokenError(err error) bool { + if err == nil { + return false + } + msg := err.Error() + return strings.Contains(msg, "API error 401") || strings.Contains(msg, "API error 403") +} + +// redirectOnTokenError checks if the error is a token auth error and redirects +// to /settings with an error banner. Returns true if a redirect was performed. +func redirectOnTokenError(w http.ResponseWriter, r *http.Request, err error) bool { + if !isTokenError(err) { + return false + } + slog.Warn("Gitea API token expired or revoked, redirecting to settings", "error", err) + if isHTMX(r) { + w.Header().Set("HX-Redirect", "/settings?error=token_expired") + w.WriteHeader(http.StatusOK) + } else { + http.Redirect(w, r, "/settings?error=token_expired", http.StatusSeeOther) + } + return true +} + // getUserOrgs returns the list of org names the user belongs to. func (h *Handler) getUserOrgs(r *http.Request) []string { token := getToken(r) @@ -263,6 +288,9 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) { queue, err := h.Client.GetTriageQueue(r.Context(), token, queryOrgs) if err != nil { + if redirectOnTokenError(w, r, err) { + return + } slog.Error("failed to get triage queue", "error", err) data.Error = "Error loading triage queue." } else { @@ -346,6 +374,9 @@ func (h *Handler) ListIssues(w http.ResponseWriter, r *http.Request) { result, err := h.Client.ListAllIssues(r.Context(), token, queryOrgs, selectedState, page, selectedLabel, selectedRepo) if err != nil { + if redirectOnTokenError(w, r, err) { + return + } slog.Error("failed to list issues", "error", err) data.Error = "Error loading issues." } else { @@ -451,6 +482,9 @@ func (h *Handler) ListPulls(w http.ResponseWriter, r *http.Request) { result, err := h.Client.ListAllPullRequests(r.Context(), token, queryOrgs, selectedState, page, selectedLabel, selectedRepo) if err != nil { + if redirectOnTokenError(w, r, err) { + return + } slog.Error("failed to list pull requests", "error", err) data.Error = "Error loading pull requests." } else { diff --git a/internal/handlers/settings.go b/internal/handlers/settings.go index 4e4be37..04b608d 100644 --- a/internal/handlers/settings.go +++ b/internal/handlers/settings.go @@ -45,6 +45,13 @@ func (h *SettingsHandler) handleGet(w http.ResponseWriter, r *http.Request) { } data := settingsData{HasToken: hasToken} + + // Show error banner when redirected due to expired/revoked token. + if r.URL.Query().Get("error") == "token_expired" { + data.Message = "Your Gitea API token is expired or has been revoked. Please enter a new token." + data.MessageType = "error" + } + h.renderSettings(w, data) }