summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/dashboard/main.go2
-rw-r--r--internal/handlers/handlers.go4
-rw-r--r--internal/handlers/handlers_test.go35
-rw-r--r--internal/handlers/settings.go18
-rw-r--r--test/acceptance_test.go2
-rw-r--r--web/templates/settings.html7
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;
}