diff --git a/commander.yaml b/commander.yaml index 3261c493c..9a45767f4 100644 --- a/commander.yaml +++ b/commander.yaml @@ -605,3 +605,37 @@ tests: - STAGE_PRE_USER_REGISTRATION_MAX_ATTEMPTS - STAGE_PRE_USER_REGISTRATION_RATE exit-code: 0 + + attack protection update breached password detection: + command: auth0 attack-protection breached-password-detection update --enabled + stdout: + contains: + - ENABLED + - SHIELDS + - ADMIN_NOTIFICATION_FREQUENCY + - METHOD + exit-code: 0 + + attack protection update brute force protection: + command: auth0 attack-protection brute-force-protection update --enabled + stdout: + contains: + - ENABLED + - SHIELDS + - ALLOW_LIST + - MODE + - MAX_ATTEMPTS + exit-code: 0 + + attack protection update suspicious ip throttling: + command: auth0 attack-protection suspicious-ip-throttling update --enabled + stdout: + contains: + - ENABLED + - SHIELDS + - ALLOW_LIST + - STAGE_PRE_LOGIN_MAX_ATTEMPTS + - STAGE_PRE_LOGIN_RATE + - STAGE_PRE_USER_REGISTRATION_MAX_ATTEMPTS + - STAGE_PRE_USER_REGISTRATION_RATE + exit-code: 0 diff --git a/docs/auth0_attack_protection_breached_password_detection_update.md b/docs/auth0_attack_protection_breached_password_detection_update.md new file mode 100644 index 000000000..40a0dec31 --- /dev/null +++ b/docs/auth0_attack_protection_breached_password_detection_update.md @@ -0,0 +1,49 @@ +--- +layout: default +--- +## auth0 attack-protection breached-password-detection update + +Update breached password detection settings + +### Synopsis + +Update breached password detection settings. + +``` +auth0 attack-protection breached-password-detection update [flags] +``` + +### Examples + +``` +auth0 attack-protection breached-password-detection update +``` + +### Options + +``` + -f, --admin-notification-frequency strings When "admin_notification" is enabled, determines how often email notifications are sent. Possible values: + immediately, daily, weekly, monthly. Comma-separated. + -e, --enabled Enable (or disable) breached password detection. + -h, --help help for update + -m, --method string The subscription level for breached password detection methods. Use "enhanced" to enable Credential Guard. + Possible values: standard, enhanced. + -s, --shields strings Action to take when a breached password is detected. Possible values: block, user_notification, + admin_notification. Comma-separated. +``` + +### Options inherited from parent commands + +``` + --debug Enable debug mode. + --force Skip confirmation. + --format string Command output format. Options: json. + --no-color Disable colors. + --no-input Disable interactivity. + --tenant string Specific tenant to use. +``` + +### SEE ALSO + +* [auth0 attack-protection](auth0_attack_protection.md) - Manage attack protection settings +* [auth0 attack-protection breached-password-detection](auth0_attack_protection_breached_password_detection.md) - Manage breached password detection settings diff --git a/docs/auth0_attack_protection_brute_force_protection_update.md b/docs/auth0_attack_protection_brute_force_protection_update.md new file mode 100644 index 000000000..687731add --- /dev/null +++ b/docs/auth0_attack_protection_brute_force_protection_update.md @@ -0,0 +1,48 @@ +--- +layout: default +--- +## auth0 attack-protection brute-force-protection update + +Update brute force protection settings + +### Synopsis + +Update brute force protection settings. + +``` +auth0 attack-protection brute-force-protection update [flags] +``` + +### Examples + +``` +auth0 attack-protection brute-force-protection update +``` + +### Options + +``` + -l, --allowlist strings List of trusted IP addresses that will not have attack protection enforced against them. Comma-separated. + -e, --enabled Enable (or disable) brute force protection. + -h, --help help for update + -a, --max-attempts int Maximum number of unsuccessful attempts. (default 1) + -m, --mode string Account Lockout: Determines whether or not IP address is used when counting failed attempts. Possible values: + count_per_identifier_and_ip, count_per_identifier. + -s, --shields strings Action to take when a brute force protection threshold is violated. Possible values: block, user_notification. Comma-separated. +``` + +### Options inherited from parent commands + +``` + --debug Enable debug mode. + --force Skip confirmation. + --format string Command output format. Options: json. + --no-color Disable colors. + --no-input Disable interactivity. + --tenant string Specific tenant to use. +``` + +### SEE ALSO + +* [auth0 attack-protection](auth0_attack_protection.md) - Manage attack protection settings +* [auth0 attack-protection brute-force-protection](auth0_attack_protection_brute_force_protection.md) - Manage brute force protection settings diff --git a/docs/auth0_attack_protection_suspicious_ip_throttling_update.md b/docs/auth0_attack_protection_suspicious_ip_throttling_update.md new file mode 100644 index 000000000..124636090 --- /dev/null +++ b/docs/auth0_attack_protection_suspicious_ip_throttling_update.md @@ -0,0 +1,51 @@ +--- +layout: default +--- +## auth0 attack-protection suspicious-ip-throttling update + +Update suspicious ip throttling settings + +### Synopsis + +Update suspicious ip throttling settings. + +``` +auth0 attack-protection suspicious-ip-throttling update [flags] +``` + +### Examples + +``` +auth0 attack-protection suspicious-ip-throttling update +``` + +### Options + +``` + -l, --allowlist strings List of trusted IP addresses that will not have attack protection enforced against them. Comma-separated. + -e, --enabled Enable (or disable) suspicious ip throttling. + -h, --help help for update + --pre-login-max int Configuration options that apply before every login attempt. Total number of attempts allowed per day. (default 1) + --pre-login-rate int Configuration options that apply before every login attempt. Interval of time, given in milliseconds, at which new attempts + are granted. (default 34560) + --pre-registration-max int Configuration options that apply before every user registration attempt. Total number of attempts allowed. (default 1) + --pre-registration-rate int Configuration options that apply before every user registration attempt. Interval of time, given in milliseconds, at which + new attempts are granted. (default 1200) + -s, --shields strings Action to take when a suspicious IP throttling threshold is violated. Possible values: block, admin_notification. Comma-separated. +``` + +### Options inherited from parent commands + +``` + --debug Enable debug mode. + --force Skip confirmation. + --format string Command output format. Options: json. + --no-color Disable colors. + --no-input Disable interactivity. + --tenant string Specific tenant to use. +``` + +### SEE ALSO + +* [auth0 attack-protection](auth0_attack_protection.md) - Manage attack protection settings +* [auth0 attack-protection suspicious-ip-throttling](auth0_attack_protection_suspicious_ip_throttling.md) - Manage suspicious ip throttling settings diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 41afd43a5..404566bcc 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -38,7 +38,7 @@ var requiredScopes = []string{ "create:actions", "delete:actions", "read:actions", "update:actions", "create:organizations", "delete:organizations", "read:organizations", "update:organizations", "read:organization_members", "read:organization_member_roles", "read:prompts", "update:prompts", - "read:attack_protection", + "read:attack_protection", "update:attack_protection", } type Authenticator struct { diff --git a/internal/auth0/attack_protection.go b/internal/auth0/attack_protection.go index 3afc42af8..9e6c56ddc 100644 --- a/internal/auth0/attack_protection.go +++ b/internal/auth0/attack_protection.go @@ -14,6 +14,16 @@ type AttackProtectionAPI interface { opts ...management.RequestOption, ) (bpd *management.BreachedPasswordDetection, err error) + // UpdateBreachedPasswordDetection updates the breached password detection settings. + // + // Required scope: `update:attack_protection` + // + // See: https://auth0.com/docs/api/management/v2#!/Attack_Protection/patch_breached_password_detection + UpdateBreachedPasswordDetection( + bpd *management.BreachedPasswordDetection, + opts ...management.RequestOption, + ) (err error) + // GetBruteForceProtection retrieves the brute force configuration. // // Required scope: `read:attack_protection` @@ -23,6 +33,16 @@ type AttackProtectionAPI interface { opts ...management.RequestOption, ) (bfp *management.BruteForceProtection, err error) + // UpdateBruteForceProtection updates the brute force configuration. + // + // Required scope: `update:attack_protection` + // + // See: https://auth0.com/docs/api/management/v2#!/Attack_Protection/patch_brute_force_protection + UpdateBruteForceProtection( + bfp *management.BruteForceProtection, + opts ...management.RequestOption, + ) (err error) + // GetSuspiciousIPThrottling retrieves the suspicious IP throttling configuration. // // Required scope: `read:attack_protection` @@ -31,4 +51,14 @@ type AttackProtectionAPI interface { GetSuspiciousIPThrottling( opts ...management.RequestOption, ) (sit *management.SuspiciousIPThrottling, err error) + + // UpdateSuspiciousIPThrottling updates the suspicious IP throttling configuration. + // + // Required scope: `update:attack_protection` + // + // See: https://auth0.com/docs/api/management/v2#!/Attack_Protection/patch_suspicious_ip_throttling + UpdateSuspiciousIPThrottling( + sit *management.SuspiciousIPThrottling, + opts ...management.RequestOption, + ) (err error) } diff --git a/internal/cli/attack_protection_breached_password_detection.go b/internal/cli/attack_protection_breached_password_detection.go index cdcc9117b..16e9cd25a 100644 --- a/internal/cli/attack_protection_breached_password_detection.go +++ b/internal/cli/attack_protection_breached_password_detection.go @@ -1,12 +1,62 @@ package cli import ( + "strings" + "github.com/auth0/go-auth0/management" "github.com/spf13/cobra" "github.com/auth0/auth0-cli/internal/ansi" ) +var bpdFlags = breachedPasswordDetectionFlags{ + Enabled: Flag{ + Name: "Enabled", + LongForm: "enabled", + ShortForm: "e", + Help: "Enable (or disable) breached password detection.", + AlwaysPrompt: true, + }, + Shields: Flag{ + Name: "Shields", + LongForm: "shields", + ShortForm: "s", + Help: "Action to take when a breached password is detected. Possible values: block, user_notification, admin_notification. Comma-separated.", + AlwaysPrompt: true, + }, + AdminNotificationFrequency: Flag{ + Name: "Admin Notification Frequency", + LongForm: "admin-notification-frequency", + ShortForm: "f", + Help: "When \"admin_notification\" is enabled, determines how often email notifications " + + "are sent. Possible values: immediately, daily, weekly, monthly. Comma-separated.", + AlwaysPrompt: true, + }, + Method: Flag{ + Name: "Method", + LongForm: "method", + ShortForm: "m", + Help: "The subscription level for breached password detection methods. Use \"enhanced\" to enable Credential Guard. Possible values: standard, enhanced.", + AlwaysPrompt: true, + }, +} + +type ( + breachedPasswordDetectionFlags struct { + Enabled Flag + Shields Flag + AdminNotificationFrequency Flag + Method Flag + } + + breachedPasswordDetectionInputs struct { + Enabled bool + Shields []string + AdminNotificationFrequency []string + Method string + } +) + func breachedPasswordDetectionCmd(cli *cli) *cobra.Command { cmd := &cobra.Command{ Use: "breached-password-detection", @@ -19,6 +69,7 @@ func breachedPasswordDetectionCmd(cli *cli) *cobra.Command { cmd.SetUsageTemplate(resourceUsageTemplate()) cmd.AddCommand(showBreachedPasswordDetectionCmd(cli)) + cmd.AddCommand(updateBreachedPasswordDetectionCmd(cli)) return cmd } @@ -34,6 +85,26 @@ func showBreachedPasswordDetectionCmd(cli *cli) *cobra.Command { } } +func updateBreachedPasswordDetectionCmd(cli *cli) *cobra.Command { + var inputs breachedPasswordDetectionInputs + + cmd := &cobra.Command{ + Use: "update", + Args: cobra.NoArgs, + Short: "Update breached password detection settings", + Long: "Update breached password detection settings.", + Example: `auth0 protection breached-password-detection update`, + RunE: updateBreachedPasswordDetectionCmdRun(cli, &inputs), + } + + bpdFlags.Enabled.RegisterBoolU(cmd, &inputs.Enabled, false) + bpdFlags.Shields.RegisterStringSliceU(cmd, &inputs.Shields, []string{}) + bpdFlags.AdminNotificationFrequency.RegisterStringSliceU(cmd, &inputs.AdminNotificationFrequency, []string{}) + bpdFlags.Method.RegisterString(cmd, &inputs.Method, "") + + return cmd +} + func showBreachedPasswordDetectionCmdRun(cli *cli) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { var bpd *management.BreachedPasswordDetection @@ -50,3 +121,64 @@ func showBreachedPasswordDetectionCmdRun(cli *cli) func(cmd *cobra.Command, args return nil } } + +func updateBreachedPasswordDetectionCmdRun( + cli *cli, + inputs *breachedPasswordDetectionInputs, +) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + var bpd *management.BreachedPasswordDetection + err := ansi.Waiting(func() (err error) { + bpd, err = cli.api.AttackProtection.GetBreachedPasswordDetection() + return err + }) + if err != nil { + return err + } + + if err := bpdFlags.Enabled.AskBoolU(cmd, &inputs.Enabled, bpd.Enabled); err != nil { + return err + } + bpd.Enabled = &inputs.Enabled + + shieldsString := strings.Join(bpd.GetShields(), ",") + if err := bpdFlags.Shields.AskManyU(cmd, &inputs.Shields, &shieldsString); err != nil { + return err + } + if len(inputs.Shields) == 0 { + inputs.Shields = bpd.GetShields() + } + bpd.Shields = &inputs.Shields + + adminNotificationFrequencyString := strings.Join(bpd.GetAdminNotificationFrequency(), ",") + if err := bpdFlags.AdminNotificationFrequency.AskManyU( + cmd, + &inputs.AdminNotificationFrequency, + &adminNotificationFrequencyString, + ); err != nil { + return err + } + if len(inputs.AdminNotificationFrequency) == 0 { + inputs.AdminNotificationFrequency = bpd.GetAdminNotificationFrequency() + } + bpd.AdminNotificationFrequency = &inputs.AdminNotificationFrequency + + if err := bpdFlags.Method.AskU(cmd, &inputs.Method, bpd.Method); err != nil { + return err + } + if inputs.Method == "" { + inputs.Method = bpd.GetMethod() + } + bpd.Method = &inputs.Method + + if err := ansi.Waiting(func() error { + return cli.api.AttackProtection.UpdateBreachedPasswordDetection(bpd) + }); err != nil { + return err + } + + cli.renderer.BreachedPasswordDetectionUpdate(bpd) + + return nil + } +} diff --git a/internal/cli/attack_protection_brute_force_protection.go b/internal/cli/attack_protection_brute_force_protection.go index 26cc55922..82466380d 100644 --- a/internal/cli/attack_protection_brute_force_protection.go +++ b/internal/cli/attack_protection_brute_force_protection.go @@ -1,12 +1,74 @@ package cli import ( + "strconv" + "strings" + "github.com/auth0/go-auth0/management" "github.com/spf13/cobra" "github.com/auth0/auth0-cli/internal/ansi" ) +var bfpFlags = bruteForceProtectionFlags{ + Enabled: Flag{ + Name: "Enabled", + LongForm: "enabled", + ShortForm: "e", + Help: "Enable (or disable) brute force protection.", + AlwaysPrompt: true, + }, + Shields: Flag{ + Name: "Shields", + LongForm: "shields", + ShortForm: "s", + Help: "Action to take when a brute force protection threshold is violated." + + " Possible values: block, user_notification. Comma-separated.", + AlwaysPrompt: true, + }, + AllowList: Flag{ + Name: "Allow List", + LongForm: "allowlist", + ShortForm: "l", + Help: "List of trusted IP addresses that will not have " + + "attack protection enforced against them. Comma-separated.", + AlwaysPrompt: true, + }, + Mode: Flag{ + Name: "Mode", + LongForm: "mode", + ShortForm: "m", + Help: "Account Lockout: Determines whether or not IP address is used when counting " + + "failed attempts. Possible values: count_per_identifier_and_ip, count_per_identifier.", + AlwaysPrompt: true, + }, + MaxAttempts: Flag{ + Name: "MaxAttempts", + LongForm: "max-attempts", + ShortForm: "a", + Help: "Maximum number of unsuccessful attempts.", + AlwaysPrompt: true, + }, +} + +type ( + bruteForceProtectionFlags struct { + Enabled Flag + Shields Flag + AllowList Flag + Mode Flag + MaxAttempts Flag + } + + bruteForceProtectionInputs struct { + Enabled bool + Shields []string + AllowList []string + Mode string + MaxAttempts int + } +) + func bruteForceProtectionCmd(cli *cli) *cobra.Command { cmd := &cobra.Command{ Use: "brute-force-protection", @@ -19,6 +81,7 @@ func bruteForceProtectionCmd(cli *cli) *cobra.Command { cmd.SetUsageTemplate(resourceUsageTemplate()) cmd.AddCommand(showBruteForceProtectionCmd(cli)) + cmd.AddCommand(updateBruteForceProtectionCmd(cli)) return cmd } @@ -34,6 +97,27 @@ func showBruteForceProtectionCmd(cli *cli) *cobra.Command { } } +func updateBruteForceProtectionCmd(cli *cli) *cobra.Command { + var inputs bruteForceProtectionInputs + + cmd := &cobra.Command{ + Use: "update", + Args: cobra.NoArgs, + Short: "Update brute force protection settings", + Long: "Update brute force protection settings.", + Example: `auth0 protection brute-force-protection update`, + RunE: updateBruteForceDetectionCmdRun(cli, &inputs), + } + + bfpFlags.Enabled.RegisterBoolU(cmd, &inputs.Enabled, false) + bfpFlags.Shields.RegisterStringSliceU(cmd, &inputs.Shields, []string{}) + bfpFlags.AllowList.RegisterStringSliceU(cmd, &inputs.AllowList, []string{}) + bfpFlags.Mode.RegisterString(cmd, &inputs.Mode, "") + bfpFlags.MaxAttempts.RegisterIntU(cmd, &inputs.MaxAttempts, 1) + + return cmd +} + func showBruteForceProtectionCmdRun(cli *cli) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { var bfp *management.BruteForceProtection @@ -50,3 +134,73 @@ func showBruteForceProtectionCmdRun(cli *cli) func(cmd *cobra.Command, args []st return nil } } + +func updateBruteForceDetectionCmdRun( + cli *cli, + inputs *bruteForceProtectionInputs, +) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + var bfp *management.BruteForceProtection + err := ansi.Waiting(func() (err error) { + bfp, err = cli.api.AttackProtection.GetBruteForceProtection() + return err + }) + if err != nil { + return err + } + + if err := bfpFlags.Enabled.AskBoolU(cmd, &inputs.Enabled, bfp.Enabled); err != nil { + return err + } + bfp.Enabled = &inputs.Enabled + + shieldsString := strings.Join(bfp.GetShields(), ",") + if err := bfpFlags.Shields.AskManyU(cmd, &inputs.Shields, &shieldsString); err != nil { + return err + } + if len(inputs.Shields) == 0 { + inputs.Shields = bfp.GetShields() + } + bfp.Shields = &inputs.Shields + + allowListString := strings.Join(bfp.GetAllowList(), ",") + if err := bfpFlags.AllowList.AskManyU( + cmd, + &inputs.AllowList, + &allowListString, + ); err != nil { + return err + } + if len(inputs.AllowList) == 0 { + inputs.AllowList = bfp.GetAllowList() + } + bfp.AllowList = &inputs.AllowList + + if err := bfpFlags.Mode.AskU(cmd, &inputs.Mode, bfp.Mode); err != nil { + return err + } + if inputs.Mode == "" { + inputs.Mode = bfp.GetMode() + } + bfp.Mode = &inputs.Mode + + defaultMaxAttempts := strconv.Itoa(bfp.GetMaxAttempts()) + if err := bfpFlags.MaxAttempts.AskIntU(cmd, &inputs.MaxAttempts, &defaultMaxAttempts); err != nil { + return err + } + if inputs.MaxAttempts == 0 { + inputs.MaxAttempts = bfp.GetMaxAttempts() + } + bfp.MaxAttempts = &inputs.MaxAttempts + + if err := ansi.Waiting(func() error { + return cli.api.AttackProtection.UpdateBruteForceProtection(bfp) + }); err != nil { + return err + } + + cli.renderer.BruteForceProtectionUpdate(bfp) + + return nil + } +} diff --git a/internal/cli/attack_protection_suspicious_ip_throttling.go b/internal/cli/attack_protection_suspicious_ip_throttling.go index 42ecedbb5..3edc5b702 100644 --- a/internal/cli/attack_protection_suspicious_ip_throttling.go +++ b/internal/cli/attack_protection_suspicious_ip_throttling.go @@ -1,12 +1,91 @@ package cli import ( + "strconv" + "strings" + "github.com/auth0/go-auth0/management" "github.com/spf13/cobra" "github.com/auth0/auth0-cli/internal/ansi" ) +var sitFlags = suspiciousIPThrottlingFlags{ + Enabled: Flag{ + Name: "Enabled", + LongForm: "enabled", + ShortForm: "e", + Help: "Enable (or disable) suspicious ip throttling.", + AlwaysPrompt: true, + }, + Shields: Flag{ + Name: "Shields", + LongForm: "shields", + ShortForm: "s", + Help: "Action to take when a suspicious IP throttling threshold is violated. " + + "Possible values: block, admin_notification. Comma-separated.", + AlwaysPrompt: true, + }, + AllowList: Flag{ + Name: "Allow List", + LongForm: "allowlist", + ShortForm: "l", + Help: "List of trusted IP addresses that will not have attack protection enforced against " + + "them. Comma-separated.", + AlwaysPrompt: true, + }, + StagePreLoginMaxAttempts: Flag{ + Name: "StagePreLoginMaxAttempts", + LongForm: "pre-login-max", + Help: "Configuration options that apply before every login attempt. " + + "Total number of attempts allowed per day.", + AlwaysPrompt: true, + }, + StagePreLoginRate: Flag{ + Name: "StagePreLoginRate", + LongForm: "pre-login-rate", + Help: "Configuration options that apply before every login attempt. " + + "Interval of time, given in milliseconds, at which new attempts are granted.", + AlwaysPrompt: true, + }, + StagePreUserRegistrationMaxAttempts: Flag{ + Name: "StagePreUserRegistrationMaxAttempts", + LongForm: "pre-registration-max", + Help: "Configuration options that apply before every user registration attempt. " + + "Total number of attempts allowed.", + AlwaysPrompt: true, + }, + StagePreUserRegistrationRate: Flag{ + Name: "StagePreUserRegistrationRate", + LongForm: "pre-registration-rate", + Help: "Configuration options that apply before every user registration attempt. " + + "Interval of time, given in milliseconds, at which new attempts are granted.", + AlwaysPrompt: true, + }, +} + +type ( + suspiciousIPThrottlingFlags struct { + Enabled Flag + Shields Flag + AllowList Flag + StagePreLoginMaxAttempts Flag + StagePreLoginRate Flag + StagePreUserRegistrationMaxAttempts Flag + StagePreUserRegistrationRate Flag + } + + suspiciousIPThrottlingInputs struct { + Enabled bool + Shields []string + AllowList []string + StagePreLoginMaxAttempts int + StagePreLoginRate int + StagePreUserRegistrationMaxAttempts int + StagePreUserRegistrationRate int + } +) + func suspiciousIPThrottlingCmd(cli *cli) *cobra.Command { cmd := &cobra.Command{ Use: "suspicious-ip-throttling", @@ -19,6 +98,7 @@ func suspiciousIPThrottlingCmd(cli *cli) *cobra.Command { cmd.SetUsageTemplate(resourceUsageTemplate()) cmd.AddCommand(showSuspiciousIPThrottlingCmd(cli)) + cmd.AddCommand(updateSuspiciousIPThrottlingCmd(cli)) return cmd } @@ -34,6 +114,29 @@ func showSuspiciousIPThrottlingCmd(cli *cli) *cobra.Command { } } +func updateSuspiciousIPThrottlingCmd(cli *cli) *cobra.Command { + var inputs suspiciousIPThrottlingInputs + + cmd := &cobra.Command{ + Use: "update", + Args: cobra.NoArgs, + Short: "Update suspicious ip throttling settings", + Long: "Update suspicious ip throttling settings.", + Example: `auth0 protection suspicious-ip-throttling update`, + RunE: updateSuspiciousIPThrottlingCmdRun(cli, &inputs), + } + + sitFlags.Enabled.RegisterBoolU(cmd, &inputs.Enabled, false) + sitFlags.Shields.RegisterStringSliceU(cmd, &inputs.Shields, []string{}) + sitFlags.AllowList.RegisterStringSliceU(cmd, &inputs.AllowList, []string{}) + sitFlags.StagePreLoginMaxAttempts.RegisterIntU(cmd, &inputs.StagePreLoginMaxAttempts, 1) + sitFlags.StagePreLoginRate.RegisterIntU(cmd, &inputs.StagePreLoginRate, 34560) + sitFlags.StagePreUserRegistrationMaxAttempts.RegisterIntU(cmd, &inputs.StagePreUserRegistrationMaxAttempts, 1) + sitFlags.StagePreUserRegistrationRate.RegisterIntU(cmd, &inputs.StagePreUserRegistrationRate, 1200) + + return cmd +} + func showSuspiciousIPThrottlingCmdRun(cli *cli) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { var sit *management.SuspiciousIPThrottling @@ -50,3 +153,110 @@ func showSuspiciousIPThrottlingCmdRun(cli *cli) func(cmd *cobra.Command, args [] return nil } } + +func updateSuspiciousIPThrottlingCmdRun( + cli *cli, + inputs *suspiciousIPThrottlingInputs, +) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + var sit *management.SuspiciousIPThrottling + err := ansi.Waiting(func() (err error) { + sit, err = cli.api.AttackProtection.GetSuspiciousIPThrottling() + return err + }) + if err != nil { + return err + } + + if err := sitFlags.Enabled.AskBoolU(cmd, &inputs.Enabled, sit.Enabled); err != nil { + return err + } + sit.Enabled = &inputs.Enabled + + shieldsString := strings.Join(sit.GetShields(), ",") + if err := sitFlags.Shields.AskManyU(cmd, &inputs.Shields, &shieldsString); err != nil { + return err + } + if len(inputs.Shields) == 0 { + inputs.Shields = sit.GetShields() + } + sit.Shields = &inputs.Shields + + allowListString := strings.Join(sit.GetAllowList(), ",") + if err := sitFlags.AllowList.AskManyU( + cmd, + &inputs.AllowList, + &allowListString, + ); err != nil { + return err + } + if len(inputs.AllowList) == 0 { + inputs.AllowList = sit.GetAllowList() + } + sit.AllowList = &inputs.AllowList + + // Return early if stage is missing, possible within PSaaS Tenants. + if sit.Stage == nil { + cli.renderer.SuspiciousIPThrottlingUpdate(sit) + return nil + } + + defaultPreLoginMaxAttempts := strconv.Itoa(sit.Stage.PreLogin.GetMaxAttempts()) + if err := sitFlags.StagePreLoginMaxAttempts.AskIntU( + cmd, + &inputs.StagePreLoginMaxAttempts, + &defaultPreLoginMaxAttempts, + ); err != nil { + return err + } + if inputs.StagePreLoginMaxAttempts == 0 { + inputs.StagePreLoginMaxAttempts = sit.Stage.PreLogin.GetMaxAttempts() + } + sit.Stage.PreLogin.MaxAttempts = &inputs.StagePreLoginMaxAttempts + + defaultPreLoginRate := strconv.Itoa(sit.Stage.PreLogin.GetRate()) + if err := sitFlags.StagePreLoginRate.AskIntU(cmd, &inputs.StagePreLoginRate, &defaultPreLoginRate); err != nil { + return err + } + if inputs.StagePreLoginRate == 0 { + inputs.StagePreLoginRate = sit.Stage.PreLogin.GetRate() + } + sit.Stage.PreLogin.Rate = &inputs.StagePreLoginRate + + defaultPreUserRegistrationMaxAttempts := strconv.Itoa(sit.Stage.PreUserRegistration.GetMaxAttempts()) + if err := sitFlags.StagePreUserRegistrationMaxAttempts.AskIntU( + cmd, + &inputs.StagePreUserRegistrationMaxAttempts, + &defaultPreUserRegistrationMaxAttempts, + ); err != nil { + return err + } + if inputs.StagePreUserRegistrationMaxAttempts == 0 { + inputs.StagePreUserRegistrationMaxAttempts = sit.Stage.PreUserRegistration.GetMaxAttempts() + } + sit.Stage.PreUserRegistration.MaxAttempts = &inputs.StagePreUserRegistrationMaxAttempts + + defaultPreUserRegistrationRate := strconv.Itoa(sit.Stage.PreUserRegistration.GetRate()) + if err := sitFlags.StagePreUserRegistrationRate.AskIntU( + cmd, + &inputs.StagePreUserRegistrationRate, + &defaultPreUserRegistrationRate, + ); err != nil { + return err + } + if inputs.StagePreUserRegistrationRate == 0 { + inputs.StagePreUserRegistrationRate = sit.Stage.PreUserRegistration.GetRate() + } + sit.Stage.PreUserRegistration.Rate = &inputs.StagePreUserRegistrationRate + + if err := ansi.Waiting(func() error { + return cli.api.AttackProtection.UpdateSuspiciousIPThrottling(sit) + }); err != nil { + return err + } + + cli.renderer.SuspiciousIPThrottlingUpdate(sit) + + return nil + } +} diff --git a/internal/cli/flags.go b/internal/cli/flags.go index 8597340f4..23f8985df 100644 --- a/internal/cli/flags.go +++ b/internal/cli/flags.go @@ -3,9 +3,10 @@ package cli import ( "fmt" + "github.com/spf13/cobra" + "github.com/auth0/auth0-cli/internal/ansi" "github.com/auth0/auth0-cli/internal/auth0" - "github.com/spf13/cobra" ) type Flag struct { @@ -61,6 +62,14 @@ func (f *Flag) AskBoolU(cmd *cobra.Command, value *bool, defaultValue *bool) err return askBoolFlag(cmd, f, value, defaultValue, true) } +func (f *Flag) AskInt(cmd *cobra.Command, value *int, defaultValue *string) error { + return askIntFlag(cmd, f, value, defaultValue, false) +} + +func (f *Flag) AskIntU(cmd *cobra.Command, value *int, defaultValue *string) error { + return askIntFlag(cmd, f, value, defaultValue, true) +} + func (f *Flag) Select(cmd *cobra.Command, value interface{}, options []string, defaultValue *string) error { return selectFlag(cmd, f, value, options, defaultValue, false) } @@ -169,6 +178,13 @@ func askBoolFlag(cmd *cobra.Command, f *Flag, value *bool, defaultValue *bool, i return nil } +func askIntFlag(cmd *cobra.Command, f *Flag, value *int, defaultValue *string, isUpdate bool) error { + if shouldAsk(cmd, f, isUpdate) { + return askInt(cmd, f, value, defaultValue, isUpdate) + } + return nil +} + func selectFlag(cmd *cobra.Command, f *Flag, value interface{}, options []string, defaultValue *string, isUpdate bool) error { if shouldAsk(cmd, f, isUpdate) { return _select(cmd, f, value, options, defaultValue, isUpdate) diff --git a/internal/cli/input.go b/internal/cli/input.go index b65c76a8d..4aaad4de9 100644 --- a/internal/cli/input.go +++ b/internal/cli/input.go @@ -5,9 +5,10 @@ import ( "os" "github.com/AlecAivazis/survey/v2/terminal" - "github.com/auth0/auth0-cli/internal/prompt" - "github.com/spf13/cobra" "github.com/auth0/go-auth0" + "github.com/spf13/cobra" + + "github.com/auth0/auth0-cli/internal/prompt" ) type commandInput interface { @@ -36,6 +37,17 @@ func askBool(cmd *cobra.Command, i commandInput, value *bool, defaultValue *bool return nil } +func askInt(cmd *cobra.Command, i commandInput, value *int, defaultValue *string, isUpdate bool) error { + isRequired := isInputRequired(i, isUpdate) + input := prompt.TextInput(i.GetName(), i.GetLabel(), i.GetHelp(), auth0.StringValue(defaultValue), isRequired) + + if err := prompt.AskOne(input, value); err != nil { + return handleInputError(err) + } + + return nil +} + func askPassword(cmd *cobra.Command, i commandInput, value interface{}, defaultValue *string, isUpdate bool) error { isRequired := isInputRequired(i, isUpdate) input := prompt.PasswordInput("", i.GetLabel(), auth0.StringValue(defaultValue), isRequired) diff --git a/internal/display/attack_protection.go b/internal/display/attack_protection.go index cb8baf6bc..159de25df 100644 --- a/internal/display/attack_protection.go +++ b/internal/display/attack_protection.go @@ -49,6 +49,11 @@ func (r *Renderer) BreachedPasswordDetectionShow(bpd *management.BreachedPasswor r.Result(makeBreachedPasswordDetectionView(bpd)) } +func (r *Renderer) BreachedPasswordDetectionUpdate(bpd *management.BreachedPasswordDetection) { + r.Heading("Breached Password Detection Updated") + r.Result(makeBreachedPasswordDetectionView(bpd)) +} + func makeBreachedPasswordDetectionView(bpd *management.BreachedPasswordDetection) *breachedPasswordDetectionView { return &breachedPasswordDetectionView{ Enabled: boolean(bpd.GetEnabled()), @@ -103,6 +108,11 @@ func (r *Renderer) BruteForceProtectionShow(bfp *management.BruteForceProtection r.Result(makeBruteForceProtectionView(bfp)) } +func (r *Renderer) BruteForceProtectionUpdate(bfp *management.BruteForceProtection) { + r.Heading("Brute Force Protection Updated") + r.Result(makeBruteForceProtectionView(bfp)) +} + func makeBruteForceProtectionView(bfp *management.BruteForceProtection) *bruteForceProtectionView { return &bruteForceProtectionView{ Enabled: boolean(bfp.GetEnabled()), @@ -172,6 +182,11 @@ func (r *Renderer) SuspiciousIPThrottlingShow(sit *management.SuspiciousIPThrott r.Result(makeSuspiciousIPThrottlingView(sit)) } +func (r *Renderer) SuspiciousIPThrottlingUpdate(sit *management.SuspiciousIPThrottling) { + r.Heading("Suspicious IP Throttling Updated") + r.Result(makeSuspiciousIPThrottlingView(sit)) +} + func makeSuspiciousIPThrottlingView(sit *management.SuspiciousIPThrottling) *suspiciousIPThrottlingView { view := &suspiciousIPThrottlingView{ Enabled: boolean(sit.GetEnabled()),