diff --git a/.golangci.yml b/.golangci.yml index 471f639d4..e1278a0c3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -32,12 +32,13 @@ linters-settings: capital: true goimports: local-prefixes: "github.com/auth0/auth0-cli" - revive: - rules: - - name: package-comments - severity: warning - disabled: true issues: exclude-use-default: false - fix: true + exclude: + - "should have a package comment" + - "package comment should be of the form" + - "should have comment" + - "be unexported" + - "error strings should not be capitalized or end with punctuation or a newline" + - "blank-imports" diff --git a/Makefile b/Makefile index 94e6f8728..c7d1c636c 100644 --- a/Makefile +++ b/Makefile @@ -116,7 +116,7 @@ install-with-cover: ## Install the cli binary for the native platform with cover lint: $(GO_BIN)/golangci-lint ## Run go linter checks ${call print, "Running golangci-lint over project"} - @golangci-lint run -v -c .golangci.yml ./... + @golangci-lint run -v --fix -c .golangci.yml ./... check-vuln: $(GO_BIN)/govulncheck ## Check go vulnerabilities ${call print, "Running govulncheck over project"} diff --git a/internal/auth/authutil/login.go b/internal/auth/authutil/login.go index e146be2eb..94a5e13d0 100644 --- a/internal/auth/authutil/login.go +++ b/internal/auth/authutil/login.go @@ -8,8 +8,6 @@ import ( // BuildLoginURL constructs a URL + query string that can be used to // initiate a user-facing login-flow from the CLI. func BuildLoginURL(domain, clientID, callbackURL, state, connectionName, audience, prompt string, scopes []string) (string, error) { - var path string = "/authorize" - q := url.Values{} q.Add("client_id", clientID) q.Add("response_type", "code") @@ -35,7 +33,7 @@ func BuildLoginURL(domain, clientID, callbackURL, state, connectionName, audienc u := &url.URL{ Scheme: "https", Host: domain, - Path: path, + Path: "/authorize", RawQuery: q.Encode(), } diff --git a/internal/cli/api_test.go b/internal/cli/api_test.go index 52f92bd81..d43824b76 100644 --- a/internal/cli/api_test.go +++ b/internal/cli/api_test.go @@ -78,10 +78,9 @@ func TestAPICmdInputs_FromArgs(t *testing.T) { if testCase.expectedError != "" { assert.EqualError(t, err, testCase.expectedError) return - } else { - assert.NoError(t, err) } + assert.NoError(t, err) assert.Equal(t, testCase.expectedMethod, actualInputs.Method) assert.Equal(t, testCase.expectedURL, actualInputs.URL.String()) }) diff --git a/internal/cli/apis.go b/internal/cli/apis.go index 82303e5ad..23e293033 100644 --- a/internal/cli/apis.go +++ b/internal/cli/apis.go @@ -74,11 +74,11 @@ func apisCmd(cli *cli) *cobra.Command { cmd.SetUsageTemplate(resourceUsageTemplate()) cmd.AddCommand(listApisCmd(cli)) - cmd.AddCommand(createApiCmd(cli)) - cmd.AddCommand(showApiCmd(cli)) - cmd.AddCommand(updateApiCmd(cli)) - cmd.AddCommand(deleteApiCmd(cli)) - cmd.AddCommand(openApiCmd(cli)) + cmd.AddCommand(createAPICmd(cli)) + cmd.AddCommand(showAPICmd(cli)) + cmd.AddCommand(updateAPICmd(cli)) + cmd.AddCommand(deleteAPICmd(cli)) + cmd.AddCommand(openAPICmd(cli)) cmd.AddCommand(scopesCmd(cli)) return cmd @@ -142,7 +142,7 @@ func listApisCmd(cli *cli) *cobra.Command { apis = append(apis, item.(*management.ResourceServer)) } - cli.renderer.ApiList(apis) + cli.renderer.APIList(apis) return nil }, @@ -154,7 +154,7 @@ func listApisCmd(cli *cli) *cobra.Command { return cmd } -func showApiCmd(cli *cli) *cobra.Command { +func showAPICmd(cli *cli) *cobra.Command { var inputs struct { ID string } @@ -187,7 +187,7 @@ func showApiCmd(cli *cli) *cobra.Command { return fmt.Errorf("Unable to get an API with Id '%s': %w", inputs.ID, err) } - cli.renderer.ApiShow(api, cli.json) + cli.renderer.APIShow(api, cli.json) return nil }, } @@ -197,7 +197,7 @@ func showApiCmd(cli *cli) *cobra.Command { return cmd } -func createApiCmd(cli *cli) *cobra.Command { +func createAPICmd(cli *cli) *cobra.Command { var inputs struct { Name string Identifier string @@ -267,7 +267,7 @@ func createApiCmd(cli *cli) *cobra.Command { return fmt.Errorf("An unexpected error occurred while attempting to create an API with name '%s' and identifier '%s': %w", inputs.Name, inputs.Identifier, err) } - cli.renderer.ApiCreate(api) + cli.renderer.APICreate(api) return nil }, } @@ -282,7 +282,7 @@ func createApiCmd(cli *cli) *cobra.Command { return cmd } -func updateApiCmd(cli *cli) *cobra.Command { +func updateAPICmd(cli *cli) *cobra.Command { var inputs struct { ID string Name string @@ -375,7 +375,7 @@ func updateApiCmd(cli *cli) *cobra.Command { return fmt.Errorf("An unexpected error occurred while trying to update an API with Id '%s': %w", inputs.ID, err) } - cli.renderer.ApiUpdate(api) + cli.renderer.APIUpdate(api) return nil }, } @@ -389,7 +389,7 @@ func updateApiCmd(cli *cli) *cobra.Command { return cmd } -func deleteApiCmd(cli *cli) *cobra.Command { +func deleteAPICmd(cli *cli) *cobra.Command { var inputs struct { ID string } @@ -439,7 +439,7 @@ func deleteApiCmd(cli *cli) *cobra.Command { return cmd } -func openApiCmd(cli *cli) *cobra.Command { +func openAPICmd(cli *cli) *cobra.Command { var inputs struct { ID string } @@ -480,7 +480,7 @@ func openApiCmd(cli *cli) *cobra.Command { } } - openManageURL(cli, cli.config.DefaultTenant, formatApiSettingsPath(inputs.ID)) + openManageURL(cli, cli.config.DefaultTenant, formatAPISettingsPath(inputs.ID)) return nil }, } @@ -532,7 +532,7 @@ func listScopesCmd(cli *cli) *cobra.Command { return cmd } -func formatApiSettingsPath(id string) string { +func formatAPISettingsPath(id string) string { if len(id) == 0 { return "" } diff --git a/internal/cli/flags.go b/internal/cli/flags.go index dff59a540..89833e3cd 100644 --- a/internal/cli/flags.go +++ b/internal/cli/flags.go @@ -232,9 +232,8 @@ func openEditorFlag(cmd *cobra.Command, f *Flag, value *string, defaultValue str if shouldAsk(cmd, f, false) { // Always open the editor on update if isUpdate { return openUpdateEditor(cmd, f, value, defaultValue, filename) - } else { - return openCreateEditor(cmd, f, value, defaultValue, filename, infoFn, tempFileFn) } + return openCreateEditor(cmd, f, value, defaultValue, filename, infoFn, tempFileFn) } return nil diff --git a/internal/cli/log_streams_datadog.go b/internal/cli/log_streams_datadog.go index 8166bd7e6..bf93c9854 100644 --- a/internal/cli/log_streams_datadog.go +++ b/internal/cli/log_streams_datadog.go @@ -22,7 +22,7 @@ var ( datadogRegionOptions = []string{"eu", "us", "us3", "us5"} - datadogApiKey = Flag{ + datadogAPIKey = Flag{ Name: "Datadog API Key", LongForm: "api-key", ShortForm: "k", @@ -60,7 +60,7 @@ func createLogStreamsDatadogCmd(cli *cli) *cobra.Command { return err } - if err := datadogApiKey.AskPassword(cmd, &inputs.DatadogAPIKey, nil); err != nil { + if err := datadogAPIKey.AskPassword(cmd, &inputs.DatadogAPIKey, nil); err != nil { return err } @@ -87,7 +87,7 @@ func createLogStreamsDatadogCmd(cli *cli) *cobra.Command { cmd.Flags().BoolVar(&cli.json, "json", false, "Output in json format.") logStreamName.RegisterString(cmd, &inputs.Name, "") - datadogApiKey.RegisterString(cmd, &inputs.DatadogAPIKey, "") + datadogAPIKey.RegisterString(cmd, &inputs.DatadogAPIKey, "") datadogRegion.RegisterString(cmd, &inputs.DatadogRegion, "") return cmd @@ -146,7 +146,7 @@ func updateLogStreamsDatadogCmd(cli *cli) *cobra.Command { return err } - if err := datadogApiKey.AskPasswordU(cmd, &inputs.DatadogAPIKey, datadogSink.APIKey); err != nil { + if err := datadogAPIKey.AskPasswordU(cmd, &inputs.DatadogAPIKey, datadogSink.APIKey); err != nil { return err } @@ -178,7 +178,7 @@ func updateLogStreamsDatadogCmd(cli *cli) *cobra.Command { cmd.Flags().BoolVar(&cli.json, "json", false, "Output in json format.") logStreamName.RegisterStringU(cmd, &inputs.Name, "") - datadogApiKey.RegisterStringU(cmd, &inputs.DatadogAPIKey, "") + datadogAPIKey.RegisterStringU(cmd, &inputs.DatadogAPIKey, "") datadogRegion.RegisterStringU(cmd, &inputs.DatadogRegion, "") return cmd diff --git a/internal/cli/log_streams_http.go b/internal/cli/log_streams_http.go index 501912234..bcc61a55c 100644 --- a/internal/cli/log_streams_http.go +++ b/internal/cli/log_streams_http.go @@ -48,10 +48,10 @@ var ( func createLogStreamsCustomWebhookCmd(cli *cli) *cobra.Command { var inputs struct { Name string - HttpEndpoint string - HttpContentType string - HttpContentFormat string - HttpAuthorization string + HTTPEndpoint string + HTTPContentType string + HTTPContentFormat string + HTTPAuthorization string } cmd := &cobra.Command{ @@ -74,19 +74,19 @@ func createLogStreamsCustomWebhookCmd(cli *cli) *cobra.Command { return err } - if err := httpEndpoint.Ask(cmd, &inputs.HttpEndpoint, nil); err != nil { + if err := httpEndpoint.Ask(cmd, &inputs.HTTPEndpoint, nil); err != nil { return err } - if err := httpContentType.Ask(cmd, &inputs.HttpContentType, nil); err != nil { + if err := httpContentType.Ask(cmd, &inputs.HTTPContentType, nil); err != nil { return err } - if err := httpContentFormat.Select(cmd, &inputs.HttpContentFormat, httpContentFormatOptions, nil); err != nil { + if err := httpContentFormat.Select(cmd, &inputs.HTTPContentFormat, httpContentFormatOptions, nil); err != nil { return err } - if err := httpAuthorization.AskPassword(cmd, &inputs.HttpAuthorization, nil); err != nil { + if err := httpAuthorization.AskPassword(cmd, &inputs.HTTPAuthorization, nil); err != nil { return err } @@ -95,16 +95,16 @@ func createLogStreamsCustomWebhookCmd(cli *cli) *cobra.Command { Type: auth0.String(string(logStreamTypeHTTP)), } sink := &management.LogStreamSinkHTTP{ - Endpoint: &inputs.HttpEndpoint, + Endpoint: &inputs.HTTPEndpoint, } - if inputs.HttpAuthorization != "" { - sink.Authorization = &inputs.HttpAuthorization + if inputs.HTTPAuthorization != "" { + sink.Authorization = &inputs.HTTPAuthorization } - if inputs.HttpContentType != "" { - sink.ContentType = &inputs.HttpContentType + if inputs.HTTPContentType != "" { + sink.ContentType = &inputs.HTTPContentType } - if inputs.HttpContentFormat != "" { - sink.ContentFormat = apiHTTPContentFormatFor(inputs.HttpContentFormat) + if inputs.HTTPContentFormat != "" { + sink.ContentFormat = apiHTTPContentFormatFor(inputs.HTTPContentFormat) } newLogStream.Sink = sink @@ -122,10 +122,10 @@ func createLogStreamsCustomWebhookCmd(cli *cli) *cobra.Command { cmd.Flags().BoolVar(&cli.json, "json", false, "Output in json format.") logStreamName.RegisterString(cmd, &inputs.Name, "") - httpEndpoint.RegisterString(cmd, &inputs.HttpEndpoint, "") - httpContentType.RegisterString(cmd, &inputs.HttpContentType, "") - httpContentFormat.RegisterString(cmd, &inputs.HttpContentFormat, "") - httpAuthorization.RegisterString(cmd, &inputs.HttpAuthorization, "") + httpEndpoint.RegisterString(cmd, &inputs.HTTPEndpoint, "") + httpContentType.RegisterString(cmd, &inputs.HTTPContentType, "") + httpContentFormat.RegisterString(cmd, &inputs.HTTPContentFormat, "") + httpAuthorization.RegisterString(cmd, &inputs.HTTPAuthorization, "") return cmd } @@ -134,10 +134,10 @@ func updateLogStreamsCustomWebhookCmd(cli *cli) *cobra.Command { var inputs struct { ID string Name string - HttpEndpoint string - HttpContentType string - HttpContentFormat string - HttpAuthorization string + HTTPEndpoint string + HTTPContentType string + HTTPContentFormat string + HTTPAuthorization string } cmd := &cobra.Command{ @@ -183,16 +183,16 @@ func updateLogStreamsCustomWebhookCmd(cli *cli) *cobra.Command { httpSink := oldLogStream.Sink.(*management.LogStreamSinkHTTP) - if err := httpEndpoint.AskU(cmd, &inputs.HttpEndpoint, httpSink.Endpoint); err != nil { + if err := httpEndpoint.AskU(cmd, &inputs.HTTPEndpoint, httpSink.Endpoint); err != nil { return err } - if err := httpContentType.AskU(cmd, &inputs.HttpContentType, httpSink.ContentType); err != nil { + if err := httpContentType.AskU(cmd, &inputs.HTTPContentType, httpSink.ContentType); err != nil { return err } - if err := httpContentFormat.SelectU(cmd, &inputs.HttpContentFormat, httpContentFormatOptions, httpSink.ContentFormat); err != nil { + if err := httpContentFormat.SelectU(cmd, &inputs.HTTPContentFormat, httpContentFormatOptions, httpSink.ContentFormat); err != nil { return err } - if err := httpAuthorization.AskPasswordU(cmd, &inputs.HttpAuthorization, httpSink.Authorization); err != nil { + if err := httpAuthorization.AskPasswordU(cmd, &inputs.HTTPAuthorization, httpSink.Authorization); err != nil { return err } @@ -201,17 +201,17 @@ func updateLogStreamsCustomWebhookCmd(cli *cli) *cobra.Command { if inputs.Name != "" { updatedLogStream.Name = &inputs.Name } - if inputs.HttpEndpoint != "" { - httpSink.Endpoint = &inputs.HttpEndpoint + if inputs.HTTPEndpoint != "" { + httpSink.Endpoint = &inputs.HTTPEndpoint } - if inputs.HttpAuthorization != "" { - httpSink.Authorization = &inputs.HttpAuthorization + if inputs.HTTPAuthorization != "" { + httpSink.Authorization = &inputs.HTTPAuthorization } - if inputs.HttpContentType != "" { - httpSink.ContentType = &inputs.HttpContentType + if inputs.HTTPContentType != "" { + httpSink.ContentType = &inputs.HTTPContentType } - if inputs.HttpContentFormat != "" { - httpSink.ContentFormat = apiHTTPContentFormatFor(inputs.HttpContentFormat) + if inputs.HTTPContentFormat != "" { + httpSink.ContentFormat = apiHTTPContentFormatFor(inputs.HTTPContentFormat) } updatedLogStream.Sink = httpSink @@ -230,10 +230,10 @@ func updateLogStreamsCustomWebhookCmd(cli *cli) *cobra.Command { cmd.Flags().BoolVar(&cli.json, "json", false, "Output in json format.") logStreamName.RegisterStringU(cmd, &inputs.Name, "") - httpEndpoint.RegisterStringU(cmd, &inputs.HttpEndpoint, "") - httpContentType.RegisterStringU(cmd, &inputs.HttpContentType, "") - httpContentFormat.RegisterStringU(cmd, &inputs.HttpContentFormat, "") - httpAuthorization.RegisterStringU(cmd, &inputs.HttpAuthorization, "") + httpEndpoint.RegisterStringU(cmd, &inputs.HTTPEndpoint, "") + httpContentType.RegisterStringU(cmd, &inputs.HTTPContentType, "") + httpContentFormat.RegisterStringU(cmd, &inputs.HTTPContentFormat, "") + httpAuthorization.RegisterStringU(cmd, &inputs.HTTPAuthorization, "") return cmd } diff --git a/internal/cli/test.go b/internal/cli/test.go index 2de91a9ef..2d62a3054 100644 --- a/internal/cli/test.go +++ b/internal/cli/test.go @@ -14,7 +14,7 @@ import ( ) const ( - NEW_CLIENT = "NEW CLIENT" + newClientOption = "NEW CLIENT" ) var ( @@ -282,7 +282,7 @@ func selectClientToUseForTestsAndValidateExistence(cli *cli, cmd *cobra.Command, return nil, err } - if inputs.ClientID == NEW_CLIENT { + if inputs.ClientID == newClientOption { client := &management.Client{ Name: auth0.String(cliLoginTestingClientName), Description: auth0.String(cliLoginTestingClientDescription), @@ -362,7 +362,7 @@ func (c *cli) appPickerWithCreateOption() (pickerOptions, error) { enhancedOptions := []pickerOption{ { - value: NEW_CLIENT, + value: newClientOption, label: "Create a new client to use for testing the login", }, } diff --git a/internal/cli/users.go b/internal/cli/users.go index e9cd7cdea..7f76a1a11 100644 --- a/internal/cli/users.go +++ b/internal/cli/users.go @@ -533,7 +533,7 @@ func openUserCmd(cli *cli) *cobra.Command { func importUsersCmd(cli *cli) *cobra.Command { var inputs struct { Connection string - ConnectionId string + ConnectionID string Template string TemplateBody string Upsert bool @@ -561,10 +561,10 @@ The file size limit for a bulk import is 500KB. You will need to start multiple conn, connErr := cli.api.Connection.ReadByName(inputs.Connection) if connErr != nil { return fmt.Errorf("Connection does not exist: %w", connErr) - } else { - inputs.ConnectionId = *conn.ID } + inputs.ConnectionID = *conn.ID + // Present user with template options if templateErr := userImportTemplate.Select(cmd, &inputs.Template, userImportOptions.labels(), nil); templateErr != nil { return templateErr @@ -600,7 +600,7 @@ The file size limit for a bulk import is 500KB. You will need to start multiple err := ansi.Waiting(func() error { return cli.api.Jobs.ImportUsers(&management.Job{ - ConnectionID: &inputs.ConnectionId, + ConnectionID: &inputs.ConnectionID, Users: jsonmap, Upsert: &inputs.Upsert, SendCompletionEmail: &inputs.SendCompletionEmail, diff --git a/internal/display/apis.go b/internal/display/apis.go index b870935fd..7531a30fd 100644 --- a/internal/display/apis.go +++ b/internal/display/apis.go @@ -67,7 +67,7 @@ func (v *apiTableView) Object() interface{} { return v.raw } -func (r *Renderer) ApiList(apis []*management.ResourceServer) { +func (r *Renderer) APIList(apis []*management.ResourceServer) { resource := "apis" r.Heading(resource) @@ -82,15 +82,15 @@ func (r *Renderer) ApiList(apis []*management.ResourceServer) { results := []View{} for _, api := range apis { - results = append(results, makeApiTableView(api)) + results = append(results, makeAPITableView(api)) } r.Results(results) } -func (r *Renderer) ApiShow(api *management.ResourceServer, jsonFlag bool) { +func (r *Renderer) APIShow(api *management.ResourceServer, jsonFlag bool) { r.Heading("api") - view, scopesTruncated := makeApiView(api) + view, scopesTruncated := makeAPIView(api) r.Result(view) if scopesTruncated && !jsonFlag { r.Newline() @@ -98,19 +98,19 @@ func (r *Renderer) ApiShow(api *management.ResourceServer, jsonFlag bool) { } } -func (r *Renderer) ApiCreate(api *management.ResourceServer) { +func (r *Renderer) APICreate(api *management.ResourceServer) { r.Heading("api created") - view, _ := makeApiView(api) + view, _ := makeAPIView(api) r.Result(view) } -func (r *Renderer) ApiUpdate(api *management.ResourceServer) { +func (r *Renderer) APIUpdate(api *management.ResourceServer) { r.Heading("api updated") - view, _ := makeApiView(api) + view, _ := makeAPIView(api) r.Result(view) } -func makeApiView(api *management.ResourceServer) (*apiView, bool) { +func makeAPIView(api *management.ResourceServer) (*apiView, bool) { scopes, scopesTruncated := getScopes(api.GetScopes()) view := &apiView{ ID: ansi.Faint(api.GetID()), @@ -125,7 +125,7 @@ func makeApiView(api *management.ResourceServer) (*apiView, bool) { return view, scopesTruncated } -func makeApiTableView(api *management.ResourceServer) *apiTableView { +func makeAPITableView(api *management.ResourceServer) *apiTableView { scopes := len(api.GetScopes()) return &apiTableView{ diff --git a/internal/display/logs.go b/internal/display/logs.go index 44e184585..2bf626eab 100644 --- a/internal/display/logs.go +++ b/internal/display/logs.go @@ -48,12 +48,10 @@ func (v *logView) getConnection() string { return v } return notApplicable - } else { - return notApplicable } - } else { return notApplicable } + return notApplicable } func (v *logView) AsTableRow() []string { diff --git a/internal/prompt/surveyext.go b/internal/prompt/surveyext.go index 832d0c318..c44e3fa9b 100644 --- a/internal/prompt/surveyext.go +++ b/internal/prompt/surveyext.go @@ -7,7 +7,8 @@ import ( "github.com/AlecAivazis/survey/v2/terminal" ) -// EXTENDED to enable different prompting behavior. +// Editor is an extended survey.Editor to +// enable different prompting behavior. type Editor struct { *survey.Editor EditorCommand string @@ -22,7 +23,6 @@ func (e *Editor) editorCommand() string { return e.EditorCommand } -// EXTENDED to change prompt text. var EditorQuestionTemplate = ` {{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} {{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}} @@ -35,7 +35,6 @@ var EditorQuestionTemplate = ` {{- color "cyan"}}[(e) to launch {{ .EditorCommand }}{{- if .BlankAllowed }}, enter to skip{{ end }}] {{color "reset"}} {{- end}}` -// EXTENDED to pass editor name (to use in prompt). type EditorTemplateData struct { survey.Editor EditorCommand string @@ -46,7 +45,6 @@ type EditorTemplateData struct { Config *survey.PromptConfig } -// EXTENDED to augment prompt text and keypress handling. func (e *Editor) prompt(initialValue string, config *survey.PromptConfig) (interface{}, error) { err := e.Render( EditorQuestionTemplate, @@ -86,9 +84,8 @@ func (e *Editor) prompt(initialValue string, config *survey.PromptConfig) (inter if r == '\r' || r == '\n' { if e.BlankAllowed { return "", nil - } else { - continue } + continue } if r == terminal.KeyInterrupt { return "", terminal.InterruptErr @@ -128,7 +125,7 @@ func (e *Editor) prompt(initialValue string, config *survey.PromptConfig) (inter return text, nil } -// EXTENDED This is straight copypasta from survey to get our overridden prompt called. +// Prompt is a straight copy from survey to get our overridden prompt called. func (e *Editor) Prompt(config *survey.PromptConfig) (interface{}, error) { initialValue := "" if e.Default != "" && e.AppendDefault { diff --git a/internal/users/users_embed.go b/internal/users/users_embed.go index ee90a4566..99ec413b9 100644 --- a/internal/users/users_embed.go +++ b/internal/users/users_embed.go @@ -5,15 +5,19 @@ import ( ) var ( + // EmptyExample for the user import options. //go:embed data/empty-example.json EmptyExample string + // BasicExample for the user import options. //go:embed data/basic-example.json BasicExample string + // CustomPasswordHashExample for the user import options. //go:embed data/custom-password-hash-example.json CustomPasswordHashExample string + // MFAFactors for the user import options. //go:embed data/mfa-factors.json MFAFactors string )