feat(felis): add Operator break-glass op behind an operation menu
When a staff account already exists, the break-glass console now opens on a thin top-level menu (menuModel) where account operations are peers rather than tails of one wizard: provision/reset the Owner, or add an Operator. A fresh machine with no Owner skips the menu and goes straight to Owner bootstrap, since minting an Operator first would create a staff account the login gate rejects. The Operator path reuses ownerModel via a bgOperation discriminator. It is insert-only (performAddOperator -> InsertOperator), wraps a duplicate username as api.ErrConflict and routes back to the provision form for a retry rather than tearing down, and deliberately never flips the global local_auth toggle the way the Owner thread does. The post-exit summary and audit trail distinguish the two outcomes (isOperator); only the Owner provision claims local-password login was enabled. Tests cover the operator-model defaults, path selection (insert vs upsert and the local-auth gate), conflict-retry versus generic teardown, isOperator propagation, and the root menu routing for both fresh and admin-present machines.
This commit is contained in:
6 files changed
+513
-28
No files matched your search
@@ -149,14 +149,19 @@ func TestRootRerunLandsOnStatus(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRootBreakGlassQuitsAfterOwner(t *testing.T) {
|
||||
// Break-glass starts at owner and must quit on owner completion without
|
||||
// entering the connection chooser.
|
||||
// Break-glass with a staff account present opens on the operation menu; choosing
|
||||
// "provision/reset the Owner" lands on the owner screen, which must quit on
|
||||
// completion without entering the connection chooser (that step is setup-only).
|
||||
m := newTestRoot(true, consoleModeBreakGlass, "")
|
||||
if m.stage != stageMenu {
|
||||
t.Fatalf("break-glass initial stage = %v, want stageMenu", m.stage)
|
||||
}
|
||||
m = drive(t, m, menuChoiceMsg{op: bgProvisionOwner})
|
||||
if m.stage != stageOwner {
|
||||
t.Fatalf("break-glass initial stage = %v, want stageOwner", m.stage)
|
||||
t.Fatalf("after the menu choice, stage = %v, want stageOwner", m.stage)
|
||||
}
|
||||
if _, ok := m.screen.(*ownerModel); !ok {
|
||||
t.Fatalf("break-glass initial screen = %T, want *ownerModel", m.screen)
|
||||
t.Fatalf("after the menu choice, screen = %T, want *ownerModel", m.screen)
|
||||
}
|
||||
|
||||
next, cmd := m.Update(ownerResultMsg{username: "owner", displayPassword: "pw", mode: "recovery"})
|
||||
|
||||
Reference in new issue
Block a user