feat(auth): add Secret Service and env var support for login

- Introduce `secret_tool_lookup` to retrieve passwords via `secret-tool`
- Extract `login_with_username` to try Secret Service before prompting
- Read `JASPER_USERNAME` env var to skip username prompt when set
- Fall back to `inputsecret` prompt if Secret Service lookup fails
This commit is contained in:
2026-05-02 17:39:52 +02:00
parent 4a5acb17be
commit 53fbabe33e

View File

@@ -88,32 +88,69 @@ function M.login(username, password)
return data.auth_token, nil return data.auth_token, nil
end end
--- Try to retrieve a password from the system Secret Service via secret-tool.
--- @param username string
--- @return string|nil password
local function secret_tool_lookup(username)
local result = vim.system({ "secret-tool", "lookup", "service", "jasper", "user", username }, { text = true })
:wait()
if result.code == 0 and result.stdout and result.stdout ~= "" then
-- strip trailing newline
return (result.stdout:gsub("%s+$", ""))
end
return nil
end
--- Attempt login with the given username: try Secret Service first, then prompt.
--- @param username string
--- @param callback fun(token: string|nil)
local function login_with_username(username, callback)
local password = secret_tool_lookup(username)
if password then
local token, err = M.login(username, password)
if token and not err then
vim.notify("[Jasper] Login successful (Secret Service)", vim.log.levels.INFO)
callback(token)
return
end
-- Secret Service password was wrong or login failed — fall through to prompt
vim.notify("[Jasper] Secret Service password failed, prompting", vim.log.levels.WARN)
end
-- Prompt for password
local pwd = vim.fn.inputsecret("Jasper password: ")
if not pwd or pwd == "" then
vim.notify("[Jasper] Login cancelled", vim.log.levels.WARN)
callback(nil)
return
end
local token, err = M.login(username, pwd)
if err or not token then
vim.notify("[Jasper] " .. (err or "Login failed"), vim.log.levels.ERROR)
callback(nil)
return
end
vim.notify("[Jasper] Login successful", vim.log.levels.INFO)
callback(token)
end
--- Interactively ask the user for credentials using vim.ui.input, --- Interactively ask the user for credentials using vim.ui.input,
--- then call login(). Calls `callback(token)` on success, `callback(nil)` on failure. --- then call login(). Calls `callback(token)` on success, `callback(nil)` on failure.
--- Checks JASPER_USERNAME env var and Secret Service before prompting.
--- @param callback fun(token: string|nil) --- @param callback fun(token: string|nil)
function M.prompt_login(callback) function M.prompt_login(callback)
vim.ui.input({ prompt = "Jasper username: " }, function(username) local env_username = os.getenv("JASPER_USERNAME")
if not username or username == "" then if env_username and env_username ~= "" then
vim.notify("[Jasper] Login cancelled", vim.log.levels.WARN) login_with_username(env_username, callback)
callback(nil) else
return vim.ui.input({ prompt = "Jasper username: " }, function(username)
end if not username or username == "" then
-- vim.fn.inputsecret() is native Neovim: hides typed characters in all environments vim.notify("[Jasper] Login cancelled", vim.log.levels.WARN)
local password = vim.fn.inputsecret("Jasper password: ") callback(nil)
if not password or password == "" then return
vim.notify("[Jasper] Login cancelled", vim.log.levels.WARN) end
callback(nil) login_with_username(username, callback)
return end)
end end
local token, err = M.login(username, password)
if err or not token then
vim.notify("[Jasper] " .. (err or "Login failed"), vim.log.levels.ERROR)
callback(nil)
return
end
vim.notify("[Jasper] Login successful", vim.log.levels.INFO)
callback(token)
end)
end end
return M return M