Skip to content

[client] Keep the account email backing the SSO login hint correct - #6986

Open
pappz wants to merge 5 commits into
mainfrom
fix/gui-store-login-hint
Open

[client] Keep the account email backing the SSO login hint correct#6986
pappz wants to merge 5 commits into
mainfrom
fix/gui-store-login-hint

Conversation

@pappz

@pappz pappz commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Describe your changes

Three fixes to how the desktop GUI keeps the account email that backs the SSO login_hint. Each is independent and reviewable on its own.

1. Store the email after a GUI SSO login

The daemon returns the authenticated user's email from WaitSSOLogin but cannot persist it: it runs as root while the per-profile state file is user-owned. The CLI's handleSSOLogin writes it after its own WaitSSOLogin; the GUI path read the value and dropped it.

The profile was therefore left with no email, so Profiles.List showed no account for it, and later logins and session extends went out with no login_hint — leaving the IdP to pick an account instead of reusing the one the profile belongs to. Mirror the CLI and store it, next to the Logout path that already clears the same file for the same reason.

2. File the email against the profile the login ran for

SetActiveProfileState resolves the target itself, so it writes to whichever profile is active when it is called. A GUI SSO login spans seconds of user interaction in the browser, and the tray stays clickable throughout: switching profiles in that window left the email filed under the profile that happened to be active when the flow returned. The wrong profile then advertised an account it does not own, and offered it as the login_hint next time.

Adds SetProfileState(id, state), the write-side counterpart of the existing GetProfileState(id), and keeps SetActiveProfileState as a wrapper for callers with no particular profile in mind. Login now reports the profile it resolved so the frontend can hand it back with the SSO wait, which closes the window.

3. Delete the email when a profile is removed

Removing a profile left its state file behind: the daemon deletes what it owns, but the email file is user-owned and out of reach for a root daemon — the same split that already puts the Logout cleanup on 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.

Issue ticket number and link

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)
  • This change does not modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — OR I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See CONTRIBUTING.md.

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__

Summary by CodeRabbit

  • New Features

    • SSO login details can now be saved to the specific profile selected during sign-in.
    • Profile state can be managed independently for different profiles.
  • Bug Fixes

    • Removing a profile now also cleans up its associated saved state.
    • Cleanup issues no longer prevent successful profile removal and are handled gracefully.
    • Existing active-profile behavior remains unchanged.

The daemon returns the authenticated user's email from WaitSSOLogin but
cannot persist it: it runs as root while the per-profile state file is
user-owned. The CLI's handleSSOLogin writes it after its own WaitSSOLogin;
the GUI path read the value and dropped it.

The profile was therefore left with no email, so Profiles.List showed no
account for it, and later logins and session extends went out with no
login_hint — leaving the IdP to pick an account instead of reusing the one
the profile belongs to. Mirror the CLI and store it, next to the Logout
path that already clears the same file for the same reason.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Profile state persistence is centralized for targeted and active profiles. SSO login now carries the resolved profile ID so authenticated email is stored in the correct profile, while profile removal performs best-effort state-file cleanup.

Changes

Profile state management

Layer / File(s) Summary
Profile state persistence
client/internal/profilemanager/state.go
Adds validated per-profile state writes and makes active-profile persistence delegate to the shared implementation.
SSO profile selection and persistence
client/ui/services/connection.go
Propagates the resolved profile ID through login and stores SSO email in that profile, with active-profile fallback and non-fatal warnings.
Profile removal cleanup
client/ui/services/profile.go
Removes the profile state file after daemon removal and logs cleanup failures without failing the operation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ConnectionLogin
  participant WaitSSOLogin
  participant ProfileManager
  ConnectionLogin->>WaitSSOLogin: pass resolved ProfileID
  WaitSSOLogin->>ProfileManager: persist authenticated email
  ProfileManager-->>WaitSSOLogin: persistence result
Loading

Possibly related PRs

  • netbirdio/netbird#6744: Updates the related SSO login flow and overlaps with profile-targeted email persistence.

Suggested reviewers: mlsmaycon

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific and matches the main SSO login-hint email fix.
Description check ✅ Passed All template sections are present and the change is well described; only the issue ticket/link section is left blank.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/gui-store-login-hint

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
client/ui/services/connection.go (1)

252-258: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression coverage for the new persistence contract.

Please test that a non-empty email is stored, an empty email skips persistence, and a state-write failure still returns the daemon email without failing login.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/ui/services/connection.go` around lines 252 - 258, Extend the login
response flow around resp.GetEmail() with regression tests covering three cases:
persist a non-empty email, skip SetActiveProfileState for an empty email, and
return the daemon email successfully when SetActiveProfileState fails. Use mocks
or fixtures for profilemanager.NewProfileManager and verify the persistence call
and returned login result without changing the existing non-fatal error
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@client/ui/services/connection.go`:
- Around line 252-258: Extend the login response flow around resp.GetEmail()
with regression tests covering three cases: persist a non-empty email, skip
SetActiveProfileState for an empty email, and return the daemon email
successfully when SetActiveProfileState fails. Use mocks or fixtures for
profilemanager.NewProfileManager and verify the persistence call and returned
login result without changing the existing non-fatal error behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 63806dfe-c27f-47f8-a40e-a319500e9a85

