From 3bdddc9f8072555021970a25e15c90b738a71889 Mon Sep 17 00:00:00 2001 From: ramya18101 Date: Sun, 8 Jun 2025 19:41:51 +0530 Subject: [PATCH 1/6] Add support to fetch login credentials from the local credentials file --- go.mod | 1 + go.sum | 2 + internal/cli/login.go | 88 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index f33695d25..055da4c4b 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,7 @@ require ( golang.org/x/sys v0.33.0 golang.org/x/term v0.32.0 golang.org/x/text v0.25.0 + gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 90baa1e34..0693012f0 100644 --- a/go.sum +++ b/go.sum @@ -315,6 +315,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/dnaeon/go-vcr.v3 v3.2.0 h1:Rltp0Vf+Aq0u4rQXgmXgtgoRDStTnFN83cWgSGSoRzM= gopkg.in/dnaeon/go-vcr.v3 v3.2.0/go.mod h1:2IMOnnlx9I6u9x+YBsM3tAMx6AlOxnJ0pWxQAzZ79Ag= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/internal/cli/login.go b/internal/cli/login.go index 72f8a6b77..47bab8d80 100644 --- a/internal/cli/login.go +++ b/internal/cli/login.go @@ -4,8 +4,12 @@ import ( "context" "fmt" "net/http" + "os" + "path/filepath" "strings" + "gopkg.in/ini.v1" + "github.com/pkg/browser" "github.com/spf13/cobra" @@ -48,6 +52,14 @@ var ( IsRequired: false, AlwaysPrompt: false, } + + loginTenantProfileLabel = Flag{ + Name: "Tenant Profile Label", + LongForm: "profile", + Help: "Tenant Profile Label name to load Auth0 credentials from. If not provided, the default profile will be used.", + IsRequired: false, + AlwaysPrompt: false, + } ) type LoginInputs struct { @@ -55,14 +67,49 @@ type LoginInputs struct { ClientID string ClientSecret string AdditionalScopes []string + TenantProfile string } func (i *LoginInputs) isLoggingInWithAdditionalScopes() bool { return len(i.AdditionalScopes) > 0 } +func loadAuth0Credentials(profile string, inputs *LoginInputs) error { + // 1. Determine credentials file location + credPath := os.Getenv("AUTH0_CREDENTIALS_FILE") + if credPath == "" { + home, err := os.UserHomeDir() + if err != nil { + return err + } + + credPath = filepath.Join(home, ".auth0", "credentials") + } + + // 2. Parse the ini file + cfg, err := ini.Load(credPath) + if err != nil { + return err + } + + section := profile + + sec := cfg.Section(section) + if sec == nil { + return fmt.Errorf("profile %q not found", section) + } + + inputs.Domain = sec.Key("domain").String() + inputs.ClientID = sec.Key("client_id").String() + inputs.ClientSecret = sec.Key("client_secret").String() + + return nil +} + func loginCmd(cli *cli) *cobra.Command { - var inputs LoginInputs + var ( + inputs LoginInputs + ) cmd := &cobra.Command{ Use: "login", @@ -76,6 +123,13 @@ func loginCmd(cli *cli) *cobra.Command { auth0 login --domain --client-id --client-secret auth0 login --scopes "read:client_grants,create:client_grants"`, RunE: func(cmd *cobra.Command, args []string) error { + if inputs.TenantProfile != "" { + err := loadAuth0Credentials(inputs.TenantProfile, &inputs) + if err != nil { + cli.renderer.Warnf("Failed to load auth0 credentials from %q: %v", inputs.TenantProfile, err) + } + } + var selectedLoginType string const loginAsUser, loginAsMachine = "As a user", "As a machine" shouldLoginAsUser, shouldLoginAsMachine := false, false @@ -189,6 +243,7 @@ func loginCmd(cli *cli) *cobra.Command { loginTenantDomain.RegisterString(cmd, &inputs.Domain, "") loginClientID.RegisterString(cmd, &inputs.ClientID, "") loginClientSecret.RegisterString(cmd, &inputs.ClientSecret, "") + loginTenantProfileLabel.RegisterString(cmd, &inputs.TenantProfile, "") loginAdditionalScopes.RegisterStringSlice(cmd, &inputs.AdditionalScopes, []string{}) cmd.MarkFlagsMutuallyExclusive("client-id", "scopes") cmd.MarkFlagsMutuallyExclusive("client-secret", "scopes") @@ -317,16 +372,11 @@ func RunLoginAsUser(ctx context.Context, cli *cli, additionalScopes []string, do // RunLoginAsMachine facilitates the authentication process using client credentials (client ID, client secret). func RunLoginAsMachine(ctx context.Context, inputs LoginInputs, cli *cli, cmd *cobra.Command) error { - if err := loginTenantDomain.Ask(cmd, &inputs.Domain, nil); err != nil { - return err - } - - if err := loginClientID.Ask(cmd, &inputs.ClientID, nil); err != nil { - return err - } - - if err := loginClientSecret.AskPassword(cmd, &inputs.ClientSecret); err != nil { - return err + if inputs.TenantProfile == "" { + err := loadCredentials(cmd, inputs) + if err != nil { + return err + } } token, err := auth.GetAccessTokenFromClientCreds( @@ -371,3 +421,19 @@ func RunLoginAsMachine(ctx context.Context, inputs LoginInputs, cli *cli, cmd *c return nil } + +func loadCredentials(cmd *cobra.Command, inputs LoginInputs) error { + if err := loginTenantDomain.Ask(cmd, &inputs.Domain, nil); err != nil { + return err + } + + if err := loginClientID.Ask(cmd, &inputs.ClientID, nil); err != nil { + return err + } + + if err := loginClientSecret.AskPassword(cmd, &inputs.ClientSecret); err != nil { + return err + } + + return nil +} From e5f182b25406ec976e5c77986693088b5f43294d Mon Sep 17 00:00:00 2001 From: ramya18101 Date: Sun, 8 Jun 2025 19:42:47 +0530 Subject: [PATCH 2/6] Fix code comments and logic --- internal/cli/login.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/cli/login.go b/internal/cli/login.go index 47bab8d80..b817e169e 100644 --- a/internal/cli/login.go +++ b/internal/cli/login.go @@ -75,7 +75,6 @@ func (i *LoginInputs) isLoggingInWithAdditionalScopes() bool { } func loadAuth0Credentials(profile string, inputs *LoginInputs) error { - // 1. Determine credentials file location credPath := os.Getenv("AUTH0_CREDENTIALS_FILE") if credPath == "" { home, err := os.UserHomeDir() @@ -86,7 +85,6 @@ func loadAuth0Credentials(profile string, inputs *LoginInputs) error { credPath = filepath.Join(home, ".auth0", "credentials") } - // 2. Parse the ini file cfg, err := ini.Load(credPath) if err != nil { return err From 88b3799d7324cf27a5f84ef4515b354519b42a9b Mon Sep 17 00:00:00 2001 From: ramya18101 Date: Mon, 9 Jun 2025 08:16:57 +0530 Subject: [PATCH 3/6] Update docs --- docs/auth0_login.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/auth0_login.md b/docs/auth0_login.md index 801c38462..253188892 100644 --- a/docs/auth0_login.md +++ b/docs/auth0_login.md @@ -31,6 +31,7 @@ auth0 login [flags] --client-id string Client ID of the application when authenticating via client credentials. --client-secret string Client secret of the application when authenticating via client credentials. --domain string Tenant domain of the application when authenticating via client credentials. + --profile string Tenant Profile Label name to load Auth0 credentials from. If not provided, the default profile will be used. --scopes strings Additional scopes to request when authenticating via device code flow. By default, only scopes for first-class functions are requested. Primarily useful when using the api command to execute arbitrary Management API requests. ``` From 852d34e64c3330f6db02289122b01cc990df11bc Mon Sep 17 00:00:00 2001 From: ramya18101 Date: Mon, 9 Jun 2025 10:44:17 +0530 Subject: [PATCH 4/6] Update Readme file --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index b04a739c5..b384c7756 100644 --- a/README.md +++ b/README.md @@ -136,8 +136,39 @@ There are two ways to authenticate: - **As a user** - Recommended when invoking on a personal machine or other interactive environment. Facilitated by [device authorization](https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow) flow and cannot be used for private cloud tenants. - **As a machine** - Recommended when running on a server or non-interactive environments (ex: CI, authenticating to a **private cloud**). Facilitated by [client credentials](https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-credentials-flow) flow. Flags available for bypassing interactive shell. +- **Profile-based login:** Use `--profile` to easily switch between multiple Auth0 tenants or environments (e.g., `default`, `dev`, etc.) +--- + +### Configure Your Credentials + +Create a credentials file at `~/.auth0/credentials` (or set the `AUTH0_CREDENTIALS_FILE` environment variable to use a custom path). + +The file should use **INI format** and can contain multiple profiles. +**Example:** + +```ini +[default] +tenant = your-tenant.auth0.com +client_id = YOUR_CLIENT_ID +client_secret = YOUR_CLIENT_SECRET + +[dev] +tenant = dev-tenant.auth0.com +client_id = YOUR_DEV_CLIENT_ID +client_secret = YOUR_DEV_CLIENT_SECRET +``` +- Each section name (e.g., `[default]`, `[dev]`) is a **tenant-profile**. +- You can add as many tenant profiles as needed for different tenants/environments. + +--- + +## Environment Variables + +- `AUTH0_CREDENTIALS_FILE`: Path to the credentials file (defaults to `~/.auth0/credentials`). + +--- > **Warning** > Authenticating as a user is not supported for **private cloud** tenants. From 75c601d46a22db13e4e776abdcefb13034fec28e Mon Sep 17 00:00:00 2001 From: ramya18101 Date: Mon, 9 Jun 2025 10:49:10 +0530 Subject: [PATCH 5/6] Update login examples --- docs/auth0_login.md | 1 + internal/cli/login.go | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/auth0_login.md b/docs/auth0_login.md index 253188892..8a139c80a 100644 --- a/docs/auth0_login.md +++ b/docs/auth0_login.md @@ -22,6 +22,7 @@ auth0 login [flags] auth0 login auth0 login --domain --client-id --client-secret auth0 login --scopes "read:client_grants,create:client_grants" + auth0 login --profile ``` diff --git a/internal/cli/login.go b/internal/cli/login.go index b817e169e..8fe473f03 100644 --- a/internal/cli/login.go +++ b/internal/cli/login.go @@ -90,11 +90,9 @@ func loadAuth0Credentials(profile string, inputs *LoginInputs) error { return err } - section := profile - - sec := cfg.Section(section) + sec := cfg.Section(profile) if sec == nil { - return fmt.Errorf("profile %q not found", section) + return fmt.Errorf("profile %q not found", profile) } inputs.Domain = sec.Key("domain").String() @@ -119,12 +117,13 @@ func loginCmd(cli *cli) *cobra.Command { "this is the recommended method for Private Cloud users.\n\n", Example: ` auth0 login auth0 login --domain --client-id --client-secret - auth0 login --scopes "read:client_grants,create:client_grants"`, + auth0 login --scopes "read:client_grants,create:client_grants" + auth0 login --profile `, RunE: func(cmd *cobra.Command, args []string) error { if inputs.TenantProfile != "" { err := loadAuth0Credentials(inputs.TenantProfile, &inputs) if err != nil { - cli.renderer.Warnf("Failed to load auth0 credentials from %q: %v", inputs.TenantProfile, err) + return fmt.Errorf("failed to load auth0 credentials from %q: %v", inputs.TenantProfile, err) } } From 9702c68cf02705b0c7edab6d19624548f8c36dbb Mon Sep 17 00:00:00 2001 From: ramya18101 Date: Wed, 11 Jun 2025 09:56:08 +0530 Subject: [PATCH 6/6] Fix readme and update checks in login --- README.md | 4 ++-- internal/cli/login.go | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b384c7756..7941c0219 100644 --- a/README.md +++ b/README.md @@ -149,12 +149,12 @@ The file should use **INI format** and can contain multiple profiles. ```ini [default] -tenant = your-tenant.auth0.com +domain = your-tenant.auth0.com client_id = YOUR_CLIENT_ID client_secret = YOUR_CLIENT_SECRET [dev] -tenant = dev-tenant.auth0.com +domain = dev-tenant.auth0.com client_id = YOUR_DEV_CLIENT_ID client_secret = YOUR_DEV_CLIENT_SECRET ``` diff --git a/internal/cli/login.go b/internal/cli/login.go index 8fe473f03..860d5d5e3 100644 --- a/internal/cli/login.go +++ b/internal/cli/login.go @@ -90,11 +90,12 @@ func loadAuth0Credentials(profile string, inputs *LoginInputs) error { return err } - sec := cfg.Section(profile) - if sec == nil { + if !cfg.HasSection(profile) { return fmt.Errorf("profile %q not found", profile) } + sec := cfg.Section(profile) + inputs.Domain = sec.Key("domain").String() inputs.ClientID = sec.Key("client_id").String() inputs.ClientSecret = sec.Key("client_secret").String() @@ -370,7 +371,7 @@ func RunLoginAsUser(ctx context.Context, cli *cli, additionalScopes []string, do // RunLoginAsMachine facilitates the authentication process using client credentials (client ID, client secret). func RunLoginAsMachine(ctx context.Context, inputs LoginInputs, cli *cli, cmd *cobra.Command) error { if inputs.TenantProfile == "" { - err := loadCredentials(cmd, inputs) + err := promptForCredentials(cmd, &inputs) if err != nil { return err } @@ -419,7 +420,7 @@ func RunLoginAsMachine(ctx context.Context, inputs LoginInputs, cli *cli, cmd *c return nil } -func loadCredentials(cmd *cobra.Command, inputs LoginInputs) error { +func promptForCredentials(cmd *cobra.Command, inputs *LoginInputs) error { if err := loginTenantDomain.Ask(cmd, &inputs.Domain, nil); err != nil { return err }