diff --git a/internal/db/start/start.go b/internal/db/start/start.go index a30619398f..cf9cdc8dfd 100644 --- a/internal/db/start/start.go +++ b/internal/db/start/start.go @@ -320,7 +320,7 @@ func initAuthJob(host string) utils.DockerJob { return utils.DockerJob{ Image: utils.Config.Auth.Image, Env: []string{ - "API_EXTERNAL_URL=" + utils.Config.Api.ExternalUrl, + "API_EXTERNAL_URL=" + utils.Config.AuthExternalURL(), "GOTRUE_LOG_LEVEL=error", "GOTRUE_DB_DRIVER=postgres", fmt.Sprintf("GOTRUE_DB_DATABASE_URL=postgresql://supabase_auth_admin:%s@%s:5432/postgres", utils.Config.Db.Password, host), diff --git a/internal/start/start.go b/internal/start/start.go index 846e87e4b7..dd195cfb66 100644 --- a/internal/start/start.go +++ b/internal/start/start.go @@ -573,78 +573,7 @@ EOF // Start GoTrue. if utils.Config.Auth.Enabled && !isContainerExcluded(utils.Config.Auth.Image, excluded) { - var testOTP bytes.Buffer - if len(utils.Config.Auth.Sms.TestOTP) > 0 { - formatMapForEnvConfig(utils.Config.Auth.Sms.TestOTP, &testOTP) - } - - env := []string{ - "API_EXTERNAL_URL=" + utils.Config.Api.ExternalUrl, - - "GOTRUE_API_HOST=0.0.0.0", - "GOTRUE_API_PORT=9999", - - "GOTRUE_DB_DRIVER=postgres", - fmt.Sprintf("GOTRUE_DB_DATABASE_URL=postgresql://supabase_auth_admin:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database), - - "GOTRUE_SITE_URL=" + utils.Config.Auth.SiteUrl, - "GOTRUE_URI_ALLOW_LIST=" + strings.Join(utils.Config.Auth.AdditionalRedirectUrls, ","), - fmt.Sprintf("GOTRUE_DISABLE_SIGNUP=%v", !utils.Config.Auth.EnableSignup), - - "GOTRUE_JWT_ADMIN_ROLES=service_role", - "GOTRUE_JWT_AUD=authenticated", - "GOTRUE_JWT_DEFAULT_GROUP_NAME=authenticated", - fmt.Sprintf("GOTRUE_JWT_EXP=%v", utils.Config.Auth.JwtExpiry), - "GOTRUE_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value, - "GOTRUE_JWT_ISSUER=" + utils.Config.Auth.JwtIssuer, - - fmt.Sprintf("GOTRUE_EXTERNAL_EMAIL_ENABLED=%v", utils.Config.Auth.Email.EnableSignup), - fmt.Sprintf("GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED=%v", utils.Config.Auth.Email.DoubleConfirmChanges), - fmt.Sprintf("GOTRUE_MAILER_AUTOCONFIRM=%v", !utils.Config.Auth.Email.EnableConfirmations), - fmt.Sprintf("GOTRUE_MAILER_OTP_LENGTH=%v", utils.Config.Auth.Email.OtpLength), - fmt.Sprintf("GOTRUE_MAILER_OTP_EXP=%v", utils.Config.Auth.Email.OtpExpiry), - "GOTRUE_MAILER_TEMPLATE_RELOADING_ENABLED=true", - - fmt.Sprintf("GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=%v", utils.Config.Auth.EnableAnonymousSignIns), - - fmt.Sprintf("GOTRUE_SMTP_MAX_FREQUENCY=%v", utils.Config.Auth.Email.MaxFrequency), - - fmt.Sprintf("GOTRUE_MAILER_URLPATHS_INVITE=%s/verify", utils.Config.Auth.JwtIssuer), - fmt.Sprintf("GOTRUE_MAILER_URLPATHS_CONFIRMATION=%s/verify", utils.Config.Auth.JwtIssuer), - fmt.Sprintf("GOTRUE_MAILER_URLPATHS_RECOVERY=%s/verify", utils.Config.Auth.JwtIssuer), - fmt.Sprintf("GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=%s/verify", utils.Config.Auth.JwtIssuer), - "GOTRUE_RATE_LIMIT_EMAIL_SENT=360000", - - fmt.Sprintf("GOTRUE_EXTERNAL_PHONE_ENABLED=%v", utils.Config.Auth.Sms.EnableSignup), - fmt.Sprintf("GOTRUE_SMS_AUTOCONFIRM=%v", !utils.Config.Auth.Sms.EnableConfirmations), - fmt.Sprintf("GOTRUE_SMS_MAX_FREQUENCY=%v", utils.Config.Auth.Sms.MaxFrequency), - "GOTRUE_SMS_OTP_EXP=6000", - "GOTRUE_SMS_OTP_LENGTH=6", - fmt.Sprintf("GOTRUE_SMS_TEMPLATE=%v", utils.Config.Auth.Sms.Template), - "GOTRUE_SMS_TEST_OTP=" + testOTP.String(), - - fmt.Sprintf("GOTRUE_PASSWORD_MIN_LENGTH=%v", utils.Config.Auth.MinimumPasswordLength), - fmt.Sprintf("GOTRUE_PASSWORD_REQUIRED_CHARACTERS=%v", utils.Config.Auth.PasswordRequirements.ToChar()), - fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=%v", utils.Config.Auth.EnableRefreshTokenRotation), - fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=%v", utils.Config.Auth.RefreshTokenReuseInterval), - fmt.Sprintf("GOTRUE_SECURITY_MANUAL_LINKING_ENABLED=%v", utils.Config.Auth.EnableManualLinking), - fmt.Sprintf("GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION=%v", utils.Config.Auth.Email.SecurePasswordChange), - fmt.Sprintf("GOTRUE_MFA_PHONE_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.Phone.EnrollEnabled), - fmt.Sprintf("GOTRUE_MFA_PHONE_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.Phone.VerifyEnabled), - fmt.Sprintf("GOTRUE_MFA_TOTP_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.TOTP.EnrollEnabled), - fmt.Sprintf("GOTRUE_MFA_TOTP_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.TOTP.VerifyEnabled), - fmt.Sprintf("GOTRUE_MFA_WEB_AUTHN_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.WebAuthn.EnrollEnabled), - fmt.Sprintf("GOTRUE_MFA_WEB_AUTHN_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.WebAuthn.VerifyEnabled), - fmt.Sprintf("GOTRUE_MFA_MAX_ENROLLED_FACTORS=%v", utils.Config.Auth.MFA.MaxEnrolledFactors), - - // Add rate limit configurations - fmt.Sprintf("GOTRUE_RATE_LIMIT_ANONYMOUS_USERS=%v", utils.Config.Auth.RateLimit.AnonymousUsers), - fmt.Sprintf("GOTRUE_RATE_LIMIT_TOKEN_REFRESH=%v", utils.Config.Auth.RateLimit.TokenRefresh), - fmt.Sprintf("GOTRUE_RATE_LIMIT_OTP=%v", utils.Config.Auth.RateLimit.SignInSignUps), - fmt.Sprintf("GOTRUE_RATE_LIMIT_VERIFY=%v", utils.Config.Auth.RateLimit.TokenVerifications), - fmt.Sprintf("GOTRUE_RATE_LIMIT_SMS_SENT=%v", utils.Config.Auth.RateLimit.SmsSent), - fmt.Sprintf("GOTRUE_RATE_LIMIT_WEB3=%v", utils.Config.Auth.RateLimit.Web3), - } + env := buildGotrueEnv(dbConfig) // Serialise default or custom signing keys if keys, err := json.Marshal(utils.Config.Auth.SigningKeys); err == nil { @@ -817,26 +746,7 @@ EOF ) } - for name, config := range utils.Config.Auth.External { - env = append( - 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.Value), - fmt.Sprintf("GOTRUE_EXTERNAL_%s_SKIP_NONCE_CHECK=%t", strings.ToUpper(name), config.SkipNonceCheck), - fmt.Sprintf("GOTRUE_EXTERNAL_%s_EMAIL_OPTIONAL=%t", strings.ToUpper(name), config.EmailOptional), - ) - - redirectUri := config.RedirectUri - if redirectUri == "" { - redirectUri = utils.Config.Auth.JwtIssuer + "/callback" - } - env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=%s", strings.ToUpper(name), redirectUri)) - - if config.Url != "" { - env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_URL=%s", strings.ToUpper(name), config.Url)) - } - } + env = appendGotrueExternalProviderEnv(env) env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_WEB3_SOLANA_ENABLED=%v", utils.Config.Auth.Web3.Solana.Enabled), fmt.Sprintf("GOTRUE_EXTERNAL_WEB3_ETHEREUM_ENABLED=%v", utils.Config.Auth.Web3.Ethereum.Enabled), @@ -1368,6 +1278,100 @@ func formatMapForEnvConfig(input map[string]string, output *bytes.Buffer) { } } +func buildGotrueEnv(dbConfig pgconn.Config) []string { + var testOTP bytes.Buffer + if len(utils.Config.Auth.Sms.TestOTP) > 0 { + formatMapForEnvConfig(utils.Config.Auth.Sms.TestOTP, &testOTP) + } + + return []string{ + "API_EXTERNAL_URL=" + utils.Config.AuthExternalURL(), + + "GOTRUE_API_HOST=0.0.0.0", + "GOTRUE_API_PORT=9999", + + "GOTRUE_DB_DRIVER=postgres", + fmt.Sprintf("GOTRUE_DB_DATABASE_URL=postgresql://supabase_auth_admin:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database), + + "GOTRUE_SITE_URL=" + utils.Config.Auth.SiteUrl, + "GOTRUE_URI_ALLOW_LIST=" + strings.Join(utils.Config.Auth.AdditionalRedirectUrls, ","), + fmt.Sprintf("GOTRUE_DISABLE_SIGNUP=%v", !utils.Config.Auth.EnableSignup), + + "GOTRUE_JWT_ADMIN_ROLES=service_role", + "GOTRUE_JWT_AUD=authenticated", + "GOTRUE_JWT_DEFAULT_GROUP_NAME=authenticated", + fmt.Sprintf("GOTRUE_JWT_EXP=%v", utils.Config.Auth.JwtExpiry), + "GOTRUE_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value, + "GOTRUE_JWT_ISSUER=" + utils.Config.Auth.JwtIssuer, + + fmt.Sprintf("GOTRUE_EXTERNAL_EMAIL_ENABLED=%v", utils.Config.Auth.Email.EnableSignup), + fmt.Sprintf("GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED=%v", utils.Config.Auth.Email.DoubleConfirmChanges), + fmt.Sprintf("GOTRUE_MAILER_AUTOCONFIRM=%v", !utils.Config.Auth.Email.EnableConfirmations), + fmt.Sprintf("GOTRUE_MAILER_OTP_LENGTH=%v", utils.Config.Auth.Email.OtpLength), + fmt.Sprintf("GOTRUE_MAILER_OTP_EXP=%v", utils.Config.Auth.Email.OtpExpiry), + "GOTRUE_MAILER_TEMPLATE_RELOADING_ENABLED=true", + + fmt.Sprintf("GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=%v", utils.Config.Auth.EnableAnonymousSignIns), + + fmt.Sprintf("GOTRUE_SMTP_MAX_FREQUENCY=%v", utils.Config.Auth.Email.MaxFrequency), + + "GOTRUE_MAILER_URLPATHS_INVITE=/verify", + "GOTRUE_MAILER_URLPATHS_CONFIRMATION=/verify", + "GOTRUE_MAILER_URLPATHS_RECOVERY=/verify", + "GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=/verify", + "GOTRUE_RATE_LIMIT_EMAIL_SENT=360000", + + fmt.Sprintf("GOTRUE_EXTERNAL_PHONE_ENABLED=%v", utils.Config.Auth.Sms.EnableSignup), + fmt.Sprintf("GOTRUE_SMS_AUTOCONFIRM=%v", !utils.Config.Auth.Sms.EnableConfirmations), + fmt.Sprintf("GOTRUE_SMS_MAX_FREQUENCY=%v", utils.Config.Auth.Sms.MaxFrequency), + "GOTRUE_SMS_OTP_EXP=6000", + "GOTRUE_SMS_OTP_LENGTH=6", + fmt.Sprintf("GOTRUE_SMS_TEMPLATE=%v", utils.Config.Auth.Sms.Template), + "GOTRUE_SMS_TEST_OTP=" + testOTP.String(), + + fmt.Sprintf("GOTRUE_PASSWORD_MIN_LENGTH=%v", utils.Config.Auth.MinimumPasswordLength), + fmt.Sprintf("GOTRUE_PASSWORD_REQUIRED_CHARACTERS=%v", utils.Config.Auth.PasswordRequirements.ToChar()), + fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=%v", utils.Config.Auth.EnableRefreshTokenRotation), + fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=%v", utils.Config.Auth.RefreshTokenReuseInterval), + fmt.Sprintf("GOTRUE_SECURITY_MANUAL_LINKING_ENABLED=%v", utils.Config.Auth.EnableManualLinking), + fmt.Sprintf("GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION=%v", utils.Config.Auth.Email.SecurePasswordChange), + fmt.Sprintf("GOTRUE_MFA_PHONE_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.Phone.EnrollEnabled), + fmt.Sprintf("GOTRUE_MFA_PHONE_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.Phone.VerifyEnabled), + fmt.Sprintf("GOTRUE_MFA_TOTP_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.TOTP.EnrollEnabled), + fmt.Sprintf("GOTRUE_MFA_TOTP_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.TOTP.VerifyEnabled), + fmt.Sprintf("GOTRUE_MFA_WEB_AUTHN_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.WebAuthn.EnrollEnabled), + fmt.Sprintf("GOTRUE_MFA_WEB_AUTHN_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.WebAuthn.VerifyEnabled), + fmt.Sprintf("GOTRUE_MFA_MAX_ENROLLED_FACTORS=%v", utils.Config.Auth.MFA.MaxEnrolledFactors), + + fmt.Sprintf("GOTRUE_RATE_LIMIT_ANONYMOUS_USERS=%v", utils.Config.Auth.RateLimit.AnonymousUsers), + fmt.Sprintf("GOTRUE_RATE_LIMIT_TOKEN_REFRESH=%v", utils.Config.Auth.RateLimit.TokenRefresh), + fmt.Sprintf("GOTRUE_RATE_LIMIT_OTP=%v", utils.Config.Auth.RateLimit.SignInSignUps), + fmt.Sprintf("GOTRUE_RATE_LIMIT_VERIFY=%v", utils.Config.Auth.RateLimit.TokenVerifications), + fmt.Sprintf("GOTRUE_RATE_LIMIT_SMS_SENT=%v", utils.Config.Auth.RateLimit.SmsSent), + fmt.Sprintf("GOTRUE_RATE_LIMIT_WEB3=%v", utils.Config.Auth.RateLimit.Web3), + } +} + +func appendGotrueExternalProviderEnv(env []string) []string { + for name, config := range utils.Config.Auth.External { + env = append( + 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.Value), + fmt.Sprintf("GOTRUE_EXTERNAL_%s_SKIP_NONCE_CHECK=%t", strings.ToUpper(name), config.SkipNonceCheck), + fmt.Sprintf("GOTRUE_EXTERNAL_%s_EMAIL_OPTIONAL=%t", strings.ToUpper(name), config.EmailOptional), + ) + if config.RedirectUri != "" { + env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=%s", strings.ToUpper(name), config.RedirectUri)) + } + if config.Url != "" { + env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_URL=%s", strings.ToUpper(name), config.Url)) + } + } + return env +} + func printSecurityNotice() { fmt.Fprintln(os.Stderr, utils.Yellow("Local dev security notice")) fmt.Fprintln(os.Stderr, "All services bind to 0.0.0.0 (network-accessible, not just localhost)") diff --git a/internal/start/start_test.go b/internal/start/start_test.go index 56d237e2b9..573c28b092 100644 --- a/internal/start/start_test.go +++ b/internal/start/start_test.go @@ -6,6 +6,7 @@ import ( "errors" "net/http" "regexp" + "strings" "testing" "time" @@ -300,6 +301,59 @@ func TestDatabaseStart(t *testing.T) { }) } +func TestBuildGotrueEnv(t *testing.T) { + original := utils.Config + t.Cleanup(func() { + utils.Config = original + }) + + t.Run("uses auth scoped external url and relative mailer paths", func(t *testing.T) { + utils.Config = config.NewConfig() + utils.Config.Api.ExternalUrl = "http://127.0.0.1:54321" + utils.Config.Auth.ExternalUrl = "http://127.0.0.1:54321/auth/v1" + utils.Config.Auth.JwtIssuer = utils.Config.Auth.ExternalUrl + utils.Config.Auth.SiteUrl = "http://127.0.0.1:3000" + provider := utils.Config.Auth.External["github"] + provider.Enabled = true + provider.ClientId = "client-id" + provider.Secret.Value = "secret" + utils.Config.Auth.External["github"] = provider + + env := envToMap(appendGotrueExternalProviderEnv(buildGotrueEnv(pgconn.Config{ + Host: "db", + Port: 5432, + Database: "postgres", + Password: "postgres", + }))) + + assert.Equal(t, "http://127.0.0.1:54321/auth/v1", env["API_EXTERNAL_URL"]) + assert.Equal(t, "http://127.0.0.1:54321/auth/v1", env["GOTRUE_JWT_ISSUER"]) + assert.Equal(t, "/verify", env["GOTRUE_MAILER_URLPATHS_INVITE"]) + assert.Equal(t, "/verify", env["GOTRUE_MAILER_URLPATHS_CONFIRMATION"]) + assert.Equal(t, "/verify", env["GOTRUE_MAILER_URLPATHS_RECOVERY"]) + assert.Equal(t, "/verify", env["GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE"]) + assert.NotContains(t, env, "GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI") + }) + + t.Run("preserves explicit provider redirect override", func(t *testing.T) { + utils.Config = config.NewConfig() + utils.Config.Api.ExternalUrl = "http://127.0.0.1:54321" + utils.Config.Auth.ExternalUrl = "http://127.0.0.1:54321/auth/v1" + utils.Config.Auth.JwtIssuer = "https://issuer.example.com/auth/v1" + utils.Config.Auth.SiteUrl = "http://127.0.0.1:3000" + provider := utils.Config.Auth.External["azure"] + provider.Enabled = true + provider.RedirectUri = "https://example.com/custom/callback" + utils.Config.Auth.External["azure"] = provider + + env := envToMap(appendGotrueExternalProviderEnv(buildGotrueEnv(pgconn.Config{}))) + + assert.Equal(t, "http://127.0.0.1:54321/auth/v1", env["API_EXTERNAL_URL"]) + assert.Equal(t, "https://issuer.example.com/auth/v1", env["GOTRUE_JWT_ISSUER"]) + assert.Equal(t, "https://example.com/custom/callback", env["GOTRUE_EXTERNAL_AZURE_REDIRECT_URI"]) + }) +} + func TestFormatMapForEnvConfig(t *testing.T) { t.Run("It produces the correct format and removes the trailing comma", func(t *testing.T) { testcases := []struct { @@ -347,3 +401,14 @@ func TestFormatMapForEnvConfig(t *testing.T) { } }) } + +func envToMap(env []string) map[string]string { + result := make(map[string]string, len(env)) + for _, item := range env { + key, value, ok := strings.Cut(item, "=") + if ok { + result[key] = value + } + } + return result +} diff --git a/internal/start/templates/kong.yml b/internal/start/templates/kong.yml index 0c185eb6e4..4cbfc3b1eb 100644 --- a/internal/start/templates/kong.yml +++ b/internal/start/templates/kong.yml @@ -2,7 +2,7 @@ _format_version: "1.1" services: # Tenant project endpoints - name: auth-v1-open - _comment: "GoTrue: /auth/v1/verify* -> http://auth:9999/verify*" + _comment: "GoTrue external /auth/v1/verify* -> internal root /verify*" url: http://{{ .GotrueId }}:9999/verify routes: - name: auth-v1-open @@ -12,7 +12,7 @@ services: plugins: - name: cors - name: auth-v1-open-callback - _comment: "GoTrue: /auth/v1/callback* -> http://auth:9999/callback*" + _comment: "GoTrue external /auth/v1/callback* -> internal root /callback*" url: http://{{ .GotrueId }}:9999/callback routes: - name: auth-v1-open-callback @@ -22,7 +22,7 @@ services: plugins: - name: cors - name: auth-v1-open-authorize - _comment: "GoTrue: /auth/v1/authorize* -> http://auth:9999/authorize*" + _comment: "GoTrue external /auth/v1/authorize* -> internal root /authorize*" url: http://{{ .GotrueId }}:9999/authorize routes: - name: auth-v1-open-authorize @@ -32,7 +32,7 @@ services: plugins: - name: cors - name: auth-v1 - _comment: "GoTrue: /auth/v1/* -> http://auth:9999/*" + _comment: "GoTrue external /auth/v1/* -> internal root /*" url: http://{{ .GotrueId }}:9999/ routes: - name: auth-v1-all diff --git a/pkg/config/auth.go b/pkg/config/auth.go index c1795c8971..4517affb5b 100644 --- a/pkg/config/auth.go +++ b/pkg/config/auth.go @@ -150,6 +150,7 @@ type ( Image string `toml:"-" json:"-"` SiteUrl string `toml:"site_url" json:"site_url"` + ExternalUrl string `toml:"external_url" json:"external_url"` AdditionalRedirectUrls []string `toml:"additional_redirect_urls" json:"additional_redirect_urls"` JwtExpiry uint `toml:"jwt_expiry" json:"jwt_expiry"` JwtIssuer string `toml:"jwt_issuer" json:"jwt_issuer"` @@ -397,6 +398,13 @@ type ( } ) +func (a auth) GetExternalURL(apiExternalURL string) string { + if len(a.ExternalUrl) > 0 { + return a.ExternalUrl + } + return strings.TrimRight(apiExternalURL, "/") + "/auth/v1" +} + func (a *auth) ToUpdateAuthConfigBody() v1API.UpdateAuthConfigBody { body := v1API.UpdateAuthConfigBody{ SiteUrl: nullable.NewNullableWithValue(a.SiteUrl), diff --git a/pkg/config/config.go b/pkg/config/config.go index d17b894416..8e5a4b943f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -445,6 +445,10 @@ func NewConfig(editors ...ConfigEditor) config { return initial } +func (c *config) AuthExternalURL() string { + return c.Auth.GetExternalURL(c.Api.ExternalUrl) +} + var ( //go:embed templates/certs/kong.local.crt kongCert []byte @@ -632,9 +636,12 @@ func (c *config) Load(path string, fsys fs.FS, overrides ...ConfigEditor) error } c.Api.ExternalUrl = apiUrl.String() } + if len(c.Auth.ExternalUrl) == 0 { + c.Auth.ExternalUrl = c.AuthExternalURL() + } // Set default JWT issuer if not configured if len(c.Auth.JwtIssuer) == 0 { - c.Auth.JwtIssuer = c.Api.ExternalUrl + "/auth/v1" + c.Auth.JwtIssuer = c.Auth.ExternalUrl } // Update image versions switch c.Db.MajorVersion { diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index f019b7cbce..abd581d11e 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -39,14 +39,43 @@ func TestConfigParsing(t *testing.T) { assert.NoError(t, err) }) + t.Run("auth external url defaults from api external url", func(t *testing.T) { + config := NewConfig() + require.NoError(t, config.Load("", fs.MapFS{})) + + assert.Equal(t, strings.TrimRight(config.Api.ExternalUrl, "/")+"/auth/v1", config.Auth.ExternalUrl) + assert.Equal(t, config.Auth.ExternalUrl, config.Auth.JwtIssuer) + }) + + t.Run("auth external url and jwt issuer preserve explicit overrides", func(t *testing.T) { + config := NewConfig() + fsys := fs.MapFS{ + "config.toml": &fs.MapFile{Data: []byte(` +[api] +external_url = "https://api.example.com/" + +[auth] +site_url = "https://app.example.com" +external_url = "https://auth.example.com/custom/" +jwt_issuer = "https://issuer.example.com/custom/" +`)}, + } + + require.NoError(t, config.Load("config.toml", fsys)) + assert.Equal(t, "https://auth.example.com/custom/", config.Auth.ExternalUrl) + assert.Equal(t, "https://issuer.example.com/custom/", config.Auth.JwtIssuer) + }) + t.Run("config file with environment variables", func(t *testing.T) { config := NewConfig() // Setup in-memory fs fsys := fs.MapFS{ - "supabase/config.toml": &fs.MapFile{Data: testInitConfigEmbed}, - "supabase/templates/invite.html": &fs.MapFile{}, - "certs/my-cert.pem": &fs.MapFile{}, - "certs/my-key.pem": &fs.MapFile{}, + "supabase/config.toml": &fs.MapFile{Data: testInitConfigEmbed}, + "supabase/templates/invite.html": &fs.MapFile{}, + "supabase/templates/password_changed_notification.html": &fs.MapFile{}, + "supabase/signing_keys.json": &fs.MapFile{Data: []byte("[]")}, + "certs/my-cert.pem": &fs.MapFile{}, + "certs/my-key.pem": &fs.MapFile{}, } // Run test t.Setenv("TWILIO_AUTH_TOKEN", "token") diff --git a/pkg/config/templates/config.toml b/pkg/config/templates/config.toml index 97ed4e5665..d34c36c9ba 100644 --- a/pkg/config/templates/config.toml +++ b/pkg/config/templates/config.toml @@ -152,11 +152,13 @@ enabled = true # The base URL of your website. Used as an allow-list for redirects and for constructing URLs used # in emails. site_url = "http://127.0.0.1:3000" +# The public URL that Auth serves on. Defaults to the API external URL with `/auth/v1` appended. +# external_url = "" # A list of *exact* URLs that auth providers are permitted to redirect to post authentication. additional_redirect_urls = ["https://127.0.0.1:3000"] # How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). jwt_expiry = 3600 -# JWT issuer URL. If not set, defaults to the local API URL (http://127.0.0.1:/auth/v1). +# JWT issuer URL. If not set, defaults to auth.external_url. # jwt_issuer = "" # Path to JWT signing key. DO NOT commit your signing keys file to git. # signing_keys_path = "./signing_keys.json" @@ -317,7 +319,7 @@ enabled = false client_id = "" # DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)" -# Overrides the default auth redirectUrl. +# Overrides the default auth callback URL derived from auth.external_url. redirect_uri = "" # Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, # or any other third-party OIDC providers. diff --git a/pkg/config/testdata/TestAuthDiff/local_enabled_and_disabled.diff b/pkg/config/testdata/TestAuthDiff/local_enabled_and_disabled.diff index 5425d12659..21094fdd40 100644 --- a/pkg/config/testdata/TestAuthDiff/local_enabled_and_disabled.diff +++ b/pkg/config/testdata/TestAuthDiff/local_enabled_and_disabled.diff @@ -1,11 +1,16 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -1,14 +1,14 @@ +@@ -1,16 +1,16 @@ enabled = false -site_url = "" ++site_url = "http://127.0.0.1:3000" + external_url = "" -additional_redirect_urls = ["https://127.0.0.1:3000", "https://ref.supabase.co"] -jwt_expiry = 0 ++additional_redirect_urls = ["https://127.0.0.1:3000"] ++jwt_expiry = 3600 + jwt_issuer = "" -enable_refresh_token_rotation = true -refresh_token_reuse_interval = 0 -enable_manual_linking = true @@ -13,9 +18,6 @@ diff remote[auth] local[auth] -enable_anonymous_sign_ins = true -minimum_password_length = 8 -password_requirements = "letters_digits" -+site_url = "http://127.0.0.1:3000" -+additional_redirect_urls = ["https://127.0.0.1:3000"] -+jwt_expiry = 3600 +enable_refresh_token_rotation = false +refresh_token_reuse_interval = 10 +enable_manual_linking = false @@ -23,6 +25,6 @@ diff remote[auth] local[auth] +enable_anonymous_sign_ins = false +minimum_password_length = 6 +password_requirements = "lower_upper_letters_digits_symbols" - jwt_secret = "" - anon_key = "" - service_role_key = "" + signing_keys_path = "" + publishable_key = "" + secret_key = ""