📥 Commits

Reviewing files that changed from the base of the PR and between c1f0006 and 4d12550.

📒 Files selected for processing (1)
  • client/ui/services/connection.go

pappz added 2 commits July 30, 2026 19:48
SetActiveProfileState resolves the target itself, so it writes to whichever
profile is active when it is called. A GUI SSO login spans seconds of user
interaction in the browser, and the tray stays clickable throughout: switching
profiles in that window left the email filed under the profile that happened
to be active when the flow returned. The wrong profile then advertised an
account it does not own, and offered it as the login_hint next time.

Add SetProfileState(id, state), the write-side counterpart of the existing
GetProfileState(id), and keep SetActiveProfileState as a wrapper for callers
with no particular profile in mind. Login now reports the profile it resolved
so the frontend can hand it back with the SSO wait, which closes the window.
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.
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

Release artifacts

Built for PR head c0a1d95 in workflow run #17302.

Artifact Link
All release artifacts Download
Linux packages Download
Windows packages Download
macOS packages Download
UI artifacts Download
UI macOS artifacts Download

GHCR images (amd64)

This comment is updated by the Release workflow. Artifact links expire according to the workflow retention policy.

@pappz pappz changed the title [client] Store the account email after a GUI SSO login [client] Keep the account email backing the SSO login hint correct Jul 30, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@client/ui/services/connection.go`:
- Line 133: Use canonical daemon-resolved profile IDs for SSO state persistence
and cleanup: in client/ui/services/connection.go lines 133 and 228, use
LoginResponse.Id or resolve the profile’s canonical ID before passing it to
ProfileState, SetProfileState, or RemoveProfileState; in
client/ui/services/profile.go lines 163-173, resolve the removed profile by
username and pass its canonical ID to RemoveProfileState, supporting removals by
username only.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0519fd65-1bf9-4244-bd27-2b8fb41bb208

📥 Commits

Reviewing files that changed from the base of the PR and between 4d12550 and 9ebf10b.

⛔ Files ignored due to path filters (1)
  • client/ui/frontend/src/lib/connection.ts is excluded by !**/*.ts
📒 Files selected for processing (3)
  • client/internal/profilemanager/state.go
  • client/ui/services/connection.go
  • client/ui/services/profile.go

Comment thread client/ui/services/connection.go Outdated
pappz added 2 commits July 30, 2026 20:45
RemoveProfile takes a handle — a display name or an ID prefix resolve just as
well as a full ID — but the state file holding the account email is named after
the ID. Passing the request handle straight through therefore named a
different file, or none, leaving the email behind for a recreated profile to
inherit.

The daemon already echoes back the ID it resolved for exactly this purpose;
use it.
LoginResult.ProfileID was filled from the request's ProfileName, which is a
handle: a display name or an ID prefix resolve just as well. waitSSOLogin names
the state file after it, so a handle would have written the account email to a
file no reader looks for — the email silently lost, plus a stray file.

Fill it only on the branch where the daemon supplied the ID, and leave it empty
otherwise; waitSSOLogin then falls back to the active profile, as it did before
the field existed.
@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
client/ui/services/connection.go (1)

260-284: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression test for the profile-switch window.

Capture LoginResult.ProfileID, switch the active profile before WaitSSOLogin, and verify the email is written to the captured profile rather than the new active profile. Also cover the empty-ID fallback path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/ui/services/connection.go` around lines 260 - 284, 添加回归测试覆盖 SSO 等待期间切换
profile 的场景:捕获 LoginResult.ProfileID,在调用 WaitSSOLogin 前切换活动 profile,并验证邮箱通过该
captured profile ID 写入原 profile 而非新活动 profile。另增加 ProfileID 为空时的测试,确认邮箱写入当前活动
profile 的 fallback 路径。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@client/ui/services/connection.go`:
- Around line 260-284: 添加回归测试覆盖 SSO 等待期间切换 profile 的场景:捕获
LoginResult.ProfileID,在调用 WaitSSOLogin 前切换活动 profile,并验证邮箱通过该 captured profile
ID 写入原 profile 而非新活动 profile。另增加 ProfileID 为空时的测试,确认邮箱写入当前活动 profile 的 fallback
路径。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a58bee2f-d820-4546-abd4-b43002a1b2ed

📥 Commits

Reviewing files that changed from the base of the PR and between 9ebf10b and c0a1d95.

📒 Files selected for processing (2)
  • client/ui/services/connection.go
  • client/ui/services/profile.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants