diff --git a/internal/router/router.go b/internal/router/router.go index e98a5b5..8bb5bd4 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -9,6 +9,7 @@ import ( "bookhoard/internal/sync" "log" "net/http" + "strings" "time" "github.com/go-playground/validator/v10" @@ -85,11 +86,44 @@ func createJWTMiddleware(cfg *Config) echo.MiddlewareFunc { }) }, ErrorHandler: func(c echo.Context, err error) error { - return c.JSON(http.StatusUnauthorized, map[string]string{"error": err.Error()}) + // Check if this is a page request (browser navigation) + if wantsHTML(c.Request().Header) { + // Page request → Redirect to login with message + loginURL := "/login?session=expired" + return c.Redirect(http.StatusFound, loginURL) + } + + // API request → Return JSON error + return c.JSON(http.StatusUnauthorized, map[string]string{ + "error": "session_expired", + "message": "Your session has expired. Please log in again.", + }) }, }) } +// wantsHTML determines if the request expects HTML response +func wantsHTML(header http.Header) bool { + // Check Accept header + accept := header.Get("Accept") + if accept != "" && (accept == "text/html" || strings.Contains(accept, "text/html")) { + return true + } + + // Check HTMX request + if header.Get("HX-Request") == "true" { + return true + } + + // Check for AJAX requests (should get JSON) + if header.Get("X-Requested-With") == "XMLHttpRequest" { + return false + } + + // Default to JSON for API routes + return false +} + // RegisterRoutes registers all application routes func RegisterRoutes(cfg *Config) *handlers.Handler { e := cfg.Echo