Skip to content

Commit 75327d9

Browse files
authored
[client] Add login_hint to oidc flows (#4724)
1 parent c92e6c1 commit 75327d9

File tree

13 files changed

+109
-23
lines changed

13 files changed

+109
-23
lines changed

client/android/login.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ func (a *Auth) login(urlOpener URLOpener) error {
200200
}
201201

202202
func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*auth.TokenInfo, error) {
203-
oAuthFlow, err := auth.NewOAuthFlow(a.ctx, a.config, false)
203+
oAuthFlow, err := auth.NewOAuthFlow(a.ctx, a.config, false, "")
204204
if err != nil {
205205
return nil, err
206206
}

client/cmd/login.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ func doDaemonLogin(ctx context.Context, cmd *cobra.Command, providedSetupKey str
106106
Username: &username,
107107
}
108108

109+
profileState, err := pm.GetProfileState(activeProf.Name)
110+
if err != nil {
111+
log.Debugf("failed to get profile state for login hint: %v", err)
112+
} else if profileState.Email != "" {
113+
loginRequest.Hint = &profileState.Email
114+
}
115+
109116
if rootCmd.PersistentFlags().Changed(preSharedKeyFlag) {
110117
loginRequest.OptionalPreSharedKey = &preSharedKey
111118
}
@@ -241,7 +248,7 @@ func doForegroundLogin(ctx context.Context, cmd *cobra.Command, setupKey string,
241248
return fmt.Errorf("read config file %s: %v", configFilePath, err)
242249
}
243250

244-
err = foregroundLogin(ctx, cmd, config, setupKey)
251+
err = foregroundLogin(ctx, cmd, config, setupKey, activeProf.Name)
245252
if err != nil {
246253
return fmt.Errorf("foreground login failed: %v", err)
247254
}
@@ -269,7 +276,7 @@ func handleSSOLogin(ctx context.Context, cmd *cobra.Command, loginResp *proto.Lo
269276
return nil
270277
}
271278

272-
func foregroundLogin(ctx context.Context, cmd *cobra.Command, config *profilemanager.Config, setupKey string) error {
279+
func foregroundLogin(ctx context.Context, cmd *cobra.Command, config *profilemanager.Config, setupKey, profileName string) error {
273280
needsLogin := false
274281

275282
err := WithBackOff(func() error {
@@ -286,7 +293,7 @@ func foregroundLogin(ctx context.Context, cmd *cobra.Command, config *profileman
286293

287294
jwtToken := ""
288295
if setupKey == "" && needsLogin {
289-
tokenInfo, err := foregroundGetTokenInfo(ctx, cmd, config)
296+
tokenInfo, err := foregroundGetTokenInfo(ctx, cmd, config, profileName)
290297
if err != nil {
291298
return fmt.Errorf("interactive sso login failed: %v", err)
292299
}
@@ -315,8 +322,17 @@ func foregroundLogin(ctx context.Context, cmd *cobra.Command, config *profileman
315322
return nil
316323
}
317324

318-
func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *profilemanager.Config) (*auth.TokenInfo, error) {
319-
oAuthFlow, err := auth.NewOAuthFlow(ctx, config, isUnixRunningDesktop())
325+
func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *profilemanager.Config, profileName string) (*auth.TokenInfo, error) {
326+
hint := ""
327+
pm := profilemanager.NewProfileManager()
328+
profileState, err := pm.GetProfileState(profileName)
329+
if err != nil {
330+
log.Debugf("failed to get profile state for login hint: %v", err)
331+
} else if profileState.Email != "" {
332+
hint = profileState.Email
333+
}
334+
335+
oAuthFlow, err := auth.NewOAuthFlow(ctx, config, isUnixRunningDesktop(), hint)
320336
if err != nil {
321337
return nil, err
322338
}

client/cmd/up.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command, activeProf *pr
185185

186186
_, _ = profilemanager.UpdateOldManagementURL(ctx, config, configFilePath)
187187

188-
err = foregroundLogin(ctx, cmd, config, providedSetupKey)
188+
err = foregroundLogin(ctx, cmd, config, providedSetupKey, activeProf.Name)
189189
if err != nil {
190190
return fmt.Errorf("foreground login failed: %v", err)
191191
}
@@ -286,6 +286,13 @@ func doDaemonUp(ctx context.Context, cmd *cobra.Command, client proto.DaemonServ
286286
loginRequest.ProfileName = &activeProf.Name
287287
loginRequest.Username = &username
288288

289+
profileState, err := pm.GetProfileState(activeProf.Name)
290+
if err != nil {
291+
log.Debugf("failed to get profile state for login hint: %v", err)
292+
} else if profileState.Email != "" {
293+
loginRequest.Hint = &profileState.Email
294+
}
295+
289296
var loginErr error
290297
var loginResp *proto.LoginResponse
291298

client/internal/auth/device_flow.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,34 @@ func (d *DeviceAuthorizationFlow) RequestAuthInfo(ctx context.Context) (AuthFlow
128128
deviceCode.VerificationURIComplete = deviceCode.VerificationURI
129129
}
130130

131+
if d.providerConfig.LoginHint != "" {
132+
deviceCode.VerificationURIComplete = appendLoginHint(deviceCode.VerificationURIComplete, d.providerConfig.LoginHint)
133+
if deviceCode.VerificationURI != "" {
134+
deviceCode.VerificationURI = appendLoginHint(deviceCode.VerificationURI, d.providerConfig.LoginHint)
135+
}
136+
}
137+
131138
return deviceCode, err
132139
}
133140

141+
func appendLoginHint(uri, loginHint string) string {
142+
if uri == "" || loginHint == "" {
143+
return uri
144+
}
145+
146+
parsedURL, err := url.Parse(uri)
147+
if err != nil {
148+
log.Debugf("failed to parse verification URI for login_hint: %v", err)
149+
return uri
150+
}
151+
152+
query := parsedURL.Query()
153+
query.Set("login_hint", loginHint)
154+
parsedURL.RawQuery = query.Encode()
155+
156+
return parsedURL.String()
157+
}
158+
134159
func (d *DeviceAuthorizationFlow) requestToken(info AuthFlowInfo) (TokenRequestResponse, error) {
135160
form := url.Values{}
136161
form.Add("client_id", d.providerConfig.ClientID)

client/internal/auth/oauth.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,32 +66,34 @@ func (t TokenInfo) GetTokenToUse() string {
6666
// and if that also fails, the authentication process is deemed unsuccessful
6767
//
6868
// On Linux distros without desktop environment support, it only tries to initialize the Device Code Flow
69-
func NewOAuthFlow(ctx context.Context, config *profilemanager.Config, isUnixDesktopClient bool) (OAuthFlow, error) {
69+
func NewOAuthFlow(ctx context.Context, config *profilemanager.Config, isUnixDesktopClient bool, hint string) (OAuthFlow, error) {
7070
if (runtime.GOOS == "linux" || runtime.GOOS == "freebsd") && !isUnixDesktopClient {
71-
return authenticateWithDeviceCodeFlow(ctx, config)
71+
return authenticateWithDeviceCodeFlow(ctx, config, hint)
7272
}
7373

74-
pkceFlow, err := authenticateWithPKCEFlow(ctx, config)
74+
pkceFlow, err := authenticateWithPKCEFlow(ctx, config, hint)
7575
if err != nil {
76-
// fallback to device code flow
7776
log.Debugf("failed to initialize pkce authentication with error: %v\n", err)
7877
log.Debug("falling back to device code flow")
79-
return authenticateWithDeviceCodeFlow(ctx, config)
78+
return authenticateWithDeviceCodeFlow(ctx, config, hint)
8079
}
8180
return pkceFlow, nil
8281
}
8382

8483
// authenticateWithPKCEFlow initializes the Proof Key for Code Exchange flow auth flow
85-
func authenticateWithPKCEFlow(ctx context.Context, config *profilemanager.Config) (OAuthFlow, error) {
84+
func authenticateWithPKCEFlow(ctx context.Context, config *profilemanager.Config, hint string) (OAuthFlow, error) {
8685
pkceFlowInfo, err := internal.GetPKCEAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL, config.ClientCertKeyPair)
8786
if err != nil {
8887
return nil, fmt.Errorf("getting pkce authorization flow info failed with error: %v", err)
8988
}
89+
90+
pkceFlowInfo.ProviderConfig.LoginHint = hint
91+
9092
return NewPKCEAuthorizationFlow(pkceFlowInfo.ProviderConfig)
9193
}
9294

9395
// authenticateWithDeviceCodeFlow initializes the Device Code auth Flow
94-
func authenticateWithDeviceCodeFlow(ctx context.Context, config *profilemanager.Config) (OAuthFlow, error) {
96+
func authenticateWithDeviceCodeFlow(ctx context.Context, config *profilemanager.Config, hint string) (OAuthFlow, error) {
9597
deviceFlowInfo, err := internal.GetDeviceAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
9698
if err != nil {
9799
switch s, ok := gstatus.FromError(err); {
@@ -107,5 +109,7 @@ func authenticateWithDeviceCodeFlow(ctx context.Context, config *profilemanager.
107109
}
108110
}
109111

112+
deviceFlowInfo.ProviderConfig.LoginHint = hint
113+
110114
return NewDeviceAuthorizationFlow(deviceFlowInfo.ProviderConfig)
111115
}

client/internal/auth/pkce_flow.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ func (p *PKCEAuthorizationFlow) RequestAuthInfo(ctx context.Context) (AuthFlowIn
109109
params = append(params, oauth2.SetAuthURLParam("max_age", "0"))
110110
}
111111
}
112+
if p.providerConfig.LoginHint != "" {
113+
params = append(params, oauth2.SetAuthURLParam("login_hint", p.providerConfig.LoginHint))
114+
}
112115

113116
authURL := p.oAuthConfig.AuthCodeURL(state, params...)
114117

client/internal/device_auth.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ type DeviceAuthProviderConfig struct {
3838
Scope string
3939
// UseIDToken indicates if the id token should be used for authentication
4040
UseIDToken bool
41+
// LoginHint is used to pre-fill the email/username field during authentication
42+
LoginHint string
4143
}
4244

4345
// GetDeviceAuthorizationFlowInfo initialize a DeviceAuthorizationFlow instance and return with it

client/internal/pkce_auth.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ type PKCEAuthProviderConfig struct {
4444
DisablePromptLogin bool
4545
// LoginFlag is used to configure the PKCE flow login behavior
4646
LoginFlag common.LoginFlag
47+
// LoginHint is used to pre-fill the email/username field during authentication
48+
LoginHint string
4749
}
4850

4951
// GetPKCEAuthorizationFlowInfo initialize a PKCEAuthorizationFlow instance and return with it

client/ios/NetBirdSDK/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ func (c *Client) LoginForMobile() string {
228228
ConfigPath: c.cfgFile,
229229
})
230230

231-
oAuthFlow, err := auth.NewOAuthFlow(ctx, cfg, false)
231+
oAuthFlow, err := auth.NewOAuthFlow(ctx, cfg, false, "")
232232
if err != nil {
233233
return err.Error()
234234
}

client/proto/daemon.pb.go

Lines changed: 16 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)