From 53fbabe33ed6a8689e42ed1bce9a1d921dfd8fac Mon Sep 17 00:00:00 2001 From: Paolo Donadeo Date: Sat, 2 May 2026 17:39:52 +0200 Subject: [PATCH] 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 --- lua/jasper/auth.lua | 81 +++++++++++++++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 22 deletions(-) diff --git a/lua/jasper/auth.lua b/lua/jasper/auth.lua index 6e903f9..72bd195 100644 --- a/lua/jasper/auth.lua +++ b/lua/jasper/auth.lua @@ -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