From 69f066a1aaa076aa083d7f69fadf29a5476bfd3c Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Fri, 15 Nov 2024 01:07:55 +0000 Subject: [PATCH 1/9] feat: add minimal password length and password requirements config --- internal/start/start.go | 2 ++ pkg/config/auth.go | 2 ++ pkg/config/templates/config.toml | 7 +++++++ 3 files changed, 11 insertions(+) diff --git a/internal/start/start.go b/internal/start/start.go index bdb0989aa..9c997255f 100644 --- a/internal/start/start.go +++ b/internal/start/start.go @@ -506,6 +506,8 @@ EOF fmt.Sprintf("GOTRUE_SMS_TEMPLATE=%v", utils.Config.Auth.Sms.Template), "GOTRUE_SMS_TEST_OTP=" + testOTP.String(), + fmt.Sprintf("GOTRUE_PASSWORD_MIN_LENGTH=%v", utils.Config.Auth.MinimumPasswordLength), + fmt.Sprintf("GOTRUE_PASSWORD_REQUIRED_CHARACTERS=%v", utils.Config.Auth.PasswordRequirements), fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=%v", utils.Config.Auth.EnableRefreshTokenRotation), fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=%v", utils.Config.Auth.RefreshTokenReuseInterval), fmt.Sprintf("GOTRUE_SECURITY_MANUAL_LINKING_ENABLED=%v", utils.Config.Auth.EnableManualLinking), diff --git a/pkg/config/auth.go b/pkg/config/auth.go index a2ea016f2..167b41308 100644 --- a/pkg/config/auth.go +++ b/pkg/config/auth.go @@ -18,6 +18,8 @@ type ( SiteUrl string `toml:"site_url"` AdditionalRedirectUrls []string `toml:"additional_redirect_urls"` JwtExpiry uint `toml:"jwt_expiry"` + MinimumPasswordLength uint `toml:"minimum_password_length"` + PasswordRequirements string `toml:"password_requirements"` EnableRefreshTokenRotation bool `toml:"enable_refresh_token_rotation"` RefreshTokenReuseInterval uint `toml:"refresh_token_reuse_interval"` EnableManualLinking bool `toml:"enable_manual_linking"` diff --git a/pkg/config/templates/config.toml b/pkg/config/templates/config.toml index 43854f7d6..79e720839 100644 --- a/pkg/config/templates/config.toml +++ b/pkg/config/templates/config.toml @@ -98,6 +98,13 @@ additional_redirect_urls = ["https://127.0.0.1:3000"] # How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). jwt_expiry = 3600 # If disabled, the refresh token will never expire. +minimum_password_length = 6 +# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more. +password_requirements = "" +# Passwords that do not have at least one of each will be rejected as weak. +# Letters and digits: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789 +# Lowercase, uppercase letters and digits: abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789 +# Lowercase, uppercase letters, digits and symbols: abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};'\\\\:\"|<>?,./`~ enable_refresh_token_rotation = true # Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. # Requires enable_refresh_token_rotation = true. From 260897afcdbd94ea0204639bddf90a75b354f6ef Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Fri, 15 Nov 2024 11:55:00 +0000 Subject: [PATCH 2/9] Update auth config body --- pkg/config/auth.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/config/auth.go b/pkg/config/auth.go index 167b41308..180908d7f 100644 --- a/pkg/config/auth.go +++ b/pkg/config/auth.go @@ -194,6 +194,8 @@ func (a *auth) ToUpdateAuthConfigBody() v1API.UpdateAuthConfigBody { SecurityManualLinkingEnabled: &a.EnableManualLinking, DisableSignup: cast.Ptr(!a.EnableSignup), ExternalAnonymousUsersEnabled: &a.EnableAnonymousSignIns, + PasswordMinLength: cast.UintToIntPtr(&a.MinimumPasswordLength), + PasswordRequiredCharacters: (*v1API.UpdateAuthConfigBodyPasswordRequiredCharacters)(&a.PasswordRequirements), } a.Hook.toAuthConfigBody(&body) a.MFA.toAuthConfigBody(&body) From 0030a062029ef59cbe19ee911dcd0ebbebae8b07 Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Tue, 26 Nov 2024 12:47:09 +0000 Subject: [PATCH 3/9] fix: change password requirements to a map --- internal/start/start.go | 9 ++++++++- pkg/config/auth.go | 29 +++++++++++++++++++---------- pkg/config/config.go | 4 ++++ 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/internal/start/start.go b/internal/start/start.go index 9c997255f..026a75a0a 100644 --- a/internal/start/start.go +++ b/internal/start/start.go @@ -462,6 +462,13 @@ EOF formatMapForEnvConfig(utils.Config.Auth.Sms.TestOTP, &testOTP) } + var password_requirements = map[config.PasswordRequirements]string{ + "": "", + "letters_digits": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789", + "lower_upper_letters_digits": "abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789", + "lower_upper_letters_digits_symbols": "abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};'\\\\:\"|<>?,./`~", + } + env := []string{ "API_EXTERNAL_URL=" + utils.Config.Api.ExternalUrl, @@ -507,7 +514,7 @@ EOF "GOTRUE_SMS_TEST_OTP=" + testOTP.String(), fmt.Sprintf("GOTRUE_PASSWORD_MIN_LENGTH=%v", utils.Config.Auth.MinimumPasswordLength), - fmt.Sprintf("GOTRUE_PASSWORD_REQUIRED_CHARACTERS=%v", utils.Config.Auth.PasswordRequirements), + fmt.Sprintf("GOTRUE_PASSWORD_REQUIRED_CHARACTERS=%v", password_requirements[utils.Config.Auth.PasswordRequirements]), fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=%v", utils.Config.Auth.EnableRefreshTokenRotation), fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=%v", utils.Config.Auth.RefreshTokenReuseInterval), fmt.Sprintf("GOTRUE_SECURITY_MANUAL_LINKING_ENABLED=%v", utils.Config.Auth.EnableManualLinking), diff --git a/pkg/config/auth.go b/pkg/config/auth.go index 180908d7f..3baf53984 100644 --- a/pkg/config/auth.go +++ b/pkg/config/auth.go @@ -10,21 +10,30 @@ import ( "github.com/supabase/cli/pkg/diff" ) +type PasswordRequirements string + +const ( + NoRequirements PasswordRequirements = "" + LettersDigits PasswordRequirements = "letters_digits" + LowerUpperLettersDigits PasswordRequirements = "lower_upper_letters_digits" + LowerUpperLettersDigitsSymbols PasswordRequirements = "lower_upper_letters_digits_symbols" +) + type ( auth struct { Enabled bool `toml:"enabled"` Image string `toml:"-"` - SiteUrl string `toml:"site_url"` - AdditionalRedirectUrls []string `toml:"additional_redirect_urls"` - JwtExpiry uint `toml:"jwt_expiry"` - MinimumPasswordLength uint `toml:"minimum_password_length"` - PasswordRequirements string `toml:"password_requirements"` - EnableRefreshTokenRotation bool `toml:"enable_refresh_token_rotation"` - RefreshTokenReuseInterval uint `toml:"refresh_token_reuse_interval"` - EnableManualLinking bool `toml:"enable_manual_linking"` - EnableSignup bool `toml:"enable_signup"` - EnableAnonymousSignIns bool `toml:"enable_anonymous_sign_ins"` + SiteUrl string `toml:"site_url"` + AdditionalRedirectUrls []string `toml:"additional_redirect_urls"` + JwtExpiry uint `toml:"jwt_expiry"` + MinimumPasswordLength uint `toml:"minimum_password_length"` + PasswordRequirements PasswordRequirements `toml:"password_requirements"` + EnableRefreshTokenRotation bool `toml:"enable_refresh_token_rotation"` + RefreshTokenReuseInterval uint `toml:"refresh_token_reuse_interval"` + EnableManualLinking bool `toml:"enable_manual_linking"` + EnableSignup bool `toml:"enable_signup"` + EnableAnonymousSignIns bool `toml:"enable_anonymous_sign_ins"` Hook hook `toml:"hook"` MFA mfa `toml:"mfa"` diff --git a/pkg/config/config.go b/pkg/config/config.go index b1e7ed570..dcb1ffd4e 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -666,6 +666,10 @@ func (c *baseConfig) Validate(fsys fs.FS) error { return errors.Errorf("Invalid config for auth.additional_redirect_urls[%d]: %v", i, err) } } + allowed := []PasswordRequirements{NoRequirements, LettersDigits, LowerUpperLettersDigits, LowerUpperLettersDigitsSymbols} + if !sliceContains(allowed, c.Auth.PasswordRequirements) { + return errors.Errorf("Invalid config for auth.password_requirements. Must be one of: %v", allowed) + } if err := c.Auth.Hook.validate(); err != nil { return err } From 30ba941de81c655dcc7ef9af55a4e53d0512f85f Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Tue, 26 Nov 2024 13:13:49 +0000 Subject: [PATCH 4/9] tests: update config and diff files --- pkg/config/templates/config.toml | 6 +++--- .../TestEmailDiff/local_disabled_remote_enabled.diff | 4 ++-- .../TestEmailDiff/local_enabled_remote_disabled.diff | 2 +- .../TestExternalDiff/local_enabled_and_disabled.diff | 4 ++-- .../testdata/TestHookDiff/local_enabled_and_disabled.diff | 4 ++-- .../testdata/TestMfaDiff/local_enabled_and_disabled.diff | 2 +- .../TestSmsDiff/enable_sign_up_without_provider.diff | 2 +- .../TestSmsDiff/local_disabled_remote_enabled.diff | 4 ++-- .../TestSmsDiff/local_enabled_remote_disabled.diff | 6 +++--- pkg/config/testdata/config.toml | 7 +++++++ 10 files changed, 24 insertions(+), 17 deletions(-) diff --git a/pkg/config/templates/config.toml b/pkg/config/templates/config.toml index 79e720839..bebb2b3b9 100644 --- a/pkg/config/templates/config.toml +++ b/pkg/config/templates/config.toml @@ -102,9 +102,9 @@ minimum_password_length = 6 # Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more. password_requirements = "" # Passwords that do not have at least one of each will be rejected as weak. -# Letters and digits: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789 -# Lowercase, uppercase letters and digits: abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789 -# Lowercase, uppercase letters, digits and symbols: abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};'\\\\:\"|<>?,./`~ +# - letters_digits +# - lower_upper_letters_digits +# - lower_upper_letters_digits_symbols enable_refresh_token_rotation = true # Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. # Requires enable_refresh_token_rotation = true. diff --git a/pkg/config/testdata/TestEmailDiff/local_disabled_remote_enabled.diff b/pkg/config/testdata/TestEmailDiff/local_disabled_remote_enabled.diff index d6c7f6dca..3123336fa 100644 --- a/pkg/config/testdata/TestEmailDiff/local_disabled_remote_enabled.diff +++ b/pkg/config/testdata/TestEmailDiff/local_disabled_remote_enabled.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -49,13 +49,13 @@ +@@ -51,13 +51,13 @@ inactivity_timeout = "0s" [email] @@ -22,7 +22,7 @@ diff remote[auth] local[auth] [email.template] [email.template.confirmation] content_path = "" -@@ -69,13 +69,6 @@ +@@ -71,13 +71,6 @@ content_path = "" [email.template.recovery] content_path = "" diff --git a/pkg/config/testdata/TestEmailDiff/local_enabled_remote_disabled.diff b/pkg/config/testdata/TestEmailDiff/local_enabled_remote_disabled.diff index 9bcf8ccba..24386ae91 100644 --- a/pkg/config/testdata/TestEmailDiff/local_enabled_remote_disabled.diff +++ b/pkg/config/testdata/TestEmailDiff/local_enabled_remote_disabled.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -49,28 +49,43 @@ +@@ -51,28 +51,43 @@ inactivity_timeout = "0s" [email] diff --git a/pkg/config/testdata/TestExternalDiff/local_enabled_and_disabled.diff b/pkg/config/testdata/TestExternalDiff/local_enabled_and_disabled.diff index fcfb3d1d0..cf82d1cc9 100644 --- a/pkg/config/testdata/TestExternalDiff/local_enabled_and_disabled.diff +++ b/pkg/config/testdata/TestExternalDiff/local_enabled_and_disabled.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -88,7 +88,7 @@ +@@ -90,7 +90,7 @@ [external] [external.apple] @@ -10,7 +10,7 @@ diff remote[auth] local[auth] client_id = "test-client-1,test-client-2" secret = "hash:ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252" url = "" -@@ -144,7 +144,7 @@ +@@ -146,7 +146,7 @@ redirect_uri = "" skip_nonce_check = false [external.google] diff --git a/pkg/config/testdata/TestHookDiff/local_enabled_and_disabled.diff b/pkg/config/testdata/TestHookDiff/local_enabled_and_disabled.diff index b45808d4c..b21cd4073 100644 --- a/pkg/config/testdata/TestHookDiff/local_enabled_and_disabled.diff +++ b/pkg/config/testdata/TestHookDiff/local_enabled_and_disabled.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -9,7 +9,7 @@ +@@ -11,7 +11,7 @@ [hook] [hook.mfa_verification_attempt] @@ -10,7 +10,7 @@ diff remote[auth] local[auth] uri = "" secrets = "" [hook.password_verification_attempt] -@@ -17,7 +17,7 @@ +@@ -19,7 +19,7 @@ uri = "" secrets = "" [hook.custom_access_token] diff --git a/pkg/config/testdata/TestMfaDiff/local_enabled_and_disabled.diff b/pkg/config/testdata/TestMfaDiff/local_enabled_and_disabled.diff index f7935bfa4..866ce8fae 100644 --- a/pkg/config/testdata/TestMfaDiff/local_enabled_and_disabled.diff +++ b/pkg/config/testdata/TestMfaDiff/local_enabled_and_disabled.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -30,16 +30,16 @@ +@@ -32,16 +32,16 @@ secrets = "" [mfa] diff --git a/pkg/config/testdata/TestSmsDiff/enable_sign_up_without_provider.diff b/pkg/config/testdata/TestSmsDiff/enable_sign_up_without_provider.diff index 2e44496fd..637a0206e 100644 --- a/pkg/config/testdata/TestSmsDiff/enable_sign_up_without_provider.diff +++ b/pkg/config/testdata/TestSmsDiff/enable_sign_up_without_provider.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -58,7 +58,7 @@ +@@ -60,7 +60,7 @@ otp_expiry = 0 [sms] diff --git a/pkg/config/testdata/TestSmsDiff/local_disabled_remote_enabled.diff b/pkg/config/testdata/TestSmsDiff/local_disabled_remote_enabled.diff index a173a8adf..9a34f0ad2 100644 --- a/pkg/config/testdata/TestSmsDiff/local_disabled_remote_enabled.diff +++ b/pkg/config/testdata/TestSmsDiff/local_disabled_remote_enabled.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -58,12 +58,12 @@ +@@ -60,12 +60,12 @@ otp_expiry = 0 [sms] @@ -19,7 +19,7 @@ diff remote[auth] local[auth] account_sid = "" message_service_sid = "" auth_token = "" -@@ -85,9 +85,6 @@ +@@ -87,9 +87,6 @@ from = "" api_key = "" api_secret = "" diff --git a/pkg/config/testdata/TestSmsDiff/local_enabled_remote_disabled.diff b/pkg/config/testdata/TestSmsDiff/local_enabled_remote_disabled.diff index f9ee1d7f0..44877a962 100644 --- a/pkg/config/testdata/TestSmsDiff/local_enabled_remote_disabled.diff +++ b/pkg/config/testdata/TestSmsDiff/local_enabled_remote_disabled.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -58,12 +58,12 @@ +@@ -60,12 +60,12 @@ otp_expiry = 0 [sms] @@ -19,7 +19,7 @@ diff remote[auth] local[auth] account_sid = "" message_service_sid = "" auth_token = "" -@@ -73,9 +73,9 @@ +@@ -75,9 +75,9 @@ message_service_sid = "" auth_token = "" [sms.messagebird] @@ -32,7 +32,7 @@ diff remote[auth] local[auth] [sms.textlocal] enabled = false sender = "" -@@ -85,6 +85,8 @@ +@@ -87,6 +87,8 @@ from = "" api_key = "" api_secret = "" diff --git a/pkg/config/testdata/config.toml b/pkg/config/testdata/config.toml index 9aba86c3d..585bf8c25 100644 --- a/pkg/config/testdata/config.toml +++ b/pkg/config/testdata/config.toml @@ -98,6 +98,13 @@ additional_redirect_urls = ["https://127.0.0.1:3000", "env(AUTH_CALLBACK_URL)"] # How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). jwt_expiry = 3600 # If disabled, the refresh token will never expire. +minimum_password_length = 6 +# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more. +password_requirements = "" +# Passwords that do not have at least one of each will be rejected as weak. +# - letters_digits +# - lower_upper_letters_digits +# - lower_upper_letters_digits_symbols enable_refresh_token_rotation = true # Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. # Requires enable_refresh_token_rotation = true. From 8036aefb4c01183cbf7cff6b8ff3c748e1609042 Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Tue, 26 Nov 2024 13:27:28 +0000 Subject: [PATCH 5/9] update diffs for tests --- .../TestExternalDiff/local_enabled_and_disabled.diff | 5 +++-- .../testdata/TestSmsDiff/local_disabled_remote_enabled.diff | 3 +-- .../testdata/TestSmsDiff/local_enabled_remote_disabled.diff | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/config/testdata/TestExternalDiff/local_enabled_and_disabled.diff b/pkg/config/testdata/TestExternalDiff/local_enabled_and_disabled.diff index e68c1b76e..234e422f4 100644 --- a/pkg/config/testdata/TestExternalDiff/local_enabled_and_disabled.diff +++ b/pkg/config/testdata/TestExternalDiff/local_enabled_and_disabled.diff @@ -1,7 +1,8 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -90,7 +90,7 @@ +@@ -91,7 +91,7 @@ + [external] [external.apple] -enabled = false @@ -9,7 +10,7 @@ diff remote[auth] local[auth] client_id = "test-client-1,test-client-2" secret = "hash:ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252" url = "" -@@ -146,7 +146,7 @@ +@@ -147,7 +147,7 @@ redirect_uri = "" skip_nonce_check = false [external.google] diff --git a/pkg/config/testdata/TestSmsDiff/local_disabled_remote_enabled.diff b/pkg/config/testdata/TestSmsDiff/local_disabled_remote_enabled.diff index d88f2c65c..4348c80ba 100644 --- a/pkg/config/testdata/TestSmsDiff/local_disabled_remote_enabled.diff +++ b/pkg/config/testdata/TestSmsDiff/local_disabled_remote_enabled.diff @@ -19,8 +19,7 @@ diff remote[auth] local[auth] account_sid = "" message_service_sid = "" auth_token = "" -@@ -87,9 +87,6 @@ - from = "" +@@ -88,8 +88,6 @@ api_key = "" api_secret = "" [sms.test_otp] diff --git a/pkg/config/testdata/TestSmsDiff/local_enabled_remote_disabled.diff b/pkg/config/testdata/TestSmsDiff/local_enabled_remote_disabled.diff index a9fd38d93..e29c287ed 100644 --- a/pkg/config/testdata/TestSmsDiff/local_enabled_remote_disabled.diff +++ b/pkg/config/testdata/TestSmsDiff/local_enabled_remote_disabled.diff @@ -32,8 +32,7 @@ diff remote[auth] local[auth] [sms.textlocal] enabled = false sender = "" -@@ -87,6 +87,8 @@ - from = "" +@@ -88,6 +88,7 @@ api_key = "" api_secret = "" [sms.test_otp] From a2efba42c0193b57a1db498afb68d3a74fa6e3af Mon Sep 17 00:00:00 2001 From: Qiao Han Date: Thu, 28 Nov 2024 18:38:54 +0800 Subject: [PATCH 6/9] chore: convert between local and remote password --- internal/start/start.go | 9 +-------- pkg/config/auth.go | 29 ++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/internal/start/start.go b/internal/start/start.go index 231f5e818..defb8902a 100644 --- a/internal/start/start.go +++ b/internal/start/start.go @@ -462,13 +462,6 @@ EOF formatMapForEnvConfig(utils.Config.Auth.Sms.TestOTP, &testOTP) } - var password_requirements = map[config.PasswordRequirements]string{ - "": "", - "letters_digits": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789", - "lower_upper_letters_digits": "abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789", - "lower_upper_letters_digits_symbols": "abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};'\\\\:\"|<>?,./`~", - } - env := []string{ "API_EXTERNAL_URL=" + utils.Config.Api.ExternalUrl, @@ -514,7 +507,7 @@ EOF "GOTRUE_SMS_TEST_OTP=" + testOTP.String(), fmt.Sprintf("GOTRUE_PASSWORD_MIN_LENGTH=%v", utils.Config.Auth.MinimumPasswordLength), - fmt.Sprintf("GOTRUE_PASSWORD_REQUIRED_CHARACTERS=%v", password_requirements[utils.Config.Auth.PasswordRequirements]), + fmt.Sprintf("GOTRUE_PASSWORD_REQUIRED_CHARACTERS=%v", utils.Config.Auth.PasswordRequirements.ToChar()), fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=%v", utils.Config.Auth.EnableRefreshTokenRotation), fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=%v", utils.Config.Auth.RefreshTokenReuseInterval), fmt.Sprintf("GOTRUE_SECURITY_MANUAL_LINKING_ENABLED=%v", utils.Config.Auth.EnableManualLinking), diff --git a/pkg/config/auth.go b/pkg/config/auth.go index a806555b9..77de76201 100644 --- a/pkg/config/auth.go +++ b/pkg/config/auth.go @@ -19,6 +19,30 @@ const ( LowerUpperLettersDigitsSymbols PasswordRequirements = "lower_upper_letters_digits_symbols" ) +func (r PasswordRequirements) ToChar() v1API.UpdateAuthConfigBodyPasswordRequiredCharacters { + switch r { + case LettersDigits: + return v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 + case LowerUpperLettersDigits: + return v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567891 + case LowerUpperLettersDigitsSymbols: + return v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567892 + } + return v1API.Empty +} + +func NewPasswordRequirement(c v1API.UpdateAuthConfigBodyPasswordRequiredCharacters) PasswordRequirements { + switch c { + case v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789: + return LettersDigits + case v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567891: + return LowerUpperLettersDigits + case v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567892: + return LowerUpperLettersDigitsSymbols + } + return NoRequirements +} + type ( auth struct { Enabled bool `toml:"enabled"` @@ -204,7 +228,7 @@ func (a *auth) ToUpdateAuthConfigBody() v1API.UpdateAuthConfigBody { DisableSignup: cast.Ptr(!a.EnableSignup), ExternalAnonymousUsersEnabled: &a.EnableAnonymousSignIns, PasswordMinLength: cast.UintToIntPtr(&a.MinimumPasswordLength), - PasswordRequiredCharacters: (*v1API.UpdateAuthConfigBodyPasswordRequiredCharacters)(&a.PasswordRequirements), + PasswordRequiredCharacters: cast.Ptr(a.PasswordRequirements.ToChar()), } a.Hook.toAuthConfigBody(&body) a.MFA.toAuthConfigBody(&body) @@ -224,6 +248,9 @@ func (a *auth) FromRemoteAuthConfig(remoteConfig v1API.AuthConfigResponse) { a.EnableManualLinking = cast.Val(remoteConfig.SecurityManualLinkingEnabled, false) a.EnableSignup = !cast.Val(remoteConfig.DisableSignup, false) a.EnableAnonymousSignIns = cast.Val(remoteConfig.ExternalAnonymousUsersEnabled, false) + a.MinimumPasswordLength = cast.IntToUint(cast.Val(remoteConfig.PasswordMinLength, 0)) + prc := cast.Val(remoteConfig.PasswordRequiredCharacters, "") + a.PasswordRequirements = NewPasswordRequirement(v1API.UpdateAuthConfigBodyPasswordRequiredCharacters(prc)) a.Hook.fromAuthConfig(remoteConfig) a.MFA.fromAuthConfig(remoteConfig) a.Sessions.fromAuthConfig(remoteConfig) From 21a64ad3cd6caf3d091b7f8943c24817aa3db2e1 Mon Sep 17 00:00:00 2001 From: Qiao Han Date: Thu, 28 Nov 2024 18:51:01 +0800 Subject: [PATCH 7/9] chore: refactor config fields --- pkg/config/auth.go | 4 ++-- pkg/config/templates/config.toml | 12 +++++------- pkg/config/testdata/config.toml | 12 +++++------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/pkg/config/auth.go b/pkg/config/auth.go index 77de76201..572b88950 100644 --- a/pkg/config/auth.go +++ b/pkg/config/auth.go @@ -51,13 +51,13 @@ type ( SiteUrl string `toml:"site_url"` AdditionalRedirectUrls []string `toml:"additional_redirect_urls"` JwtExpiry uint `toml:"jwt_expiry"` - MinimumPasswordLength uint `toml:"minimum_password_length"` - PasswordRequirements PasswordRequirements `toml:"password_requirements"` EnableRefreshTokenRotation bool `toml:"enable_refresh_token_rotation"` RefreshTokenReuseInterval uint `toml:"refresh_token_reuse_interval"` EnableManualLinking bool `toml:"enable_manual_linking"` EnableSignup bool `toml:"enable_signup"` EnableAnonymousSignIns bool `toml:"enable_anonymous_sign_ins"` + MinimumPasswordLength uint `toml:"minimum_password_length"` + PasswordRequirements PasswordRequirements `toml:"password_requirements"` Hook hook `toml:"hook"` MFA mfa `toml:"mfa"` diff --git a/pkg/config/templates/config.toml b/pkg/config/templates/config.toml index ee410caf1..a4f6ec071 100644 --- a/pkg/config/templates/config.toml +++ b/pkg/config/templates/config.toml @@ -100,13 +100,6 @@ additional_redirect_urls = ["https://127.0.0.1:3000"] # How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). jwt_expiry = 3600 # If disabled, the refresh token will never expire. -minimum_password_length = 6 -# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more. -password_requirements = "" -# Passwords that do not have at least one of each will be rejected as weak. -# - letters_digits -# - lower_upper_letters_digits -# - lower_upper_letters_digits_symbols enable_refresh_token_rotation = true # Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. # Requires enable_refresh_token_rotation = true. @@ -117,6 +110,11 @@ enable_signup = true enable_anonymous_sign_ins = false # Allow/disallow testing manual linking of accounts enable_manual_linking = false +# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more. +minimum_password_length = 6 +# Passwords that do not meet the following requirements will be rejected as weak. Supported values +# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols` +password_requirements = "" [auth.email] # Allow/disallow new user signups via email to your project. diff --git a/pkg/config/testdata/config.toml b/pkg/config/testdata/config.toml index bd1676cbc..ce845743e 100644 --- a/pkg/config/testdata/config.toml +++ b/pkg/config/testdata/config.toml @@ -98,13 +98,6 @@ additional_redirect_urls = ["https://127.0.0.1:3000", "env(AUTH_CALLBACK_URL)"] # How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). jwt_expiry = 3600 # If disabled, the refresh token will never expire. -minimum_password_length = 6 -# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more. -password_requirements = "" -# Passwords that do not have at least one of each will be rejected as weak. -# - letters_digits -# - lower_upper_letters_digits -# - lower_upper_letters_digits_symbols enable_refresh_token_rotation = true # Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. # Requires enable_refresh_token_rotation = true. @@ -113,6 +106,11 @@ refresh_token_reuse_interval = 10 enable_signup = true # Allow/disallow testing manual linking of accounts enable_manual_linking = true +# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more. +minimum_password_length = 6 +# Passwords that do not meet the following requirements will be rejected as weak. Supported values +# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols` +password_requirements = "" [auth.email] # Allow/disallow new user signups via email to your project. From 879808c6b4d9f8c5fec6cd38f597e34d583d40b0 Mon Sep 17 00:00:00 2001 From: Qiao Han Date: Thu, 28 Nov 2024 19:52:31 +0800 Subject: [PATCH 8/9] chore: add unit tests for password requirement --- pkg/config/auth_test.go | 83 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/pkg/config/auth_test.go b/pkg/config/auth_test.go index 119cdf65d..cba5cd9a1 100644 --- a/pkg/config/auth_test.go +++ b/pkg/config/auth_test.go @@ -34,6 +34,89 @@ func assertSnapshotEqual(t *testing.T, actual []byte) { assert.Equal(t, string(expected), string(actual)) } +func TestAuthDiff(t *testing.T) { + t.Run("local and remote enabled", func(t *testing.T) { + c := newWithDefaults() + c.SiteUrl = "http://127.0.0.1:3000" + c.AdditionalRedirectUrls = []string{"https://127.0.0.1:3000"} + c.JwtExpiry = 3600 + c.EnableRefreshTokenRotation = true + c.RefreshTokenReuseInterval = 10 + c.EnableManualLinking = true + c.EnableSignup = true + c.EnableAnonymousSignIns = true + c.MinimumPasswordLength = 6 + c.PasswordRequirements = LettersDigits + // Run test + diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + SiteUrl: cast.Ptr("http://127.0.0.1:3000"), + UriAllowList: cast.Ptr("https://127.0.0.1:3000"), + JwtExp: cast.Ptr(3600), + RefreshTokenRotationEnabled: cast.Ptr(true), + SecurityRefreshTokenReuseInterval: cast.Ptr(10), + SecurityManualLinkingEnabled: cast.Ptr(true), + DisableSignup: cast.Ptr(false), + ExternalAnonymousUsersEnabled: cast.Ptr(true), + PasswordMinLength: cast.Ptr(6), + PasswordRequiredCharacters: cast.Ptr(string(v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789)), + }) + // Check error + assert.NoError(t, err) + assert.Empty(t, string(diff)) + }) + + t.Run("local enabled and disabled", func(t *testing.T) { + c := newWithDefaults() + c.SiteUrl = "http://127.0.0.1:3000" + c.AdditionalRedirectUrls = []string{"https://127.0.0.1:3000"} + c.JwtExpiry = 3600 + c.EnableRefreshTokenRotation = false + c.RefreshTokenReuseInterval = 10 + c.EnableManualLinking = false + c.EnableSignup = false + c.EnableAnonymousSignIns = false + c.MinimumPasswordLength = 6 + c.PasswordRequirements = LowerUpperLettersDigitsSymbols + // Run test + diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + SiteUrl: cast.Ptr(""), + UriAllowList: cast.Ptr("https://127.0.0.1:3000,https://ref.supabase.co"), + JwtExp: cast.Ptr(0), + RefreshTokenRotationEnabled: cast.Ptr(true), + SecurityRefreshTokenReuseInterval: cast.Ptr(0), + SecurityManualLinkingEnabled: cast.Ptr(true), + DisableSignup: cast.Ptr(false), + ExternalAnonymousUsersEnabled: cast.Ptr(true), + PasswordMinLength: cast.Ptr(8), + PasswordRequiredCharacters: cast.Ptr(string(v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789)), + }) + // Check error + assert.NoError(t, err) + assertSnapshotEqual(t, diff) + }) + + t.Run("local and remote disabled", func(t *testing.T) { + c := newWithDefaults() + c.EnableSignup = false + // Run test + diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + SiteUrl: cast.Ptr(""), + UriAllowList: cast.Ptr(""), + JwtExp: cast.Ptr(0), + RefreshTokenRotationEnabled: cast.Ptr(false), + SecurityRefreshTokenReuseInterval: cast.Ptr(0), + SecurityManualLinkingEnabled: cast.Ptr(false), + DisableSignup: cast.Ptr(true), + ExternalAnonymousUsersEnabled: cast.Ptr(false), + PasswordMinLength: cast.Ptr(0), + PasswordRequiredCharacters: cast.Ptr(""), + }) + // Check error + assert.NoError(t, err) + assert.Empty(t, string(diff)) + }) +} + func TestHookDiff(t *testing.T) { t.Run("local and remote enabled", func(t *testing.T) { c := newWithDefaults() From a7e74f89cab55e0898fbb6ec99c8996ed068ae3b Mon Sep 17 00:00:00 2001 From: Qiao Han Date: Thu, 28 Nov 2024 19:54:11 +0800 Subject: [PATCH 9/9] chore: add missing testdata --- .../local_enabled_and_disabled.diff | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 pkg/config/testdata/TestAuthDiff/local_enabled_and_disabled.diff diff --git a/pkg/config/testdata/TestAuthDiff/local_enabled_and_disabled.diff b/pkg/config/testdata/TestAuthDiff/local_enabled_and_disabled.diff new file mode 100644 index 000000000..3db4d5462 --- /dev/null +++ b/pkg/config/testdata/TestAuthDiff/local_enabled_and_disabled.diff @@ -0,0 +1,28 @@ +diff remote[auth] local[auth] +--- remote[auth] ++++ local[auth] +@@ -1,14 +1,14 @@ + enabled = false +-site_url = "" +-additional_redirect_urls = ["https://127.0.0.1:3000", "https://ref.supabase.co"] +-jwt_expiry = 0 +-enable_refresh_token_rotation = true +-refresh_token_reuse_interval = 0 +-enable_manual_linking = true +-enable_signup = true +-enable_anonymous_sign_ins = true +-minimum_password_length = 8 +-password_requirements = "letters_digits" ++site_url = "http://127.0.0.1:3000" ++additional_redirect_urls = ["https://127.0.0.1:3000"] ++jwt_expiry = 3600 ++enable_refresh_token_rotation = false ++refresh_token_reuse_interval = 10 ++enable_manual_linking = false ++enable_signup = false ++enable_anonymous_sign_ins = false ++minimum_password_length = 6 ++password_requirements = "lower_upper_letters_digits_symbols" + + [hook] + [hook.mfa_verification_attempt]