feat(cli): two-key role routing + init bootstrap
openStore(role) selects the DEK wrap slot; admin commands require EMCLI_ADMIN_KEY (admin slot only, no agent fallback); init writes both slots from both keys. Test helpers seed the wrap slots. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,15 +7,28 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.dcglab.co.uk/steve/emcli/internal/crypto"
|
||||
"git.dcglab.co.uk/steve/emcli/internal/store"
|
||||
)
|
||||
|
||||
// adminEnv points EMCLI_KEY/EMCLI_DB at a fresh temp DB and returns its path.
|
||||
// adminEnv points both keys + EMCLI_DB at a fresh, initialized temp DB.
|
||||
func adminEnv(t *testing.T) string {
|
||||
t.Helper()
|
||||
db := filepath.Join(t.TempDir(), "emcli.db")
|
||||
t.Setenv("EMCLI_KEY", b64Key())
|
||||
t.Setenv("EMCLI_ADMIN_KEY", b64Key())
|
||||
t.Setenv("EMCLI_KEY", b64AgentKey())
|
||||
t.Setenv("EMCLI_DB", db)
|
||||
|
||||
st, err := store.Open(db)
|
||||
if err != nil {
|
||||
t.Fatalf("Open: %v", err)
|
||||
}
|
||||
adminKey, _ := crypto.AdminKeyFromEnv()
|
||||
agentKey, _ := crypto.AgentKeyFromEnv()
|
||||
if err := st.InitKeys(adminKey, agentKey); err != nil {
|
||||
t.Fatalf("InitKeys: %v", err)
|
||||
}
|
||||
st.Close()
|
||||
return db
|
||||
}
|
||||
|
||||
@@ -75,11 +88,16 @@ func TestAccountEditPartialPreservesOtherFields(t *testing.T) {
|
||||
"--smtp-host", "smtp.x.com", "--smtp-port", "587", "--smtp-security", "starttls"); code != 0 {
|
||||
t.Fatalf("edit failed: %s", e)
|
||||
}
|
||||
st, err := store.Open(db, mustKey())
|
||||
st, err := store.Open(db)
|
||||
if err != nil {
|
||||
t.Fatalf("open: %v", err)
|
||||
}
|
||||
defer st.Close()
|
||||
adminKey, _ := crypto.AdminKeyFromEnv()
|
||||
agentKey, _ := crypto.AgentKeyFromEnv()
|
||||
if err := st.Unlock(store.RoleAdmin, adminKey, agentKey); err != nil {
|
||||
t.Fatalf("Unlock: %v", err)
|
||||
}
|
||||
got, err := st.GetAccount("ed")
|
||||
if err != nil {
|
||||
t.Fatalf("GetAccount: %v", err)
|
||||
@@ -93,11 +111,14 @@ func TestAccountEditPartialPreservesOtherFields(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuditListCoreRenders(t *testing.T) {
|
||||
st, err := store.Open(filepath.Join(t.TempDir(), "e.db"), testKey())
|
||||
st, err := store.Open(filepath.Join(t.TempDir(), "e.db"))
|
||||
if err != nil {
|
||||
t.Fatalf("open: %v", err)
|
||||
}
|
||||
defer st.Close()
|
||||
if err := st.InitKeys(testKey(), testKey()); err != nil {
|
||||
t.Fatalf("InitKeys: %v", err)
|
||||
}
|
||||
now := time.Date(2026, 6, 22, 0, 0, 0, 0, time.UTC)
|
||||
_ = st.Audit(now, store.AuditEntry{Account: "a", Action: "list", Target: "INBOX", Result: "allowed"})
|
||||
_ = st.Audit(now, store.AuditEntry{Account: "a", Action: "send", Target: "x@y.com", Result: "blocked", Reason: "whitelist_out"})
|
||||
@@ -111,5 +132,3 @@ func TestAuditListCoreRenders(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// mustKey decodes the same 32-zero-byte key used by b64Key for store reopen.
|
||||
func mustKey() []byte { return make([]byte, 32) }
|
||||
|
||||
Reference in New Issue
Block a user