diff --git a/.golangci.yml b/.golangci.yml index 9d5189836..b91c1afd6 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -20,4 +20,4 @@ linters-settings: stylecheck: checks: ["all", "-ST1003"] dupl: - threshold: 200 + threshold: 250 diff --git a/cmd/link.go b/cmd/link.go index b1aec1d7a..59c93545c 100644 --- a/cmd/link.go +++ b/cmd/link.go @@ -31,6 +31,7 @@ var ( return err } fsys := afero.NewOsFs() + utils.Config.ProjectId = flags.ProjectRef if err := utils.LoadConfigFS(fsys); err != nil { return err } diff --git a/go.mod b/go.mod index 62b544b34..4e5fcaff0 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/docker/docker v27.4.1+incompatible github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 + github.com/ecies/go/v2 v2.0.10 github.com/getsentry/sentry-go v0.31.1 github.com/go-errors/errors v1.5.1 github.com/go-git/go-git/v5 v5.13.1 @@ -111,6 +112,7 @@ require ( github.com/daixiang0/gci v0.13.5 // indirect github.com/danieljoos/wincred v1.2.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/dlclark/regexp2 v1.11.0 // indirect @@ -120,6 +122,7 @@ require ( github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect + github.com/ethereum/go-ethereum v1.14.12 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fatih/structtag v1.2.0 // indirect @@ -215,7 +218,7 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mgechev/revive v1.5.1 // indirect github.com/microcosm-cc/bluemonday v1.0.25 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect @@ -244,7 +247,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polyfloyd/go-errorlint v1.7.0 // indirect github.com/prometheus/client_golang v1.12.1 // indirect - github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect diff --git a/go.sum b/go.sum index 8b6bf5eda..d475808d6 100644 --- a/go.sum +++ b/go.sum @@ -215,6 +215,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/deepmap/oapi-codegen/v2 v2.2.0 h1:FW4f7C0Xb6EaezBSB3GYw2QGwHD5ChDflG+3xSZBdvY= github.com/deepmap/oapi-codegen/v2 v2.2.0/go.mod h1:L4zUv7ULYDtYSb/aYk/xO3OYcQU6BoU/0viULkbi2DE= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= @@ -248,6 +250,8 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= +github.com/ecies/go/v2 v2.0.10 h1:AaLxGio0MLLbvWur4rKnLzw+K9zI+wMScIDAtqCqOtU= +github.com/ecies/go/v2 v2.0.10/go.mod h1:N73OyuR6tuKznit2LhXjrZ0XAQ234uKbzYz8pEPYzlI= github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ= github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -257,6 +261,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/ethereum/go-ethereum v1.14.12 h1:8hl57x77HSUo+cXExrURjU/w1VhL+ShCTJrTwcCQSe4= +github.com/ethereum/go-ethereum v1.14.12/go.mod h1:RAC2gVMWJ6FkxSPESfbshrcKpIokgQKsVKmAuqdekDY= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= @@ -678,8 +684,9 @@ github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mgechev/revive v1.5.1 h1:hE+QPeq0/wIzJwOphdVyUJ82njdd8Khp4fUIHGZHW3M= github.com/mgechev/revive v1.5.1/go.mod h1:lC9AhkJIBs5zwx8wkudyHrU+IJkrEKmpCmGMnIJPk4o= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= @@ -792,8 +799,9 @@ github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1: github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a h1:CmF68hwI0XsOQ5UwlBopMi2Ow4Pbg32akc4KIVCOm+Y= +github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= diff --git a/internal/config/push/push.go b/internal/config/push/push.go index a4c901473..b1b887765 100644 --- a/internal/config/push/push.go +++ b/internal/config/push/push.go @@ -11,15 +11,12 @@ import ( ) func Run(ctx context.Context, ref string, fsys afero.Fs) error { + utils.Config.ProjectId = ref if err := utils.LoadConfigFS(fsys); err != nil { return err } client := config.NewConfigUpdater(*utils.GetSupabase()) - remote, err := utils.Config.GetRemoteByProjectRef(ref) - if err != nil { - // Use base config when no remote is declared - remote.ProjectId = ref - } + remote, _ := utils.Config.GetRemoteByProjectRef(ref) fmt.Fprintln(os.Stderr, "Pushing config to project:", remote.ProjectId) console := utils.NewConsole() keep := func(name string) bool { diff --git a/internal/link/link.go b/internal/link/link.go index e791271b8..d35321e2e 100644 --- a/internal/link/link.go +++ b/internal/link/link.go @@ -26,7 +26,6 @@ import ( func Run(ctx context.Context, projectRef string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error { copy := utils.Config.Clone() - copy.Auth.HashSecrets(projectRef) original, err := cliConfig.ToTomlBytes(copy) if err != nil { fmt.Fprintln(utils.GetDebugLogger(), err) diff --git a/internal/start/start.go b/internal/start/start.go index c3684c14b..6f75d1377 100644 --- a/internal/start/start.go +++ b/internal/start/start.go @@ -507,7 +507,7 @@ EOF fmt.Sprintf("GOTRUE_SMTP_HOST=%s", utils.Config.Auth.Email.Smtp.Host), fmt.Sprintf("GOTRUE_SMTP_PORT=%d", utils.Config.Auth.Email.Smtp.Port), fmt.Sprintf("GOTRUE_SMTP_USER=%s", utils.Config.Auth.Email.Smtp.User), - fmt.Sprintf("GOTRUE_SMTP_PASS=%s", utils.Config.Auth.Email.Smtp.Pass), + fmt.Sprintf("GOTRUE_SMTP_PASS=%s", utils.Config.Auth.Email.Smtp.Pass.Value), fmt.Sprintf("GOTRUE_SMTP_ADMIN_EMAIL=%s", utils.Config.Auth.Email.Smtp.AdminEmail), fmt.Sprintf("GOTRUE_SMTP_SENDER_NAME=%s", utils.Config.Auth.Email.Smtp.SenderName), ) @@ -550,7 +550,7 @@ EOF env, "GOTRUE_SMS_PROVIDER=twilio", "GOTRUE_SMS_TWILIO_ACCOUNT_SID="+utils.Config.Auth.Sms.Twilio.AccountSid, - "GOTRUE_SMS_TWILIO_AUTH_TOKEN="+utils.Config.Auth.Sms.Twilio.AuthToken, + "GOTRUE_SMS_TWILIO_AUTH_TOKEN="+utils.Config.Auth.Sms.Twilio.AuthToken.Value, "GOTRUE_SMS_TWILIO_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.Twilio.MessageServiceSid, ) case utils.Config.Auth.Sms.TwilioVerify.Enabled: @@ -558,21 +558,21 @@ EOF env, "GOTRUE_SMS_PROVIDER=twilio_verify", "GOTRUE_SMS_TWILIO_VERIFY_ACCOUNT_SID="+utils.Config.Auth.Sms.TwilioVerify.AccountSid, - "GOTRUE_SMS_TWILIO_VERIFY_AUTH_TOKEN="+utils.Config.Auth.Sms.TwilioVerify.AuthToken, + "GOTRUE_SMS_TWILIO_VERIFY_AUTH_TOKEN="+utils.Config.Auth.Sms.TwilioVerify.AuthToken.Value, "GOTRUE_SMS_TWILIO_VERIFY_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.TwilioVerify.MessageServiceSid, ) case utils.Config.Auth.Sms.Messagebird.Enabled: env = append( env, "GOTRUE_SMS_PROVIDER=messagebird", - "GOTRUE_SMS_MESSAGEBIRD_ACCESS_KEY="+utils.Config.Auth.Sms.Messagebird.AccessKey, + "GOTRUE_SMS_MESSAGEBIRD_ACCESS_KEY="+utils.Config.Auth.Sms.Messagebird.AccessKey.Value, "GOTRUE_SMS_MESSAGEBIRD_ORIGINATOR="+utils.Config.Auth.Sms.Messagebird.Originator, ) case utils.Config.Auth.Sms.Textlocal.Enabled: env = append( env, "GOTRUE_SMS_PROVIDER=textlocal", - "GOTRUE_SMS_TEXTLOCAL_API_KEY="+utils.Config.Auth.Sms.Textlocal.ApiKey, + "GOTRUE_SMS_TEXTLOCAL_API_KEY="+utils.Config.Auth.Sms.Textlocal.ApiKey.Value, "GOTRUE_SMS_TEXTLOCAL_SENDER="+utils.Config.Auth.Sms.Textlocal.Sender, ) case utils.Config.Auth.Sms.Vonage.Enabled: @@ -580,7 +580,7 @@ EOF env, "GOTRUE_SMS_PROVIDER=vonage", "GOTRUE_SMS_VONAGE_API_KEY="+utils.Config.Auth.Sms.Vonage.ApiKey, - "GOTRUE_SMS_VONAGE_API_SECRET="+utils.Config.Auth.Sms.Vonage.ApiSecret, + "GOTRUE_SMS_VONAGE_API_SECRET="+utils.Config.Auth.Sms.Vonage.ApiSecret.Value, "GOTRUE_SMS_VONAGE_FROM="+utils.Config.Auth.Sms.Vonage.From, ) } @@ -590,7 +590,7 @@ EOF env, "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED=true", "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI="+hook.URI, - "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_SECRETS="+hook.Secrets, + "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_SECRETS="+hook.Secrets.Value, ) } if hook := utils.Config.Auth.Hook.PasswordVerificationAttempt; hook != nil && hook.Enabled { @@ -598,7 +598,7 @@ EOF env, "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED=true", "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI="+hook.URI, - "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_SECRETS="+hook.Secrets, + "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_SECRETS="+hook.Secrets.Value, ) } if hook := utils.Config.Auth.Hook.CustomAccessToken; hook != nil && hook.Enabled { @@ -606,7 +606,7 @@ EOF env, "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED=true", "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI="+hook.URI, - "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRETS="+hook.Secrets, + "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRETS="+hook.Secrets.Value, ) } if hook := utils.Config.Auth.Hook.SendSMS; hook != nil && hook.Enabled { @@ -614,7 +614,7 @@ EOF env, "GOTRUE_HOOK_SEND_SMS_ENABLED=true", "GOTRUE_HOOK_SEND_SMS_URI="+hook.URI, - "GOTRUE_HOOK_SEND_SMS_SECRETS="+hook.Secrets, + "GOTRUE_HOOK_SEND_SMS_SECRETS="+hook.Secrets.Value, ) } if hook := utils.Config.Auth.Hook.SendEmail; hook != nil && hook.Enabled { @@ -622,7 +622,7 @@ EOF env, "GOTRUE_HOOK_SEND_EMAIL_ENABLED=true", "GOTRUE_HOOK_SEND_EMAIL_URI="+hook.URI, - "GOTRUE_HOOK_SEND_EMAIL_SECRETS="+hook.Secrets, + "GOTRUE_HOOK_SEND_EMAIL_SECRETS="+hook.Secrets.Value, ) } @@ -640,7 +640,7 @@ EOF env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_ENABLED=%v", strings.ToUpper(name), config.Enabled), fmt.Sprintf("GOTRUE_EXTERNAL_%s_CLIENT_ID=%s", strings.ToUpper(name), config.ClientId), - fmt.Sprintf("GOTRUE_EXTERNAL_%s_SECRET=%s", strings.ToUpper(name), config.Secret), + fmt.Sprintf("GOTRUE_EXTERNAL_%s_SECRET=%s", strings.ToUpper(name), config.Secret.Value), fmt.Sprintf("GOTRUE_EXTERNAL_%s_SKIP_NONCE_CHECK=%t", strings.ToUpper(name), config.SkipNonceCheck), ) diff --git a/pkg/config/auth.go b/pkg/config/auth.go index b93123bd2..098764fff 100644 --- a/pkg/config/auth.go +++ b/pkg/config/auth.go @@ -119,7 +119,7 @@ type ( Host string `toml:"host"` Port uint16 `toml:"port"` User string `toml:"user"` - Pass string `toml:"pass"` + Pass Secret `toml:"pass"` AdminEmail string `toml:"admin_email"` SenderName string `toml:"sender_name"` } @@ -174,7 +174,7 @@ type ( hookConfig struct { Enabled bool `toml:"enabled"` URI string `toml:"uri"` - Secrets string `toml:"secrets"` + Secrets Secret `toml:"secrets"` } sessions struct { @@ -186,32 +186,32 @@ type ( Enabled bool `toml:"enabled"` AccountSid string `toml:"account_sid"` MessageServiceSid string `toml:"message_service_sid"` - AuthToken string `toml:"auth_token" mapstructure:"auth_token"` + AuthToken Secret `toml:"auth_token" mapstructure:"auth_token"` } messagebirdConfig struct { Enabled bool `toml:"enabled"` Originator string `toml:"originator"` - AccessKey string `toml:"access_key" mapstructure:"access_key"` + AccessKey Secret `toml:"access_key" mapstructure:"access_key"` } textlocalConfig struct { Enabled bool `toml:"enabled"` Sender string `toml:"sender"` - ApiKey string `toml:"api_key" mapstructure:"api_key"` + ApiKey Secret `toml:"api_key" mapstructure:"api_key"` } vonageConfig struct { Enabled bool `toml:"enabled"` From string `toml:"from"` ApiKey string `toml:"api_key" mapstructure:"api_key"` - ApiSecret string `toml:"api_secret" mapstructure:"api_secret"` + ApiSecret Secret `toml:"api_secret" mapstructure:"api_secret"` } provider struct { Enabled bool `toml:"enabled"` ClientId string `toml:"client_id"` - Secret string `toml:"secret"` + Secret Secret `toml:"secret"` Url string `toml:"url"` RedirectUri string `toml:"redirect_uri"` SkipNonceCheck bool `toml:"skip_nonce_check"` @@ -265,24 +265,24 @@ func (h hook) toAuthConfigBody(body *v1API.UpdateAuthConfigBody) { if hook := h.CustomAccessToken; hook != nil { if body.HookCustomAccessTokenEnabled = &hook.Enabled; hook.Enabled { body.HookCustomAccessTokenUri = &hook.URI - if len(hook.Secrets) > 0 { - body.HookCustomAccessTokenSecrets = &hook.Secrets + if len(hook.Secrets.Value) > 0 { + body.HookCustomAccessTokenSecrets = &hook.Secrets.Value } } } if hook := h.SendEmail; hook != nil { if body.HookSendEmailEnabled = &hook.Enabled; hook.Enabled { body.HookSendEmailUri = &hook.URI - if len(hook.Secrets) > 0 { - body.HookSendEmailSecrets = &hook.Secrets + if len(hook.Secrets.Value) > 0 { + body.HookSendEmailSecrets = &hook.Secrets.Value } } } if hook := h.SendSMS; hook != nil { if body.HookSendSmsEnabled = &hook.Enabled; hook.Enabled { body.HookSendSmsUri = &hook.URI - if len(hook.Secrets) > 0 { - body.HookSendSmsSecrets = &hook.Secrets + if len(hook.Secrets.Value) > 0 { + body.HookSendSmsSecrets = &hook.Secrets.Value } } } @@ -290,16 +290,16 @@ func (h hook) toAuthConfigBody(body *v1API.UpdateAuthConfigBody) { if hook := h.MFAVerificationAttempt; hook != nil { if body.HookMfaVerificationAttemptEnabled = &hook.Enabled; hook.Enabled { body.HookMfaVerificationAttemptUri = &hook.URI - if len(hook.Secrets) > 0 { - body.HookMfaVerificationAttemptSecrets = &hook.Secrets + if len(hook.Secrets.Value) > 0 { + body.HookMfaVerificationAttemptSecrets = &hook.Secrets.Value } } } if hook := h.PasswordVerificationAttempt; hook != nil { if body.HookPasswordVerificationAttemptEnabled = &hook.Enabled; hook.Enabled { body.HookPasswordVerificationAttemptUri = &hook.URI - if len(hook.Secrets) > 0 { - body.HookPasswordVerificationAttemptSecrets = &hook.Secrets + if len(hook.Secrets.Value) > 0 { + body.HookPasswordVerificationAttemptSecrets = &hook.Secrets.Value } } } @@ -310,8 +310,8 @@ func (h *hook) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { // Ignore disabled hooks because their envs are not loaded if hook.Enabled { hook.URI = cast.Val(remoteConfig.HookCustomAccessTokenUri, "") - if hook.Secrets != hashPrefix { - hook.Secrets = hashPrefix + cast.Val(remoteConfig.HookCustomAccessTokenSecrets, "") + if len(hook.Secrets.SHA256) > 0 { + hook.Secrets.SHA256 = cast.Val(remoteConfig.HookCustomAccessTokenSecrets, "") } } hook.Enabled = cast.Val(remoteConfig.HookCustomAccessTokenEnabled, false) @@ -319,8 +319,8 @@ func (h *hook) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if hook := h.SendEmail; hook != nil { if hook.Enabled { hook.URI = cast.Val(remoteConfig.HookSendEmailUri, "") - if hook.Secrets != hashPrefix { - hook.Secrets = hashPrefix + cast.Val(remoteConfig.HookSendEmailSecrets, "") + if len(hook.Secrets.SHA256) > 0 { + hook.Secrets.SHA256 = cast.Val(remoteConfig.HookSendEmailSecrets, "") } } hook.Enabled = cast.Val(remoteConfig.HookSendEmailEnabled, false) @@ -328,8 +328,8 @@ func (h *hook) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if hook := h.SendSMS; hook != nil { if hook.Enabled { hook.URI = cast.Val(remoteConfig.HookSendSmsUri, "") - if hook.Secrets != hashPrefix { - hook.Secrets = hashPrefix + cast.Val(remoteConfig.HookSendSmsSecrets, "") + if len(hook.Secrets.SHA256) > 0 { + hook.Secrets.SHA256 = cast.Val(remoteConfig.HookSendSmsSecrets, "") } } hook.Enabled = cast.Val(remoteConfig.HookSendSmsEnabled, false) @@ -338,8 +338,8 @@ func (h *hook) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if hook := h.MFAVerificationAttempt; hook != nil { if hook.Enabled { hook.URI = cast.Val(remoteConfig.HookMfaVerificationAttemptUri, "") - if hook.Secrets != hashPrefix { - hook.Secrets = hashPrefix + cast.Val(remoteConfig.HookMfaVerificationAttemptSecrets, "") + if len(hook.Secrets.SHA256) > 0 { + hook.Secrets.SHA256 = cast.Val(remoteConfig.HookMfaVerificationAttemptSecrets, "") } } hook.Enabled = cast.Val(remoteConfig.HookMfaVerificationAttemptEnabled, false) @@ -347,8 +347,8 @@ func (h *hook) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if hook := h.PasswordVerificationAttempt; hook != nil { if hook.Enabled { hook.URI = cast.Val(remoteConfig.HookPasswordVerificationAttemptUri, "") - if hook.Secrets != hashPrefix { - hook.Secrets = hashPrefix + cast.Val(remoteConfig.HookPasswordVerificationAttemptSecrets, "") + if len(hook.Secrets.SHA256) > 0 { + hook.Secrets.SHA256 = cast.Val(remoteConfig.HookPasswordVerificationAttemptSecrets, "") } } hook.Enabled = cast.Val(remoteConfig.HookPasswordVerificationAttemptEnabled, false) @@ -512,7 +512,9 @@ func (s smtp) toAuthConfigBody(body *v1API.UpdateAuthConfigBody) { body.SmtpHost = &s.Host body.SmtpPort = cast.Ptr(strconv.Itoa(int(s.Port))) body.SmtpUser = &s.User - body.SmtpPass = &s.Pass + if len(s.Pass.Value) > 0 { + body.SmtpPass = &s.Pass.Value + } body.SmtpAdminEmail = &s.AdminEmail body.SmtpSenderName = &s.SenderName } @@ -528,7 +530,9 @@ func (s *smtp) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { } s.Host = cast.Val(remoteConfig.SmtpHost, "") s.User = cast.Val(remoteConfig.SmtpUser, "") - s.Pass = hashPrefix + cast.Val(remoteConfig.SmtpPass, "") + if len(s.Pass.SHA256) > 0 { + s.Pass.SHA256 = cast.Val(remoteConfig.SmtpPass, "") + } s.AdminEmail = cast.Val(remoteConfig.SmtpAdminEmail, "") s.SenderName = cast.Val(remoteConfig.SmtpSenderName, "") portStr := cast.Val(remoteConfig.SmtpPort, "0") @@ -552,34 +556,34 @@ func (s sms) toAuthConfigBody(body *v1API.UpdateAuthConfigBody) { switch { case s.Twilio.Enabled: body.SmsProvider = cast.Ptr("twilio") - if len(s.Twilio.AuthToken) > 0 { - body.SmsTwilioAuthToken = &s.Twilio.AuthToken + if len(s.Twilio.AuthToken.Value) > 0 { + body.SmsTwilioAuthToken = &s.Twilio.AuthToken.Value } body.SmsTwilioAccountSid = &s.Twilio.AccountSid body.SmsTwilioMessageServiceSid = &s.Twilio.MessageServiceSid case s.TwilioVerify.Enabled: body.SmsProvider = cast.Ptr("twilio_verify") - if len(s.TwilioVerify.AuthToken) > 0 { - body.SmsTwilioVerifyAuthToken = &s.TwilioVerify.AuthToken + if len(s.TwilioVerify.AuthToken.Value) > 0 { + body.SmsTwilioVerifyAuthToken = &s.TwilioVerify.AuthToken.Value } body.SmsTwilioVerifyAccountSid = &s.TwilioVerify.AccountSid body.SmsTwilioVerifyMessageServiceSid = &s.TwilioVerify.MessageServiceSid case s.Messagebird.Enabled: body.SmsProvider = cast.Ptr("messagebird") - if len(s.Messagebird.AccessKey) > 0 { - body.SmsMessagebirdAccessKey = &s.Messagebird.AccessKey + if len(s.Messagebird.AccessKey.Value) > 0 { + body.SmsMessagebirdAccessKey = &s.Messagebird.AccessKey.Value } body.SmsMessagebirdOriginator = &s.Messagebird.Originator case s.Textlocal.Enabled: body.SmsProvider = cast.Ptr("textlocal") - if len(s.Textlocal.ApiKey) > 0 { - body.SmsTextlocalApiKey = &s.Textlocal.ApiKey + if len(s.Textlocal.ApiKey.Value) > 0 { + body.SmsTextlocalApiKey = &s.Textlocal.ApiKey.Value } body.SmsTextlocalSender = &s.Textlocal.Sender case s.Vonage.Enabled: body.SmsProvider = cast.Ptr("vonage") - if len(s.Vonage.ApiSecret) > 0 { - body.SmsVonageApiSecret = &s.Vonage.ApiSecret + if len(s.Vonage.ApiSecret.Value) > 0 { + body.SmsVonageApiSecret = &s.Vonage.ApiSecret.Value } body.SmsVonageApiKey = &s.Vonage.ApiKey body.SmsVonageFrom = &s.Vonage.From @@ -595,30 +599,30 @@ func (s *sms) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { // We are only interested in the provider that's enabled locally switch { case s.Twilio.Enabled: - if s.Twilio.AuthToken != hashPrefix { - s.Twilio.AuthToken = hashPrefix + cast.Val(remoteConfig.SmsTwilioAuthToken, "") + if len(s.Twilio.AuthToken.SHA256) > 0 { + s.Twilio.AuthToken.SHA256 = cast.Val(remoteConfig.SmsTwilioAuthToken, "") } s.Twilio.AccountSid = cast.Val(remoteConfig.SmsTwilioAccountSid, "") s.Twilio.MessageServiceSid = cast.Val(remoteConfig.SmsTwilioMessageServiceSid, "") case s.TwilioVerify.Enabled: - if s.TwilioVerify.AuthToken != hashPrefix { - s.TwilioVerify.AuthToken = hashPrefix + cast.Val(remoteConfig.SmsTwilioVerifyAuthToken, "") + if len(s.TwilioVerify.AuthToken.SHA256) > 0 { + s.TwilioVerify.AuthToken.SHA256 = cast.Val(remoteConfig.SmsTwilioVerifyAuthToken, "") } s.TwilioVerify.AccountSid = cast.Val(remoteConfig.SmsTwilioVerifyAccountSid, "") s.TwilioVerify.MessageServiceSid = cast.Val(remoteConfig.SmsTwilioVerifyMessageServiceSid, "") case s.Messagebird.Enabled: - if s.Messagebird.AccessKey != hashPrefix { - s.Messagebird.AccessKey = hashPrefix + cast.Val(remoteConfig.SmsMessagebirdAccessKey, "") + if len(s.Messagebird.AccessKey.SHA256) > 0 { + s.Messagebird.AccessKey.SHA256 = cast.Val(remoteConfig.SmsMessagebirdAccessKey, "") } s.Messagebird.Originator = cast.Val(remoteConfig.SmsMessagebirdOriginator, "") case s.Textlocal.Enabled: - if s.Textlocal.ApiKey != hashPrefix { - s.Textlocal.ApiKey = hashPrefix + cast.Val(remoteConfig.SmsTextlocalApiKey, "") + if len(s.Textlocal.ApiKey.SHA256) > 0 { + s.Textlocal.ApiKey.SHA256 = cast.Val(remoteConfig.SmsTextlocalApiKey, "") } s.Textlocal.Sender = cast.Val(remoteConfig.SmsTextlocalSender, "") case s.Vonage.Enabled: - if s.Vonage.ApiSecret != hashPrefix { - s.Vonage.ApiSecret = hashPrefix + cast.Val(remoteConfig.SmsVonageApiSecret, "") + if len(s.Vonage.ApiSecret.SHA256) > 0 { + s.Vonage.ApiSecret.SHA256 = cast.Val(remoteConfig.SmsVonageApiSecret, "") } s.Vonage.ApiKey = cast.Val(remoteConfig.SmsVonageApiKey, "") s.Vonage.From = cast.Val(remoteConfig.SmsVonageFrom, "") @@ -643,120 +647,158 @@ func (e external) toAuthConfigBody(body *v1API.UpdateAuthConfigBody) { if p, ok := e["apple"]; ok { if body.ExternalAppleEnabled = &p.Enabled; *body.ExternalAppleEnabled { body.ExternalAppleClientId = &p.ClientId - body.ExternalAppleSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalAppleSecret = &p.Secret.Value + } } } if p, ok := e["azure"]; ok { if body.ExternalAzureEnabled = &p.Enabled; *body.ExternalAzureEnabled { body.ExternalAzureClientId = &p.ClientId - body.ExternalAzureSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalAzureSecret = &p.Secret.Value + } body.ExternalAzureUrl = &p.Url } } if p, ok := e["bitbucket"]; ok { if body.ExternalBitbucketEnabled = &p.Enabled; *body.ExternalBitbucketEnabled { body.ExternalBitbucketClientId = &p.ClientId - body.ExternalBitbucketSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalBitbucketSecret = &p.Secret.Value + } } } if p, ok := e["discord"]; ok { if body.ExternalDiscordEnabled = &p.Enabled; *body.ExternalDiscordEnabled { body.ExternalDiscordClientId = &p.ClientId - body.ExternalDiscordSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalDiscordSecret = &p.Secret.Value + } } } if p, ok := e["facebook"]; ok { if body.ExternalFacebookEnabled = &p.Enabled; *body.ExternalFacebookEnabled { body.ExternalFacebookClientId = &p.ClientId - body.ExternalFacebookSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalFacebookSecret = &p.Secret.Value + } } } if p, ok := e["figma"]; ok { if body.ExternalFigmaEnabled = &p.Enabled; *body.ExternalFigmaEnabled { body.ExternalFigmaClientId = &p.ClientId - body.ExternalFigmaSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalFigmaSecret = &p.Secret.Value + } } } if p, ok := e["github"]; ok { if body.ExternalGithubEnabled = &p.Enabled; *body.ExternalGithubEnabled { body.ExternalGithubClientId = &p.ClientId - body.ExternalGithubSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalGithubSecret = &p.Secret.Value + } } } if p, ok := e["gitlab"]; ok { if body.ExternalGitlabEnabled = &p.Enabled; *body.ExternalGitlabEnabled { body.ExternalGitlabClientId = &p.ClientId - body.ExternalGitlabSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalGitlabSecret = &p.Secret.Value + } body.ExternalGitlabUrl = &p.Url } } if p, ok := e["google"]; ok { if body.ExternalGoogleEnabled = &p.Enabled; *body.ExternalGoogleEnabled { body.ExternalGoogleClientId = &p.ClientId - body.ExternalGoogleSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalGoogleSecret = &p.Secret.Value + } body.ExternalGoogleSkipNonceCheck = &p.SkipNonceCheck } } if p, ok := e["kakao"]; ok { if body.ExternalKakaoEnabled = &p.Enabled; *body.ExternalKakaoEnabled { body.ExternalKakaoClientId = &p.ClientId - body.ExternalKakaoSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalKakaoSecret = &p.Secret.Value + } } } if p, ok := e["keycloak"]; ok { if body.ExternalKeycloakEnabled = &p.Enabled; *body.ExternalKeycloakEnabled { body.ExternalKeycloakClientId = &p.ClientId - body.ExternalKeycloakSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalKeycloakSecret = &p.Secret.Value + } body.ExternalKeycloakUrl = &p.Url } } if p, ok := e["linkedin_oidc"]; ok { if body.ExternalLinkedinOidcEnabled = &p.Enabled; *body.ExternalLinkedinOidcEnabled { body.ExternalLinkedinOidcClientId = &p.ClientId - body.ExternalLinkedinOidcSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalLinkedinOidcSecret = &p.Secret.Value + } } } if p, ok := e["notion"]; ok { if body.ExternalNotionEnabled = &p.Enabled; *body.ExternalNotionEnabled { body.ExternalNotionClientId = &p.ClientId - body.ExternalNotionSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalNotionSecret = &p.Secret.Value + } } } if p, ok := e["slack_oidc"]; ok { if body.ExternalSlackOidcEnabled = &p.Enabled; *body.ExternalSlackOidcEnabled { body.ExternalSlackOidcClientId = &p.ClientId - body.ExternalSlackOidcSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalSlackOidcSecret = &p.Secret.Value + } } } if p, ok := e["spotify"]; ok { if body.ExternalSpotifyEnabled = &p.Enabled; *body.ExternalSpotifyEnabled { body.ExternalSpotifyClientId = &p.ClientId - body.ExternalSpotifySecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalSpotifySecret = &p.Secret.Value + } } } if p, ok := e["twitch"]; ok { if body.ExternalTwitchEnabled = &p.Enabled; *body.ExternalTwitchEnabled { body.ExternalTwitchClientId = &p.ClientId - body.ExternalTwitchSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalTwitchSecret = &p.Secret.Value + } } } if p, ok := e["twitter"]; ok { if body.ExternalTwitterEnabled = &p.Enabled; *body.ExternalTwitterEnabled { body.ExternalTwitterClientId = &p.ClientId - body.ExternalTwitterSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalTwitterSecret = &p.Secret.Value + } } } if p, ok := e["workos"]; ok { if body.ExternalWorkosEnabled = &p.Enabled; *body.ExternalWorkosEnabled { body.ExternalWorkosClientId = &p.ClientId - body.ExternalWorkosSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalWorkosSecret = &p.Secret.Value + } body.ExternalWorkosUrl = &p.Url } } if p, ok := e["zoom"]; ok { if body.ExternalZoomEnabled = &p.Enabled; *body.ExternalZoomEnabled { body.ExternalZoomClientId = &p.ClientId - body.ExternalZoomSecret = &p.Secret + if len(p.Secret.Value) > 0 { + body.ExternalZoomSecret = &p.Secret.Value + } } } } @@ -772,7 +814,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if ids := cast.Val(remoteConfig.ExternalAppleAdditionalClientIds, ""); len(ids) > 0 { p.ClientId += "," + ids } - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalAppleSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalAppleSecret, "") + } } p.Enabled = cast.Val(remoteConfig.ExternalAppleEnabled, false) e["apple"] = p @@ -781,7 +825,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["azure"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalAzureClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalAzureSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalAzureSecret, "") + } p.Url = cast.Val(remoteConfig.ExternalAzureUrl, "") } p.Enabled = cast.Val(remoteConfig.ExternalAzureEnabled, false) @@ -791,7 +837,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["bitbucket"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalBitbucketClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalBitbucketSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalBitbucketSecret, "") + } } p.Enabled = cast.Val(remoteConfig.ExternalBitbucketEnabled, false) e["bitbucket"] = p @@ -800,7 +848,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["discord"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalDiscordClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalDiscordSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalDiscordSecret, "") + } } p.Enabled = cast.Val(remoteConfig.ExternalDiscordEnabled, false) e["discord"] = p @@ -809,7 +859,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["facebook"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalFacebookClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalFacebookSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalFacebookSecret, "") + } } p.Enabled = cast.Val(remoteConfig.ExternalFacebookEnabled, false) e["facebook"] = p @@ -818,7 +870,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["figma"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalFigmaClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalFigmaSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalFigmaSecret, "") + } } p.Enabled = cast.Val(remoteConfig.ExternalFigmaEnabled, false) e["figma"] = p @@ -827,7 +881,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["github"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalGithubClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalGithubSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalGithubSecret, "") + } } p.Enabled = cast.Val(remoteConfig.ExternalGithubEnabled, false) e["github"] = p @@ -836,7 +892,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["gitlab"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalGitlabClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalGitlabSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalGitlabSecret, "") + } p.Url = cast.Val(remoteConfig.ExternalGitlabUrl, "") } p.Enabled = cast.Val(remoteConfig.ExternalGitlabEnabled, false) @@ -849,7 +907,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if ids := cast.Val(remoteConfig.ExternalGoogleAdditionalClientIds, ""); len(ids) > 0 { p.ClientId += "," + ids } - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalGoogleSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalGoogleSecret, "") + } p.SkipNonceCheck = cast.Val(remoteConfig.ExternalGoogleSkipNonceCheck, false) } p.Enabled = cast.Val(remoteConfig.ExternalGoogleEnabled, false) @@ -859,7 +919,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["kakao"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalKakaoClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalKakaoSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalKakaoSecret, "") + } } p.Enabled = cast.Val(remoteConfig.ExternalKakaoEnabled, false) e["kakao"] = p @@ -868,7 +930,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["keycloak"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalKeycloakClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalKeycloakSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalKeycloakSecret, "") + } p.Url = cast.Val(remoteConfig.ExternalKeycloakUrl, "") } p.Enabled = cast.Val(remoteConfig.ExternalKeycloakEnabled, false) @@ -878,7 +942,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["linkedin_oidc"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalLinkedinOidcClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalLinkedinOidcSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalLinkedinOidcSecret, "") + } } p.Enabled = cast.Val(remoteConfig.ExternalLinkedinOidcEnabled, false) e["linkedin_oidc"] = p @@ -887,7 +953,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["notion"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalNotionClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalNotionSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalNotionSecret, "") + } } p.Enabled = cast.Val(remoteConfig.ExternalNotionEnabled, false) e["notion"] = p @@ -896,7 +964,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["slack_oidc"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalSlackOidcClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalSlackOidcSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalSlackOidcSecret, "") + } } p.Enabled = cast.Val(remoteConfig.ExternalSlackOidcEnabled, false) e["slack_oidc"] = p @@ -905,7 +975,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["spotify"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalSpotifyClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalSpotifySecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalSpotifySecret, "") + } } p.Enabled = cast.Val(remoteConfig.ExternalSpotifyEnabled, false) e["spotify"] = p @@ -914,7 +986,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["twitch"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalTwitchClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalTwitchSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalTwitchSecret, "") + } } p.Enabled = cast.Val(remoteConfig.ExternalTwitchEnabled, false) e["twitch"] = p @@ -923,7 +997,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["twitter"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalTwitterClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalTwitterSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalTwitterSecret, "") + } } p.Enabled = cast.Val(remoteConfig.ExternalTwitterEnabled, false) e["twitter"] = p @@ -932,7 +1008,9 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["workos"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalWorkosClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalWorkosSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalWorkosSecret, "") + } p.Url = cast.Val(remoteConfig.ExternalWorkosUrl, "") } p.Enabled = cast.Val(remoteConfig.ExternalWorkosEnabled, false) @@ -942,16 +1020,17 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if p, ok := e["zoom"]; ok { if p.Enabled { p.ClientId = cast.Val(remoteConfig.ExternalZoomClientId, "") - p.Secret = hashPrefix + cast.Val(remoteConfig.ExternalZoomSecret, "") + if len(p.Secret.SHA256) > 0 { + p.Secret.SHA256 = cast.Val(remoteConfig.ExternalZoomSecret, "") + } } p.Enabled = cast.Val(remoteConfig.ExternalZoomEnabled, false) e["zoom"] = p } } -func (a *auth) DiffWithRemote(projectRef string, remoteConfig v1API.AuthConfigResponse) ([]byte, error) { +func (a *auth) DiffWithRemote(remoteConfig v1API.AuthConfigResponse) ([]byte, error) { copy := a.Clone() - copy.HashSecrets(projectRef) // Convert the config values into easily comparable remoteConfig values currentValue, err := ToTomlBytes(copy) if err != nil { @@ -964,52 +1043,3 @@ func (a *auth) DiffWithRemote(projectRef string, remoteConfig v1API.AuthConfigRe } return diff.Diff("remote[auth]", remoteCompare, "local[auth]", currentValue), nil } - -const hashPrefix = "hash:" - -func (a *auth) HashSecrets(key string) { - hash := func(v string) string { - if len(v) == 0 { - return hashPrefix - } - return hashPrefix + sha256Hmac(key, v) - } - if a.Email.Smtp != nil && a.Email.Smtp.IsEnabled() { - a.Email.Smtp.Pass = hash(a.Email.Smtp.Pass) - } - // Only hash secrets for locally enabled providers because other envs won't be loaded - switch { - case a.Sms.Twilio.Enabled: - a.Sms.Twilio.AuthToken = hash(a.Sms.Twilio.AuthToken) - case a.Sms.TwilioVerify.Enabled: - a.Sms.TwilioVerify.AuthToken = hash(a.Sms.TwilioVerify.AuthToken) - case a.Sms.Messagebird.Enabled: - a.Sms.Messagebird.AccessKey = hash(a.Sms.Messagebird.AccessKey) - case a.Sms.Textlocal.Enabled: - a.Sms.Textlocal.ApiKey = hash(a.Sms.Textlocal.ApiKey) - case a.Sms.Vonage.Enabled: - a.Sms.Vonage.ApiSecret = hash(a.Sms.Vonage.ApiSecret) - } - if a.Hook.MFAVerificationAttempt != nil && a.Hook.MFAVerificationAttempt.Enabled { - a.Hook.MFAVerificationAttempt.Secrets = hash(a.Hook.MFAVerificationAttempt.Secrets) - } - if a.Hook.PasswordVerificationAttempt != nil && a.Hook.PasswordVerificationAttempt.Enabled { - a.Hook.PasswordVerificationAttempt.Secrets = hash(a.Hook.PasswordVerificationAttempt.Secrets) - } - if a.Hook.CustomAccessToken != nil && a.Hook.CustomAccessToken.Enabled { - a.Hook.CustomAccessToken.Secrets = hash(a.Hook.CustomAccessToken.Secrets) - } - if a.Hook.SendSMS != nil && a.Hook.SendSMS.Enabled { - a.Hook.SendSMS.Secrets = hash(a.Hook.SendSMS.Secrets) - } - if a.Hook.SendEmail != nil && a.Hook.SendEmail.Enabled { - a.Hook.SendEmail.Secrets = hash(a.Hook.SendEmail.Secrets) - } - for name, provider := range a.External { - if provider.Enabled { - provider.Secret = hash(provider.Secret) - } - a.External[name] = provider - } - // TODO: support SecurityCaptchaSecret in local config -} diff --git a/pkg/config/auth_test.go b/pkg/config/auth_test.go index e659da166..b34139715 100644 --- a/pkg/config/auth_test.go +++ b/pkg/config/auth_test.go @@ -48,7 +48,7 @@ func TestAuthDiff(t *testing.T) { c.MinimumPasswordLength = 6 c.PasswordRequirements = LettersDigits // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + 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), @@ -78,7 +78,7 @@ func TestAuthDiff(t *testing.T) { c.MinimumPasswordLength = 6 c.PasswordRequirements = LowerUpperLettersDigitsSymbols // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + 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), @@ -99,7 +99,7 @@ func TestAuthDiff(t *testing.T) { c := newWithDefaults() c.EnableSignup = false // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ SiteUrl: cast.Ptr(""), UriAllowList: cast.Ptr(""), JwtExp: cast.Ptr(0), @@ -124,22 +124,34 @@ func TestHookDiff(t *testing.T) { CustomAccessToken: &hookConfig{ Enabled: true, URI: "http://example.com", - Secrets: "test-secret", + Secrets: Secret{ + Value: "test-secret", + SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", + }, }, SendSMS: &hookConfig{ Enabled: true, URI: "http://example.com", - Secrets: "test-secret", + Secrets: Secret{ + Value: "test-secret", + SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", + }, }, SendEmail: &hookConfig{ Enabled: true, URI: "https://example.com", - Secrets: "test-secret", + Secrets: Secret{ + Value: "test-secret", + SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", + }, }, MFAVerificationAttempt: &hookConfig{ Enabled: true, URI: "https://example.com", - Secrets: "test-secret", + Secrets: Secret{ + Value: "test-secret", + SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", + }, }, PasswordVerificationAttempt: &hookConfig{ Enabled: true, @@ -147,7 +159,7 @@ func TestHookDiff(t *testing.T) { }, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ HookCustomAccessTokenEnabled: cast.Ptr(true), HookCustomAccessTokenUri: cast.Ptr("http://example.com"), HookCustomAccessTokenSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), @@ -177,7 +189,7 @@ func TestHookDiff(t *testing.T) { SendSMS: &hookConfig{ Enabled: false, URI: "https://example.com", - Secrets: "test-secret", + Secrets: Secret{Value: "test-secret"}, }, SendEmail: &hookConfig{ Enabled: false, @@ -189,7 +201,7 @@ func TestHookDiff(t *testing.T) { PasswordVerificationAttempt: nil, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ HookCustomAccessTokenEnabled: cast.Ptr(true), HookCustomAccessTokenUri: cast.Ptr("http://example.com"), HookCustomAccessTokenSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), @@ -215,12 +227,18 @@ func TestHookDiff(t *testing.T) { CustomAccessToken: &hookConfig{ Enabled: true, URI: "http://example.com", - Secrets: "test-secret", + Secrets: Secret{ + Value: "test-secret", + SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", + }, }, SendSMS: &hookConfig{ Enabled: true, URI: "https://example.com", - Secrets: "test-secret", + Secrets: Secret{ + Value: "test-secret", + SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", + }, }, SendEmail: &hookConfig{ Enabled: true, @@ -233,7 +251,7 @@ func TestHookDiff(t *testing.T) { PasswordVerificationAttempt: nil, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ HookCustomAccessTokenEnabled: cast.Ptr(false), HookCustomAccessTokenUri: cast.Ptr("pg-functions://postgres/public/customToken"), HookSendSmsEnabled: cast.Ptr(false), @@ -261,7 +279,7 @@ func TestHookDiff(t *testing.T) { PasswordVerificationAttempt: &hookConfig{Enabled: false}, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ HookCustomAccessTokenEnabled: cast.Ptr(false), HookSendSmsEnabled: cast.Ptr(false), HookSendEmailEnabled: cast.Ptr(false), @@ -298,7 +316,7 @@ func TestMfaDiff(t *testing.T) { MaxEnrolledFactors: 10, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ MfaMaxEnrolledFactors: cast.Ptr(10), MfaTotpEnrollEnabled: cast.Ptr(true), MfaTotpVerifyEnabled: cast.Ptr(true), @@ -330,7 +348,7 @@ func TestMfaDiff(t *testing.T) { }, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ MfaMaxEnrolledFactors: cast.Ptr(10), MfaTotpEnrollEnabled: cast.Ptr(false), MfaTotpVerifyEnabled: cast.Ptr(false), @@ -358,7 +376,7 @@ func TestMfaDiff(t *testing.T) { }, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ MfaMaxEnrolledFactors: cast.Ptr(10), MfaTotpEnrollEnabled: cast.Ptr(false), MfaTotpVerifyEnabled: cast.Ptr(false), @@ -411,11 +429,14 @@ func TestEmailDiff(t *testing.T) { }, }, Smtp: &smtp{ - Enabled: cast.Ptr(true), - Host: "smtp.sendgrid.net", - Port: 587, - User: "apikey", - Pass: "test-key", + Enabled: cast.Ptr(true), + Host: "smtp.sendgrid.net", + Port: 587, + User: "apikey", + Pass: Secret{ + Value: "test-key", + SHA256: "ed64b7695a606bc6ab4fcb41fe815b5ddf1063ccbc87afe1fa89756635db520e", + }, AdminEmail: "admin@email.com", SenderName: "Admin", }, @@ -424,7 +445,7 @@ func TestEmailDiff(t *testing.T) { OtpExpiry: 3600, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalEmailEnabled: cast.Ptr(true), MailerSecureEmailChangeEnabled: cast.Ptr(true), MailerAutoconfirm: cast.Ptr(false), @@ -489,10 +510,13 @@ func TestEmailDiff(t *testing.T) { }, }, Smtp: &smtp{ - Host: "smtp.sendgrid.net", - Port: 587, - User: "apikey", - Pass: "test-key", + Host: "smtp.sendgrid.net", + Port: 587, + User: "apikey", + Pass: Secret{ + Value: "test-key", + SHA256: "ed64b7695a606bc6ab4fcb41fe815b5ddf1063ccbc87afe1fa89756635db520e", + }, AdminEmail: "admin@email.com", SenderName: "Admin", }, @@ -501,7 +525,7 @@ func TestEmailDiff(t *testing.T) { OtpExpiry: 86400, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalEmailEnabled: cast.Ptr(false), MailerSecureEmailChangeEnabled: cast.Ptr(false), MailerAutoconfirm: cast.Ptr(true), @@ -537,7 +561,7 @@ func TestEmailDiff(t *testing.T) { OtpExpiry: 86400, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalEmailEnabled: cast.Ptr(true), MailerSecureEmailChangeEnabled: cast.Ptr(true), MailerAutoconfirm: cast.Ptr(false), @@ -583,11 +607,14 @@ func TestEmailDiff(t *testing.T) { "reauthentication": {}, }, Smtp: &smtp{ - Enabled: cast.Ptr(false), - Host: "smtp.sendgrid.net", - Port: 587, - User: "apikey", - Pass: "test-key", + Enabled: cast.Ptr(false), + Host: "smtp.sendgrid.net", + Port: 587, + User: "apikey", + Pass: Secret{ + Value: "test-key", + SHA256: "ed64b7695a606bc6ab4fcb41fe815b5ddf1063ccbc87afe1fa89756635db520e", + }, AdminEmail: "admin@email.com", SenderName: "Admin", }, @@ -596,7 +623,7 @@ func TestEmailDiff(t *testing.T) { OtpExpiry: 3600, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalEmailEnabled: cast.Ptr(false), MailerSecureEmailChangeEnabled: cast.Ptr(false), MailerAutoconfirm: cast.Ptr(true), @@ -624,11 +651,14 @@ func TestSmsDiff(t *testing.T) { Enabled: true, AccountSid: "test-account", MessageServiceSid: "test-service", - AuthToken: "test-token", + AuthToken: Secret{ + Value: "test-token", + SHA256: "c84443bc59b92caef8ec8500ff443584793756749523811eb333af2bbc74fc88", + }, }, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalPhoneEnabled: cast.Ptr(true), SmsAutoconfirm: cast.Ptr(true), SmsMaxFrequency: cast.Ptr(60), @@ -662,7 +692,7 @@ func TestSmsDiff(t *testing.T) { t.Run("local disabled remote enabled", func(t *testing.T) { c := newWithDefaults() // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalPhoneEnabled: cast.Ptr(true), SmsAutoconfirm: cast.Ptr(true), SmsMaxFrequency: cast.Ptr(60), @@ -693,11 +723,14 @@ func TestSmsDiff(t *testing.T) { Messagebird: messagebirdConfig{ Enabled: true, Originator: "test-originator", - AccessKey: "test-access-key", + AccessKey: Secret{ + Value: "test-access-key", + SHA256: "ab60d03fc809fb02dae838582f3ddc13d1d6cb32ffba77c4b969dd3caa496f13", + }, }, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalPhoneEnabled: cast.Ptr(false), SmsAutoconfirm: cast.Ptr(false), SmsMaxFrequency: cast.Ptr(0), @@ -725,7 +758,7 @@ func TestSmsDiff(t *testing.T) { MaxFrequency: time.Minute, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalPhoneEnabled: cast.Ptr(false), SmsAutoconfirm: cast.Ptr(true), SmsMaxFrequency: cast.Ptr(60), @@ -749,7 +782,7 @@ func TestSmsDiff(t *testing.T) { c := newWithDefaults() c.Sms.EnableSignup = true // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalPhoneEnabled: cast.Ptr(false), SmsProvider: cast.Ptr("twilio"), }) @@ -762,7 +795,7 @@ func TestSmsDiff(t *testing.T) { c := newWithDefaults() c.Sms.Messagebird.Enabled = true // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalPhoneEnabled: cast.Ptr(false), SmsProvider: cast.Ptr("messagebird"), SmsMessagebirdAccessKey: cast.Ptr(""), @@ -798,7 +831,7 @@ func TestExternalDiff(t *testing.T) { "zoom": {Enabled: true}, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalAppleAdditionalClientIds: cast.Ptr(""), ExternalAppleClientId: cast.Ptr(""), ExternalAppleEnabled: cast.Ptr(true), @@ -879,12 +912,18 @@ func TestExternalDiff(t *testing.T) { "apple": { Enabled: true, ClientId: "test-client-1,test-client-2", - Secret: "test-secret", + Secret: Secret{ + Value: "test-secret", + SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", + }, }, "azure": { Enabled: true, ClientId: "test-client-1", - Secret: "test-secret", + Secret: Secret{ + Value: "test-secret", + SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", + }, }, "bitbucket": {}, "discord": {}, @@ -895,7 +934,7 @@ func TestExternalDiff(t *testing.T) { "google": { Enabled: false, ClientId: "test-client-2", - Secret: "env(test_secret)", + Secret: Secret{Value: "env(test_secret)"}, SkipNonceCheck: false, }, // "kakao": {}, @@ -910,7 +949,7 @@ func TestExternalDiff(t *testing.T) { "zoom": {}, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalAppleAdditionalClientIds: cast.Ptr("test-client-2"), ExternalAppleClientId: cast.Ptr("test-client-1"), ExternalAppleEnabled: cast.Ptr(false), @@ -953,7 +992,7 @@ func TestExternalDiff(t *testing.T) { "zoom": {}, } // Run test - diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalAppleEnabled: cast.Ptr(false), ExternalAzureEnabled: cast.Ptr(false), ExternalBitbucketEnabled: cast.Ptr(false), diff --git a/pkg/config/config.go b/pkg/config/config.go index 35b84eb68..837f305a9 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -412,22 +412,27 @@ func (c *config) loadFromReader(v *viper.Viper, r io.Reader) error { v.Set("functions."+key, function{}) } } - if err := v.UnmarshalExact(c, viper.DecodeHook(mapstructure.ComposeDecodeHookFunc( - mapstructure.StringToTimeDurationHookFunc(), - mapstructure.StringToIPHookFunc(), - mapstructure.StringToSliceHookFunc(","), - mapstructure.TextUnmarshallerHookFunc(), - LoadEnvHook, - // TODO: include decrypt secret hook - )), func(dc *mapstructure.DecoderConfig) { + if err := v.UnmarshalExact(c, func(dc *mapstructure.DecoderConfig) { dc.TagName = "toml" dc.Squash = true + dc.DecodeHook = c.newDecodeHook(LoadEnvHook) }); err != nil { return errors.Errorf("failed to parse config: %w", err) } return nil } +func (c *config) newDecodeHook(fs ...mapstructure.DecodeHookFunc) mapstructure.DecodeHookFunc { + fs = append(fs, + mapstructure.StringToTimeDurationHookFunc(), + mapstructure.StringToIPHookFunc(), + mapstructure.StringToSliceHookFunc(","), + mapstructure.TextUnmarshallerHookFunc(), + DecryptSecretHookFunc(c.ProjectId), + ) + return mapstructure.ComposeDecodeHookFunc(fs...) +} + // Loads envs prefixed with supabase_ to struct fields tagged with mapstructure. func (c *config) loadFromEnv() error { v := viper.New() @@ -449,13 +454,7 @@ func (c *config) loadFromEnv() error { return errors.Errorf("failed to merge env config: %w", err) } // Writes viper state back to config struct, with automatic env substitution - if err := v.UnmarshalExact(c, viper.DecodeHook(mapstructure.ComposeDecodeHookFunc( - mapstructure.StringToTimeDurationHookFunc(), - mapstructure.StringToIPHookFunc(), - mapstructure.StringToSliceHookFunc(","), - mapstructure.TextUnmarshallerHookFunc(), - // TODO: include decrypt secret hook - ))); err != nil { + if err := v.UnmarshalExact(c, viper.DecodeHook(c.newDecodeHook())); err != nil { return errors.Errorf("failed to parse env override: %w", err) } return nil @@ -795,13 +794,13 @@ func assertEnvLoaded(s string) error { } func LoadEnvHook(f reflect.Kind, t reflect.Kind, data interface{}) (interface{}, error) { - if f != reflect.String || t != reflect.String { + if f != reflect.String { return data, nil } value := data.(string) if matches := envPattern.FindStringSubmatch(value); len(matches) > 1 { - if v, exists := os.LookupEnv(matches[1]); exists { - value = v + if env, exists := os.LookupEnv(matches[1]); exists { + value = env } } return value, nil @@ -912,13 +911,13 @@ func (e *email) validate(fsys fs.FS) (err error) { if len(e.Smtp.User) == 0 { return errors.New("Missing required field in config: auth.email.smtp.user") } - if len(e.Smtp.Pass) == 0 { + if len(e.Smtp.Pass.Value) == 0 { return errors.New("Missing required field in config: auth.email.smtp.pass") } if len(e.Smtp.AdminEmail) == 0 { return errors.New("Missing required field in config: auth.email.smtp.admin_email") } - if err := assertEnvLoaded(e.Smtp.Pass); err != nil { + if err := assertEnvLoaded(e.Smtp.Pass.Value); err != nil { return err } } @@ -934,10 +933,10 @@ func (s *sms) validate() (err error) { if len(s.Twilio.MessageServiceSid) == 0 { return errors.New("Missing required field in config: auth.sms.twilio.message_service_sid") } - if len(s.Twilio.AuthToken) == 0 { + if len(s.Twilio.AuthToken.Value) == 0 { return errors.New("Missing required field in config: auth.sms.twilio.auth_token") } - if err := assertEnvLoaded(s.Twilio.AuthToken); err != nil { + if err := assertEnvLoaded(s.Twilio.AuthToken.Value); err != nil { return err } case s.TwilioVerify.Enabled: @@ -947,30 +946,30 @@ func (s *sms) validate() (err error) { if len(s.TwilioVerify.MessageServiceSid) == 0 { return errors.New("Missing required field in config: auth.sms.twilio_verify.message_service_sid") } - if len(s.TwilioVerify.AuthToken) == 0 { + if len(s.TwilioVerify.AuthToken.Value) == 0 { return errors.New("Missing required field in config: auth.sms.twilio_verify.auth_token") } - if err := assertEnvLoaded(s.TwilioVerify.AuthToken); err != nil { + if err := assertEnvLoaded(s.TwilioVerify.AuthToken.Value); err != nil { return err } case s.Messagebird.Enabled: if len(s.Messagebird.Originator) == 0 { return errors.New("Missing required field in config: auth.sms.messagebird.originator") } - if len(s.Messagebird.AccessKey) == 0 { + if len(s.Messagebird.AccessKey.Value) == 0 { return errors.New("Missing required field in config: auth.sms.messagebird.access_key") } - if err := assertEnvLoaded(s.Messagebird.AccessKey); err != nil { + if err := assertEnvLoaded(s.Messagebird.AccessKey.Value); err != nil { return err } case s.Textlocal.Enabled: if len(s.Textlocal.Sender) == 0 { return errors.New("Missing required field in config: auth.sms.textlocal.sender") } - if len(s.Textlocal.ApiKey) == 0 { + if len(s.Textlocal.ApiKey.Value) == 0 { return errors.New("Missing required field in config: auth.sms.textlocal.api_key") } - if err := assertEnvLoaded(s.Textlocal.ApiKey); err != nil { + if err := assertEnvLoaded(s.Textlocal.ApiKey.Value); err != nil { return err } case s.Vonage.Enabled: @@ -980,13 +979,13 @@ func (s *sms) validate() (err error) { if len(s.Vonage.ApiKey) == 0 { return errors.New("Missing required field in config: auth.sms.vonage.api_key") } - if len(s.Vonage.ApiSecret) == 0 { + if len(s.Vonage.ApiSecret.Value) == 0 { return errors.New("Missing required field in config: auth.sms.vonage.api_secret") } if err := assertEnvLoaded(s.Vonage.ApiKey); err != nil { return err } - if err := assertEnvLoaded(s.Vonage.ApiSecret); err != nil { + if err := assertEnvLoaded(s.Vonage.ApiSecret.Value); err != nil { return err } case s.EnableSignup: @@ -1010,13 +1009,13 @@ func (e external) validate() (err error) { if provider.ClientId == "" { return errors.Errorf("Missing required field in config: auth.external.%s.client_id", ext) } - if !sliceContains([]string{"apple", "google"}, ext) && provider.Secret == "" { + if !sliceContains([]string{"apple", "google"}, ext) && len(provider.Secret.Value) == 0 { return errors.Errorf("Missing required field in config: auth.external.%s.secret", ext) } if err := assertEnvLoaded(provider.ClientId); err != nil { return err } - if err := assertEnvLoaded(provider.Secret); err != nil { + if err := assertEnvLoaded(provider.Secret.Value); err != nil { return err } if err := assertEnvLoaded(provider.RedirectUri); err != nil { @@ -1075,18 +1074,18 @@ func (h *hookConfig) validate(hookType string) (err error) { } switch strings.ToLower(parsed.Scheme) { case "http", "https": - if len(h.Secrets) == 0 { + if len(h.Secrets.Value) == 0 { return errors.Errorf("Missing required field in config: auth.hook.%s.secrets", hookType) - } else if err := assertEnvLoaded(h.Secrets); err != nil { + } else if err := assertEnvLoaded(h.Secrets.Value); err != nil { return err } - for _, secret := range strings.Split(h.Secrets, "|") { + for _, secret := range strings.Split(h.Secrets.Value, "|") { if !hookSecretPattern.MatchString(secret) { return errors.Errorf(`Invalid hook config: auth.hook.%s.secrets must be formatted as "v1,whsec_"`, hookType) } } case "pg-functions": - if len(h.Secrets) > 0 { + if len(h.Secrets.Value) > 0 { return errors.Errorf("Invalid hook config: auth.hook.%s.secrets is unsupported for pg-functions URI", hookType) } default: diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index e1678f2ed..45b2d04f6 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -45,7 +45,7 @@ func TestConfigParsing(t *testing.T) { assert.NoError(t, config.Load("", fsys)) // Check error assert.Equal(t, "hello", config.Auth.External["azure"].ClientId) - assert.Equal(t, "this is cool", config.Auth.External["azure"].Secret) + assert.Equal(t, "this is cool", config.Auth.External["azure"].Secret.Value) assert.Equal(t, []string{ "https://127.0.0.1:3000", "http://localhost:3000/auth/callback", @@ -214,7 +214,7 @@ func TestValidateHookURI(t *testing.T) { hookConfig: hookConfig{ Enabled: true, URI: "http://example.com", - Secrets: "v1,whsec_aWxpa2VzdXBhYmFzZXZlcnltdWNoYW5kaWhvcGV5b3Vkb3Rvbw==", + Secrets: Secret{Value: "v1,whsec_aWxpa2VzdXBhYmFzZXZlcnltdWNoYW5kaWhvcGV5b3Vkb3Rvbw=="}, }, }, { @@ -222,7 +222,7 @@ func TestValidateHookURI(t *testing.T) { hookConfig: hookConfig{ Enabled: true, URI: "https://example.com", - Secrets: "v1,whsec_aWxpa2VzdXBhYmFzZXZlcnltdWNoYW5kaWhvcGV5b3Vkb3Rvbw==", + Secrets: Secret{Value: "v1,whsec_aWxpa2VzdXBhYmFzZXZlcnltdWNoYW5kaWhvcGV5b3Vkb3Rvbw=="}, }, }, { @@ -237,7 +237,7 @@ func TestValidateHookURI(t *testing.T) { hookConfig: hookConfig{ Enabled: true, URI: "ftp://example.com", - Secrets: "v1,whsec_aWxpa2VzdXBhYmFzZXZlcnltdWNoYW5kaWhvcGV5b3Vkb3Rvbw==", + Secrets: Secret{Value: "v1,whsec_aWxpa2VzdXBhYmFzZXZlcnltdWNoYW5kaWhvcGV5b3Vkb3Rvbw=="}, }, errorMsg: "Invalid hook config: auth.hook.invalid URI with unsupported scheme.uri should be a HTTP, HTTPS, or pg-functions URI", }, @@ -246,7 +246,7 @@ func TestValidateHookURI(t *testing.T) { hookConfig: hookConfig{ Enabled: true, URI: "http://a b.com", - Secrets: "v1,whsec_aWxpa2VzdXBhYmFzZXZlcnltdWNoYW5kaWhvcGV5b3Vkb3Rvbw==", + Secrets: Secret{Value: "v1,whsec_aWxpa2VzdXBhYmFzZXZlcnltdWNoYW5kaWhvcGV5b3Vkb3Rvbw=="}, }, errorMsg: "failed to parse template url: parse \"http://a b.com\": invalid character \" \" in host name", }, @@ -263,7 +263,7 @@ func TestValidateHookURI(t *testing.T) { hookConfig: hookConfig{ Enabled: true, URI: "pg-functions://functionName", - Secrets: "test-secret", + Secrets: Secret{Value: "test-secret"}, }, errorMsg: "Invalid hook config: auth.hook.valid pg-functions URI with unsupported secrets.secrets is unsupported for pg-functions URI", }, diff --git a/pkg/config/secret.go b/pkg/config/secret.go new file mode 100644 index 000000000..64da04d77 --- /dev/null +++ b/pkg/config/secret.go @@ -0,0 +1,83 @@ +package config + +import ( + "encoding/base64" + "os" + "reflect" + "strings" + + ecies "github.com/ecies/go/v2" + "github.com/go-errors/errors" + "github.com/mitchellh/mapstructure" +) + +type Secret struct { + Value string + SHA256 string +} + +const HASHED_PREFIX = "hash:" + +func (s Secret) MarshalText() (text []byte, err error) { + if len(s.SHA256) == 0 { + return []byte{}, nil + } + return []byte(HASHED_PREFIX + s.SHA256), nil +} + +const ENCRYPTED_PREFIX = "encrypted:" + +// Decrypt secret values following dotenvx convention: +// https://github.com/dotenvx/dotenvx/blob/main/src/lib/helpers/decryptKeyValue.js +func decrypt(key, value string) (string, error) { + if !strings.HasPrefix(value, ENCRYPTED_PREFIX) { + return value, nil + } + if len(key) == 0 { + return value, errors.New("missing private key") + } + // Verify private key exists + privateKey, err := ecies.NewPrivateKeyFromHex(key) + if err != nil { + return value, errors.Errorf("failed to hex decode private key: %w", err) + } + // Verify ciphertext is base64 encoded + encoded := value[len(ENCRYPTED_PREFIX):] + ciphertext, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return value, errors.Errorf("failed to base64 decode secret: %w", err) + } + // Return decrypted value + plaintext, err := ecies.Decrypt(privateKey, ciphertext) + if err != nil { + return value, errors.Errorf("failed to decrypt secret: %w", err) + } + return string(plaintext), nil +} + +func DecryptSecretHookFunc(hashKey string) mapstructure.DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + var result Secret + if t != reflect.TypeOf(result) { + return data, nil + } + ciphertext := data.(string) + // Skip hashing unloaded env + if matches := envPattern.FindStringSubmatch(ciphertext); len(matches) > 1 { + return result, nil + } + var err error + privKey := os.Getenv("DOTENV_PRIVATE_KEY") + for _, k := range strings.Split(privKey, ",") { + result.Value, err = decrypt(k, ciphertext) + if err == nil && len(result.Value) > 0 { + result.SHA256 = sha256Hmac(hashKey, result.Value) + break + } + } + return result, err + } +} diff --git a/pkg/config/secret_test.go b/pkg/config/secret_test.go new file mode 100644 index 000000000..c4bf74e5b --- /dev/null +++ b/pkg/config/secret_test.go @@ -0,0 +1,52 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecryptSecret(t *testing.T) { + key := "7fd7210cef8f331ee8c55897996aaaafd853a2b20a4dc73d6d75759f65d2a7eb" + value := "encrypted:BKiXH15AyRzeohGyUrmB6cGjSklCrrBjdesQlX1VcXo/Xp20Bi2gGZ3AlIqxPQDmjVAALnhZamKnuY73l8Dz1P+BYiZUgxTSLzdCvdYUyVbNekj2UudbdUizBViERtZkuQwZHIv/" + + t.Run("decrypts secret value", func(t *testing.T) { + // Run test + plaintext, err := decrypt(key, value) + // Check error + assert.NoError(t, err) + assert.Equal(t, "value", plaintext) + }) + + t.Run("throws error on missing key", func(t *testing.T) { + // Run test + plaintext, err := decrypt("", value) + // Check error + assert.ErrorContains(t, err, "missing private key") + assert.Equal(t, value, plaintext) + }) + + t.Run("throws error on non-hex key", func(t *testing.T) { + // Run test + plaintext, err := decrypt("invalid", value) + // Check error + assert.ErrorContains(t, err, "failed to hex decode private key: cannot decode hex string") + assert.Equal(t, value, plaintext) + }) + + t.Run("throws error on non-base64 value", func(t *testing.T) { + // Run test + plaintext, err := decrypt(key, "encrypted:invalid") + // Check error + assert.ErrorContains(t, err, "failed to base64 decode secret: illegal base64 data at input byte 4") + assert.Equal(t, "encrypted:invalid", plaintext) + }) + + t.Run("throws error on empty ciphertext", func(t *testing.T) { + // Run test + plaintext, err := decrypt(key, "encrypted:") + // Check error + assert.ErrorContains(t, err, "failed to decrypt secret: invalid length of message") + assert.Equal(t, "encrypted:", plaintext) + }) +} diff --git a/pkg/config/testdata/TestEmailDiff/local_enabled_remote_disabled.diff b/pkg/config/testdata/TestEmailDiff/local_enabled_remote_disabled.diff index 85d30362a..ad89a5d1e 100644 --- a/pkg/config/testdata/TestEmailDiff/local_enabled_remote_disabled.diff +++ b/pkg/config/testdata/TestEmailDiff/local_enabled_remote_disabled.diff @@ -46,7 +46,7 @@ diff remote[auth] local[auth] -host = "" -port = 0 -user = "" --pass = "hash:" +-pass = "" -admin_email = "" -sender_name = "" +host = "smtp.sendgrid.net" diff --git a/pkg/config/testdata/TestExternalDiff/local_enabled_and_disabled.diff b/pkg/config/testdata/TestExternalDiff/local_enabled_and_disabled.diff index 089d74eee..8b78fb240 100644 --- a/pkg/config/testdata/TestExternalDiff/local_enabled_and_disabled.diff +++ b/pkg/config/testdata/TestExternalDiff/local_enabled_and_disabled.diff @@ -16,7 +16,7 @@ diff remote[auth] local[auth] [external.azure] -enabled = false -client_id = "" --secret = "hash:" +-secret = "" +enabled = true +client_id = "test-client-1" +secret = "hash:ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252" @@ -30,5 +30,5 @@ diff remote[auth] local[auth] -enabled = true +enabled = false client_id = "test-client-2" - secret = "env(test_secret)" + secret = "" url = "" diff --git a/pkg/config/testdata/TestHookDiff/local_disabled_remote_enabled.diff b/pkg/config/testdata/TestHookDiff/local_disabled_remote_enabled.diff index c8cf4c5f0..247618f0c 100644 --- a/pkg/config/testdata/TestHookDiff/local_disabled_remote_enabled.diff +++ b/pkg/config/testdata/TestHookDiff/local_disabled_remote_enabled.diff @@ -18,7 +18,7 @@ diff remote[auth] local[auth] -enabled = true +enabled = false uri = "https://example.com" - secrets = "test-secret" + secrets = "" [hook.send_email] -enabled = true +enabled = false diff --git a/pkg/config/testdata/TestHookDiff/local_enabled_remote_disabled.diff b/pkg/config/testdata/TestHookDiff/local_enabled_remote_disabled.diff index dc80e57a3..556b13ff5 100644 --- a/pkg/config/testdata/TestHookDiff/local_enabled_remote_disabled.diff +++ b/pkg/config/testdata/TestHookDiff/local_enabled_remote_disabled.diff @@ -8,11 +8,11 @@ diff remote[auth] local[auth] -enabled = false +enabled = true uri = "pg-functions://postgres/public/verifyMFA" - secrets = "hash:" + secrets = "" [hook.custom_access_token] -enabled = false -uri = "pg-functions://postgres/public/customToken" --secrets = "hash:" +-secrets = "" +enabled = true +uri = "http://example.com" +secrets = "hash:ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252" @@ -26,6 +26,6 @@ diff remote[auth] local[auth] -uri = "https://example.com" +enabled = true +uri = "pg-functions://postgres/public/sendEmail" - secrets = "hash:" + secrets = "" [mfa] diff --git a/pkg/config/testdata/TestSmsDiff/local_enabled_remote_disabled.diff b/pkg/config/testdata/TestSmsDiff/local_enabled_remote_disabled.diff index 4418013bf..02f0b76cd 100644 --- a/pkg/config/testdata/TestSmsDiff/local_enabled_remote_disabled.diff +++ b/pkg/config/testdata/TestSmsDiff/local_enabled_remote_disabled.diff @@ -25,7 +25,7 @@ diff remote[auth] local[auth] [sms.messagebird] -enabled = false -originator = "" --access_key = "hash:" +-access_key = "" +enabled = true +originator = "test-originator" +access_key = "hash:ab60d03fc809fb02dae838582f3ddc13d1d6cb32ffba77c4b969dd3caa496f13" diff --git a/pkg/config/updater.go b/pkg/config/updater.go index 445000d0b..e5f42aceb 100644 --- a/pkg/config/updater.go +++ b/pkg/config/updater.go @@ -110,7 +110,7 @@ func (u *ConfigUpdater) UpdateAuthConfig(ctx context.Context, projectRef string, } else if authConfig.JSON200 == nil { return errors.Errorf("unexpected status %d: %s", authConfig.StatusCode(), string(authConfig.Body)) } - authDiff, err := c.DiffWithRemote(projectRef, *authConfig.JSON200) + authDiff, err := c.DiffWithRemote(*authConfig.JSON200) if err != nil { return err } else if len(authDiff) == 0 {