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:
@@ -88,32 +88,69 @@ function M.login(username, password)
|
||||
return data.auth_token, nil
|
||||
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,
|
||||
--- 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)
|
||||
function M.prompt_login(callback)
|
||||
vim.ui.input({ prompt = "Jasper username: " }, function(username)
|
||||
if not username or username == "" then
|
||||
vim.notify("[Jasper] Login cancelled", vim.log.levels.WARN)
|
||||
callback(nil)
|
||||
return
|
||||
end
|
||||
-- vim.fn.inputsecret() is native Neovim: hides typed characters in all environments
|
||||
local password = vim.fn.inputsecret("Jasper password: ")
|
||||
if not password or password == "" then
|
||||
vim.notify("[Jasper] Login cancelled", vim.log.levels.WARN)
|
||||
callback(nil)
|
||||
return
|
||||
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)
|
||||
local env_username = os.getenv("JASPER_USERNAME")
|
||||
if env_username and env_username ~= "" then
|
||||
login_with_username(env_username, callback)
|
||||
else
|
||||
vim.ui.input({ prompt = "Jasper username: " }, function(username)
|
||||
if not username or username == "" then
|
||||
vim.notify("[Jasper] Login cancelled", vim.log.levels.WARN)
|
||||
callback(nil)
|
||||
return
|
||||
end
|
||||
login_with_username(username, callback)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
Reference in New Issue
Block a user