From 9ebf10be416dfb91d812c09e3397a565cfd3d90d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Papp?= Date: Thu, 30 Jul 2026 19:49:29 +0200 Subject: [PATCH] [client] Delete the account email when a profile is removed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removing a profile left its state file behind: the daemon deletes what it owns, but the file holding the account email is user-owned and out of reach for a root daemon, which is why Connection.Logout already clears it from the UI side. Beyond the stray file, legacy profiles are keyed by name rather than by a generated ID, so recreating a profile under a removed one's name inherited its email — shown as the account in the profile list and sent as the login_hint on the next login. --- client/ui/services/profile.go | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/client/ui/services/profile.go b/client/ui/services/profile.go index 09468c9df..ff29d514d 100644 --- a/client/ui/services/profile.go +++ b/client/ui/services/profile.go @@ -6,6 +6,8 @@ import ( "context" "os/user" + log "github.com/sirupsen/logrus" + "github.com/netbirdio/netbird/client/internal/profilemanager" "github.com/netbirdio/netbird/client/proto" ) @@ -151,11 +153,26 @@ func (s *Profiles) Remove(ctx context.Context, p ProfileRef) error { if err != nil { return err } - _, err = cli.RemoveProfile(ctx, &proto.RemoveProfileRequest{ + if _, err = cli.RemoveProfile(ctx, &proto.RemoveProfileRequest{ ProfileName: p.ProfileName, Username: p.Username, - }) - return err + }); err != nil { + return err + } + + // The daemon deletes what it owns but runs as root, so it leaves the + // user-owned state file holding the account email behind (same split as + // Connection.Logout). Legacy profiles are keyed by name rather than by a + // generated ID, so a recreated profile of the same name would inherit the + // deleted one's email and offer it as the login_hint. + if p.ProfileName != "" { + if err := profilemanager.NewProfileManager().RemoveProfileState(p.ProfileName); err != nil { + // Non-fatal: the profile itself is gone. + log.Warnf("failed to remove profile state for %s: %v", p.ProfileName, err) + } + } + + return nil } // Rename changes a profile's display name. The on-disk ID is unaffected, so