Commit graph

3 commits

Author SHA1 Message Date
149f2dac2e fix(sync): auto-trigger auth flow on unauthenticated sync actions (#120)
Problem: running a sync action (e.g. `:Pending gtasks push`) without
being authenticated would silently abort with a warning, requiring
the user to manually run `:Pending auth` first.

Solution: `oauth.with_token()` now auto-triggers the browser auth flow
when no token exists (for non-bundled credentials) and resumes the
original action on success. `auth()` and `_exchange_code()` now call
`on_complete(ok)` on all exit paths. S3 backends run
`aws sts get-caller-identity` before every sync action, auto-triggering
SSO login on expired sessions.
2026-03-10 11:36:31 -04:00
Barrett Ruth
b641c93a0a
fix(oauth): resolve re-auth deadlock and improve flow robustness (#87)
* fix(oauth): resolve re-auth deadlock and improve flow robustness

Problem: in-flight TCP server held port 18392 for up to 120 seconds.
Calling `auth()` again caused `bind()` to fail silently — the browser
opened but no listener could receive the OAuth callback. `_wipe()` on
exchange failure also destroyed credentials, forcing full re-setup.

Solution: `_active_close` at module scope cancels any in-flight server
when `auth()` or `clear_tokens()` is called. Binding is guarded with
`pcall`; the browser only opens after the server is listening. Swapped
`_wipe()` for `clear_tokens()` in `_exchange_code` to preserve
credentials on failure. Added `select_account` to `prompt` so Google
always shows the account picker on re-auth.

* test(oauth): isolate bundled-credentials fallback from real filesystem

Problem: `resolve_credentials` reads from `vim.fn.stdpath('data')`,
the real Neovim data dir. The test passed only because `_wipe()` was
incidentally deleting the user's credential file mid-run.

Solution: stub `oauth.load_json_file` for the duration of the test so
real credential files cannot interfere with the fallback assertion.

* ci: format
2026-03-06 15:47:42 -05:00
Barrett Ruth
e0e3af6787
Google Tasks sync + shared OAuth module (#60)
* feat(gtasks): add Google Tasks bidirectional sync

Problem: pending.nvim only supported one-way push to Google Calendar.
Users who use Google Tasks had no way to sync tasks bidirectionally.

Solution: add `lua/pending/sync/gtasks.lua` backend with OAuth PKCE
auth, push/pull/sync actions, and field mapping between pending tasks
and Google Tasks (category↔tasklist, `priority`/`recur` via notes).

* refactor(cli): promote sync backends to top-level subcommands

Problem: `:Pending sync gtasks auth` required an extra `sync` keyword
that added no value and made the command unnecessarily verbose.

Solution: route `gtasks` and `gcal` as top-level `:Pending` subcommands
via `SYNC_BACKEND_SET` lookup. Tab completion introspects backend
modules for available actions instead of hardcoding `{ 'auth', 'sync' }`.

* docs(gtasks): document Google Tasks backend and CLI changes

Problem: vimdoc had no coverage for the gtasks backend and still
referenced the old `:Pending sync <backend>` command form.

Solution: add `:Pending-gtasks` and `:Pending-gcal` command sections
with per-action docs, update sync backend interface, and add gtasks
config example.

* ci: format

* refactor(sync): extract shared OAuth into `oauth.lua`

Problem: `gcal.lua` and `gtasks.lua` duplicated ~250 lines of identical
OAuth code (token management, PKCE flow, credential loading, curl
helpers, url encoding).

Solution: Extract a shared `OAuthClient` metatable in `oauth.lua` with
module-level utilities and instance methods. Both backends now delegate
all OAuth to `oauth.new()`. Skip `oauth` in `health.lua` backend
discovery by checking for a `name` field.

* feat(sync): ship bundled OAuth credentials

Problem: Users must manually create a Google Cloud project and place a
credentials JSON file before sync works — terrible onboarding.

Solution: Add `client_id`/`client_secret` fields to `GcalConfig` and
`GtasksConfig`. `oauth.lua` resolves credentials in three tiers: config
fields, credentials file, then bundled defaults (placeholders for now).

* docs(sync): document bundled credentials and config fields

* ci: format
2026-03-05 01:21:18 -05:00