From bdb526f8b5dd798f004645a90de87d4bf528ebcb Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Mon, 29 Jun 2026 00:45:06 +0000 Subject: fix: widget handler timezone, auth guard, and type switch clarity Co-Authored-By: Claude Sonnet 4.6 --- internal/handlers/widget.go | 13 +++++++++++-- internal/handlers/widget_test.go | 16 +++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/internal/handlers/widget.go b/internal/handlers/widget.go index cee2ec8..a56a017 100644 --- a/internal/handlers/widget.go +++ b/internal/handlers/widget.go @@ -7,12 +7,17 @@ import ( "time" "task-dashboard/internal/api" + "task-dashboard/internal/config" "task-dashboard/internal/models" ) // WidgetAuthMiddleware validates the static bearer token for widget API endpoints. func WidgetAuthMiddleware(token string, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if token == "" { + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } auth := r.Header.Get("Authorization") if !strings.HasPrefix(auth, "Bearer ") || strings.TrimPrefix(auth, "Bearer ") != token { http.Error(w, "unauthorized", http.StatusUnauthorized) @@ -38,6 +43,9 @@ func TimelineItemToWidgetItem(item models.TimelineItem) models.WidgetItem { wi.Type = "event" case models.TimelineItemTypeMeal: wi.Type = "event" + case models.TimelineItemTypeCard, models.TimelineItemTypeGTask: + wi.Type = "task" + // not completable via widget API default: wi.Type = "task" wi.Completable = item.Source == "todoist" @@ -60,8 +68,9 @@ func TimelineItemToWidgetItem(item models.TimelineItem) models.WidgetItem { // HandleWidgetGet returns today's timeline items as JSON for the Android widget. func (h *Handler) HandleWidgetGet(w http.ResponseWriter, r *http.Request) { - now := time.Now() - start := now.Truncate(24 * time.Hour) + now := config.Now() + tz := config.GetDisplayTimezone() + start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, tz) end := start.Add(24 * time.Hour) items, err := BuildTimeline(r.Context(), h.store, start, end) diff --git a/internal/handlers/widget_test.go b/internal/handlers/widget_test.go index a82a070..ed9d941 100644 --- a/internal/handlers/widget_test.go +++ b/internal/handlers/widget_test.go @@ -1,7 +1,6 @@ package handlers_test import ( - "encoding/json" "net/http" "net/http/httptest" "strings" @@ -123,5 +122,16 @@ func TestHandleWidgetComplete_NonTodoist(t *testing.T) { } } -// Ensure json import is used (WidgetResponse roundtrip sanity check). -var _ = json.Marshal +func TestWidgetAuthMiddleware_EmptyToken(t *testing.T) { + inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + h := handlers.WidgetAuthMiddleware("", inner) + + req := httptest.NewRequest("GET", "/api/widget", nil) + req.Header.Set("Authorization", "Bearer ") + w := httptest.NewRecorder() + h.ServeHTTP(w, req) + + if w.Code != http.StatusUnauthorized { + t.Fatalf("expected 401 with empty token, got %d", w.Code) + } +} -- cgit v1.2.3