Why: Administrator-issued password and login-key paths could take over target accounts, while old credentials and plugin handles ignored current account state. Evidence: SELF receipts /tmp/iam-u3-backend-all-replay.json, /tmp/iam-u3-backend-final-pg-core.json, /tmp/iam-u3-backend-build.json, and focused HTTP/concurrency receipts in /tmp/iam-u3-*.json. Impact: Adds trusted pending-account initialization and reissue, preexisting owner-session proof, proposal/rotation/grant transactions, server-side admin search, current Host guards, and legacy HTTP/CLI closure. Production cutover and downstream pins remain pending. Refs: IAM U3 r1 stages 4-5; Dev feature/iam-u3-account-directory-recovery
57 lines
2.1 KiB
Go
57 lines
2.1 KiB
Go
package store
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func TestOwnerInitializationReissuesSamePendingIdentityAndClosesAfterCompletion(t *testing.T) {
|
|
st := openTestStore(t)
|
|
t.Logf("owner initialization dialect=%s", st.Dialect())
|
|
if err := st.CreateAccount("admin", "", "admin-hash", true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
pending, err := st.CreatePendingAccount("admin", "alice", AccountProfile{Name: "Alice"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
first, err := st.InitializePendingAccount(pending.ID, pending.StateVersion, pending.InitializationGeneration)
|
|
if err != nil || first == "" {
|
|
t.Fatalf("initialize pending: %v", err)
|
|
}
|
|
firstSession, owner, err := st.RedeemLoginKey(first)
|
|
if err != nil || owner != "alice" {
|
|
t.Fatalf("redeem first activation: %v %q", err, owner)
|
|
}
|
|
stillPending, err := st.GetAccount(pending.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, err := st.InitializePendingAccount(pending.ID, stillPending.StateVersion, stillPending.InitializationGeneration)
|
|
if err != nil || second == first {
|
|
t.Fatalf("reissue: %v", err)
|
|
}
|
|
if _, _, ok := st.SessionInfo(firstSession); ok {
|
|
t.Fatal("reissue left old restricted session active")
|
|
}
|
|
secondSession, owner, err := st.RedeemLoginKey(second)
|
|
if err != nil || owner != "alice" {
|
|
t.Fatalf("redeem second activation: %v %q", err, owner)
|
|
}
|
|
hash, err := HashPassword("alice-initialized-password")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := st.CompleteSetPasswordChecked(secondSession, hash); err != nil {
|
|
t.Fatalf("complete initial password: %v", err)
|
|
}
|
|
completed, err := st.GetAccount(pending.ID)
|
|
if err != nil || completed.ID != pending.ID || completed.Owner != pending.Owner || completed.InitializationState != InitializationInitialized ||
|
|
completed.CredentialOrigin != CredentialOriginOwnerInitialization || !completed.HasPassword {
|
|
t.Fatalf("completion changed identity or lost trusted lineage: %+v %v", completed, err)
|
|
}
|
|
if _, err := st.InitializePendingAccount(pending.ID, completed.StateVersion, completed.InitializationGeneration); !errors.Is(err, ErrAccountForbidden) {
|
|
t.Fatalf("completed account could be initialized again: %v", err)
|
|
}
|
|
}
|