@@ -27,14 +27,16 @@ import (
27
27
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/flag"
28
28
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/log"
29
29
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/prerun"
30
+ "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/prompt"
30
31
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/telemetry"
32
+ "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/usage"
31
33
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/validate"
32
34
"github.com/pkg/browser"
33
35
"github.com/spf13/cobra"
34
36
"go.mongodb.org/atlas/auth"
35
37
)
36
38
37
- //go:generate go tool go.uber.org/mock/mockgen -typed -destination=login_mock_test.go -package=auth . LoginConfig
39
+ //go:generate go tool go.uber.org/mock/mockgen -typed -destination=login_mock_test.go -package=auth . LoginConfig,TrackAsker
38
40
39
41
type SetSaver interface {
40
42
Set (string , any )
@@ -49,20 +51,119 @@ type LoginConfig interface {
49
51
ProjectID () string
50
52
}
51
53
54
+ type TrackAsker interface {
55
+ TrackAsk ([]* survey.Question , any , ... survey.AskOpt ) error
56
+ TrackAskOne (survey.Prompt , any , ... survey.AskOpt ) error
57
+ }
58
+
59
+ const (
60
+ userAccountAuth = "UserAccount"
61
+ apiKeysAuth = "APIKeys"
62
+ atlasName = "atlas"
63
+ )
64
+
52
65
var (
53
66
ErrProjectIDNotFound = errors .New ("project is inaccessible. You either don't have access to this project or the project doesn't exist" )
54
67
ErrOrgIDNotFound = errors .New ("organization is inaccessible. You don't have access to this organization or the organization doesn't exist" )
68
+ authTypeOptions = []string {userAccountAuth , apiKeysAuth }
69
+ authTypeDescription = map [string ]string {
70
+ userAccountAuth : "(best for getting started)" ,
71
+ apiKeysAuth : "(for existing automations)" ,
72
+ }
55
73
)
56
74
57
75
type LoginOpts struct {
58
76
cli.DefaultSetterOpts
59
77
cli.RefresherOpts
78
+ cli.DigestConfigOpts
60
79
AccessToken string
61
80
RefreshToken string
62
81
IsGov bool
63
82
NoBrowser bool
83
+ authType string
84
+ force bool
64
85
SkipConfig bool
65
86
config LoginConfig
87
+ Asker TrackAsker
88
+ }
89
+
90
+ func (opts * LoginOpts ) promptAuthType () error {
91
+ if opts .force {
92
+ opts .authType = userAccountAuth
93
+ return nil
94
+ }
95
+ authTypePrompt := & survey.Select {
96
+ Message : "Select authentication type:" ,
97
+ Options : authTypeOptions ,
98
+ Default : userAccountAuth ,
99
+ Description : func (value string , _ int ) string {
100
+ return authTypeDescription [value ]
101
+ },
102
+ }
103
+ return opts .Asker .TrackAskOne (authTypePrompt , & opts .authType )
104
+ }
105
+
106
+ func (opts * LoginOpts ) SetUpAccess () {
107
+ switch {
108
+ case opts .IsGov :
109
+ opts .Service = config .CloudGovService
110
+ default :
111
+ opts .Service = config .CloudService
112
+ }
113
+
114
+ opts .SetUpServiceAndKeys ()
115
+ }
116
+
117
+ func (opts * LoginOpts ) runAPIKeysLogin (ctx context.Context ) error {
118
+ _ , _ = fmt .Fprintf (opts .OutWriter , `You are configuring a profile for %s.
119
+
120
+ All values are optional and you can use environment variables (MONGODB_ATLAS_*) instead.
121
+
122
+ Enter [?] on any option to get help.
123
+
124
+ ` , atlasName )
125
+
126
+ q := prompt .AccessQuestions ()
127
+ if err := opts .Asker .TrackAsk (q , opts ); err != nil {
128
+ return err
129
+ }
130
+ opts .SetUpAccess ()
131
+
132
+ if err := opts .InitStore (ctx ); err != nil {
133
+ return err
134
+ }
135
+
136
+ if config .IsAccessSet () {
137
+ if err := opts .AskOrg (); err != nil {
138
+ return err
139
+ }
140
+ if err := opts .AskProject (); err != nil {
141
+ return err
142
+ }
143
+ } else {
144
+ q := prompt .TenantQuestions ()
145
+ if err := opts .Asker .TrackAsk (q , opts ); err != nil {
146
+ return err
147
+ }
148
+ }
149
+ opts .SetUpProject ()
150
+ opts .SetUpOrg ()
151
+
152
+ if err := opts .Asker .TrackAsk (opts .DefaultQuestions (), opts ); err != nil {
153
+ return err
154
+ }
155
+ opts .SetUpOutput ()
156
+
157
+ if err := opts .config .Save (); err != nil {
158
+ return err
159
+ }
160
+
161
+ _ , _ = fmt .Fprintf (opts .OutWriter , "\n Your profile is now configured.\n " )
162
+ if config .Name () != config .DefaultProfile {
163
+ _ , _ = fmt .Fprintf (opts .OutWriter , "To use this profile, you must set the flag [-%s %s] for every command.\n " , flag .ProfileShort , config .Name ())
164
+ }
165
+ _ , _ = fmt .Fprintf (opts .OutWriter , "You can use [%s config set] to change these settings at a later time.\n " , atlasName )
166
+ return nil
66
167
}
67
168
68
169
// SyncWithOAuthAccessProfile returns a function that is synchronizing the oauth settings
@@ -102,7 +203,7 @@ func (opts *LoginOpts) SyncWithOAuthAccessProfile(c LoginConfig) func() error {
102
203
}
103
204
}
104
205
105
- func (opts * LoginOpts ) LoginRun (ctx context.Context ) error {
206
+ func (opts * LoginOpts ) runUserAccountLogin (ctx context.Context ) error {
106
207
if err := opts .oauthFlow (ctx ); err != nil {
107
208
return err
108
209
}
@@ -141,6 +242,18 @@ func (opts *LoginOpts) LoginRun(ctx context.Context) error {
141
242
return nil
142
243
}
143
244
245
+ func (opts * LoginOpts ) LoginRun (ctx context.Context ) error {
246
+ if err := opts .promptAuthType (); err != nil {
247
+ return fmt .Errorf ("failed to select authentication type: %w" , err )
248
+ }
249
+
250
+ if opts .authType == apiKeysAuth {
251
+ return opts .runAPIKeysLogin (ctx )
252
+ }
253
+
254
+ return opts .runUserAccountLogin (ctx )
255
+ }
256
+
144
257
func (opts * LoginOpts ) checkProfile (ctx context.Context ) error {
145
258
if err := opts .InitStore (ctx ); err != nil {
146
259
return err
@@ -223,6 +336,10 @@ func (opts *LoginOpts) handleBrowser(uri string) {
223
336
return
224
337
}
225
338
339
+ if ! opts .force {
340
+ _ , _ = fmt .Fprintf (opts .OutWriter , "\n Press Enter to open the browser to complete authentication..." )
341
+ _ , _ = fmt .Scanln ()
342
+ }
226
343
if errBrowser := browser .OpenURL (uri ); errBrowser != nil {
227
344
_ , _ = log .Warningln ("There was an issue opening your browser" )
228
345
}
@@ -243,7 +360,7 @@ func (opts *LoginOpts) oauthFlow(ctx context.Context) error {
243
360
}
244
361
245
362
accessToken , _ , err := opts .PollToken (ctx , code )
246
- if retry , errRetry := shouldRetryAuthenticate (err , newRegenerationPrompt ()); errRetry != nil {
363
+ if retry , errRetry := opts . shouldRetryAuthenticate (err , newRegenerationPrompt ()); errRetry != nil {
247
364
return errRetry
248
365
} else if retry {
249
366
continue
@@ -258,11 +375,11 @@ func (opts *LoginOpts) oauthFlow(ctx context.Context) error {
258
375
}
259
376
}
260
377
261
- func shouldRetryAuthenticate (err error , p survey.Prompt ) (retry bool , errSurvey error ) {
378
+ func ( opts * LoginOpts ) shouldRetryAuthenticate (err error , p survey.Prompt ) (retry bool , errSurvey error ) {
262
379
if err == nil || ! auth .IsTimeoutErr (err ) {
263
380
return false , nil
264
381
}
265
- err = telemetry .TrackAskOne (p , & retry )
382
+ err = opts . Asker .TrackAskOne (p , & retry )
266
383
return retry , err
267
384
}
268
385
@@ -290,7 +407,9 @@ func (opts *LoginOpts) LoginPreRun(ctx context.Context) func() error {
290
407
}
291
408
292
409
func LoginBuilder () * cobra.Command {
293
- opts := & LoginOpts {}
410
+ opts := & LoginOpts {
411
+ Asker : & telemetry.Ask {},
412
+ }
294
413
295
414
cmd := & cobra.Command {
296
415
Use : "login" ,
@@ -316,8 +435,10 @@ func LoginBuilder() *cobra.Command {
316
435
}
317
436
318
437
cmd .Flags ().BoolVar (& opts .IsGov , "gov" , false , "Log in to Atlas for Government." )
319
- cmd .Flags ().BoolVar (& opts .NoBrowser , "noBrowser" , false , "Don't try to open a browser session." )
438
+ cmd .Flags ().BoolVar (& opts .NoBrowser , "noBrowser" , false , "Don't automatically open a browser session." )
320
439
cmd .Flags ().BoolVar (& opts .SkipConfig , "skipConfig" , false , "Skip profile configuration." )
321
440
_ = cmd .Flags ().MarkDeprecated ("skipConfig" , "if you configured a profile, the command skips the config step by default." )
441
+ cmd .Flags ().BoolVar (& opts .force , flag .Force , false , usage .Force )
442
+ _ = cmd .Flags ().MarkHidden (flag .Force )
322
443
return cmd
323
444
}
0 commit comments