From c724f57afc981a31db603ff4823f514093e70d82 Mon Sep 17 00:00:00 2001 From: joel Date: Wed, 14 Feb 2024 16:22:37 +0800 Subject: [PATCH] feat: accept empty URIs feat: update validation for secrets tests: add additional test for secret validation feat: add more test cases --- example.env | 12 +++++++++ internal/conf/configuration.go | 42 +++++++++++++++++++++++------ internal/conf/configuration_test.go | 36 +++++++++++++++++++++++-- 3 files changed, 80 insertions(+), 10 deletions(-) diff --git a/example.env b/example.env index 96bcc22a9f..731b34a6d3 100644 --- a/example.env +++ b/example.env @@ -225,6 +225,18 @@ GOTRUE_COOKIE_KEY="sb" GOTRUE_COOKIE_DOMAIN="localhost" GOTRUE_MAX_VERIFIED_FACTORS=10 +# Auth Hook Configuration +GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED=false +GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI="" +# Only for HTTPS Hooks +GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRET="" + +GOTRUE_HOOK_CUSTOM_SMS_PROVIDER_ENABLED=false +GOTRUE_HOOK_CUSTOM_SMS_PROVIDER_URI="" +# Only for HTTPS Hooks +GOTRUE_HOOK_CUSTOM_SMS_PROVIDER_SECRET="" + + # Test OTP Config GOTRUE_SMS_TEST_OTP=":, :..." GOTRUE_SMS_TEST_OTP_VALID_UNTIL="" # (e.g. 2023-09-29T08:14:06Z) diff --git a/internal/conf/configuration.go b/internal/conf/configuration.go index 3d26ae0387..cebceb9288 100644 --- a/internal/conf/configuration.go +++ b/internal/conf/configuration.go @@ -20,7 +20,14 @@ const defaultMinPasswordLength int = 6 const defaultChallengeExpiryDuration float64 = 300 const defaultFlowStateExpiryDuration time.Duration = 300 * time.Second +const ( + minimumSymmetricSecretLength = 27 + minimumAsymmetricSecretLength = 42 +) + var postgresNamesRegexp = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]{0,62}$`) +var symmetricSecretFormat = regexp.MustCompile(`^v1,[\w\-]+$`) +var asymmetricSecretFormat = regexp.MustCompile(`^v1a,[a-fA-F0-9]+;[a-fA-F0-9]+$`) // Time is used to represent timestamps in the configuration, as envconfig has // trouble parsing empty strings, due to time.Time.UnmarshalText(). @@ -448,10 +455,10 @@ type HookConfiguration struct { } type ExtensibilityPointConfiguration struct { - URI string `json:"uri"` - Enabled bool `json:"enabled"` - HookName string `json:"hook_name"` - Secret string `json:"secret"` + URI string `json:"uri"` + Enabled bool `json:"enabled"` + HookName string `json:"hook_name"` + HTTPHookSecrets []string `json:"secrets"` } func (h *HookConfiguration) Validate() error { @@ -459,6 +466,7 @@ func (h *HookConfiguration) Validate() error { h.MFAVerificationAttempt, h.PasswordVerificationAttempt, h.CustomAccessToken, + h.CustomSMSProvider, } for _, point := range points { if err := point.ValidateExtensibilityPoint(); err != nil { @@ -469,15 +477,18 @@ func (h *HookConfiguration) Validate() error { } func (e *ExtensibilityPointConfiguration) ValidateExtensibilityPoint() error { + if e.URI == "" { + return nil + } u, err := url.Parse(e.URI) if err != nil { return err } - switch u.Scheme { + switch strings.ToLower(u.Scheme) { case "pg-functions": return validatePostgresPath(u) case "https": - return validateHTTPSPath(u) + return validateHTTPSHookSecrets(e.HTTPHookSecrets) default: return fmt.Errorf("only postgres hooks and HTTPS functions are supported at the moment") } @@ -501,8 +512,17 @@ func validatePostgresPath(u *url.URL) error { return nil } -func validateHTTPSPath(u *url.URL) error { - // TOOD: add validation logi +func isValidSecretFormat(secret string) bool { + return (symmetricSecretFormat.MatchString(secret) && len(secret) >= minimumSymmetricSecretLength) || + (asymmetricSecretFormat.MatchString(secret) && len(secret) >= minimumAsymmetricSecretLength) +} + +func validateHTTPSHookSecrets(secrets []string) error { + for _, secret := range secrets { + if !isValidSecretFormat(secret) { + return fmt.Errorf("invalid secret format") + } + } return nil } @@ -555,6 +575,12 @@ func LoadGlobal(filename string) (*GlobalConfiguration, error) { } } + if config.Hook.CustomSMSProvider.Enabled { + if err := config.Hook.CustomSMSProvider.PopulateExtensibilityPoint(); err != nil { + return nil, err + } + } + if config.Hook.MFAVerificationAttempt.Enabled { if err := config.Hook.MFAVerificationAttempt.PopulateExtensibilityPoint(); err != nil { return nil, err diff --git a/internal/conf/configuration_test.go b/internal/conf/configuration_test.go index d6f7e52d7f..5c3309f75a 100644 --- a/internal/conf/configuration_test.go +++ b/internal/conf/configuration_test.go @@ -98,19 +98,21 @@ func TestPasswordRequiredCharactersDecode(t *testing.T) { } } -func TestValidateExtensibilityPoint(t *testing.T) { +func TestValidateExtensibilityPointURI(t *testing.T) { cases := []struct { desc string uri string expectError bool }{ // Positive test cases + {desc: "Valid HTTPS URI", uri: "https://asdfgggqqwwerty.website.co/functions/v1/custom-sms-sender", expectError: false}, + {desc: "Valid HTTPS URI", uri: "HTTPS://www.asdfgggqqwwerty.website.co/functions/v1/custom-sms-sender", expectError: false}, {desc: "Valid Postgres URI", uri: "pg-functions://postgres/auth/verification_hook_reject", expectError: false}, - {desc: "Valid HTTPS URI", uri: "https://asdfgggqqwwerty.supabase.co/functions/v1/custom-sms-sender", expectError: false}, {desc: "Another Valid URI", uri: "pg-functions://postgres/user_management/add_user", expectError: false}, {desc: "Another Valid URI", uri: "pg-functions://postgres/MySpeCial/FUNCTION_THAT_YELLS_AT_YOU", expectError: false}, // Negative test cases + {desc: "Invalid HTTPS URI (HTTP)", uri: "http://asdfgggqqwwerty.supabase.co/functions/v1/custom-sms-sender", expectError: true}, {desc: "Invalid Schema Name", uri: "pg-functions://postgres/123auth/verification_hook_reject", expectError: true}, {desc: "Invalid Function Name", uri: "pg-functions://postgres/auth/123verification_hook_reject", expectError: true}, {desc: "Insufficient Path Parts", uri: "pg-functions://postgres/auth", expectError: true}, @@ -126,3 +128,33 @@ func TestValidateExtensibilityPoint(t *testing.T) { } } } + +func TestValidateExtensibilityPointSecrets(t *testing.T) { + validHTTPSURI := "https://asdfgggqqwwerty.website.co/functions/v1/custom-sms-sender" + cases := []struct { + desc string + secret []string + expectError bool + }{ + // Positive test cases + // TODO: Transform this into whsec_ and whpk_ prefixed keys, need to base64 encode + {desc: "Valid Symmetric Secret", secret: []string{"v1,2b49264c90fd15db3bb0e05f4e1547b9c183eb06d585be8a"}, expectError: false}, + {desc: "Valid Asymmetric Secret", secret: []string{"v1a,46388e564db59fca566307aac37c0d1d475c5dd52fd540caa0325c643317296f;abc889a6b1160015025064f108a48d6aba1c7c95fa8e304b4d225e8ae0121511"}, expectError: false}, + {desc: "Valid Mix of Symmetric and asymmetric Secret", secret: []string{"v1,2b49264c90fd15db3bb0e05f4e1547b9c183eb06d585be8a", "v1a,46388e564db59fca566307aac37c0d1d475c5dd52fd540caa0325c643317296f;abc889a6b1160015025064f108a48d6aba1c7c95fa8e304b4d225e8ae0121511"}, expectError: false}, + + // Negative test cases + {desc: "Invalid Asymmetric Secret", secret: []string{"v1a,john;jill", "jill"}, expectError: true}, + {desc: "Invalid Symmetric Secret", secret: []string{"tommy"}, expectError: true}, + } + for _, tc := range cases { + ep := ExtensibilityPointConfiguration{URI: validHTTPSURI, HTTPHookSecrets: tc.secret} + err := ep.ValidateExtensibilityPoint() + if tc.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + + } + +}