feat(sync): unify Google auth under :Pending auth

Problem: users had to run `:Pending gtasks auth` and `:Pending gcal
auth` separately, producing two token files and two browser consents
for the same Google account.

Solution: introduce `oauth.google_client` with combined tasks +
calendar scopes and a single `google_tokens.json`. Remove per-backend
`auth`/`setup` from `gcal` and `gtasks`; add top-level `:Pending auth`
that prompts with `vim.ui.select` and delegates to the shared client's
`setup()` or `auth()` based on credential availability.
This commit is contained in:
Barrett Ruth 2026-03-05 20:35:14 -05:00
parent 87d8bf0896
commit 67aa8d71e6
6 changed files with 45 additions and 54 deletions

View file

@ -828,6 +828,24 @@ function M.edit(id_str, rest)
log.info('Task #' .. id .. ' updated: ' .. table.concat(feedback, ', '))
end
---@return nil
function M.auth()
local oauth = require('pending.sync.oauth')
vim.ui.select({ 'gtasks', 'gcal', 'both' }, {
prompt = 'Authenticate:',
}, function(choice)
if not choice then
return
end
local creds = oauth.google_client:resolve_credentials()
if creds.client_id == oauth.BUNDLED_CLIENT_ID then
oauth.google_client:setup()
else
oauth.google_client:auth()
end
end)
end
---@param args string
---@return nil
function M.command(args)
@ -841,6 +859,8 @@ function M.command(args)
elseif cmd == 'edit' then
local id_str, edit_rest = rest:match('^(%S+)%s*(.*)')
M.edit(id_str, edit_rest)
elseif cmd == 'auth' then
M.auth()
elseif SYNC_BACKEND_SET[cmd] then
local action = rest:match('^(%S+)')
run_sync(cmd, action)

View file

@ -7,14 +7,6 @@ local M = {}
M.name = 'gcal'
local BASE_URL = 'https://www.googleapis.com/calendar/v3'
local SCOPE = 'https://www.googleapis.com/auth/calendar'
local client = oauth.new({
name = 'gcal',
scope = SCOPE,
port = 18392,
config_key = 'gcal',
})
---@param access_token string
---@return table<string, string>? name_to_id
@ -139,15 +131,15 @@ end
---@param callback fun(access_token: string): nil
local function with_token(callback)
oauth.async(function()
local token = client:get_access_token()
local token = oauth.google_client:get_access_token()
if not token then
client:auth(function()
oauth.google_client:auth(function()
oauth.async(function()
local fresh = client:get_access_token()
local fresh = oauth.google_client:get_access_token()
if fresh then
callback(fresh)
else
log.error(client.name .. ': authorization failed or was cancelled')
log.error(oauth.google_client.name .. ': authorization failed or was cancelled')
end
end)
end)
@ -157,14 +149,6 @@ local function with_token(callback)
end)
end
function M.setup()
client:setup()
end
function M.auth()
client:auth()
end
function M.push()
with_token(function(access_token)
local calendars, cal_err = get_all_calendars(access_token)

View file

@ -7,14 +7,6 @@ local M = {}
M.name = 'gtasks'
local BASE_URL = 'https://tasks.googleapis.com/tasks/v1'
local SCOPE = 'https://www.googleapis.com/auth/tasks'
local client = oauth.new({
name = 'gtasks',
scope = SCOPE,
port = 18393,
config_key = 'gtasks',
})
---@param access_token string
---@return table<string, string>? name_to_id
@ -355,15 +347,15 @@ end
---@param callback fun(access_token: string): nil
local function with_token(callback)
oauth.async(function()
local token = client:get_access_token()
local token = oauth.google_client:get_access_token()
if not token then
client:auth(function()
oauth.google_client:auth(function()
oauth.async(function()
local fresh = client:get_access_token()
local fresh = oauth.google_client:get_access_token()
if fresh then
callback(fresh)
else
log.error(client.name .. ': authorization failed or was cancelled')
log.error(oauth.google_client.name .. ': authorization failed or was cancelled')
end
end)
end)
@ -373,14 +365,6 @@ local function with_token(callback)
end)
end
function M.setup()
client:setup()
end
function M.auth()
client:auth()
end
function M.push()
with_token(function(access_token)
local tasklists, s, now_ts = sync_setup(access_token)
@ -462,11 +446,11 @@ M._gtask_to_fields = gtask_to_fields
---@return nil
function M.health()
oauth.health(M.name)
local tokens = client:load_tokens()
local tokens = oauth.google_client:load_tokens()
if tokens and tokens.refresh_token then
vim.health.ok('gtasks tokens found')
else
vim.health.info('no gtasks tokens — run :Pending gtasks auth')
vim.health.info('no gtasks tokens — run :Pending auth')
end
end

View file

@ -501,5 +501,14 @@ end
M._BUNDLED_CLIENT_ID = BUNDLED_CLIENT_ID
M._BUNDLED_CLIENT_SECRET = BUNDLED_CLIENT_SECRET
M.BUNDLED_CLIENT_ID = BUNDLED_CLIENT_ID
M.google_client = M.new({
name = 'google',
scope = 'https://www.googleapis.com/auth/tasks'
.. ' https://www.googleapis.com/auth/calendar',
port = 18392,
config_key = 'google',
})
return M