feat(security,timer): harden auth and add multi-instance coordination
- Extract JASPER_URL into a shared constants module - Pass login credentials via a temp curl config file to avoid exposure in the process argument list (ps/proc) - Replace vim.ui.input secret prompt with vim.fn.inputsecret() - Add -s (silent) flag to all curl calls to suppress progress output - Guard curl output parser against missing newline in stdout - Track per-activity shared timestamp file (/tmp/jasper_<id>.last_activity) so the inactivity watchdog skips auto-pause when another Neovim instance is still active on the same task - Clean up leftover uv_timer on repeated begin_tracking calls - Remove shared activity file on teardown only when this instance wrote it
This commit is contained in:
@@ -4,7 +4,8 @@
|
||||
|
||||
local M = {}
|
||||
|
||||
local JASPER_URL = "https://jasper.4sigma.it"
|
||||
local constants = require("jasper.constants")
|
||||
local JASPER_URL = constants.JASPER_URL
|
||||
|
||||
--- @return string path to the token cache file
|
||||
local function token_file_path()
|
||||
@@ -46,16 +47,29 @@ end
|
||||
--- @return string|nil token
|
||||
--- @return string|nil error message
|
||||
function M.login(username, password)
|
||||
-- Write credentials to a temp curl config file so they never appear in
|
||||
-- the process argument list (ps aux / /proc/<pid>/cmdline).
|
||||
local tmpfile = os.tmpname()
|
||||
local cf = io.open(tmpfile, "w")
|
||||
if not cf then
|
||||
return nil, "cannot create temp file for curl config"
|
||||
end
|
||||
cf:write('--data-urlencode "username=' .. username .. '"\n')
|
||||
cf:write('--data-urlencode "password=' .. password .. '"\n')
|
||||
cf:close()
|
||||
|
||||
local result = vim.system({
|
||||
"curl",
|
||||
"-s",
|
||||
"--data-urlencode",
|
||||
"username=" .. username,
|
||||
"--data-urlencode",
|
||||
"password=" .. password,
|
||||
"-X",
|
||||
"POST",
|
||||
"--config",
|
||||
tmpfile,
|
||||
JASPER_URL .. "/api/v1/token/login/",
|
||||
}, { text = true }):wait()
|
||||
|
||||
os.remove(tmpfile)
|
||||
|
||||
if result.code ~= 0 then
|
||||
return nil, "curl error (exit " .. result.code .. ")"
|
||||
end
|
||||
@@ -84,21 +98,21 @@ function M.prompt_login(callback)
|
||||
callback(nil)
|
||||
return
|
||||
end
|
||||
vim.ui.input({ prompt = "Jasper password: ", secret = true }, function(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)
|
||||
-- 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)
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user