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) }