From 461f172035fdce456de06b56def29ffe256da80a Mon Sep 17 00:00:00 2001 From: "Brandon McGinty (chatgpt)" Date: Mon, 10 Aug 2026 10:26:03 -0400 Subject: [PATCH] Validate admin targets before actions --- admin.go | 30 ++++++++++++++++++++++++++++-- admin_target_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 admin_target_test.go diff --git a/admin.go b/admin.go index 00a178f..52d0500 100644 --- a/admin.go +++ b/admin.go @@ -169,7 +169,9 @@ func (b *Barnard) AdminItemKeyPress(ui *uiterm.Ui, tree *uiterm.Tree, item uiter if !ok || admin.action == nil { return } - admin.action() + if !b.withValidAdminTargets(admin.action) { + b.AddOutputLine("Admin: action target is no longer available") + } b.UiAdmin.Rebuild() b.Ui.Refresh() } @@ -549,13 +551,37 @@ func (b *Barnard) handleAdminPrompt(text string) bool { } prompt := b.pendingAdminPrompt b.pendingAdminPrompt = nil - prompt.action(strings.TrimSpace(text)) + if !b.withValidAdminTargets(func() { prompt.action(strings.TrimSpace(text)) }) { + b.AddOutputLine("Admin: action target is no longer available") + } if b.Client != nil && b.Client.Self != nil { b.UpdateInputStatus(fmt.Sprintf("[%s]", b.Client.Self.Channel.Name)) } return true } +// withValidAdminTargets runs an action only while its selected targets are +// still members of the current connection. Menu actions can outlive server +// removal events while a prompt is open. +func (b *Barnard) withValidAdminTargets(action func()) bool { + if b.Client == nil { + return false + } + valid := true + b.Client.Do(func() { + if u := b.adminTargetUser; u != nil && b.Client.Users[u.Session] != u { + valid = false + } + if ch := b.adminTargetChan; ch != nil && b.Client.Channels[ch.ID] != ch { + valid = false + } + if valid { + action() + } + }) + return valid +} + func (b *Barnard) CommandAdmin(ui *uiterm.Ui, cmd string) { b.executeAdminCommand(cmd) } diff --git a/admin_target_test.go b/admin_target_test.go new file mode 100644 index 0000000..0719c9d --- /dev/null +++ b/admin_target_test.go @@ -0,0 +1,30 @@ +package main + +import ( + "testing" + + "git.stormux.org/storm/barnard/gumble/gumble" +) + +func TestAdminActionRejectsRemovedTarget(t *testing.T) { + user := &gumble.User{Session: 1} + client := &gumble.Client{Users: gumble.Users{1: user}, Channels: gumble.Channels{}} + b := &Barnard{Client: client, adminTargetUser: user} + called := false + if !b.withValidAdminTargets(func() { called = true }) || !called { + t.Fatal("current admin target was rejected") + } + delete(client.Users, user.Session) + called = false + if b.withValidAdminTargets(func() { called = true }) || called { + t.Fatal("removed admin target was allowed to execute") + } +} + +func TestAdminActionRejectsReplacedChannelTarget(t *testing.T) { + channel := &gumble.Channel{ID: 2} + b := &Barnard{Client: &gumble.Client{Channels: gumble.Channels{2: &gumble.Channel{ID: 2}}}, adminTargetChan: channel} + if b.withValidAdminTargets(func() {}) { + t.Fatal("replaced channel target was allowed to execute") + } +}