supabase-cli/pkg/config/auth_test.go

1117 lines
40 KiB
Go

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