Skip to content

Commit da131fe

Browse files
duedares-rvjdependabot[bot]stevenwong-okta
authored
Added new login flow. Respect domain flag on auth0 login command. (#1038)
* Added use case to ensure if domain if passed in cli, it's populated to device login flow Signed-off-by: Rajat Bajaj <[email protected]> * Updated login flow, improved validations, added relevant comments. * minor update to flow * Added test cases for new login flow * Updated auth_test GetDeviceCode func call * Convert if-else clause to switch case * Fixed linitng issues * Bump golang.org/x/net from 0.26.0 to 0.27.0 (#1035) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.26.0 to 0.27.0. - [Commits](golang/net@v0.26.0...v0.27.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump github.com/auth0/go-auth0 from 1.7.0 to 1.8.0 (#1036) Bumps [github.com/auth0/go-auth0](https://github.com/auth0/go-auth0) from 1.7.0 to 1.8.0. - [Release notes](https://github.com/auth0/go-auth0/releases) - [Changelog](https://github.com/auth0/go-auth0/blob/main/CHANGELOG.md) - [Commits](auth0/go-auth0@v1.7.0...v1.8.0) --- updated-dependencies: - dependency-name: github.com/auth0/go-auth0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump rexml from 3.2.8 to 3.3.2 in /docs (#1041) Bumps [rexml](https://github.com/ruby/rexml) from 3.2.8 to 3.3.2. - [Release notes](https://github.com/ruby/rexml/releases) - [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md) - [Commits](ruby/rexml@v3.2.8...v3.3.2) --- updated-dependencies: - dependency-name: rexml dependency-type: indirect ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump github.com/lestrrat-go/jwx from 1.2.29 to 1.2.30 (#1042) Bumps [github.com/lestrrat-go/jwx](https://github.com/lestrrat-go/jwx) from 1.2.29 to 1.2.30. - [Release notes](https://github.com/lestrrat-go/jwx/releases) - [Changelog](https://github.com/lestrrat-go/jwx/blob/v1.2.30/Changes) - [Commits](lestrrat-go/jwx@v1.2.29...v1.2.30) --- updated-dependencies: - dependency-name: github.com/lestrrat-go/jwx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update codeowner file with new GitHub team name (#1039) * Converted sensitive value to env vars * Bump github.com/schollz/progressbar/v3 from 3.14.4 to 3.14.5 (#1043) Bumps [github.com/schollz/progressbar/v3](https://github.com/schollz/progressbar) from 3.14.4 to 3.14.5. - [Release notes](https://github.com/schollz/progressbar/releases) - [Commits](schollz/progressbar@v3.14.4...v3.14.5) --- updated-dependencies: - dependency-name: github.com/schollz/progressbar/v3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Removed a positive case * Bump rexml from 3.3.2 to 3.3.3 in /docs (#1044) Bumps [rexml](https://github.com/ruby/rexml) from 3.3.2 to 3.3.3. - [Release notes](https://github.com/ruby/rexml/releases) - [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md) - [Commits](ruby/rexml@v3.3.2...v3.3.3) --- updated-dependencies: - dependency-name: rexml dependency-type: indirect ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump github.com/hashicorp/hc-install from 0.7.0 to 0.8.0 (#1045) Bumps [github.com/hashicorp/hc-install](https://github.com/hashicorp/hc-install) from 0.7.0 to 0.8.0. - [Release notes](https://github.com/hashicorp/hc-install/releases) - [Commits](hashicorp/hc-install@v0.7.0...v0.8.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/hc-install dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump golang.org/x/net from 0.26.0 to 0.27.0 (#1035) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.26.0 to 0.27.0. - [Commits](golang/net@v0.26.0...v0.27.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump golang.org/x/net from 0.26.0 to 0.27.0 (#1035) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.26.0 to 0.27.0. - [Commits](golang/net@v0.26.0...v0.27.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump golang.org/x/net from 0.26.0 to 0.27.0 (#1035) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.26.0 to 0.27.0. - [Commits](golang/net@v0.26.0...v0.27.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * rebased branch with main --------- Signed-off-by: Rajat Bajaj <[email protected]> Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: stevenwong-okta <[email protected]>
1 parent 1659fe9 commit da131fe

File tree

6 files changed

+152
-28
lines changed

6 files changed

+152
-28
lines changed

internal/auth/auth.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,13 @@ var RequiredScopes = []string{
144144
// GetDeviceCode kicks-off the device authentication flow by requesting
145145
// a device code from Auth0. The returned state contains the
146146
// URI for the next step of the flow.
147-
func GetDeviceCode(ctx context.Context, httpClient *http.Client, additionalScopes []string) (State, error) {
147+
func GetDeviceCode(ctx context.Context, httpClient *http.Client, additionalScopes []string, domain string) (State, error) {
148148
a := credentials
149149

150150
data := url.Values{
151151
"client_id": []string{a.ClientID},
152152
"scope": []string{strings.Join(append(RequiredScopes, additionalScopes...), " ")},
153-
"audience": []string{a.Audience},
153+
"audience": []string{domain},
154154
}
155155

156156
request, err := http.NewRequestWithContext(

internal/auth/auth_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ func TestGetDeviceCode(t *testing.T) {
132132
u := url.URL{Scheme: "https", Host: parsedURL.Host, Path: "/oauth/device/code"}
133133
credentials.DeviceCodeEndpoint = u.String()
134134

135-
state, err := GetDeviceCode(context.Background(), ts.Client(), []string{})
135+
state, err := GetDeviceCode(context.Background(), ts.Client(), []string{}, "")
136136

137137
assert.NoError(t, err)
138138
assert.Equal(t, "device-code-here", state.DeviceCode)
@@ -180,7 +180,7 @@ func TestGetDeviceCode(t *testing.T) {
180180
u := url.URL{Scheme: "https", Host: parsedURL.Host, Path: "/oauth/device/code"}
181181
credentials.DeviceCodeEndpoint = u.String()
182182

183-
_, err = GetDeviceCode(context.Background(), ts.Client(), []string{})
183+
_, err = GetDeviceCode(context.Background(), ts.Client(), []string{}, "")
184184

185185
assert.EqualError(t, err, testCase.expect)
186186
})

internal/cli/cli.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func (c *cli) setupWithAuthentication(ctx context.Context) error {
7373
switch err {
7474
case config.ErrTokenMissingRequiredScopes:
7575
c.renderer.Warnf("Required scopes have changed. Please log in to re-authorize the CLI.\n")
76-
tenant, err = RunLoginAsUser(ctx, c, tenant.GetExtraRequestedScopes())
76+
tenant, err = RunLoginAsUser(ctx, c, tenant.GetExtraRequestedScopes(), "")
7777
if err != nil {
7878
return err
7979
}
@@ -95,7 +95,7 @@ func (c *cli) setupWithAuthentication(ctx context.Context) error {
9595
c.renderer.Warnf("Failed to renew access token: %s", err)
9696
c.renderer.Warnf("Please log in to re-authorize the CLI.\n")
9797

98-
tenant, err = RunLoginAsUser(ctx, c, tenant.GetExtraRequestedScopes())
98+
tenant, err = RunLoginAsUser(ctx, c, tenant.GetExtraRequestedScopes(), "")
9999
if err != nil {
100100
return err
101101
}
@@ -136,10 +136,6 @@ func canPrompt(cmd *cobra.Command) bool {
136136
return iostream.IsInputTerminal() && iostream.IsOutputTerminal() && !noInput
137137
}
138138

139-
func shouldPrompt(cmd *cobra.Command, flag *Flag) bool {
140-
return canPrompt(cmd) && !flag.IsSet(cmd)
141-
}
142-
143139
func shouldPromptWhenNoLocalFlagsSet(cmd *cobra.Command) bool {
144140
localFlagIsSet := false
145141
cmd.LocalFlags().VisitAll(func(f *pflag.Flag) {

internal/cli/flags.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ func shouldAsk(cmd *cobra.Command, f *Flag, isUpdate bool) bool {
287287
return shouldPromptWhenNoLocalFlagsSet(cmd)
288288
}
289289

290-
return shouldPrompt(cmd, f)
290+
return canPrompt(cmd) && !f.IsSet(cmd)
291291
}
292292

293293
func markFlagRequired(cmd *cobra.Command, f *Flag, isUpdate bool) error {

internal/cli/login.go

Lines changed: 90 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,6 @@ type LoginInputs struct {
5757
AdditionalScopes []string
5858
}
5959

60-
func (i *LoginInputs) isLoggingInAsAMachine() bool {
61-
return i.ClientID != "" || i.ClientSecret != "" || i.Domain != ""
62-
}
63-
6460
func (i *LoginInputs) isLoggingInWithAdditionalScopes() bool {
6561
return len(i.AdditionalScopes) > 0
6662
}
@@ -82,12 +78,65 @@ func loginCmd(cli *cli) *cobra.Command {
8278
RunE: func(cmd *cobra.Command, args []string) error {
8379
var selectedLoginType string
8480
const loginAsUser, loginAsMachine = "As a user", "As a machine"
81+
shouldLoginAsUser, shouldLoginAsMachine := false, false
82+
83+
/*
84+
Based on the initial inputs we'd like to determine if
85+
it's a machine login or a user login
86+
If we successfully determine it, we don't need to prompt the user.
87+
88+
The --no-input flag add strict restriction that we shall not take any further input after
89+
initial command.
90+
Hence, the flow diverges into two based on no-input flag's value.
91+
*/
92+
switch {
93+
case cli.noInput:
94+
switch {
95+
case inputs.Domain != "" && inputs.ClientSecret != "" && inputs.ClientID != "":
96+
// If all three fields are passed, machine login flag is set to true.
97+
shouldLoginAsMachine = true
98+
case inputs.Domain != "" && inputs.ClientSecret == "" && inputs.ClientID == "":
99+
/*
100+
The domain flag is common between Machine and User Login.
101+
If domain is passed without client-id and client-secret,
102+
it can be evaluated that it is a user login flow.
103+
*/
104+
shouldLoginAsUser = true
105+
case inputs.Domain != "" || inputs.ClientSecret != "" || inputs.ClientID != "":
106+
/*
107+
At this point, if AT LEAST one of the three flags are passed but not ALL three,
108+
we return an error since it's a no-input flow and it will need all three params
109+
for successful machine flow.
110+
Note that we already determined it's not a user login flow in the condition above.
111+
*/
112+
return fmt.Errorf("flags client-id, client-secret and domain are required together")
113+
default:
114+
/*
115+
If no flags are passed along with --no-input, it is defaulted to user login flow.
116+
*/
117+
shouldLoginAsUser = true
118+
}
119+
default:
120+
if inputs.ClientSecret != "" || inputs.ClientID != "" {
121+
/*
122+
If all three params are passed, we evaluate it as a Machine Login Flow.
123+
Else required params are prompted for.
124+
*/
125+
shouldLoginAsMachine = true
126+
}
127+
}
85128

86-
// We want to prompt if we don't pass the following flags:
87-
// --no-input, --scopes, --client-id, --client-secret, --domain.
88-
// Because then the prompt is unnecessary as we know the login type.
89-
shouldPrompt := !inputs.isLoggingInAsAMachine() && !cli.noInput && !inputs.isLoggingInWithAdditionalScopes()
90-
if shouldPrompt {
129+
// If additional scopes are passed we mark shouldLoginAsUser flag to be true.
130+
if inputs.isLoggingInWithAdditionalScopes() {
131+
shouldLoginAsUser = true
132+
}
133+
134+
/*
135+
If we are unable to determine if it's a user login or a machine login
136+
based on all the evaluation above, we go on to prompt the user and
137+
determine if it's LoginAsUser or LoginAsMachine
138+
*/
139+
if !shouldLoginAsUser && !shouldLoginAsMachine {
91140
cli.renderer.Output(
92141
fmt.Sprintf(
93142
"%s\n\n%s\n%s\n\n%s\n%s\n%s\n%s\n\n",
@@ -107,18 +156,16 @@ func loginCmd(cli *cli) *cobra.Command {
107156
"Authenticating as a user is recommended if performing ad-hoc operations or working locally.",
108157
"Alternatively, authenticating as a machine is recommended for automated workflows (ex:CI).",
109158
)
110-
input := prompt.SelectInput("", label, help, []string{loginAsUser, loginAsMachine}, loginAsUser, shouldPrompt)
159+
input := prompt.SelectInput("", label, help, []string{loginAsUser, loginAsMachine}, loginAsUser, true)
111160
if err := prompt.AskOne(input, &selectedLoginType); err != nil {
112161
return handleInputError(err)
113162
}
114163
}
115164

116165
ctx := cmd.Context()
117166

118-
// Allows to skip to user login if either the --no-input or --scopes flag is passed.
119-
shouldLoginAsUser := (cli.noInput && !inputs.isLoggingInAsAMachine()) || inputs.isLoggingInWithAdditionalScopes() || selectedLoginType == loginAsUser
120-
if shouldLoginAsUser {
121-
if _, err := RunLoginAsUser(ctx, cli, inputs.AdditionalScopes); err != nil {
167+
if shouldLoginAsUser || selectedLoginType == loginAsUser {
168+
if _, err := RunLoginAsUser(ctx, cli, inputs.AdditionalScopes, inputs.Domain); err != nil {
122169
return fmt.Errorf("failed to start the authentication process: %w", err)
123170
}
124171
} else {
@@ -143,8 +190,8 @@ func loginCmd(cli *cli) *cobra.Command {
143190
loginClientID.RegisterString(cmd, &inputs.ClientID, "")
144191
loginClientSecret.RegisterString(cmd, &inputs.ClientSecret, "")
145192
loginAdditionalScopes.RegisterStringSlice(cmd, &inputs.AdditionalScopes, []string{})
146-
cmd.MarkFlagsRequiredTogether("client-id", "client-secret", "domain")
147193
cmd.MarkFlagsMutuallyExclusive("client-id", "scopes")
194+
cmd.MarkFlagsMutuallyExclusive("client-secret", "scopes")
148195

149196
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
150197
_ = cmd.Flags().MarkHidden("tenant")
@@ -154,10 +201,36 @@ func loginCmd(cli *cli) *cobra.Command {
154201
return cmd
155202
}
156203

204+
func ensureAuth0URL(input string) (string, error) {
205+
if input == "" {
206+
return "https://*.auth0.com/api/v2/", nil
207+
}
208+
input = strings.TrimPrefix(input, "http://")
209+
input = strings.TrimPrefix(input, "https://")
210+
input = strings.TrimSuffix(input, "/api/v2")
211+
212+
// Check if the input ends with auth0.com .
213+
if !strings.HasSuffix(input, "auth0.com") {
214+
return "", fmt.Errorf("not a valid auth0.com domain")
215+
}
216+
217+
// Extract the domain part without any path.
218+
domainParts := strings.Split(input, "/")
219+
domain := domainParts[0]
220+
221+
// Return the formatted URL.
222+
return fmt.Sprintf("https://%s/api/v2/", domain), nil
223+
}
224+
157225
// RunLoginAsUser runs the login flow guiding the user through the process
158226
// by showing the login instructions, opening the browser.
159-
func RunLoginAsUser(ctx context.Context, cli *cli, additionalScopes []string) (config.Tenant, error) {
160-
state, err := auth.GetDeviceCode(ctx, http.DefaultClient, additionalScopes)
227+
func RunLoginAsUser(ctx context.Context, cli *cli, additionalScopes []string, domain string) (config.Tenant, error) {
228+
domain, err := ensureAuth0URL(domain)
229+
if err != nil {
230+
return config.Tenant{}, err
231+
}
232+
233+
state, err := auth.GetDeviceCode(ctx, http.DefaultClient, additionalScopes, domain)
161234
if err != nil {
162235
return config.Tenant{}, fmt.Errorf("failed to get the device code: %w", err)
163236
}

internal/cli/login_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package cli
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestLoginCommand(t *testing.T) {
10+
t.Run("Negative Test: it returns an error since client-id, client-secret and domain must be passed together", func(t *testing.T) {
11+
cli := &cli{}
12+
cli.noInput = true
13+
cmd := loginCmd(cli)
14+
cmd.SetArgs([]string{"--client-id", "t3dbMFeTokYBguVu1Ty88gqntUXELSn9"})
15+
err := cmd.Execute()
16+
17+
assert.EqualError(t, err, "flags client-id, client-secret and domain are required together")
18+
})
19+
20+
t.Run("Negative Test: it returns an error since client-id, client-secret and domain must be passed together", func(t *testing.T) {
21+
cli := &cli{}
22+
cli.noInput = true
23+
cmd := loginCmd(cli)
24+
cmd.SetArgs([]string{"--client-secret", "3OAzE7j2HTnGOPeCRFX3Hg-0sipaEnodzQK8xpwsRiTkqdjjwEFT04rgCjfslianfs"})
25+
err := cmd.Execute()
26+
assert.EqualError(t, err, "flags client-id, client-secret and domain are required together")
27+
})
28+
29+
t.Run("Negative Test: it returns an error since client-id, client-secret and domain must be passed together", func(t *testing.T) {
30+
cli := &cli{}
31+
cli.noInput = true
32+
cmd := loginCmd(cli)
33+
cmd.SetArgs([]string{"--client-id", "t3dbMFeTokYBguVu1Ty88gqntUXELSn9", "--client-secret", "3OAzE7j2HTnGOPeCRFX3Hg-0sipaEnodzQK8xpkqdjjwEFT0EFT04rgCp4PZL4Z"})
34+
err := cmd.Execute()
35+
assert.EqualError(t, err, "flags client-id, client-secret and domain are required together")
36+
})
37+
38+
t.Run("Negative Test: it returns an error since client-id, client-secret and domain must be passed together", func(t *testing.T) {
39+
cli := &cli{}
40+
cli.noInput = true
41+
cmd := loginCmd(cli)
42+
cmd.SetArgs([]string{"--client-id", "t3dbMFeTokYBguVu1Ty88gqntUXELSn9", "--domain", "duedares.us.auth0.com"})
43+
err := cmd.Execute()
44+
assert.EqualError(t, err, "flags client-id, client-secret and domain are required together")
45+
})
46+
47+
t.Run("Negative Test: it returns an error since client-id, client-secret and domain must be passed together", func(t *testing.T) {
48+
cli := &cli{}
49+
cli.noInput = true
50+
cmd := loginCmd(cli)
51+
cmd.SetArgs([]string{"--client-secret", "3OAzE7j2HTnGOPeCRFX3Hg-0sipaEnodzQK8xpkqdjjwEFT0EFT04rgCp4PZL4Z", "--domain", "duedares.us.auth0.com"})
52+
err := cmd.Execute()
53+
assert.EqualError(t, err, "flags client-id, client-secret and domain are required together")
54+
})
55+
}

0 commit comments

Comments
 (0)