diff options
| -rw-r--r-- | cmd/dashboard/main.go | 2 | ||||
| -rw-r--r-- | internal/handlers/handlers.go | 4 | ||||
| -rw-r--r-- | internal/handlers/handlers_test.go | 35 | ||||
| -rw-r--r-- | internal/handlers/settings.go | 18 | ||||
| -rw-r--r-- | test/acceptance_test.go | 2 | ||||
| -rw-r--r-- | web/templates/settings.html | 7 |
6 files changed, 55 insertions, 13 deletions
diff --git a/cmd/dashboard/main.go b/cmd/dashboard/main.go index 073364f..e44d533 100644 --- a/cmd/dashboard/main.go +++ b/cmd/dashboard/main.go @@ -146,7 +146,7 @@ func main() { } // Initialize handlers - h := handlers.New(db, todoistClient, trelloClient, planToEatClient, googleCalendarClient, googleTasksClient, cfg, buildCommit) + h := handlers.New(db, todoistClient, trelloClient, planToEatClient, googleCalendarClient, googleTasksClient, cfg, buildCommit, wa != nil) // Set up router r := chi.NewRouter() diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 226f117..876326e 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -30,10 +30,11 @@ type Handler struct { config *config.Config renderer Renderer BuildVersion string + WebAuthnEnabled bool } // New creates a new Handler instance -func New(s *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, planToEat api.PlanToEatAPI, googleCalendar api.GoogleCalendarAPI, googleTasks api.GoogleTasksAPI, cfg *config.Config, buildVersion string) *Handler { +func New(s *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, planToEat api.PlanToEatAPI, googleCalendar api.GoogleCalendarAPI, googleTasks api.GoogleTasksAPI, cfg *config.Config, buildVersion string, webAuthnEnabled bool) *Handler { // Template functions funcMap := template.FuncMap{ "subtract": func(a, b int) int { return a - b }, @@ -61,6 +62,7 @@ func New(s *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, planToEat config: cfg, renderer: NewTemplateRenderer(tmpl), BuildVersion: buildVersion, + WebAuthnEnabled: webAuthnEnabled, } } diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go index 0ad36b2..b338aa2 100644 --- a/internal/handlers/handlers_test.go +++ b/internal/handlers/handlers_test.go @@ -1971,6 +1971,41 @@ func TestSettingsTemplate_HasCSRFToken(t *testing.T) { "settings.html must expose .CSRFToken (e.g. via meta tag) for JavaScript passkey registration") } +// TestSettingsTemplate_HidesPasskeysWhenDisabled verifies that the settings +// template conditionally shows the passkeys section based on WebAuthnEnabled. +func TestSettingsTemplate_HidesPasskeysWhenDisabled(t *testing.T) { + assertTemplateContains(t, "../../web/templates/settings.html", + ".WebAuthnEnabled", + "settings.html must check .WebAuthnEnabled to conditionally show passkeys section") +} + +// TestHandleSettingsPage_PassesWebAuthnEnabled verifies the settings handler +// includes WebAuthnEnabled in template data. +func TestHandleSettingsPage_PassesWebAuthnEnabled(t *testing.T) { + h, cleanup := setupTestHandler(t) + defer cleanup() + + req := httptest.NewRequest("GET", "/settings", nil) + w := httptest.NewRecorder() + + h.HandleSettingsPage(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("Expected status 200, got %d", w.Code) + } + + mock := h.renderer.(*MockRenderer) + if len(mock.Calls) == 0 { + t.Fatal("Expected renderer to be called") + } + + lastCall := mock.Calls[len(mock.Calls)-1] + dataStr := fmt.Sprintf("%+v", lastCall.Data) + if !strings.Contains(dataStr, "WebAuthnEnabled") { + t.Error("Settings page template data must include WebAuthnEnabled field") + } +} + // TestHandleSettingsPage_PassesCSRFToken verifies the settings handler includes // a CSRFToken in its template data so the passkey registration JS can use it. func TestHandleSettingsPage_PassesCSRFToken(t *testing.T) { diff --git a/internal/handlers/settings.go b/internal/handlers/settings.go index 60fc6be..0ad362b 100644 --- a/internal/handlers/settings.go +++ b/internal/handlers/settings.go @@ -22,15 +22,17 @@ func (h *Handler) HandleSettingsPage(w http.ResponseWriter, r *http.Request) { } data := struct { - Configs map[string][]models.SourceConfig - Sources []string - Toggles []models.FeatureToggle - CSRFToken string + Configs map[string][]models.SourceConfig + Sources []string + Toggles []models.FeatureToggle + CSRFToken string + WebAuthnEnabled bool }{ - Configs: bySource, - Sources: []string{"trello", "todoist", "gcal", "gtasks"}, - Toggles: toggles, - CSRFToken: auth.GetCSRFTokenFromContext(r.Context()), + Configs: bySource, + Sources: []string{"trello", "todoist", "gcal", "gtasks"}, + Toggles: toggles, + CSRFToken: auth.GetCSRFTokenFromContext(r.Context()), + WebAuthnEnabled: h.WebAuthnEnabled, } if err := h.renderer.Render(w, "settings.html", data); err != nil { diff --git a/test/acceptance_test.go b/test/acceptance_test.go index 0bcf974..e561e98 100644 --- a/test/acceptance_test.go +++ b/test/acceptance_test.go @@ -82,7 +82,7 @@ func setupTestServer(t *testing.T) (*httptest.Server, *store.Store, *http.Client } // Initialize handlers - h := handlers.New(db, todoistClient, trelloClient, nil, nil, nil, cfg, "test") + h := handlers.New(db, todoistClient, trelloClient, nil, nil, nil, cfg, "test", false) // Set up router (same as main.go) r := chi.NewRouter() diff --git a/web/templates/settings.html b/web/templates/settings.html index 0803ae3..964d319 100644 --- a/web/templates/settings.html +++ b/web/templates/settings.html @@ -206,6 +206,7 @@ </div> <!-- Passkeys Section --> + {{if .WebAuthnEnabled}} <div class="card" id="passkeys-card"> <div class="card-header"> <div class="card-title">Passkeys</div> @@ -219,10 +220,12 @@ </div> <p id="passkey-status" style="color: var(--text-secondary); font-size: 0.85em; margin-top: 8px; display: none;"></p> </div> + {{end}} <script> (function() { - if (!window.PublicKeyCredential) { - document.getElementById('passkeys-card').style.display = 'none'; + if (!document.getElementById('passkeys-card') || !window.PublicKeyCredential) { + var card = document.getElementById('passkeys-card'); + if (card) card.style.display = 'none'; return; } |
