diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-08-16 00:03:51 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-08-16 00:03:51 +0000 |
| commit | 3660486153a16760d2b980e546bbbd29408fb8d4 (patch) | |
| tree | e29199d88b7365eca0438e41ac23fa0bd533f7c2 /internal/handlers/settings.go | |
| parent | b55cfbbd433bed6035dfa228ee700e2cca060ca4 (diff) | |
Replace Google Tasks service-account auth with real OAuth
Service-account auth structurally cannot see a regular user's personal
task lists (no equivalent of Calendar's per-item sharing model) --
confirmed via GetTaskLists returning exactly the service account's own
empty "My Tasks" list, never the real user's three lists. Zero rows
were ever cached in production as a result.
Adds a standard 3-legged OAuth flow: /settings/google-tasks/connect
redirects to Google's consent screen (AccessTypeOffline+ApprovalForce
so a refresh_token is always issued), /callback exchanges the code and
persists the token (new oauth_tokens table), /disconnect clears it.
GoogleTasksClient now takes an option.ClientOption instead of a
credentials file path; NewGoogleTasksOAuthClient wraps it with a
dbTokenSource that reloads/refreshes from the DB on each access-token
expiry and re-persists -- carefully preserving the original
refresh_token when Google's refresh response omits one (it usually
does), which would otherwise silently and permanently break future
refreshes.
Settings page shows connection status and a Connect/Disconnect
button. Calendar keeps using service-account auth (that one actually
works). Requires a one-time manual step: create an OAuth 2.0 Client ID
in Google Cloud Console and set GOOGLE_OAUTH_CLIENT_ID/SECRET in .env
-- documented in .env.example.
Diffstat (limited to 'internal/handlers/settings.go')
| -rw-r--r-- | internal/handlers/settings.go | 34 |
1 files changed, 21 insertions, 13 deletions
diff --git a/internal/handlers/settings.go b/internal/handlers/settings.go index 9ab832c..d140aaf 100644 --- a/internal/handlers/settings.go +++ b/internal/handlers/settings.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net/http" + "task-dashboard/internal/api" "task-dashboard/internal/auth" "task-dashboard/internal/models" "task-dashboard/internal/store" @@ -21,20 +22,28 @@ func (h *Handler) HandleSettingsPage(w http.ResponseWriter, r *http.Request) { bySource[cfg.Source] = append(bySource[cfg.Source], cfg) } + googleTasksToken, _ := h.store.GetOAuthToken(api.GoogleTasksOAuthSource) + data := struct { - Configs map[string][]models.SourceConfig - Sources []string - SyncLog []store.SyncLogEntry - Agents []models.Agent - CSRFToken string - WebAuthnEnabled bool + Configs map[string][]models.SourceConfig + Sources []string + SyncLog []store.SyncLogEntry + Agents []models.Agent + CSRFToken string + WebAuthnEnabled bool + GoogleTasksOAuthReady bool + GoogleTasksConnected bool + GoogleTasksError string }{ - Configs: bySource, - Sources: []string{"trello", "gcal", "gtasks"}, - SyncLog: syncLog, - Agents: agents, - CSRFToken: auth.GetCSRFTokenFromContext(r.Context()), - WebAuthnEnabled: h.WebAuthnEnabled, + Configs: bySource, + Sources: []string{"trello", "gcal", "gtasks"}, + SyncLog: syncLog, + Agents: agents, + CSRFToken: auth.GetCSRFTokenFromContext(r.Context()), + WebAuthnEnabled: h.WebAuthnEnabled, + GoogleTasksOAuthReady: h.googleTasksOAuthConfig != nil, + GoogleTasksConnected: googleTasksToken != nil, + GoogleTasksError: r.URL.Query().Get("google_tasks_error"), } if err := h.renderer.Render(w, "settings.html", data); err != nil { @@ -148,4 +157,3 @@ func (h *Handler) HandleToggleSourceConfig(w http.ResponseWriter, r *http.Reques w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]bool{"enabled": enabled}) } - |
