feat(sync): interactive setup, auth continuation, and credential resolution fixes (#70)
* feat(sync): add `setup` command to configure credentials interactively
Problem: users had to manually create a JSON credentials file at the
correct path before authenticating, with no guidance from the plugin.
Solution: add `OAuthClient:setup()` that prompts for client ID and
secret via `vim.ui.input`, writes to the shared
`google_credentials.json`, then immediately starts the OAuth flow.
Expose as `:Pending {gtasks,gcal} setup`. Also extend
`resolve_credentials()` to fall back to a shared `google_credentials.json`
so one file covers both backends.
* fix(sync): improve `setup` input loop with validation and masking
Problem: `setup()` used async `vim.ui.input` for both prompts, causing
newline and re-prompt issues when validation failed. The secret was also
echoed in plain text.
Solution: switch to synchronous `vim.fn.input` / `vim.fn.inputsecret`
loops with `vim.cmd.redraw()` + `nvim_echo` for inline error display and
re-prompting. Validate client ID format and `GOCSPX-` secret prefix
before saving.
* fix(oauth): fix `ipairs` nil truncation in `resolve_credentials` and add file-path setup option
Problem: `resolve_credentials` built `cred_paths` with a potentially nil
first element (`credentials_path`), causing `ipairs` to stop immediately
and always fall through to bundled placeholder credentials.
Solution: build `cred_paths` without nil entries using `table.insert`.
Also add a `2. Load from JSON file path` option to `setup()` via
`vim.fn.inputlist`, with `vim.fn.expand` for `~`/`$HOME` support and
the `installed` wrapper unwrap.
* doc: cleanup
* ci: format
This commit is contained in:
parent
715b6d4d12
commit
517a6c0aed
4 changed files with 115 additions and 26 deletions
|
|
@ -155,6 +155,10 @@ local function with_token(callback)
|
|||
end)
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
client:setup()
|
||||
end
|
||||
|
||||
function M.auth()
|
||||
client:auth()
|
||||
end
|
||||
|
|
|
|||
|
|
@ -371,6 +371,10 @@ local function with_token(callback)
|
|||
end)
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
client:setup()
|
||||
end
|
||||
|
||||
function M.auth()
|
||||
client:auth()
|
||||
end
|
||||
|
|
|
|||
|
|
@ -166,18 +166,26 @@ function OAuthClient:resolve_credentials()
|
|||
}
|
||||
end
|
||||
|
||||
local cred_path = backend_cfg.credentials_path
|
||||
or (vim.fn.stdpath('data') .. '/pending/' .. self.name .. '_credentials.json')
|
||||
local creds = M.load_json_file(cred_path)
|
||||
if creds then
|
||||
if creds.installed then
|
||||
creds = creds.installed
|
||||
end
|
||||
if creds.client_id and creds.client_secret then
|
||||
return creds --[[@as pending.OAuthCredentials]]
|
||||
local data_dir = vim.fn.stdpath('data') .. '/pending/'
|
||||
local cred_paths = {}
|
||||
if backend_cfg.credentials_path then
|
||||
table.insert(cred_paths, backend_cfg.credentials_path)
|
||||
end
|
||||
table.insert(cred_paths, data_dir .. self.name .. '_credentials.json')
|
||||
table.insert(cred_paths, data_dir .. 'google_credentials.json')
|
||||
for _, cred_path in ipairs(cred_paths) do
|
||||
if cred_path then
|
||||
local creds = M.load_json_file(cred_path)
|
||||
if creds then
|
||||
if creds.installed then
|
||||
creds = creds.installed
|
||||
end
|
||||
if creds.client_id and creds.client_secret then
|
||||
return creds --[[@as pending.OAuthCredentials]]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
client_id = BUNDLED_CLIENT_ID,
|
||||
client_secret = BUNDLED_CLIENT_SECRET,
|
||||
|
|
@ -251,6 +259,92 @@ function OAuthClient:get_access_token()
|
|||
return tokens.access_token
|
||||
end
|
||||
|
||||
---@return nil
|
||||
function OAuthClient:setup()
|
||||
local choice = vim.fn.inputlist({
|
||||
self.name .. ' setup:',
|
||||
'1. Enter client ID and secret',
|
||||
'2. Load from JSON file path',
|
||||
})
|
||||
vim.cmd.redraw()
|
||||
|
||||
local id, secret
|
||||
|
||||
if choice == 1 then
|
||||
while true do
|
||||
id = vim.trim(vim.fn.input(self.name .. ' client ID: '))
|
||||
if id == '' then
|
||||
return
|
||||
end
|
||||
if id:match('^%d+%-[%w_]+%.apps%.googleusercontent%.com$') then
|
||||
break
|
||||
end
|
||||
vim.cmd.redraw()
|
||||
vim.api.nvim_echo({
|
||||
{
|
||||
'invalid client ID — expected <numbers>-<hash>.apps.googleusercontent.com',
|
||||
'ErrorMsg',
|
||||
},
|
||||
}, false, {})
|
||||
end
|
||||
|
||||
while true do
|
||||
secret = vim.trim(vim.fn.inputsecret(self.name .. ' client secret: '))
|
||||
if secret == '' then
|
||||
return
|
||||
end
|
||||
if secret:match('^GOCSPX%-') then
|
||||
break
|
||||
end
|
||||
vim.cmd.redraw()
|
||||
vim.api.nvim_echo(
|
||||
{ { 'invalid client secret — expected GOCSPX-...', 'ErrorMsg' } },
|
||||
false,
|
||||
{}
|
||||
)
|
||||
end
|
||||
elseif choice == 2 then
|
||||
local fpath
|
||||
while true do
|
||||
fpath = vim.trim(vim.fn.input(self.name .. ' credentials file: ', '', 'file'))
|
||||
if fpath == '' then
|
||||
return
|
||||
end
|
||||
fpath = vim.fn.expand(fpath)
|
||||
local creds = M.load_json_file(fpath)
|
||||
if creds then
|
||||
if creds.installed then
|
||||
creds = creds.installed
|
||||
end
|
||||
if creds.client_id and creds.client_secret then
|
||||
id = creds.client_id
|
||||
secret = creds.client_secret
|
||||
break
|
||||
end
|
||||
end
|
||||
vim.cmd.redraw()
|
||||
vim.api.nvim_echo(
|
||||
{ { 'could not read client_id/client_secret from ' .. fpath, 'ErrorMsg' } },
|
||||
false,
|
||||
{}
|
||||
)
|
||||
end
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
vim.schedule(function()
|
||||
local path = vim.fn.stdpath('data') .. '/pending/google_credentials.json'
|
||||
local ok = M.save_json_file(path, { client_id = id, client_secret = secret })
|
||||
if not ok then
|
||||
log.error(self.name .. ': failed to save credentials')
|
||||
return
|
||||
end
|
||||
log.info(self.name .. ': credentials saved, starting authorization...')
|
||||
self:auth()
|
||||
end)
|
||||
end
|
||||
|
||||
---@param on_complete? fun(): nil
|
||||
---@return nil
|
||||
function OAuthClient:auth(on_complete)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue