mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-06 13:39:07 +02:00
Merge branch 'main' into fix/pkce-flow-session-extend
Both sides extended the OAuth flow entry points: main threads a login hint through GetOAuthFlow/NewOAuthFlow, this branch threads a sessionExtend flag. The merged signatures carry both. client/android/login.go keeps main's structure — the hint reaches the flow through GetOAuthFlow rather than a loginHintSetter assertion, profile email helpers live in client/mobile, and runOAuthFlow is the shared driver — with this branch's sessionExtend plumbing and wrong-account retry layered on top. The branch's runInteractiveFlow is dropped in favour of runOAuthFlow. client/android/ssh_client.go is new from main and merged without conflict, but calls NewOAuthFlow; SSH login is a fresh login, so it passes false. management.pb.go regenerated with the pinned protoc-gen-go v1.26 from generate.sh. The .proto merged cleanly: main's LazyState and proxy_embedded are disjoint from this branch's SessionExtend field.
This commit is contained in:
+36
-32
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/auth"
|
||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||
"github.com/netbirdio/netbird/client/mobile"
|
||||
"github.com/netbirdio/netbird/client/system"
|
||||
)
|
||||
|
||||
@@ -181,7 +182,7 @@ func (a *Auth) login(urlOpener URLOpener, isAndroidTV bool) error {
|
||||
// Stored after Login, not before: a rejected token must not leave a hint
|
||||
// pointing at an account that cannot be used.
|
||||
if email != "" && a.cfgPath != "" {
|
||||
if err := writeProfileEmail(a.cfgPath, email); err != nil {
|
||||
if err := mobile.WriteProfileEmail(a.cfgPath, email); err != nil {
|
||||
log.Warnf("failed to store profile account email: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -191,13 +192,6 @@ func (a *Auth) login(urlOpener URLOpener, isAndroidTV bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// loginHintSetter is implemented by both concrete flows (PKCE and device code)
|
||||
// but absent from the OAuthFlow interface, hence the assertion below — the same
|
||||
// way internal/auth wires it in authenticateWithPKCEFlow.
|
||||
type loginHintSetter interface {
|
||||
SetLoginHint(hint string)
|
||||
}
|
||||
|
||||
func (a *Auth) foregroundGetTokenInfo(authClient *auth.Auth, urlOpener URLOpener, isAndroidTV bool) (*auth.TokenInfo, error) {
|
||||
return a.foregroundGetTokenInfoFlow(authClient, urlOpener, isAndroidTV, false)
|
||||
}
|
||||
@@ -207,25 +201,14 @@ func (a *Auth) foregroundGetTokenInfo(authClient *auth.Auth, urlOpener URLOpener
|
||||
// it can rule out a silent authorization the IdP could answer from an unrelated
|
||||
// account. See PKCEAuthorizationFlowRequest.
|
||||
func (a *Auth) foregroundGetTokenInfoFlow(authClient *auth.Auth, urlOpener URLOpener, isAndroidTV bool, sessionExtend bool) (*auth.TokenInfo, error) {
|
||||
oAuthFlow, err := authClient.GetOAuthFlow(a.ctx, isAndroidTV, sessionExtend)
|
||||
hint := profileLoginHint(a.cfgPath)
|
||||
|
||||
oAuthFlow, err := authClient.GetOAuthFlow(a.ctx, isAndroidTV, sessionExtend, hint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get OAuth flow: %v", err)
|
||||
}
|
||||
|
||||
// An empty hint is deliberate, not a fallback: a fresh profile leaves the
|
||||
// choice to the IdP. Switching accounts is done by switching or removing
|
||||
// profiles, not by logging out — logout keeps the email.
|
||||
hint := ""
|
||||
if a.cfgPath != "" {
|
||||
hint = readProfileEmail(a.cfgPath)
|
||||
}
|
||||
if hint != "" {
|
||||
if setter, ok := oAuthFlow.(loginHintSetter); ok {
|
||||
setter.SetLoginHint(hint)
|
||||
}
|
||||
}
|
||||
|
||||
tokenInfo, err := a.runInteractiveFlow(oAuthFlow, urlOpener)
|
||||
tokenInfo, err := runOAuthFlow(a.ctx, oAuthFlow, urlOpener, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -244,7 +227,7 @@ func (a *Auth) foregroundGetTokenInfoFlow(authClient *auth.Auth, urlOpener URLOp
|
||||
return tokenInfo, nil
|
||||
}
|
||||
|
||||
retryToken, err := a.runInteractiveFlow(retryFlow, urlOpener)
|
||||
retryToken, err := runOAuthFlow(a.ctx, retryFlow, urlOpener, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -255,19 +238,40 @@ func (a *Auth) foregroundGetTokenInfoFlow(authClient *auth.Auth, urlOpener URLOp
|
||||
return retryToken, nil
|
||||
}
|
||||
|
||||
// runInteractiveFlow requests the authorization info, hands the URL to the
|
||||
// user and blocks until the token comes back.
|
||||
func (a *Auth) runInteractiveFlow(oAuthFlow auth.OAuthFlow, urlOpener URLOpener) (*auth.TokenInfo, error) {
|
||||
flowInfo, err := oAuthFlow.RequestAuthInfo(context.TODO())
|
||||
// profileLoginHint returns the stored account email for the profile at cfgPath.
|
||||
// An empty hint is deliberate, not a fallback: a fresh profile leaves the
|
||||
// choice to the IdP. Switching accounts is done by switching or removing
|
||||
// profiles, not by logging out — logout keeps the email.
|
||||
func profileLoginHint(cfgPath string) string {
|
||||
if cfgPath == "" {
|
||||
return ""
|
||||
}
|
||||
return mobile.ReadProfileEmail(cfgPath)
|
||||
}
|
||||
|
||||
// runOAuthFlow drives an already acquired OAuth flow to a token: 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, urlOpener URLOpener, onWaiting func()) (*auth.TokenInfo, error) {
|
||||
flowInfo, err := flow.RequestAuthInfo(ctx)
|
||||
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 {
|
||||
return nil, fmt.Errorf("waiting for browser login failed: %v", err)
|
||||
return nil, fmt.Errorf("wait for token: %w", err)
|
||||
}
|
||||
|
||||
return &tokenInfo, nil
|
||||
|
||||
Reference in New Issue
Block a user