mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-12 08:29:06 +02:00
[android] Deduplicate the OAuth token flow and fix the SSH login hint
Extract the shared RequestAuthInfo -> Open -> WaitToken sequence from the login flow and the SSH JWT flow into runOAuthFlow. Open is now called synchronously by both flows, matching iOS; openers must post their UI work instead of blocking, which the app-side openers already do. The SSH flow read its login hint via profilemanager.GetLoginHint, which resolves desktop-layout files that the Android app never writes, so the hint was always empty and the device-code flow could prompt for account selection. Both flows now read the hint from the profile account file via the config path, taken from authSnapshot so a concurrent profile switch cannot pair one profile's config with another's hint.
This commit is contained in:
+34
-12
@@ -204,26 +204,48 @@ func (a *Auth) foregroundGetTokenInfo(authClient *auth.Auth, urlOpener URLOpener
|
|||||||
return nil, fmt.Errorf("failed to get OAuth flow: %v", err)
|
return nil, fmt.Errorf("failed to get OAuth flow: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// An empty hint is deliberate, not a fallback: a fresh or logged-out profile
|
return runOAuthFlow(a.ctx, oAuthFlow, profileLoginHint(a.cfgPath), urlOpener, nil)
|
||||||
// leaves the choice to the IdP, which is how accounts get switched.
|
}
|
||||||
if a.cfgPath != "" {
|
|
||||||
if hint := readProfileEmail(a.cfgPath); hint != "" {
|
// profileLoginHint returns the stored account email for the profile at cfgPath.
|
||||||
if setter, ok := oAuthFlow.(loginHintSetter); ok {
|
// An empty hint is deliberate, not a fallback: a fresh or logged-out profile
|
||||||
setter.SetLoginHint(hint)
|
// leaves the choice to the IdP, which is how accounts get switched.
|
||||||
}
|
func profileLoginHint(cfgPath string) string {
|
||||||
|
if cfgPath == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return readProfileEmail(cfgPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runOAuthFlow drives an already acquired OAuth flow to a token: applies the
|
||||||
|
// login hint, requests the flow info, presents the verification URL through
|
||||||
|
// the opener and waits for the browser round-trip. Open is called
|
||||||
|
// synchronously — it is what marks the surface as opened on the client side,
|
||||||
|
// and a fast token's OnLoginSuccess is a no-op until it has, so the dismissal
|
||||||
|
// would be dropped rather than delayed. Openers must therefore not block:
|
||||||
|
// they post their UI work and return. onWaiting, when set, runs after the URL
|
||||||
|
// is shown, right before the blocking wait.
|
||||||
|
func runOAuthFlow(ctx context.Context, flow auth.OAuthFlow, hint string, urlOpener URLOpener, onWaiting func()) (*auth.TokenInfo, error) {
|
||||||
|
if hint != "" {
|
||||||
|
if setter, ok := flow.(loginHintSetter); ok {
|
||||||
|
setter.SetLoginHint(hint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flowInfo, err := oAuthFlow.RequestAuthInfo(context.TODO())
|
flowInfo, err := flow.RequestAuthInfo(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("getting a request OAuth flow info failed: %v", err)
|
return nil, fmt.Errorf("request auth info: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go urlOpener.Open(flowInfo.VerificationURIComplete, flowInfo.UserCode)
|
urlOpener.Open(flowInfo.VerificationURIComplete, flowInfo.UserCode)
|
||||||
|
|
||||||
tokenInfo, err := oAuthFlow.WaitToken(a.ctx, flowInfo)
|
if onWaiting != nil {
|
||||||
|
onWaiting()
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenInfo, err := flow.WaitToken(ctx, flowInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("waiting for browser login failed: %v", err)
|
return nil, fmt.Errorf("wait for token: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &tokenInfo, nil
|
return &tokenInfo, nil
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ func (s *SSHClient) Connect(host string, port int, user, password string) error
|
|||||||
return fmt.Errorf("invalid port: %d", port)
|
return fmt.Errorf("invalid port: %d", port)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, _, cc := s.nb.stateSnapshot()
|
cfg, cfgPath, cc := s.nb.authSnapshot()
|
||||||
if cc == nil {
|
if cc == nil {
|
||||||
return errors.New("netbird client not running")
|
return errors.New("netbird client not running")
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@ func (s *SSHClient) Connect(host string, port int, user, password string) error
|
|||||||
serverType := detectServerType(host, port)
|
serverType := detectServerType(host, port)
|
||||||
log.Debugf("SSH server type: %s", serverType)
|
log.Debugf("SSH server type: %s", serverType)
|
||||||
|
|
||||||
authMethods, hostKeyCallback, err := s.buildAuth(cfg, engine, serverType, password)
|
authMethods, hostKeyCallback, err := s.buildAuth(cfg, cfgPath, engine, serverType, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -345,12 +345,12 @@ func (s *SSHClient) startSession(cols, rows int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SSHClient) buildAuth(cfg *profilemanager.Config, engine *internal.Engine,
|
func (s *SSHClient) buildAuth(cfg *profilemanager.Config, cfgPath string, engine *internal.Engine,
|
||||||
serverType detection.ServerType, password string) ([]gossh.AuthMethod, gossh.HostKeyCallback, error) {
|
serverType detection.ServerType, password string) ([]gossh.AuthMethod, gossh.HostKeyCallback, error) {
|
||||||
|
|
||||||
switch serverType {
|
switch serverType {
|
||||||
case detection.ServerTypeNetBirdJWT:
|
case detection.ServerTypeNetBirdJWT:
|
||||||
token, err := s.requestJWTToken(cfg)
|
token, err := s.requestJWTToken(cfg, cfgPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("jwt: %w", err)
|
return nil, nil, fmt.Errorf("jwt: %w", err)
|
||||||
}
|
}
|
||||||
@@ -465,7 +465,7 @@ func (s *SSHClient) tofuHostKeyCallback() (gossh.HostKeyCallback, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SSHClient) requestJWTToken(cfg *profilemanager.Config) (string, error) {
|
func (s *SSHClient) requestJWTToken(cfg *profilemanager.Config, cfgPath string) (string, error) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
urlOpener := s.urlOpener
|
urlOpener := s.urlOpener
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
@@ -476,29 +476,18 @@ func (s *SSHClient) requestJWTToken(cfg *profilemanager.Config) (string, error)
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
flow, err := auth.NewOAuthFlow(ctx, cfg, false, true, profilemanager.GetLoginHint())
|
flow, err := auth.NewOAuthFlow(ctx, cfg, false, true, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("create oauth flow: %w", err)
|
return "", fmt.Errorf("create oauth flow: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
flowInfo, err := flow.RequestAuthInfo(ctx)
|
// The status callback covers the browser round-trip, which would
|
||||||
|
// otherwise leave the terminal blank.
|
||||||
|
tokenInfo, err := runOAuthFlow(ctx, flow, profileLoginHint(cfgPath), urlOpener, func() {
|
||||||
|
s.notifyStatus("Waiting for browser authentication...")
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("request auth info: %w", err)
|
return "", err
|
||||||
}
|
|
||||||
|
|
||||||
// Called synchronously: Open is what marks the surface as opened on the
|
|
||||||
// client side, and OnLoginSuccess below is a no-op until it has. Starting
|
|
||||||
// both in their own goroutines let them race, so a fast token left the
|
|
||||||
// browser in front of the terminal.
|
|
||||||
urlOpener.Open(flowInfo.VerificationURIComplete, flowInfo.UserCode)
|
|
||||||
|
|
||||||
// WaitToken blocks for as long as the browser round-trip takes, so say so
|
|
||||||
// rather than leaving the terminal blank.
|
|
||||||
s.notifyStatus("Waiting for browser authentication...")
|
|
||||||
|
|
||||||
tokenInfo, err := flow.WaitToken(ctx, flowInfo)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("wait for token: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
token := tokenInfo.GetTokenToUse()
|
token := tokenInfo.GetTokenToUse()
|
||||||
|
|||||||
Reference in New Issue
Block a user