Skip to content

Commit

Permalink
fix(config): syncing for postgres hook without secrets
Browse files Browse the repository at this point in the history
  • Loading branch information
avallete committed Nov 29, 2024
1 parent d3cd223 commit 1ef2255
Show file tree
Hide file tree
Showing 13 changed files with 130 additions and 68 deletions.
11 changes: 6 additions & 5 deletions internal/start/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/supabase/cli/internal/status"
"github.com/supabase/cli/internal/utils"
"github.com/supabase/cli/internal/utils/flags"
"github.com/supabase/cli/pkg/cast"
"github.com/supabase/cli/pkg/config"
"golang.org/x/mod/semver"
)
Expand Down Expand Up @@ -609,39 +610,39 @@ EOF
env,
"GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED=true",
"GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI="+utils.Config.Auth.Hook.MFAVerificationAttempt.URI,
"GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_SECRETS="+utils.Config.Auth.Hook.MFAVerificationAttempt.Secrets,
"GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_SECRETS="+cast.Val(utils.Config.Auth.Hook.MFAVerificationAttempt.Secrets, ""),
)
}
if utils.Config.Auth.Hook.PasswordVerificationAttempt.Enabled {
env = append(
env,
"GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED=true",
"GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI="+utils.Config.Auth.Hook.PasswordVerificationAttempt.URI,
"GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_SECRETS="+utils.Config.Auth.Hook.PasswordVerificationAttempt.Secrets,
"GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_SECRETS="+cast.Val(utils.Config.Auth.Hook.PasswordVerificationAttempt.Secrets, ""),
)
}
if utils.Config.Auth.Hook.CustomAccessToken.Enabled {
env = append(
env,
"GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED=true",
"GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI="+utils.Config.Auth.Hook.CustomAccessToken.URI,
"GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRETS="+utils.Config.Auth.Hook.CustomAccessToken.Secrets,
"GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRETS="+cast.Val(utils.Config.Auth.Hook.CustomAccessToken.Secrets, ""),
)
}
if utils.Config.Auth.Hook.SendSMS.Enabled {
env = append(
env,
"GOTRUE_HOOK_SEND_SMS_ENABLED=true",
"GOTRUE_HOOK_SEND_SMS_URI="+utils.Config.Auth.Hook.SendSMS.URI,
"GOTRUE_HOOK_SEND_SMS_SECRETS="+utils.Config.Auth.Hook.SendSMS.Secrets,
"GOTRUE_HOOK_SEND_SMS_SECRETS="+cast.Val(utils.Config.Auth.Hook.SendSMS.Secrets, ""),
)
}
if utils.Config.Auth.Hook.SendEmail.Enabled {
env = append(
env,
"GOTRUE_HOOK_SEND_EMAIL_ENABLED=true",
"GOTRUE_HOOK_SEND_EMAIL_URI="+utils.Config.Auth.Hook.SendEmail.URI,
"GOTRUE_HOOK_SEND_EMAIL_SECRETS="+utils.Config.Auth.Hook.SendEmail.Secrets,
"GOTRUE_HOOK_SEND_EMAIL_SECRETS="+cast.Val(utils.Config.Auth.Hook.SendEmail.Secrets, ""),
)
}

Expand Down
61 changes: 37 additions & 24 deletions pkg/config/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,9 @@ type (
}

hookConfig struct {
Enabled bool `toml:"enabled"`
URI string `toml:"uri"`
Secrets string `toml:"secrets"`
Enabled bool `toml:"enabled"`
URI string `toml:"uri"`
Secrets *string `toml:"secrets"`
}

