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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user