d8a590eb79
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) <noreply@anthropic.com>
115 lines
3.0 KiB
Go
115 lines
3.0 KiB
Go
package handlers
|
|
|
|
import (
|
|
"html/template"
|
|
"log/slog"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"gitea.leeworks.dev/0xwheatyz/gitea-mobile/internal/auth"
|
|
"gitea.leeworks.dev/0xwheatyz/gitea-mobile/internal/middleware"
|
|
)
|
|
|
|
const settingsTemplatePath = "internal/templates/settings.html"
|
|
|
|
// SettingsHandler handles GET and POST requests for the settings page.
|
|
type SettingsHandler struct {
|
|
SessionSecret string
|
|
SecureCookies bool
|
|
}
|
|
|
|
type settingsData struct {
|
|
HasToken bool
|
|
Message string
|
|
MessageType string // "success", "error", "info"
|
|
}
|
|
|
|
// ServeHTTP handles the settings page.
|
|
func (h *SettingsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
h.handleGet(w, r)
|
|
case http.MethodPost:
|
|
h.handlePost(w, r)
|
|
default:
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func (h *SettingsHandler) handleGet(w http.ResponseWriter, r *http.Request) {
|
|
hasToken := false
|
|
if token := middleware.TokenFromContext(r.Context()); token != "" {
|
|
hasToken = true
|
|
} else if _, err := auth.GetToken(r, h.SessionSecret); err == nil {
|
|
hasToken = true
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func (h *SettingsHandler) handlePost(w http.ResponseWriter, r *http.Request) {
|
|
if err := r.ParseForm(); err != nil {
|
|
h.renderWithMessage(w, r, "Failed to parse form.", "error")
|
|
return
|
|
}
|
|
|
|
action := r.FormValue("action")
|
|
|
|
switch action {
|
|
case "logout":
|
|
auth.ClearTokenCookie(w, h.SecureCookies)
|
|
h.renderWithMessage(w, r, "Token removed successfully.", "success")
|
|
return
|
|
|
|
case "save":
|
|
token := strings.TrimSpace(r.FormValue("token"))
|
|
if token == "" {
|
|
h.renderWithMessage(w, r, "Token cannot be empty.", "error")
|
|
return
|
|
}
|
|
|
|
auth.SetTokenCookie(w, token, h.SessionSecret, h.SecureCookies)
|
|
// After saving, redirect to dashboard.
|
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
|
return
|
|
|
|
default:
|
|
h.renderWithMessage(w, r, "Unknown action.", "error")
|
|
}
|
|
}
|
|
|
|
func (h *SettingsHandler) renderWithMessage(w http.ResponseWriter, r *http.Request, msg, msgType string) {
|
|
hasToken := false
|
|
if _, err := auth.GetToken(r, h.SessionSecret); err == nil {
|
|
hasToken = true
|
|
}
|
|
|
|
data := settingsData{
|
|
HasToken: hasToken,
|
|
Message: msg,
|
|
MessageType: msgType,
|
|
}
|
|
h.renderSettings(w, data)
|
|
}
|
|
|
|
func (h *SettingsHandler) renderSettings(w http.ResponseWriter, data settingsData) {
|
|
tmpl, err := template.ParseFiles(settingsTemplatePath)
|
|
if err != nil {
|
|
slog.Error("failed to parse settings template", "error", err)
|
|
http.Error(w, "template error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
if err := tmpl.Execute(w, data); err != nil {
|
|
slog.Error("failed to execute settings template", "error", err)
|
|
}
|
|
}
|