package config import ( "os" "path/filepath" "testing" "time" "github.com/go-errors/errors" "github.com/stretchr/testify/assert" v1API "github.com/supabase/cli/pkg/api" "github.com/supabase/cli/pkg/cast" ) func newWithDefaults() auth { return auth{ EnableSignup: true, Email: email{ EnableConfirmations: true, }, Sms: sms{ TestOTP: map[string]string{}, }, } } func assertSnapshotEqual(t *testing.T, actual []byte) { snapshot := filepath.Join("testdata", filepath.FromSlash(t.Name())) + ".diff" expected, err := os.ReadFile(snapshot) if errors.Is(err, os.ErrNotExist) { assert.NoError(t, os.MkdirAll(filepath.Dir(snapshot), 0755)) assert.NoError(t, os.WriteFile(snapshot, actual, 0600)) } assert.Equal(t, string(expected), string(actual)) } func TestAuthDiff(t *testing.T) { t.Run("local and remote enabled", func(t *testing.T) { c := newWithDefaults() c.SiteUrl = "http://127.0.0.1:3000" c.AdditionalRedirectUrls = []string{"https://127.0.0.1:3000"} c.JwtExpiry = 3600 c.EnableRefreshTokenRotation = true c.RefreshTokenReuseInterval = 10 c.EnableManualLinking = true c.EnableSignup = true c.EnableAnonymousSignIns = true c.MinimumPasswordLength = 6 c.PasswordRequirements = LettersDigits // Run test 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), RefreshTokenRotationEnabled: cast.Ptr(true), SecurityRefreshTokenReuseInterval: cast.Ptr(10), SecurityManualLinkingEnabled: cast.Ptr(true), DisableSignup: cast.Ptr(false), ExternalAnonymousUsersEnabled: cast.Ptr(true), PasswordMinLength: cast.Ptr(6), PasswordRequiredCharacters: cast.Ptr(string(v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789)), }) // Check error assert.NoError(t, err) assert.Empty(t, string(diff)) }) t.Run("local enabled and disabled", func(t *testing.T) { c := newWithDefaults() c.SiteUrl = "http://127.0.0.1:3000" c.AdditionalRedirectUrls = []string{"https://127.0.0.1:3000"} c.JwtExpiry = 3600 c.EnableRefreshTokenRotation = false c.RefreshTokenReuseInterval = 10 c.EnableManualLinking = false c.EnableSignup = false c.EnableAnonymousSignIns = false c.MinimumPasswordLength = 6 c.PasswordRequirements = LowerUpperLettersDigitsSymbols // Run test 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), RefreshTokenRotationEnabled: cast.Ptr(true), SecurityRefreshTokenReuseInterval: cast.Ptr(0), SecurityManualLinkingEnabled: cast.Ptr(true), DisableSignup: cast.Ptr(false), ExternalAnonymousUsersEnabled: cast.Ptr(true), PasswordMinLength: cast.Ptr(8), PasswordRequiredCharacters: cast.Ptr(string(v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789)), }) // Check error assert.NoError(t, err) assertSnapshotEqual(t, diff) }) t.Run("local and remote disabled", func(t *testing.T) { c := newWithDefaults() c.EnableSignup = false // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ SiteUrl: cast.Ptr(""), UriAllowList: cast.Ptr(""), JwtExp: cast.Ptr(0), RefreshTokenRotationEnabled: cast.Ptr(false), SecurityRefreshTokenReuseInterval: cast.Ptr(0), SecurityManualLinkingEnabled: cast.Ptr(false), DisableSignup: cast.Ptr(true), ExternalAnonymousUsersEnabled: cast.Ptr(false), PasswordMinLength: cast.Ptr(0), PasswordRequiredCharacters: cast.Ptr(""), }) // Check error assert.NoError(t, err) assert.Empty(t, string(diff)) }) } func TestCaptchaDiff(t *testing.T) { t.Run("local and remote enabled", func(t *testing.T) { c := newWithDefaults() c.Captcha = &captcha{ Enabled: true, Provider: HCaptchaProvider, Secret: Secret{ Value: "test-secret", SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", }, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ SecurityCaptchaEnabled: cast.Ptr(true), SecurityCaptchaProvider: cast.Ptr("hcaptcha"), SecurityCaptchaSecret: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), }) // Check error assert.NoError(t, err) assert.Empty(t, string(diff)) }) t.Run("local disabled remote enabled", func(t *testing.T) { c := newWithDefaults() c.Captcha = &captcha{ Enabled: false, Provider: TurnstileProvider, Secret: Secret{ Value: "test-key", SHA256: "ed64b7695a606bc6ab4fcb41fe815b5ddf1063ccbc87afe1fa89756635db520e", }, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ SecurityCaptchaEnabled: cast.Ptr(true), SecurityCaptchaProvider: cast.Ptr("hcaptcha"), SecurityCaptchaSecret: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), }) // Check error assert.NoError(t, err) assertSnapshotEqual(t, diff) }) t.Run("local enabled remote disabled", func(t *testing.T) { c := newWithDefaults() c.Captcha = &captcha{ Enabled: true, Provider: TurnstileProvider, Secret: Secret{ Value: "test-key", SHA256: "ed64b7695a606bc6ab4fcb41fe815b5ddf1063ccbc87afe1fa89756635db520e", }, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ SecurityCaptchaEnabled: cast.Ptr(false), SecurityCaptchaProvider: cast.Ptr("hcaptcha"), SecurityCaptchaSecret: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), }) // Check error assert.NoError(t, err) assertSnapshotEqual(t, diff) }) t.Run("local and remote disabled", func(t *testing.T) { c := newWithDefaults() c.Captcha = &captcha{ Enabled: false, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ SecurityCaptchaEnabled: cast.Ptr(false), }) // Check error assert.NoError(t, err) assert.Empty(t, string(diff)) }) t.Run("ignores undefined config", func(t *testing.T) { c := newWithDefaults() // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ SecurityCaptchaEnabled: cast.Ptr(true), SecurityCaptchaProvider: cast.Ptr("hcaptcha"), SecurityCaptchaSecret: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), }) // Check error assert.NoError(t, err) assert.Empty(t, string(diff)) }) } func TestHookDiff(t *testing.T) { t.Run("local and remote enabled", func(t *testing.T) { c := newWithDefaults() c.Hook = hook{ CustomAccessToken: &hookConfig{ Enabled: true, URI: "http://example.com", Secrets: Secret{ Value: "test-secret", SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", }, }, SendSMS: &hookConfig{ Enabled: true, URI: "http://example.com", Secrets: Secret{ Value: "test-secret", SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", }, }, SendEmail: &hookConfig{ Enabled: true, URI: "https://example.com", Secrets: Secret{ Value: "test-secret", SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", }, }, MFAVerificationAttempt: &hookConfig{ Enabled: true, URI: "https://example.com", Secrets: Secret{ Value: "test-secret", SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", }, }, PasswordVerificationAttempt: &hookConfig{ Enabled: true, URI: "pg-functions://verifyPassword", }, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ HookCustomAccessTokenEnabled: cast.Ptr(true), HookCustomAccessTokenUri: cast.Ptr("http://example.com"), HookCustomAccessTokenSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), HookSendSmsEnabled: cast.Ptr(true), HookSendSmsUri: cast.Ptr("http://example.com"), HookSendSmsSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), HookSendEmailEnabled: cast.Ptr(true), HookSendEmailUri: cast.Ptr("https://example.com"), HookSendEmailSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), HookMfaVerificationAttemptEnabled: cast.Ptr(true), HookMfaVerificationAttemptUri: cast.Ptr("https://example.com"), HookMfaVerificationAttemptSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), HookPasswordVerificationAttemptEnabled: cast.Ptr(true), HookPasswordVerificationAttemptUri: cast.Ptr("pg-functions://verifyPassword"), }) // Check error assert.NoError(t, err) assert.Empty(t, string(diff)) }) t.Run("local disabled remote enabled", func(t *testing.T) { c := newWithDefaults() c.Hook = hook{ CustomAccessToken: &hookConfig{ Enabled: false, }, SendSMS: &hookConfig{ Enabled: false, URI: "https://example.com", Secrets: Secret{Value: "test-secret"}, }, SendEmail: &hookConfig{ Enabled: false, }, MFAVerificationAttempt: &hookConfig{ Enabled: false, URI: "pg-functions://postgres/public/verifyMFA", }, PasswordVerificationAttempt: nil, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ HookCustomAccessTokenEnabled: cast.Ptr(true), HookCustomAccessTokenUri: cast.Ptr("http://example.com"), HookCustomAccessTokenSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), HookSendSmsEnabled: cast.Ptr(true), HookSendSmsUri: cast.Ptr("https://example.com"), HookSendSmsSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), HookSendEmailEnabled: cast.Ptr(true), HookSendEmailUri: cast.Ptr("pg-functions://postgres/public/sendEmail"), HookMfaVerificationAttemptEnabled: cast.Ptr(true), HookMfaVerificationAttemptUri: cast.Ptr("pg-functions://postgres/public/verifyMFA"), HookPasswordVerificationAttemptEnabled: cast.Ptr(true), HookPasswordVerificationAttemptUri: cast.Ptr("https://example.com"), HookPasswordVerificationAttemptSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), }) // Check error assert.NoError(t, err) assertSnapshotEqual(t, diff) }) t.Run("local enabled remote disabled", func(t *testing.T) { c := newWithDefaults() c.Hook = hook{ CustomAccessToken: &hookConfig{ Enabled: true, URI: "http://example.com", Secrets: Secret{ Value: "test-secret", SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", }, }, SendSMS: &hookConfig{ Enabled: true, URI: "https://example.com", Secrets: Secret{ Value: "test-secret", SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", }, }, SendEmail: &hookConfig{ Enabled: true, URI: "pg-functions://postgres/public/sendEmail", }, MFAVerificationAttempt: &hookConfig{ Enabled: true, URI: "pg-functions://postgres/public/verifyMFA", }, PasswordVerificationAttempt: nil, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ HookCustomAccessTokenEnabled: cast.Ptr(false), HookCustomAccessTokenUri: cast.Ptr("pg-functions://postgres/public/customToken"), HookSendSmsEnabled: cast.Ptr(false), HookSendSmsUri: cast.Ptr("https://example.com"), HookSendSmsSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), HookSendEmailEnabled: cast.Ptr(false), HookSendEmailUri: cast.Ptr("https://example.com"), HookSendEmailSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), HookMfaVerificationAttemptEnabled: cast.Ptr(false), HookMfaVerificationAttemptUri: cast.Ptr("pg-functions://postgres/public/verifyMFA"), HookPasswordVerificationAttemptEnabled: cast.Ptr(false), }) // Check error assert.NoError(t, err) assertSnapshotEqual(t, diff) }) t.Run("local and remote disabled", func(t *testing.T) { c := newWithDefaults() c.Hook = hook{ CustomAccessToken: &hookConfig{Enabled: false}, SendSMS: &hookConfig{Enabled: false}, SendEmail: &hookConfig{Enabled: false}, MFAVerificationAttempt: &hookConfig{Enabled: false}, PasswordVerificationAttempt: &hookConfig{Enabled: false}, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ HookCustomAccessTokenEnabled: cast.Ptr(false), HookSendSmsEnabled: cast.Ptr(false), HookSendEmailEnabled: cast.Ptr(false), HookMfaVerificationAttemptEnabled: cast.Ptr(false), HookPasswordVerificationAttemptEnabled: cast.Ptr(false), }) // Check error assert.NoError(t, err) assert.Empty(t, string(diff)) }) } func TestMfaDiff(t *testing.T) { t.Run("local and remote enabled", func(t *testing.T) { c := newWithDefaults() c.MFA = mfa{ TOTP: factorTypeConfiguration{ EnrollEnabled: true, VerifyEnabled: true, }, Phone: phoneFactorTypeConfiguration{ factorTypeConfiguration: factorTypeConfiguration{ EnrollEnabled: true, VerifyEnabled: true, }, OtpLength: 6, Template: "Your code is {{ .Code }}", MaxFrequency: 5 * time.Second, }, WebAuthn: factorTypeConfiguration{ EnrollEnabled: true, VerifyEnabled: true, }, MaxEnrolledFactors: 10, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ MfaMaxEnrolledFactors: cast.Ptr(10), MfaTotpEnrollEnabled: cast.Ptr(true), MfaTotpVerifyEnabled: cast.Ptr(true), MfaPhoneEnrollEnabled: cast.Ptr(true), MfaPhoneVerifyEnabled: cast.Ptr(true), MfaPhoneOtpLength: 6, MfaPhoneTemplate: cast.Ptr("Your code is {{ .Code }}"), MfaPhoneMaxFrequency: cast.Ptr(5), MfaWebAuthnEnrollEnabled: cast.Ptr(true), MfaWebAuthnVerifyEnabled: cast.Ptr(true), }) // Check error assert.NoError(t, err) assert.Empty(t, string(diff)) }) t.Run("local enabled and disabled", func(t *testing.T) { c := newWithDefaults() c.MFA = mfa{ TOTP: factorTypeConfiguration{ EnrollEnabled: false, VerifyEnabled: false, }, Phone: phoneFactorTypeConfiguration{ factorTypeConfiguration: factorTypeConfiguration{ EnrollEnabled: true, VerifyEnabled: true, }, }, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ MfaMaxEnrolledFactors: cast.Ptr(10), MfaTotpEnrollEnabled: cast.Ptr(false), MfaTotpVerifyEnabled: cast.Ptr(false), MfaPhoneEnrollEnabled: cast.Ptr(false), MfaPhoneVerifyEnabled: cast.Ptr(false), MfaPhoneOtpLength: 6, MfaPhoneTemplate: cast.Ptr("Your code is {{ .Code }}"), MfaPhoneMaxFrequency: cast.Ptr(5), MfaWebAuthnEnrollEnabled: cast.Ptr(false), MfaWebAuthnVerifyEnabled: cast.Ptr(false), }) // Check error assert.NoError(t, err) assertSnapshotEqual(t, diff) }) t.Run("local and remote disabled", func(t *testing.T) { c := newWithDefaults() c.MFA = mfa{ MaxEnrolledFactors: 10, Phone: phoneFactorTypeConfiguration{ OtpLength: 6, Template: "Your code is {{ .Code }}", MaxFrequency: 5 * time.Second, }, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ MfaMaxEnrolledFactors: cast.Ptr(10), MfaTotpEnrollEnabled: cast.Ptr(false), MfaTotpVerifyEnabled: cast.Ptr(false), MfaPhoneEnrollEnabled: cast.Ptr(false), MfaPhoneVerifyEnabled: cast.Ptr(false), MfaPhoneOtpLength: 6, MfaPhoneTemplate: cast.Ptr("Your code is {{ .Code }}"), MfaPhoneMaxFrequency: cast.Ptr(5), MfaWebAuthnEnrollEnabled: cast.Ptr(false), MfaWebAuthnVerifyEnabled: cast.Ptr(false), }) // Check error assert.NoError(t, err) assert.Empty(t, string(diff)) }) } func TestEmailDiff(t *testing.T) { t.Run("local enabled remote enabled", func(t *testing.T) { c := newWithDefaults() c.Email = email{ EnableSignup: true, DoubleConfirmChanges: true, EnableConfirmations: true, SecurePasswordChange: true, Template: map[string]emailTemplate{ "invite": { Subject: cast.Ptr("invite-subject"), Content: cast.Ptr("invite-content"), }, "confirmation": { Subject: cast.Ptr("confirmation-subject"), Content: cast.Ptr("confirmation-content"), }, "recovery": { Subject: cast.Ptr("recovery-subject"), Content: cast.Ptr("recovery-content"), }, "magic_link": { Subject: cast.Ptr("magic-link-subject"), Content: cast.Ptr("magic-link-content"), }, "email_change": { Subject: cast.Ptr("email-change-subject"), Content: cast.Ptr("email-change-content"), }, "reauthentication": { Subject: cast.Ptr("reauthentication-subject"), Content: cast.Ptr("reauthentication-content"), }, }, Smtp: &smtp{ Enabled: true, Host: "smtp.sendgrid.net", Port: 587, User: "apikey", Pass: Secret{ Value: "test-key", SHA256: "ed64b7695a606bc6ab4fcb41fe815b5ddf1063ccbc87afe1fa89756635db520e", }, AdminEmail: "admin@email.com", SenderName: "Admin", }, MaxFrequency: time.Second, OtpLength: 6, OtpExpiry: 3600, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalEmailEnabled: cast.Ptr(true), MailerSecureEmailChangeEnabled: cast.Ptr(true), MailerAutoconfirm: cast.Ptr(false), MailerOtpLength: cast.Ptr(6), MailerOtpExp: 3600, SecurityUpdatePasswordRequireReauthentication: cast.Ptr(true), SmtpHost: cast.Ptr("smtp.sendgrid.net"), SmtpPort: cast.Ptr("587"), SmtpUser: cast.Ptr("apikey"), SmtpPass: cast.Ptr("ed64b7695a606bc6ab4fcb41fe815b5ddf1063ccbc87afe1fa89756635db520e"), SmtpAdminEmail: cast.Ptr("admin@email.com"), SmtpSenderName: cast.Ptr("Admin"), SmtpMaxFrequency: cast.Ptr(1), // Custom templates MailerSubjectsInvite: cast.Ptr("invite-subject"), MailerTemplatesInviteContent: cast.Ptr("invite-content"), MailerSubjectsConfirmation: cast.Ptr("confirmation-subject"), MailerTemplatesConfirmationContent: cast.Ptr("confirmation-content"), MailerSubjectsRecovery: cast.Ptr("recovery-subject"), MailerTemplatesRecoveryContent: cast.Ptr("recovery-content"), MailerSubjectsMagicLink: cast.Ptr("magic-link-subject"), MailerTemplatesMagicLinkContent: cast.Ptr("magic-link-content"), MailerSubjectsEmailChange: cast.Ptr("email-change-subject"), MailerTemplatesEmailChangeContent: cast.Ptr("email-change-content"), MailerSubjectsReauthentication: cast.Ptr("reauthentication-subject"), MailerTemplatesReauthenticationContent: cast.Ptr("reauthentication-content"), }) // Check error assert.NoError(t, err) assert.Empty(t, string(diff)) }) t.Run("local enabled remote disabled", func(t *testing.T) { c := newWithDefaults() c.Email = email{ EnableSignup: true, DoubleConfirmChanges: true, EnableConfirmations: true, SecurePasswordChange: true, Template: map[string]emailTemplate{ "invite": { Subject: cast.Ptr("invite-subject"), Content: cast.Ptr("invite-content"), }, "confirmation": { Subject: cast.Ptr("confirmation-subject"), }, "recovery": { Content: cast.Ptr("recovery-content"), }, "magic_link": { Subject: cast.Ptr("magic-link-subject"), Content: cast.Ptr("magic-link-content"), }, "email_change": { Subject: cast.Ptr("email-change-subject"), Content: cast.Ptr("email-change-content"), }, "reauthentication": { Subject: cast.Ptr(""), Content: cast.Ptr(""), }, }, Smtp: &smtp{ Enabled: true, Host: "smtp.sendgrid.net", Port: 587, User: "apikey", Pass: Secret{ Value: "test-key", SHA256: "ed64b7695a606bc6ab4fcb41fe815b5ddf1063ccbc87afe1fa89756635db520e", }, AdminEmail: "admin@email.com", SenderName: "Admin", }, MaxFrequency: time.Second, OtpLength: 8, OtpExpiry: 86400, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalEmailEnabled: cast.Ptr(false), MailerSecureEmailChangeEnabled: cast.Ptr(false), MailerAutoconfirm: cast.Ptr(true), MailerOtpLength: cast.Ptr(6), MailerOtpExp: 3600, SecurityUpdatePasswordRequireReauthentication: cast.Ptr(false), SmtpMaxFrequency: cast.Ptr(60), // Custom templates MailerTemplatesConfirmationContent: cast.Ptr("confirmation-content"), MailerSubjectsRecovery: cast.Ptr("recovery-subject"), MailerSubjectsMagicLink: cast.Ptr("magic-link-subject"), MailerTemplatesEmailChangeContent: cast.Ptr("email-change-content"), }) // Check error assert.NoError(t, err) assertSnapshotEqual(t, diff) }) t.Run("local disabled remote enabled", func(t *testing.T) { c := newWithDefaults() c.Email = email{ EnableConfirmations: false, Template: map[string]emailTemplate{ "invite": {}, "confirmation": {}, "recovery": {}, "magic_link": {}, "email_change": {}, "reauthentication": {}, }, MaxFrequency: time.Minute, OtpLength: 8, OtpExpiry: 86400, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalEmailEnabled: cast.Ptr(true), MailerSecureEmailChangeEnabled: cast.Ptr(true), MailerAutoconfirm: cast.Ptr(false), MailerOtpLength: cast.Ptr(6), MailerOtpExp: 3600, SecurityUpdatePasswordRequireReauthentication: cast.Ptr(true), SmtpHost: cast.Ptr("smtp.sendgrid.net"), SmtpPort: cast.Ptr("587"), SmtpUser: cast.Ptr("apikey"), SmtpPass: cast.Ptr("ed64b7695a606bc6ab4fcb41fe815b5ddf1063ccbc87afe1fa89756635db520e"), SmtpAdminEmail: cast.Ptr("admin@email.com"), SmtpSenderName: cast.Ptr("Admin"), SmtpMaxFrequency: cast.Ptr(1), // Custom templates MailerSubjectsInvite: cast.Ptr("invite-subject"), MailerTemplatesInviteContent: cast.Ptr("invite-content"), MailerSubjectsConfirmation: cast.Ptr("confirmation-subject"), MailerTemplatesConfirmationContent: cast.Ptr("confirmation-content"), MailerSubjectsRecovery: cast.Ptr("recovery-subject"), MailerTemplatesRecoveryContent: cast.Ptr("recovery-content"), MailerSubjectsMagicLink: cast.Ptr("magic-link-subject"), MailerTemplatesMagicLinkContent: cast.Ptr("magic-link-content"), MailerSubjectsEmailChange: cast.Ptr("email-change-subject"), MailerTemplatesEmailChangeContent: cast.Ptr("email-change-content"), MailerSubjectsReauthentication: cast.Ptr("reauthentication-subject"), MailerTemplatesReauthenticationContent: cast.Ptr("reauthentication-content"), }) // Check error assert.NoError(t, err) assertSnapshotEqual(t, diff) }) t.Run("local disabled remote disabled", func(t *testing.T) { c := newWithDefaults() c.Email = email{ EnableConfirmations: false, Template: map[string]emailTemplate{ "invite": {}, "confirmation": {}, "recovery": {}, "magic_link": {}, "email_change": {}, "reauthentication": {}, }, Smtp: &smtp{ Enabled: false, Host: "smtp.sendgrid.net", Port: 587, User: "apikey", Pass: Secret{ Value: "test-key", SHA256: "ed64b7695a606bc6ab4fcb41fe815b5ddf1063ccbc87afe1fa89756635db520e", }, AdminEmail: "admin@email.com", SenderName: "Admin", }, MaxFrequency: time.Minute, OtpLength: 6, OtpExpiry: 3600, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalEmailEnabled: cast.Ptr(false), MailerSecureEmailChangeEnabled: cast.Ptr(false), MailerAutoconfirm: cast.Ptr(true), MailerOtpLength: cast.Ptr(6), MailerOtpExp: 3600, SecurityUpdatePasswordRequireReauthentication: cast.Ptr(false), SmtpMaxFrequency: cast.Ptr(60), }) // Check error assert.NoError(t, err) assert.Empty(t, string(diff)) }) } func TestSmsDiff(t *testing.T) { t.Run("local enabled remote enabled", func(t *testing.T) { c := newWithDefaults() c.Sms = sms{ EnableSignup: true, EnableConfirmations: true, Template: "Your code is {{ .Code }}", TestOTP: map[string]string{"123": "456"}, MaxFrequency: time.Minute, Twilio: twilioConfig{ Enabled: true, AccountSid: "test-account", MessageServiceSid: "test-service", AuthToken: Secret{ Value: "test-token", SHA256: "c84443bc59b92caef8ec8500ff443584793756749523811eb333af2bbc74fc88", }, }, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalPhoneEnabled: cast.Ptr(true), SmsAutoconfirm: cast.Ptr(true), SmsMaxFrequency: cast.Ptr(60), SmsOtpExp: cast.Ptr(3600), SmsOtpLength: 6, SmsProvider: cast.Ptr("twilio"), SmsTemplate: cast.Ptr("Your code is {{ .Code }}"), SmsTestOtp: cast.Ptr("123=456"), SmsTestOtpValidUntil: cast.Ptr("2050-01-01T01:00:00Z"), SmsTwilioAccountSid: cast.Ptr("test-account"), SmsTwilioAuthToken: cast.Ptr("c84443bc59b92caef8ec8500ff443584793756749523811eb333af2bbc74fc88"), SmsTwilioContentSid: cast.Ptr("test-content"), SmsTwilioMessageServiceSid: cast.Ptr("test-service"), // Extra configs returned from api can be ignored SmsMessagebirdAccessKey: cast.Ptr("test-messagebird-key"), SmsMessagebirdOriginator: cast.Ptr("test-messagebird-originator"), SmsTextlocalApiKey: cast.Ptr("test-textlocal-key"), SmsTextlocalSender: cast.Ptr("test-textlocal-sencer"), SmsTwilioVerifyAccountSid: cast.Ptr("test-verify-account"), SmsTwilioVerifyAuthToken: cast.Ptr("test-verify-token"), SmsTwilioVerifyMessageServiceSid: cast.Ptr("test-verify-service"), SmsVonageApiKey: cast.Ptr("test-vonage-key"), SmsVonageApiSecret: cast.Ptr("test-vonage-secret"), SmsVonageFrom: cast.Ptr("test-vonage-from"), }) // Check error assert.NoError(t, err) assert.Empty(t, string(diff)) }) t.Run("local disabled remote enabled", func(t *testing.T) { c := newWithDefaults() // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalPhoneEnabled: cast.Ptr(true), SmsAutoconfirm: cast.Ptr(true), SmsMaxFrequency: cast.Ptr(60), SmsOtpExp: cast.Ptr(3600), SmsOtpLength: 6, SmsProvider: cast.Ptr("twilio"), SmsTemplate: cast.Ptr("Your code is {{ .Code }}"), SmsTestOtp: cast.Ptr("123=456,456=123"), SmsTestOtpValidUntil: cast.Ptr("2050-01-01T01:00:00Z"), SmsTwilioAccountSid: cast.Ptr("test-account"), SmsTwilioAuthToken: cast.Ptr("c84443bc59b92caef8ec8500ff443584793756749523811eb333af2bbc74fc88"), SmsTwilioContentSid: cast.Ptr("test-content"), SmsTwilioMessageServiceSid: cast.Ptr("test-service"), }) // Check error assert.NoError(t, err) assertSnapshotEqual(t, diff) }) t.Run("local enabled remote disabled", func(t *testing.T) { c := newWithDefaults() c.Sms = sms{ EnableSignup: true, EnableConfirmations: true, Template: "Your code is {{ .Code }}", TestOTP: map[string]string{"123": "456"}, MaxFrequency: time.Minute, Messagebird: messagebirdConfig{ Enabled: true, Originator: "test-originator", AccessKey: Secret{ Value: "test-access-key", SHA256: "ab60d03fc809fb02dae838582f3ddc13d1d6cb32ffba77c4b969dd3caa496f13", }, }, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalPhoneEnabled: cast.Ptr(false), SmsAutoconfirm: cast.Ptr(false), SmsMaxFrequency: cast.Ptr(0), SmsOtpExp: cast.Ptr(3600), SmsOtpLength: 6, SmsProvider: cast.Ptr("twilio"), SmsTemplate: cast.Ptr(""), SmsTwilioAccountSid: cast.Ptr("test-account"), SmsTwilioAuthToken: cast.Ptr("c84443bc59b92caef8ec8500ff443584793756749523811eb333af2bbc74fc88"), SmsTwilioContentSid: cast.Ptr("test-content"), SmsTwilioMessageServiceSid: cast.Ptr("test-service"), }) // Check error assert.NoError(t, err) assertSnapshotEqual(t, diff) }) t.Run("local disabled remote disabled", func(t *testing.T) { c := newWithDefaults() c.Sms = sms{ EnableSignup: false, EnableConfirmations: true, Template: "Your code is {{ .Code }}", TestOTP: map[string]string{"123": "456"}, MaxFrequency: time.Minute, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalPhoneEnabled: cast.Ptr(false), SmsAutoconfirm: cast.Ptr(true), SmsMaxFrequency: cast.Ptr(60), SmsOtpExp: cast.Ptr(3600), SmsOtpLength: 6, SmsTemplate: cast.Ptr("Your code is {{ .Code }}"), SmsTestOtp: cast.Ptr("123=456"), SmsTestOtpValidUntil: cast.Ptr("2050-01-01T01:00:00Z"), SmsProvider: cast.Ptr("messagebird"), SmsMessagebirdAccessKey: cast.Ptr("test-messagebird-key"), SmsMessagebirdOriginator: cast.Ptr("test-messagebird-originator"), }) // Check error assert.NoError(t, err) assert.Empty(t, string(diff)) }) t.Run("enable sign up without provider", func(t *testing.T) { // This is not a valid config because platform requires a SMS provider. // For consistency, we handle this in config.Load and emit a warning. c := newWithDefaults() c.Sms.EnableSignup = true // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalPhoneEnabled: cast.Ptr(false), SmsProvider: cast.Ptr("twilio"), }) // Check error assert.NoError(t, err) assertSnapshotEqual(t, diff) }) t.Run("enable provider without sign up", func(t *testing.T) { c := newWithDefaults() c.Sms.Messagebird.Enabled = true // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalPhoneEnabled: cast.Ptr(false), SmsProvider: cast.Ptr("messagebird"), SmsMessagebirdAccessKey: cast.Ptr(""), }) // Check error assert.NoError(t, err) assert.Empty(t, string(diff)) }) } func TestExternalDiff(t *testing.T) { t.Run("local and remote enabled", func(t *testing.T) { c := newWithDefaults() c.External = map[string]provider{ "apple": {Enabled: true}, "azure": {Enabled: true}, "bitbucket": {Enabled: true}, "discord": {Enabled: true}, "facebook": {Enabled: true}, "figma": {Enabled: true}, "github": {Enabled: true}, "gitlab": {Enabled: true}, "google": {Enabled: true}, "kakao": {Enabled: true}, "keycloak": {Enabled: true}, "linkedin_oidc": {Enabled: true}, "notion": {Enabled: true}, "slack_oidc": {Enabled: true}, "spotify": {Enabled: true}, "twitch": {Enabled: true}, "twitter": {Enabled: true}, "workos": {Enabled: true}, "zoom": {Enabled: true}, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalAppleAdditionalClientIds: cast.Ptr(""), ExternalAppleClientId: cast.Ptr(""), ExternalAppleEnabled: cast.Ptr(true), ExternalAppleSecret: cast.Ptr(""), ExternalAzureClientId: cast.Ptr(""), ExternalAzureEnabled: cast.Ptr(true), ExternalAzureSecret: cast.Ptr(""), ExternalAzureUrl: cast.Ptr(""), ExternalBitbucketClientId: cast.Ptr(""), ExternalBitbucketEnabled: cast.Ptr(true), ExternalBitbucketSecret: cast.Ptr(""), ExternalDiscordClientId: cast.Ptr(""), ExternalDiscordEnabled: cast.Ptr(true), ExternalDiscordSecret: cast.Ptr(""), ExternalFacebookClientId: cast.Ptr(""), ExternalFacebookEnabled: cast.Ptr(true), ExternalFacebookSecret: cast.Ptr(""), ExternalFigmaClientId: cast.Ptr(""), ExternalFigmaEnabled: cast.Ptr(true), ExternalFigmaSecret: cast.Ptr(""), ExternalGithubClientId: cast.Ptr(""), ExternalGithubEnabled: cast.Ptr(true), ExternalGithubSecret: cast.Ptr(""), ExternalGitlabClientId: cast.Ptr(""), ExternalGitlabEnabled: cast.Ptr(true), ExternalGitlabSecret: cast.Ptr(""), ExternalGitlabUrl: cast.Ptr(""), ExternalGoogleAdditionalClientIds: cast.Ptr(""), ExternalGoogleClientId: cast.Ptr(""), ExternalGoogleEnabled: cast.Ptr(true), ExternalGoogleSecret: cast.Ptr(""), ExternalGoogleSkipNonceCheck: cast.Ptr(false), ExternalKakaoClientId: cast.Ptr(""), ExternalKakaoEnabled: cast.Ptr(true), ExternalKakaoSecret: cast.Ptr(""), ExternalKeycloakClientId: cast.Ptr(""), ExternalKeycloakEnabled: cast.Ptr(true), ExternalKeycloakSecret: cast.Ptr(""), ExternalKeycloakUrl: cast.Ptr(""), ExternalLinkedinOidcClientId: cast.Ptr(""), ExternalLinkedinOidcEnabled: cast.Ptr(true), ExternalLinkedinOidcSecret: cast.Ptr(""), ExternalNotionClientId: cast.Ptr(""), ExternalNotionEnabled: cast.Ptr(true), ExternalNotionSecret: cast.Ptr(""), ExternalSlackOidcClientId: cast.Ptr(""), ExternalSlackOidcEnabled: cast.Ptr(true), ExternalSlackOidcSecret: cast.Ptr(""), ExternalSpotifyClientId: cast.Ptr(""), ExternalSpotifyEnabled: cast.Ptr(true), ExternalSpotifySecret: cast.Ptr(""), ExternalTwitchClientId: cast.Ptr(""), ExternalTwitchEnabled: cast.Ptr(true), ExternalTwitchSecret: cast.Ptr(""), ExternalTwitterClientId: cast.Ptr(""), ExternalTwitterEnabled: cast.Ptr(true), ExternalTwitterSecret: cast.Ptr(""), ExternalWorkosClientId: cast.Ptr(""), ExternalWorkosEnabled: cast.Ptr(true), ExternalWorkosSecret: cast.Ptr(""), ExternalWorkosUrl: cast.Ptr(""), ExternalZoomClientId: cast.Ptr(""), ExternalZoomEnabled: cast.Ptr(true), ExternalZoomSecret: cast.Ptr(""), // Deprecated fields should be ignored ExternalSlackClientId: cast.Ptr(""), ExternalSlackEnabled: cast.Ptr(true), ExternalSlackSecret: cast.Ptr(""), }) // Check error assert.NoError(t, err) assert.Empty(t, string(diff)) }) t.Run("local enabled and disabled", func(t *testing.T) { c := newWithDefaults() c.External = map[string]provider{ "apple": { Enabled: true, ClientId: "test-client-1,test-client-2", Secret: Secret{ Value: "test-secret", SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", }, }, "azure": { Enabled: true, ClientId: "test-client-1", Secret: Secret{ Value: "test-secret", SHA256: "ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252", }, }, "bitbucket": {}, "discord": {}, "facebook": {}, "figma": {}, "github": {}, "gitlab": {}, "google": { Enabled: false, ClientId: "test-client-2", Secret: Secret{Value: "env(test_secret)"}, SkipNonceCheck: false, }, // "kakao": {}, "keycloak": {}, "linkedin_oidc": {}, "notion": {}, "slack_oidc": {}, "spotify": {}, "twitch": {}, "twitter": {}, "workos": {}, "zoom": {}, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalAppleAdditionalClientIds: cast.Ptr("test-client-2"), ExternalAppleClientId: cast.Ptr("test-client-1"), ExternalAppleEnabled: cast.Ptr(false), ExternalAppleSecret: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), ExternalGoogleAdditionalClientIds: cast.Ptr("test-client-2"), ExternalGoogleClientId: cast.Ptr("test-client-1"), ExternalGoogleEnabled: cast.Ptr(true), ExternalGoogleSecret: cast.Ptr("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"), ExternalGoogleSkipNonceCheck: cast.Ptr(true), ExternalKakaoClientId: cast.Ptr("test-client-2"), ExternalKakaoEnabled: cast.Ptr(true), ExternalKakaoSecret: cast.Ptr("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"), }) // Check error assert.NoError(t, err) assertSnapshotEqual(t, diff) }) t.Run("local and remote disabled", func(t *testing.T) { c := newWithDefaults() c.External = map[string]provider{ "apple": {}, "azure": {}, "bitbucket": {}, "discord": {}, "facebook": {}, "figma": {}, "github": {}, "gitlab": {}, "google": {}, "kakao": {}, "keycloak": {}, "linkedin_oidc": {}, "notion": {}, "slack_oidc": {}, "spotify": {}, "twitch": {}, "twitter": {}, "workos": {}, "zoom": {}, } // Run test diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ ExternalAppleEnabled: cast.Ptr(false), ExternalAzureEnabled: cast.Ptr(false), ExternalBitbucketEnabled: cast.Ptr(false), ExternalDiscordEnabled: cast.Ptr(false), ExternalFacebookEnabled: cast.Ptr(false), ExternalFigmaEnabled: cast.Ptr(false), ExternalGithubEnabled: cast.Ptr(false), ExternalGitlabEnabled: cast.Ptr(false), ExternalGoogleEnabled: cast.Ptr(false), ExternalGoogleSkipNonceCheck: cast.Ptr(false), ExternalKakaoEnabled: cast.Ptr(false), ExternalKeycloakEnabled: cast.Ptr(false), ExternalLinkedinOidcEnabled: cast.Ptr(false), ExternalNotionEnabled: cast.Ptr(false), ExternalSlackOidcEnabled: cast.Ptr(false), ExternalSpotifyEnabled: cast.Ptr(false), ExternalTwitchEnabled: cast.Ptr(false), ExternalTwitterEnabled: cast.Ptr(false), ExternalWorkosEnabled: cast.Ptr(false), ExternalZoomEnabled: cast.Ptr(false), // Deprecated fields should be ignored ExternalSlackEnabled: cast.Ptr(false), }) // Check error assert.NoError(t, err) assert.Empty(t, string(diff)) }) }