fix(cli): recognize account ls alias for agent role; align account show output; document edit password invariant

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-27 12:42:19 +01:00
parent dbefb68611
commit 5476c04443
4 changed files with 37 additions and 2 deletions
+1 -1
View File
@@ -44,6 +44,6 @@ func accountShow(st *store.Store, rest []string, out, errOut io.Writer) int {
fmt.Fprintf(out, "send-from: %s\n", a.SendFrom())
fmt.Fprintf(out, "subject filter: %s\n", subj)
fmt.Fprintf(out, "inbound whitelist: %s\n", onOff(a.WhitelistInEnabled))
fmt.Fprintf(out, "outbound whitelist:%s\n", onOff(a.WhitelistOutEnabled))
fmt.Fprintf(out, "outbound whitelist: %s\n", onOff(a.WhitelistOutEnabled))
return 0
}
+4
View File
@@ -167,6 +167,10 @@ func runAccount(args []string, role store.Role, out, errOut io.Writer) int {
acc.SubjectRegex = *subj
}
})
// GetAccount loaded the existing decrypted password into acc; fs.Visit
// overwrites acc.Password only when --password was passed; UpdateAccount
// re-seals whatever non-empty password is present, so omitting --password
// on edit preserves the existing password unchanged.
if err := st.UpdateAccount(acc); err != nil {
fmt.Fprintf(errOut, "edit: %v\n", err)
return 1
+2 -1
View File
@@ -32,7 +32,8 @@ func commandRole(args []string) store.Role {
case "account":
// account list is a read-only discovery view available to agents;
// add/edit/remove mutate config and require admin.
if len(args) >= 2 && args[1] == "list" {
// Normalize the verb so `account ls` routes the same as `account list`.
if len(args) >= 2 && normalizeVerb(args[1]) == "list" {
return store.RoleAgent
}
return store.RoleAdmin
+30
View File
@@ -91,3 +91,33 @@ func TestTopLevelLsAlias(t *testing.T) {
t.Fatalf("ls should alias list (usage about --account): code=%d out=%q", code, out)
}
}
// TestAccountLsAliasAgentRole verifies that `account ls` is treated as an agent
// command (not admin) so a caller with only EMCLI_KEY can use it and gets the
// same reduced-JSON envelope as `account list`.
func TestAccountLsAliasAgentRole(t *testing.T) {
adminEnv(t) // sets up both keys + initialized temp DB
run(t, "account", "add", "work", "--mode", "RW",
"--imap-host", "imap.example.com", "--smtp-host", "smtp.example.com",
"--username", "login@example.com", "--from", "me@example.com")
// Drop the admin key — caller is agent-only.
t.Setenv("EMCLI_ADMIN_KEY", "")
// `account ls` must succeed with the reduced JSON view, same as `account list`.
code, out, errOut := run(t, "account", "ls")
if code != 0 {
t.Fatalf("agent account ls should succeed: code=%d out=%q err=%q", code, out, errOut)
}
var env map[string]any
if err := json.Unmarshal([]byte(out), &env); err != nil {
t.Fatalf("account ls output is not JSON: %v\n%s", err, out)
}
if env["error"] == true {
t.Fatalf("account ls returned error envelope: %s", out)
}
// The agent view must not leak IMAP host or login username.
if strings.Contains(out, "imap.example.com") || strings.Contains(out, "login@example.com") {
t.Fatalf("account ls leaked host/username:\n%s", out)
}
}