sessions struct {
Expand Down Expand Up @@ -262,53 +262,66 @@ func (a *auth) FromRemoteAuthConfig(remoteConfig v1API.AuthConfigResponse) {
func (h hook) toAuthConfigBody(body *v1API.UpdateAuthConfigBody) {
if body.HookCustomAccessTokenEnabled = &h.CustomAccessToken.Enabled; *body.HookCustomAccessTokenEnabled {
body.HookCustomAccessTokenUri = &h.CustomAccessToken.URI
body.HookCustomAccessTokenSecrets = &h.CustomAccessToken.Secrets
body.HookCustomAccessTokenSecrets = h.CustomAccessToken.Secrets
}
if body.HookSendEmailEnabled = &h.SendEmail.Enabled; *body.HookSendEmailEnabled {
body.HookSendEmailUri = &h.SendEmail.URI
body.HookSendEmailSecrets = &h.SendEmail.Secrets
body.HookSendEmailSecrets = h.SendEmail.Secrets
}
if body.HookSendSmsEnabled = &h.SendSMS.Enabled; *body.HookSendSmsEnabled {
body.HookSendSmsUri = &h.SendSMS.URI
body.HookSendSmsSecrets = &h.SendSMS.Secrets
body.HookSendSmsSecrets = h.SendSMS.Secrets
}
// Enterprise and team only features
if body.HookMfaVerificationAttemptEnabled = &h.MFAVerificationAttempt.Enabled; *body.HookMfaVerificationAttemptEnabled {
body.HookMfaVerificationAttemptUri = &h.MFAVerificationAttempt.URI
body.HookMfaVerificationAttemptSecrets = &h.MFAVerificationAttempt.Secrets
body.HookMfaVerificationAttemptSecrets = h.MFAVerificationAttempt.Secrets
}
if body.HookPasswordVerificationAttemptEnabled = &h.PasswordVerificationAttempt.Enabled; *body.HookPasswordVerificationAttemptEnabled {
body.HookPasswordVerificationAttemptUri = &h.PasswordVerificationAttempt.URI
body.HookPasswordVerificationAttemptSecrets = &h.PasswordVerificationAttempt.Secrets
body.HookPasswordVerificationAttemptSecrets = h.PasswordVerificationAttempt.Secrets
}
}

func (h *hook) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) {
// Ignore disabled hooks because their envs are not loaded
if h.CustomAccessToken.Enabled {
h.CustomAccessToken.URI = cast.Val(remoteConfig.HookCustomAccessTokenUri, "")
h.CustomAccessToken.Secrets = hashPrefix + cast.Val(remoteConfig.HookCustomAccessTokenSecrets, "")
if remoteConfig.HookCustomAccessTokenSecrets != nil {
h.CustomAccessToken.Secrets = cast.Ptr(hashPrefix + *remoteConfig.HookCustomAccessTokenSecrets)
}
}
h.CustomAccessToken.Enabled = cast.Val(remoteConfig.HookCustomAccessTokenEnabled, false)

if h.SendEmail.Enabled {
h.SendEmail.URI = cast.Val(remoteConfig.HookSendEmailUri, "")
h.SendEmail.Secrets = hashPrefix + cast.Val(remoteConfig.HookSendEmailSecrets, "")
if remoteConfig.HookSendEmailSecrets != nil {
h.SendEmail.Secrets = cast.Ptr(hashPrefix + *remoteConfig.HookSendEmailSecrets)
}
}
h.SendEmail.Enabled = cast.Val(remoteConfig.HookSendEmailEnabled, false)

if h.SendSMS.Enabled {
h.SendSMS.URI = cast.Val(remoteConfig.HookSendSmsUri, "")
h.SendSMS.Secrets = hashPrefix + cast.Val(remoteConfig.HookSendSmsSecrets, "")
if remoteConfig.HookSendSmsSecrets != nil {
h.SendSMS.Secrets = cast.Ptr(hashPrefix + *remoteConfig.HookSendSmsSecrets)
}
}
h.SendSMS.Enabled = cast.Val(remoteConfig.HookSendSmsEnabled, false)

// Enterprise and team only features
if h.MFAVerificationAttempt.Enabled {
h.MFAVerificationAttempt.URI = cast.Val(remoteConfig.HookMfaVerificationAttemptUri, "")
h.MFAVerificationAttempt.Secrets = hashPrefix + cast.Val(remoteConfig.HookMfaVerificationAttemptSecrets, "")
if remoteConfig.HookMfaVerificationAttemptSecrets != nil {
h.MFAVerificationAttempt.Secrets = cast.Ptr(hashPrefix + *remoteConfig.HookMfaVerificationAttemptSecrets)
}
}
h.MFAVerificationAttempt.Enabled = cast.Val(remoteConfig.HookMfaVerificationAttemptEnabled, false)

if h.PasswordVerificationAttempt.Enabled {
h.PasswordVerificationAttempt.URI = cast.Val(remoteConfig.HookPasswordVerificationAttemptUri, "")
h.PasswordVerificationAttempt.Secrets = hashPrefix + cast.Val(remoteConfig.HookPasswordVerificationAttemptSecrets, "")
if remoteConfig.HookPasswordVerificationAttemptSecrets != nil {
h.PasswordVerificationAttempt.Secrets = cast.Ptr(hashPrefix + *remoteConfig.HookPasswordVerificationAttemptSecrets)
}
}
h.PasswordVerificationAttempt.Enabled = cast.Val(remoteConfig.HookPasswordVerificationAttemptEnabled, false)
}
Expand Down Expand Up @@ -851,20 +864,20 @@ func (a *auth) HashSecrets(key string) {
case a.Sms.Vonage.Enabled:
a.Sms.Vonage.ApiSecret = hash(a.Sms.Vonage.ApiSecret)
}
if a.Hook.MFAVerificationAttempt.Enabled {
a.Hook.MFAVerificationAttempt.Secrets = hash(a.Hook.MFAVerificationAttempt.Secrets)
if a.Hook.MFAVerificationAttempt.Enabled && a.Hook.MFAVerificationAttempt.Secrets != nil {
a.Hook.MFAVerificationAttempt.Secrets = cast.Ptr(hash(*a.Hook.MFAVerificationAttempt.Secrets))
}
if a.Hook.PasswordVerificationAttempt.Enabled {
a.Hook.PasswordVerificationAttempt.Secrets = hash(a.Hook.PasswordVerificationAttempt.Secrets)
if a.Hook.PasswordVerificationAttempt.Enabled && a.Hook.PasswordVerificationAttempt.Secrets != nil {
a.Hook.PasswordVerificationAttempt.Secrets = cast.Ptr(hash(*a.Hook.PasswordVerificationAttempt.Secrets))
}
if a.Hook.CustomAccessToken.Enabled {
a.Hook.CustomAccessToken.Secrets = hash(a.Hook.CustomAccessToken.Secrets)
if a.Hook.CustomAccessToken.Enabled && a.Hook.CustomAccessToken.Secrets != nil {
a.Hook.CustomAccessToken.Secrets = cast.Ptr(hash(*a.Hook.CustomAccessToken.Secrets))
}
if a.Hook.SendSMS.Enabled {
a.Hook.SendSMS.Secrets = hash(a.Hook.SendSMS.Secrets)
if a.Hook.SendSMS.Enabled && a.Hook.SendSMS.Secrets != nil {
a.Hook.SendSMS.Secrets = cast.Ptr(hash(*a.Hook.SendSMS.Secrets))
}
if a.Hook.SendEmail.Enabled {
a.Hook.SendEmail.Secrets = hash(a.Hook.SendEmail.Secrets)
if a.Hook.SendEmail.Enabled && a.Hook.SendEmail.Secrets != nil {
a.Hook.SendEmail.Secrets = cast.Ptr(hash(*a.Hook.SendEmail.Secrets))
}
for name, provider := range a.External {
if provider.Enabled {
Expand Down
60 changes: 45 additions & 15 deletions pkg/config/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ func newWithDefaults() auth {
}
}

func defaultHttpHookConfig() hookConfig {
return hookConfig{
Enabled: true,
URI: "http://example.com",
Secrets: cast.Ptr("test-secret"),
}
}

func defaultPostgresHookConfig() hookConfig {
return hookConfig{
Enabled: true,
URI: "pg-functions://functionName",
}
}

func assertSnapshotEqual(t *testing.T, actual []byte) {
snapshot := filepath.Join("testdata", filepath.FromSlash(t.Name())) + ".diff"
expected, err := os.ReadFile(snapshot)
Expand Down Expand Up @@ -121,29 +136,44 @@ func TestHookDiff(t *testing.T) {
t.Run("local and remote enabled", func(t *testing.T) {
c := newWithDefaults()
c.Hook = hook{
CustomAccessToken: hookConfig{Enabled: true},
SendSMS: hookConfig{Enabled: true},
SendEmail: hookConfig{Enabled: true},
MFAVerificationAttempt: hookConfig{Enabled: true},
PasswordVerificationAttempt: hookConfig{Enabled: true},
CustomAccessToken: defaultHttpHookConfig(),
SendSMS: defaultHttpHookConfig(),
SendEmail: defaultHttpHookConfig(),
MFAVerificationAttempt: defaultHttpHookConfig(),
PasswordVerificationAttempt: defaultPostgresHookConfig(),
}
// Run test
diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{
HookCustomAccessTokenEnabled: cast.Ptr(true),
HookCustomAccessTokenUri: cast.Ptr(""),
HookCustomAccessTokenSecrets: cast.Ptr("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"),
HookCustomAccessTokenUri: cast.Ptr("http://example.com"),
HookCustomAccessTokenSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"),
HookSendEmailEnabled: cast.Ptr(true),
HookSendEmailUri: cast.Ptr(""),
HookSendEmailSecrets: cast.Ptr("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"),
HookSendEmailUri: cast.Ptr("http://example.com"),
HookSendEmailSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"),
HookSendSmsEnabled: cast.Ptr(true),
HookSendSmsUri: cast.Ptr(""),
HookSendSmsSecrets: cast.Ptr("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"),
HookSendSmsUri: cast.Ptr("http://example.com"),
HookSendSmsSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"),
HookMfaVerificationAttemptEnabled: cast.Ptr(true),
HookMfaVerificationAttemptUri: cast.Ptr(""),
HookMfaVerificationAttemptSecrets: cast.Ptr("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"),
HookMfaVerificationAttemptUri: cast.Ptr("http://example.com"),
HookMfaVerificationAttemptSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"),
HookPasswordVerificationAttemptEnabled: cast.Ptr(true),
HookPasswordVerificationAttemptUri: cast.Ptr(""),
HookPasswordVerificationAttemptSecrets: cast.Ptr("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"),
HookPasswordVerificationAttemptUri: cast.Ptr("pg-functions://functionName"),
HookPasswordVerificationAttemptSecrets: nil,
})
// Check error
assert.NoError(t, err)
assert.Empty(t, string(diff))
})
t.Run("local and remote enabled pg-function custom hook", func(t *testing.T) {
c := newWithDefaults()
c.Hook = hook{
CustomAccessToken: defaultPostgresHookConfig(),
}
// Run test
diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{
HookCustomAccessTokenEnabled: cast.Ptr(true),
HookCustomAccessTokenUri: cast.Ptr("pg-functions://functionName"),
HookCustomAccessTokenSecrets: nil,
})
// Check error
assert.NoError(t, err)
Expand Down
12 changes: 9 additions & 3 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -997,10 +997,16 @@ func (h *hookConfig) validate(hookType string) (err error) {
} else if parsed, err := url.Parse(h.URI); err != nil {
return errors.Errorf("failed to parse template url: %w", err)
} else if !(parsed.Scheme == "http" || parsed.Scheme == "https" || parsed.Scheme == "pg-functions") {
return errors.Errorf("Invalid HTTP hook config: auth.hook.%v should be a Postgres function URI, or a HTTP or HTTPS URL", hookType)
return errors.Errorf("Invalid auth hook config: auth.hook.%v should be a Postgres function URI, or a HTTP or HTTPS URL", hookType)
} else if (parsed.Scheme == "http" || parsed.Scheme == "https") && h.Secrets == nil {
return errors.Errorf("Invalid HTTP config: auth.hook.%v missing required secret value for the http hook endpoint.", hookType)
}
if h.Secrets, err = maybeLoadEnv(h.Secrets); err != nil {
return errors.Errorf("missing required field in config: auth.hook.%s.secrets", hookType)
if h.Secrets != nil {
if envLoadedSecret, err := maybeLoadEnv(*h.Secrets); err != nil {
return errors.Errorf("missing field in config: auth.hook.%s.secrets", hookType)
} else {
h.Secrets = cast.Ptr(envLoadedSecret)
}
}
return nil
}
Expand Down
15 changes: 13 additions & 2 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/BurntSushi/toml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/supabase/cli/pkg/cast"
)

//go:embed testdata/config.toml
Expand Down Expand Up @@ -234,7 +235,7 @@ func TestValidateHookURI(t *testing.T) {
uri: "ftp://example.com",
hookName: "malformedHook",
shouldErr: true,
errorMsg: "Invalid HTTP hook config: auth.hook.malformedHook should be a Postgres function URI, or a HTTP or HTTPS URL",
errorMsg: "Invalid auth hook config: auth.hook.malformedHook should be a Postgres function URI, or a HTTP or HTTPS URL",
},
{
name: "invalid URI with parsing error",
Expand All @@ -250,7 +251,7 @@ func TestValidateHookURI(t *testing.T) {
h := hookConfig{
Enabled: true,
URI: tt.uri,
Secrets: "test-secret",
Secrets: cast.Ptr("test-secret"),
}
err := h.validate(tt.hookName)
if tt.shouldErr {
Expand All @@ -261,6 +262,16 @@ func TestValidateHookURI(t *testing.T) {
}
})
}
t.Run("Valid http hook with missing secret", func(t *testing.T) {
h := hookConfig{
Enabled: true,
URI: "http://example.com",
Secrets: nil,
}
err := h.validate("testHook")
assert.Error(t, err)
assert.EqualError(t, err, "Invalid HTTP config: auth.hook.testHook missing required secret value for the http hook endpoint.")
})
}

func TestLoadSeedPaths(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
diff remote[auth] local[auth]
--- remote[auth]
+++ local[auth]
@@ -51,13 +51,13 @@
@@ -46,13 +46,13 @@
inactivity_timeout = "0s"

[email]
Expand All @@ -22,7 +22,7 @@ diff remote[auth] local[auth]
[email.template]
[email.template.confirmation]
content_path = ""
@@ -71,13 +71,6 @@
@@ -66,13 +66,6 @@
content_path = ""
[email.template.recovery]
content_path = ""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
diff remote[auth] local[auth]
--- remote[auth]
+++ local[auth]
@@ -51,28 +51,43 @@
@@ -46,28 +46,43 @@
inactivity_timeout = "0s"

[email]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
diff remote[auth] local[auth]
--- remote[auth]
+++ local[auth]
@@ -91,7 +91,7 @@
@@ -86,7 +86,7 @@

[external]
[external.apple]
Expand All @@ -10,7 +10,7 @@ diff remote[auth] local[auth]
client_id = "test-client-1,test-client-2"
secret = "hash:ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"
url = ""
@@ -147,7 +147,7 @@
@@ -142,7 +142,7 @@
redirect_uri = ""
skip_nonce_check = false
[external.google]
Expand Down
13 changes: 7 additions & 6 deletions pkg/config/testdata/TestHookDiff/local_enabled_and_disabled.diff
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
diff remote[auth] local[auth]
--- remote[auth]
+++ local[auth]
@@ -11,7 +11,7 @@
@@ -11,15 +11,14 @@

[hook]
[hook.mfa_verification_attempt]
-enabled = true
+enabled = false
uri = ""
secrets = ""
[hook.password_verification_attempt]
@@ -19,7 +19,7 @@
enabled = false
uri = ""
secrets = ""
[hook.custom_access_token]
-enabled = false
-uri = ""
-secrets = "hash:b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"
+enabled = true
uri = ""
secrets = "hash:b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"
+uri = ""
[hook.send_sms]
enabled = false
uri = ""
Loading

0 comments on commit 1ef2255

Please sign in to comment.