Compare commits
5 commits
ff73164b61
...
90f1378cb6
| Author | SHA1 | Date | |
|---|---|---|---|
| 90f1378cb6 | |||
| 36a5025198 | |||
| 480c832306 | |||
| f2c7efdd6f | |||
| 2742d1f310 |
4 changed files with 62 additions and 34 deletions
|
|
@ -169,20 +169,8 @@ local function fmt_counts(parts)
|
||||||
return table.concat(items, ' | ')
|
return table.concat(items, ' | ')
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param callback fun(access_token: string): nil
|
|
||||||
local function with_token(callback)
|
|
||||||
oauth.async(function()
|
|
||||||
local token = oauth.google_client:get_access_token()
|
|
||||||
if not token then
|
|
||||||
log.warn('not authenticated — run :Pending auth')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
callback(token)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.push()
|
function M.push()
|
||||||
with_token(function(access_token)
|
oauth.with_token(oauth.google_client, 'gcal', function(access_token)
|
||||||
local calendars, cal_err = get_all_calendars(access_token)
|
local calendars, cal_err = get_all_calendars(access_token)
|
||||||
if cal_err or not calendars then
|
if cal_err or not calendars then
|
||||||
log.error(cal_err or 'failed to fetch calendars')
|
log.error(cal_err or 'failed to fetch calendars')
|
||||||
|
|
|
||||||
|
|
@ -436,20 +436,9 @@ local function sync_setup(access_token)
|
||||||
return tasklists, s, now_ts
|
return tasklists, s, now_ts
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param callback fun(access_token: string): nil
|
|
||||||
local function with_token(callback)
|
|
||||||
oauth.async(function()
|
|
||||||
local token = oauth.google_client:get_access_token()
|
|
||||||
if not token then
|
|
||||||
log.warn('not authenticated — run :Pending auth')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
callback(token)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.push()
|
function M.push()
|
||||||
with_token(function(access_token)
|
oauth.with_token(oauth.google_client, 'gtasks', function(access_token)
|
||||||
local tasklists, s, now_ts = sync_setup(access_token)
|
local tasklists, s, now_ts = sync_setup(access_token)
|
||||||
if not tasklists then
|
if not tasklists then
|
||||||
return
|
return
|
||||||
|
|
@ -475,7 +464,7 @@ function M.push()
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.pull()
|
function M.pull()
|
||||||
with_token(function(access_token)
|
oauth.with_token(oauth.google_client, 'gtasks', function(access_token)
|
||||||
local tasklists, s, now_ts = sync_setup(access_token)
|
local tasklists, s, now_ts = sync_setup(access_token)
|
||||||
if not tasklists then
|
if not tasklists then
|
||||||
return
|
return
|
||||||
|
|
@ -502,7 +491,7 @@ function M.pull()
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.sync()
|
function M.sync()
|
||||||
with_token(function(access_token)
|
oauth.with_token(oauth.google_client, 'gtasks', function(access_token)
|
||||||
local tasklists, s, now_ts = sync_setup(access_token)
|
local tasklists, s, now_ts = sync_setup(access_token)
|
||||||
if not tasklists then
|
if not tasklists then
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ local BUNDLED_CLIENT_SECRET = 'PLACEHOLDER'
|
||||||
local OAuthClient = {}
|
local OAuthClient = {}
|
||||||
OAuthClient.__index = OAuthClient
|
OAuthClient.__index = OAuthClient
|
||||||
|
|
||||||
|
local _active_close = nil
|
||||||
|
local _sync_in_flight = false
|
||||||
|
|
||||||
---@class pending.oauth
|
---@class pending.oauth
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
|
@ -49,6 +52,30 @@ function M.async(fn)
|
||||||
coroutine.resume(coroutine.create(fn))
|
coroutine.resume(coroutine.create(fn))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param client pending.OAuthClient
|
||||||
|
---@param name string
|
||||||
|
---@param callback fun(access_token: string): nil
|
||||||
|
function M.with_token(client, name, callback)
|
||||||
|
if _sync_in_flight then
|
||||||
|
require('pending.log').warn(name .. ': sync operation in progress — please wait')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
_sync_in_flight = true
|
||||||
|
M.async(function()
|
||||||
|
local token = client:get_access_token()
|
||||||
|
if not token then
|
||||||
|
_sync_in_flight = false
|
||||||
|
require('pending.log').warn(name .. ': not authenticated — run :Pending auth')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local ok, err = pcall(callback, token)
|
||||||
|
_sync_in_flight = false
|
||||||
|
if not ok then
|
||||||
|
require('pending.log').error(name .. ': ' .. tostring(err))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
---@param str string
|
---@param str string
|
||||||
---@return string
|
---@return string
|
||||||
function M.url_encode(str)
|
function M.url_encode(str)
|
||||||
|
|
@ -348,6 +375,11 @@ end
|
||||||
---@param on_complete? fun(): nil
|
---@param on_complete? fun(): nil
|
||||||
---@return nil
|
---@return nil
|
||||||
function OAuthClient:auth(on_complete)
|
function OAuthClient:auth(on_complete)
|
||||||
|
if _active_close then
|
||||||
|
_active_close()
|
||||||
|
_active_close = nil
|
||||||
|
end
|
||||||
|
|
||||||
local creds = self:resolve_credentials()
|
local creds = self:resolve_credentials()
|
||||||
if creds.client_id == BUNDLED_CLIENT_ID then
|
if creds.client_id == BUNDLED_CLIENT_ID then
|
||||||
log.error(self.name .. ': no credentials configured — run :Pending auth')
|
log.error(self.name .. ': no credentials configured — run :Pending auth')
|
||||||
|
|
@ -379,14 +411,11 @@ function OAuthClient:auth(on_complete)
|
||||||
.. '&scope='
|
.. '&scope='
|
||||||
.. M.url_encode(self.scope)
|
.. M.url_encode(self.scope)
|
||||||
.. '&access_type=offline'
|
.. '&access_type=offline'
|
||||||
.. '&prompt=consent'
|
.. '&prompt=select_account%20consent'
|
||||||
.. '&code_challenge='
|
.. '&code_challenge='
|
||||||
.. M.url_encode(code_challenge)
|
.. M.url_encode(code_challenge)
|
||||||
.. '&code_challenge_method=S256'
|
.. '&code_challenge_method=S256'
|
||||||
|
|
||||||
vim.ui.open(auth_url)
|
|
||||||
log.info('Opening browser for Google authorization...')
|
|
||||||
|
|
||||||
local server = vim.uv.new_tcp()
|
local server = vim.uv.new_tcp()
|
||||||
local server_closed = false
|
local server_closed = false
|
||||||
local function close_server()
|
local function close_server()
|
||||||
|
|
@ -394,10 +423,20 @@ function OAuthClient:auth(on_complete)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
server_closed = true
|
server_closed = true
|
||||||
|
if _active_close == close_server then
|
||||||
|
_active_close = nil
|
||||||
|
end
|
||||||
server:close()
|
server:close()
|
||||||
end
|
end
|
||||||
|
_active_close = close_server
|
||||||
|
|
||||||
|
local bind_ok, bind_err = pcall(server.bind, server, '127.0.0.1', port)
|
||||||
|
if not bind_ok or bind_err == nil then
|
||||||
|
close_server()
|
||||||
|
log.error(self.name .. ': port ' .. port .. ' already in use — try again in a moment')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
server:bind('127.0.0.1', port)
|
|
||||||
server:listen(1, function(err)
|
server:listen(1, function(err)
|
||||||
if err then
|
if err then
|
||||||
return
|
return
|
||||||
|
|
@ -430,6 +469,9 @@ function OAuthClient:auth(on_complete)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
vim.ui.open(auth_url)
|
||||||
|
log.info('Opening browser for Google authorization...')
|
||||||
|
|
||||||
vim.defer_fn(function()
|
vim.defer_fn(function()
|
||||||
if not server_closed then
|
if not server_closed then
|
||||||
close_server()
|
close_server()
|
||||||
|
|
@ -470,14 +512,14 @@ function OAuthClient:_exchange_code(creds, code, code_verifier, port, on_complet
|
||||||
}, { text = true })
|
}, { text = true })
|
||||||
|
|
||||||
if result.code ~= 0 then
|
if result.code ~= 0 then
|
||||||
self:_wipe()
|
self:clear_tokens()
|
||||||
log.error('Token exchange failed.')
|
log.error('Token exchange failed.')
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local ok, decoded = pcall(vim.json.decode, result.stdout or '')
|
local ok, decoded = pcall(vim.json.decode, result.stdout or '')
|
||||||
if not ok or not decoded.access_token then
|
if not ok or not decoded.access_token then
|
||||||
self:_wipe()
|
self:clear_tokens()
|
||||||
log.error('Invalid token response.')
|
log.error('Invalid token response.')
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -498,6 +540,10 @@ end
|
||||||
|
|
||||||
---@return nil
|
---@return nil
|
||||||
function OAuthClient:clear_tokens()
|
function OAuthClient:clear_tokens()
|
||||||
|
if _active_close then
|
||||||
|
_active_close()
|
||||||
|
_active_close = nil
|
||||||
|
end
|
||||||
os.remove(self:token_path())
|
os.remove(self:token_path())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -142,8 +142,13 @@ describe('oauth', function()
|
||||||
it('falls back to bundled credentials', function()
|
it('falls back to bundled credentials', function()
|
||||||
config.reset()
|
config.reset()
|
||||||
vim.g.pending = { data_path = tmpdir .. '/tasks.json' }
|
vim.g.pending = { data_path = tmpdir .. '/tasks.json' }
|
||||||
|
local orig_load = oauth.load_json_file
|
||||||
|
oauth.load_json_file = function()
|
||||||
|
return nil
|
||||||
|
end
|
||||||
local c = oauth.new({ name = 'gtasks', scope = 'x', port = 0, config_key = 'gtasks' })
|
local c = oauth.new({ name = 'gtasks', scope = 'x', port = 0, config_key = 'gtasks' })
|
||||||
local creds = c:resolve_credentials()
|
local creds = c:resolve_credentials()
|
||||||
|
oauth.load_json_file = orig_load
|
||||||
assert.equals(oauth._BUNDLED_CLIENT_ID, creds.client_id)
|
assert.equals(oauth._BUNDLED_CLIENT_ID, creds.client_id)
|
||||||
assert.equals(oauth._BUNDLED_CLIENT_SECRET, creds.client_secret)
|
assert.equals(oauth._BUNDLED_CLIENT_SECRET, creds.client_secret)
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue