Skip to content

Commit

Permalink
feat: add HTTP hook Secret Configuration (#2129)
Browse files Browse the repository at this point in the history
  • Loading branch information
J0 authored May 8, 2024
2 parents 2647536 + 63f0f7c commit 3ec0dc4
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 16 deletions.
21 changes: 21 additions & 0 deletions internal/start/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@ 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,
)
}

Expand All @@ -505,6 +506,7 @@ EOF
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,
)
}

Expand All @@ -513,6 +515,25 @@ EOF
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,
)
}

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,
)
}

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,
)
}

Expand Down
61 changes: 45 additions & 16 deletions internal/utils/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
_ "embed"
"fmt"
"net/url"
"os"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -387,11 +388,14 @@ type (
MFAVerificationAttempt hookConfig `toml:"mfa_verification_attempt"`
PasswordVerificationAttempt hookConfig `toml:"password_verification_attempt"`
CustomAccessToken hookConfig `toml:"custom_access_token"`
SendSMS hookConfig `toml:"send_sms"`
SendEmail hookConfig `toml:"send_email"`
}

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

twilioConfig struct {
Expand Down Expand Up @@ -460,6 +464,24 @@ type (
// }
)

func (h *hookConfig) HandleHook(hookType string) error {
// If not enabled do nothing
if !h.Enabled {
return nil
}
if h.URI == "" {
return errors.Errorf("missing required field in config: auth.hook.%s.uri", hookType)
}
if err := validateHookURI(h.URI, hookType); err != nil {
return err
}
var err error
if h.Secrets, err = maybeLoadEnv(h.Secrets); err != nil {
return errors.Errorf("missing required field in config: auth.hook.%s.secrets", hookType)
}
return nil
}

func LoadConfigFS(fsys afero.Fs) error {
// Load default values
var buf bytes.Buffer
Expand Down Expand Up @@ -687,25 +709,21 @@ func LoadConfigFS(fsys afero.Fs) error {
return err
}
}

if Config.Auth.Hook.MFAVerificationAttempt.Enabled {
if Config.Auth.Hook.MFAVerificationAttempt.URI == "" {
return errors.New("Missing required field in config: auth.hook.mfa_verification_atempt.uri")
}
if err := Config.Auth.Hook.MFAVerificationAttempt.HandleHook("mfa_verification_attempt"); err != nil {
return err
}

if Config.Auth.Hook.PasswordVerificationAttempt.Enabled {
if Config.Auth.Hook.PasswordVerificationAttempt.URI == "" {
return errors.New("Missing required field in config: auth.hook.password_verification_attempt.uri")
}
if err := Config.Auth.Hook.PasswordVerificationAttempt.HandleHook("password_verification_attempt"); err != nil {
return err
}

if Config.Auth.Hook.CustomAccessToken.Enabled {
if Config.Auth.Hook.CustomAccessToken.URI == "" {
return errors.New("Missing required field in config: auth.hook.custom_access_token.uri")
}
if err := Config.Auth.Hook.CustomAccessToken.HandleHook("custom_access_token"); err != nil {
return err
}
if err := Config.Auth.Hook.SendSMS.HandleHook("send_sms"); err != nil {
return err
}
if err := Config.Auth.Hook.SendEmail.HandleHook("send_email"); err != nil {
return err
}

// Validate oauth config
for ext, provider := range Config.Auth.External {
if !provider.Enabled {
Expand Down Expand Up @@ -862,3 +880,14 @@ func loadEnvIfExists(path string) error {
}
return nil
}

func validateHookURI(uri, hookName string) error {
parsed, err := url.Parse(uri)
if err != nil {
return errors.Errorf("failed to parse template url: %w", err)
}
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", hookName)
}
return nil
}
56 changes: 56 additions & 0 deletions internal/utils/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func TestConfigParsing(t *testing.T) {
t.Setenv("TWILIO_AUTH_TOKEN", "token")
t.Setenv("AZURE_CLIENT_ID", "hello")
t.Setenv("AZURE_SECRET", "this is cool")
t.Setenv("AUTH_SEND_SMS_SECRETS", "v1,whsec_aWxpa2VzdXBhYmFzZXZlcnltdWNoYW5kaWhvcGV5b3Vkb3Rvbw==")
assert.NoError(t, LoadConfigFS(fsys))
// Check error
assert.Equal(t, "hello", Config.Auth.External["azure"].ClientId)
Expand Down Expand Up @@ -155,3 +156,58 @@ func TestSigningJWT(t *testing.T) {
assert.Equal(t, defaultServiceRoleKey, signed)
})
}

func TestValidateHookURI(t *testing.T) {
tests := []struct {
name string
uri string
hookName string
shouldErr bool
errorMsg string
}{
{
name: "valid http URL",
uri: "http://example.com",
hookName: "testHook",
shouldErr: false,
},
{
name: "valid https URL",
uri: "https://example.com",
hookName: "testHook",
shouldErr: false,
},
{
name: "valid pg-functions URI",
uri: "pg-functions://functionName",
hookName: "pgHook",
shouldErr: false,
},
{
name: "invalid URI with unsupported scheme",
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",
},
{
name: "invalid URI with parsing error",
uri: "http://a b.com",
hookName: "errorHook",
shouldErr: true,
errorMsg: "failed to parse template url: parse \"http://a b.com\": invalid character \" \" in host name",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateHookURI(tt.uri, tt.hookName)
if tt.shouldErr {
assert.Error(t, err, "Expected an error for %v", tt.name)
assert.EqualError(t, err, tt.errorMsg, "Expected error message does not match for %v", tt.name)
} else {
assert.NoError(t, err, "Expected no error for %v", tt.name)
}
})
}
}
5 changes: 5 additions & 0 deletions internal/utils/templates/init_config.test.toml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ max_frequency = "5s"
enabled = true
uri = "pg-functions://postgres/auth/custom-access-token-hook"

[auth.hook.send_sms]
enabled = true
uri = "http://host.docker.internal/functions/v1/send_sms"
secrets = "env(AUTH_SEND_SMS_SECRETS)"


# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`.
[auth.sms.twilio]
Expand Down

0 comments on commit 3ec0dc4

Please sign in to comment.