mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-10 23:49:09 +02:00
[client] Pass stored email as login hint from UI and keep it on logout (#7199)
* [client] Pass stored email as login hint from UI and keep it on logout Follow the CLI pattern: the Wails UI now reads the account email from the user-owned profile state file and passes it as the OIDC login_hint on login and session extend, since the daemon-side fallback runs as root and cannot see the user's state file. Logout no longer deletes the stored email, so a later login preselects the account at the IdP; profile removal remains the operation that deletes it. * [client] Log ignored profile lookup errors in extend-session hint fallback
This commit is contained in:
@@ -87,9 +87,10 @@ func (pm *ProfileManager) SetActiveProfileState(state *ProfileState) error {
|
|||||||
|
|
||||||
// RemoveProfileState deletes the per-profile state file (which holds the
|
// RemoveProfileState deletes the per-profile state file (which holds the
|
||||||
// account email used for the SSO login hint and the UI display). Called after
|
// account email used for the SSO login hint and the UI display). Called after
|
||||||
// a successful logout so a logged-out profile no longer shows a stale account
|
// profile removal; logout keeps the file so the next login can pass the email
|
||||||
// email. The state file only stores the email, so deleting it is equivalent to
|
// as the login_hint. The state file only stores the email, so deleting it is
|
||||||
// clearing it; the next SSO login recreates it. A missing file is not an error.
|
// equivalent to clearing it; the next SSO login recreates it. A missing file
|
||||||
|
// is not an error.
|
||||||
func (pm *ProfileManager) RemoveProfileState(profileName string) error {
|
func (pm *ProfileManager) RemoveProfileState(profileName string) error {
|
||||||
configDir, err := getConfigDir()
|
configDir, err := getConfigDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
gstatus "google.golang.org/grpc/status"
|
gstatus "google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,9 +62,19 @@ func (s *Session) RequestExtend(ctx context.Context, p ExtendStartParams) (Exten
|
|||||||
|
|
||||||
// a request from the UI implies a graphical session, which the daemon cannot detect itself
|
// a request from the UI implies a graphical session, which the daemon cannot detect itself
|
||||||
req := &proto.RequestExtendAuthSessionRequest{HasGraphicalSession: true}
|
req := &proto.RequestExtendAuthSessionRequest{HasGraphicalSession: true}
|
||||||
if p.Hint != "" {
|
hint := p.Hint
|
||||||
h := p.Hint
|
if hint == "" {
|
||||||
req.Hint = &h
|
pm := profilemanager.NewProfileManager()
|
||||||
|
if active, perr := pm.GetActiveProfile(); perr != nil {
|
||||||
|
log.Debugf("failed to get active profile for login hint: %v", perr)
|
||||||
|
} else if state, serr := pm.GetProfileState(active.ID); serr != nil {
|
||||||
|
log.Debugf("failed to get profile state for login hint: %v", serr)
|
||||||
|
} else {
|
||||||
|
hint = state.Email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hint != "" {
|
||||||
|
req.Hint = &hint
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := cli.RequestExtendAuthSession(ctx, req)
|
resp, err := cli.RequestExtendAuthSession(ctx, req)
|
||||||
|
|||||||
@@ -123,8 +123,16 @@ func (s *Connection) Login(ctx context.Context, p LoginParams) (LoginResult, err
|
|||||||
if p.PreSharedKey != "" {
|
if p.PreSharedKey != "" {
|
||||||
req.OptionalPreSharedKey = ptrStr(p.PreSharedKey)
|
req.OptionalPreSharedKey = ptrStr(p.PreSharedKey)
|
||||||
}
|
}
|
||||||
if p.Hint != "" {
|
hint := p.Hint
|
||||||
req.Hint = ptrStr(p.Hint)
|
if hint == "" && profileID != "" {
|
||||||
|
if state, serr := profilemanager.NewProfileManager().GetProfileState(profilemanager.ID(profileID)); serr == nil {
|
||||||
|
hint = state.Email
|
||||||
|
} else {
|
||||||
|
log.Debugf("failed to get profile state for login hint: %v", serr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hint != "" {
|
||||||
|
req.Hint = ptrStr(hint)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := cli.Login(ctx, req)
|
resp, err := cli.Login(ctx, req)
|
||||||
@@ -228,16 +236,6 @@ func (s *Connection) Logout(ctx context.Context, p LogoutParams) error {
|
|||||||
return s.classifyDaemonError(err)
|
return s.classifyDaemonError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The daemon runs as root and can't reach the user-owned per-profile state
|
|
||||||
// file holding the account email (see Profiles.List), so clear the stale
|
|
||||||
// email here; the next SSO login recreates it.
|
|
||||||
if p.ProfileName != "" {
|
|
||||||
if err := profilemanager.NewProfileManager().RemoveProfileState(p.ProfileName); err != nil {
|
|
||||||
// Non-fatal: the logout itself succeeded.
|
|
||||||
log.Warnf("failed to remove profile state for %s: %v", p.ProfileName, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,7 +259,7 @@ func (s *Connection) waitSSOLogin(ctx context.Context, p WaitSSOParams) (string,
|
|||||||
|
|
||||||
// Persist the account email the same way the CLI does after its own
|
// Persist the account email the same way the CLI does after its own
|
||||||
// WaitSSOLogin: the daemon returns it but cannot store it, since it runs as
|
// WaitSSOLogin: the daemon returns it but cannot store it, since it runs as
|
||||||
// root and the per-profile state file is user-owned (see Logout below).
|
// root and the per-profile state file is user-owned (see Profiles.List).
|
||||||
// Without this the profile has no email, so Profiles.List shows no account
|
// Without this the profile has no email, so Profiles.List shows no account
|
||||||
// and later logins and session extends go out without a login_hint —
|
// and later logins and session extends go out without a login_hint —
|
||||||
// leaving the IdP to guess which account was meant.
|
// leaving the IdP to guess which account was meant.
|
||||||
|
|||||||
@@ -162,8 +162,9 @@ func (s *Profiles) Remove(ctx context.Context, p ProfileRef) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The daemon deletes what it owns but runs as root, so it leaves the
|
// 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
|
// user-owned state file holding the account email behind. Logout keeps the
|
||||||
// Connection.Logout). Legacy profiles are keyed by name rather than by a
|
// email on purpose so later logins can pass it as the login_hint; profile
|
||||||
|
// removal is what deletes it. Legacy profiles are keyed by name rather than by a
|
||||||
// generated ID, so a recreated profile of the same name would inherit the
|
// generated ID, so a recreated profile of the same name would inherit the
|
||||||
// deleted one's email and offer it as the login_hint.
|
// deleted one's email and offer it as the login_hint.
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in New Issue
Block a user