feat(settings): add secret generator type to settings RT implementation (#11152)

# Which Problems Are Solved

The `SecretGenerators` setting is missing among the instance settings in
the new relational model.

# How the Problems Are Solved

This PR adds:
* `SettingTypeSecretGenerator` type to the settings model
* `SecretGeneratorSettingsRepository` and
`secretGeneratorSettingsJSONChanges` interfaces to set, retrieve, and
delete `secret_generator` settings, and corresponding unit tests.
* projection reducers to reduce the following events:
  * `SecretGeneratorAddedEvent`
  * `SecretGeneratorChangedEvent`
  * `SecretGeneratorRemovedEvent`
* integration tests to test that the events get reduced correctly 

# Additional Changes
N/A

# Additional Context
- Part of https://github.com/zitadel/zitadel/issues/10207

---------

Co-authored-by: Copilot <[email protected]>
Co-authored-by: Marco A. <[email protected]>
This commit is contained in:
Gayathri Vijayan
2025-12-11 12:33:19 +01:00
committed by GitHub
co-authored by Copilot Marco A.
parent 826039c620
commit 3903355d18
9 changed files with 2209 additions and 49 deletions
@@ -8,7 +8,8 @@ CREATE TYPE zitadel.settings_type AS ENUM (
'security',
'organization',
'notification',
'legal_and_support'
'legal_and_support',
'secret_generator'
);
CREATE TYPE zitadel.settings_state AS ENUM (
+2 -1
View File
@@ -6,7 +6,8 @@ import (
)
var (
ErrNoChanges = errors.New("update must contain a change")
ErrNoChanges = errors.New("update must contain a change")
ErrInvalidChangeTarget = errors.New("change target must not be nil")
)
type MissingConditionError struct {
@@ -26,6 +26,7 @@ import (
"github.com/zitadel/zitadel/pkg/grpc/admin"
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
"github.com/zitadel/zitadel/pkg/grpc/policy"
settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings"
)
//go:embed picture.png
@@ -1387,3 +1388,150 @@ func TestServer_TestInstanceLegalAndSupportSettingsReduces(t *testing.T) {
}, retryDuration, tick)
})
}
func TestServer_TestInstanceSecretGeneratorSettingsReduces(t *testing.T) {
t.Parallel()
settingsRepo := repository.SecretGeneratorSettingsRepository()
newInstance := integration.NewInstance(t.Context())
IAMCTX := newInstance.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
t.Run("test secret generator settings set", func(t *testing.T) {
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(IAMCTX, time.Second*20)
assert.EventuallyWithT(t, func(collect *assert.CollectT) {
setting, err := settingsRepo.Get(
IAMCTX, pool,
database.WithCondition(
settingsRepo.UniqueCondition(newInstance.ID(), nil, domain.SettingTypeSecretGenerator, domain.SettingStateActive),
),
)
require.NoError(collect, err)
// ClientSecret
assert.Equal(collect, uint(64), *setting.ClientSecret.Length)
assert.Equal(collect, true, *setting.ClientSecret.IncludeLowerLetters)
assert.Equal(collect, true, *setting.ClientSecret.IncludeUpperLetters)
assert.Equal(collect, true, *setting.ClientSecret.IncludeDigits)
assert.Equal(collect, false, *setting.ClientSecret.IncludeSymbols)
// InitializeUserCode
assert.Equal(collect, uint(6), *setting.InitializeUserCode.Length)
assert.Equal(collect, 72*time.Hour, *setting.InitializeUserCode.Expiry)
assert.Equal(collect, false, *setting.InitializeUserCode.IncludeLowerLetters)
assert.Equal(collect, true, *setting.InitializeUserCode.IncludeUpperLetters)
assert.Equal(collect, true, *setting.InitializeUserCode.IncludeDigits)
assert.Equal(collect, false, *setting.InitializeUserCode.IncludeSymbols)
// EmailVerificationCode
assert.Equal(collect, uint(6), *setting.EmailVerificationCode.Length)
assert.Equal(collect, 1*time.Hour, *setting.EmailVerificationCode.Expiry)
assert.Equal(collect, false, *setting.EmailVerificationCode.IncludeLowerLetters)
assert.Equal(collect, true, *setting.EmailVerificationCode.IncludeUpperLetters)
assert.Equal(collect, true, *setting.EmailVerificationCode.IncludeDigits)
assert.Equal(collect, false, *setting.EmailVerificationCode.IncludeSymbols)
// PhoneVerificationCode
assert.Equal(collect, uint(6), *setting.PhoneVerificationCode.Length)
assert.Equal(collect, 1*time.Hour, *setting.PhoneVerificationCode.Expiry)
assert.Equal(collect, false, *setting.PhoneVerificationCode.IncludeLowerLetters)
assert.Equal(collect, true, *setting.PhoneVerificationCode.IncludeUpperLetters)
assert.Equal(collect, true, *setting.PhoneVerificationCode.IncludeDigits)
assert.Equal(collect, false, *setting.PhoneVerificationCode.IncludeSymbols)
// PasswordVerificationCode
assert.Equal(collect, uint(6), *setting.PasswordVerificationCode.Length)
assert.Equal(collect, 1*time.Hour, *setting.PasswordVerificationCode.Expiry)
assert.Equal(collect, false, *setting.PasswordVerificationCode.IncludeLowerLetters)
assert.Equal(collect, true, *setting.PasswordVerificationCode.IncludeUpperLetters)
assert.Equal(collect, true, *setting.PasswordVerificationCode.IncludeDigits)
assert.Equal(collect, false, *setting.PasswordVerificationCode.IncludeSymbols)
// PasswordlessInitCode
assert.Equal(collect, uint(12), *setting.PasswordlessInitCode.Length)
assert.Equal(collect, 1*time.Hour, *setting.PasswordlessInitCode.Expiry)
assert.Equal(collect, true, *setting.PasswordlessInitCode.IncludeLowerLetters)
assert.Equal(collect, true, *setting.PasswordlessInitCode.IncludeUpperLetters)
assert.Equal(collect, true, *setting.PasswordlessInitCode.IncludeDigits)
assert.Equal(collect, false, *setting.PasswordlessInitCode.IncludeSymbols)
// DomainVerification
assert.Equal(collect, uint(32), *setting.DomainVerification.Length)
assert.Equal(collect, true, *setting.DomainVerification.IncludeLowerLetters)
assert.Equal(collect, true, *setting.DomainVerification.IncludeUpperLetters)
assert.Equal(collect, true, *setting.DomainVerification.IncludeDigits)
assert.Equal(collect, false, *setting.DomainVerification.IncludeSymbols)
// OTPSMS
assert.Equal(collect, uint(8), *setting.OTPSMS.Length)
assert.Equal(collect, 5*time.Minute, *setting.OTPSMS.Expiry)
assert.Equal(collect, false, *setting.OTPSMS.IncludeLowerLetters)
assert.Equal(collect, false, *setting.OTPSMS.IncludeUpperLetters)
assert.Equal(collect, true, *setting.OTPSMS.IncludeDigits)
assert.Equal(collect, false, *setting.OTPSMS.IncludeSymbols)
// OTPEmail
assert.Equal(collect, uint(8), *setting.OTPEmail.Length)
assert.Equal(collect, 5*time.Minute, *setting.OTPEmail.Expiry)
assert.Equal(collect, false, *setting.OTPEmail.IncludeLowerLetters)
assert.Equal(collect, false, *setting.OTPEmail.IncludeUpperLetters)
assert.Equal(collect, true, *setting.OTPEmail.IncludeDigits)
assert.Equal(collect, false, *setting.OTPEmail.IncludeSymbols)
}, retryDuration, tick)
})
t.Run("test secret generator settings update", func(t *testing.T) {
before := time.Now()
_, err := newInstance.Client.Admin.UpdateSecretGenerator(IAMCTX, &admin.UpdateSecretGeneratorRequest{
GeneratorType: settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_INIT_CODE,
IncludeLowerLetters: true,
Expiry: durationpb.New(24 * time.Hour),
Length: uint32(8),
})
require.NoError(t, err)
after := time.Now()
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(IAMCTX, time.Second*20)
assert.EventuallyWithT(t, func(collect *assert.CollectT) {
setting, err := settingsRepo.Get(
IAMCTX, pool,
database.WithCondition(
settingsRepo.UniqueCondition(newInstance.ID(), nil, domain.SettingTypeSecretGenerator, domain.SettingStateActive),
),
)
require.NoError(t, err)
assert.Equal(collect, uint(8), *setting.InitializeUserCode.Length)
assert.Equal(collect, 24*time.Hour, *setting.InitializeUserCode.Expiry)
assert.Equal(collect, true, *setting.InitializeUserCode.IncludeLowerLetters)
assert.WithinRange(collect, setting.UpdatedAt, before, after)
}, retryDuration, tick)
})
t.Run("test delete instance reduces", func(t *testing.T) {
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(IAMCTX, time.Second*20)
assert.EventuallyWithT(t, func(collect *assert.CollectT) {
setting, err := settingsRepo.Get(
IAMCTX, pool,
database.WithCondition(
settingsRepo.UniqueCondition(newInstance.ID(), nil, domain.SettingTypeSecretGenerator, domain.SettingStateActive),
),
)
require.NoError(collect, err)
require.NotNil(collect, setting)
}, retryDuration, tick)
_, err := newInstance.Client.InstanceV2Beta.DeleteInstance(CTX, &instance.DeleteInstanceRequest{
InstanceId: newInstance.ID(),
})
require.NoError(t, err)
retryDuration, tick = integration.WaitForAndTickWithMaxDuration(IAMCTX, time.Second*20)
assert.EventuallyWithT(t, func(collect *assert.CollectT) {
_, err := settingsRepo.Get(
IAMCTX, pool,
database.WithCondition(
settingsRepo.UniqueCondition(newInstance.ID(), nil, domain.SettingTypeSecretGenerator, domain.SettingStateActive),
),
)
require.ErrorIs(collect, err, new(database.NoRowFoundError))
}, retryDuration, tick)
})
}
@@ -149,6 +149,10 @@ func (s settings) list(ctx context.Context, client database.QueryExecutor, opts
}
func (s settings) set(ctx context.Context, client database.QueryExecutor, settings *domain.Settings, changes ...database.Change) error {
if settings == nil {
return database.ErrInvalidChangeTarget
}
var (
id, createdAt, updatedAt any = database.DefaultInstruction, database.DefaultInstruction, database.DefaultInstruction
)
@@ -378,6 +382,10 @@ func LoginSettingsRepository() domain.LoginSettingsRepository {
var _ domain.LoginSettingsRepository = (*loginSettings)(nil)
func (s loginSettings) Set(ctx context.Context, client database.QueryExecutor, settings *domain.LoginSettings) error {
if settings == nil {
return database.ErrInvalidChangeTarget
}
settings.Type = domain.SettingTypeLogin
settings.State = domain.SettingStateActive
@@ -572,6 +580,10 @@ func BrandingSettingsRepository() domain.BrandingSettingsRepository {
var _ domain.BrandingSettingsRepository = (*brandingSettings)(nil)
func (s brandingSettings) Set(ctx context.Context, client database.QueryExecutor, settings *domain.BrandingSettings) (err error) {
if settings == nil {
return database.ErrInvalidChangeTarget
}
settings.Type = domain.SettingTypeBranding
settings.State = domain.SettingStatePreview
@@ -719,6 +731,10 @@ func PasswordComplexitySettingsRepository() domain.PasswordComplexitySettingsRep
var _ domain.PasswordComplexitySettingsRepository = (*passwordComplexitySettings)(nil)
func (s passwordComplexitySettings) Set(ctx context.Context, client database.QueryExecutor, settings *domain.PasswordComplexitySettings) (err error) {
if settings == nil {
return database.ErrInvalidChangeTarget
}
settings.Type = domain.SettingTypePasswordComplexity
settings.State = domain.SettingStateActive
@@ -801,6 +817,10 @@ func PasswordExpirySettingsRepository() domain.PasswordExpirySettingsRepository
}
func (s passwordExpirySettings) Set(ctx context.Context, client database.QueryExecutor, settings *domain.PasswordExpirySettings) (err error) {
if settings == nil {
return database.ErrInvalidChangeTarget
}
settings.Type = domain.SettingTypePasswordExpiry
settings.State = domain.SettingStateActive
@@ -889,6 +909,10 @@ func LockoutSettingsRepository() domain.LockoutSettingsRepository {
var _ domain.LockoutSettingsRepository = (*lockoutSettings)(nil)
func (s lockoutSettings) Set(ctx context.Context, client database.QueryExecutor, settings *domain.LockoutSettings) error {
if settings == nil {
return database.ErrInvalidChangeTarget
}
settings.Type = domain.SettingTypeLockout
settings.State = domain.SettingStateActive
@@ -977,6 +1001,10 @@ func SecuritySettingsRepository() domain.SecuritySettingsRepository {
var _ domain.SecuritySettingsRepository = (*securitySettings)(nil)
func (s securitySettings) Set(ctx context.Context, client database.QueryExecutor, settings *domain.SecuritySettings) (err error) {
if settings == nil {
return database.ErrInvalidChangeTarget
}
settings.Type = domain.SettingTypeSecurity
settings.State = domain.SettingStateActive
@@ -1065,6 +1093,10 @@ func DomainSettingsRepository() domain.DomainSettingsRepository {
var _ domain.DomainSettingsRepository = (*domainSettings)(nil)
func (s domainSettings) Set(ctx context.Context, client database.QueryExecutor, settings *domain.DomainSettings) (err error) {
if settings == nil {
return database.ErrInvalidChangeTarget
}
settings.Type = domain.SettingTypeDomain
settings.State = domain.SettingStateActive
@@ -1139,6 +1171,10 @@ func OrganizationSettingsRepository() domain.OrganizationSettingsRepository {
var _ domain.OrganizationSettingsRepository = (*organizationSettings)(nil)
func (s organizationSettings) Set(ctx context.Context, client database.QueryExecutor, settings *domain.OrganizationSettings) (err error) {
if settings == nil {
return database.ErrInvalidChangeTarget
}
settings.Type = domain.SettingTypeOrganization
settings.State = domain.SettingStateActive
@@ -1213,6 +1249,10 @@ func NotificationSettingsRepository() domain.NotificationSettingsRepository {
var _ domain.NotificationSettingsRepository = (*notificationSettings)(nil)
func (s notificationSettings) Set(ctx context.Context, client database.QueryExecutor, settings *domain.NotificationSettings) (err error) {
if settings == nil {
return database.ErrInvalidChangeTarget
}
settings.Type = domain.SettingTypeNotification
settings.State = domain.SettingStateActive
@@ -1329,6 +1369,10 @@ func LegalAndSupportSettingsRepository() domain.LegalAndSupportSettingsRepositor
var _ domain.LegalAndSupportSettingsRepository = (*legalAndSupportSettings)(nil)
func (s legalAndSupportSettings) Set(ctx context.Context, client database.QueryExecutor, settings *domain.LegalAndSupportSettings) (err error) {
if settings == nil {
return database.ErrInvalidChangeTarget
}
settings.Type = domain.SettingTypeLegalAndSupport
settings.State = domain.SettingStateActive
@@ -1375,6 +1419,141 @@ func (s legalAndSupportSettings) Get(ctx context.Context, client database.QueryE
return item, nil
}
// -------------------------------------------------------------
// secret generators
// -------------------------------------------------------------
type secretGeneratorSettings struct {
settings
}
func (s secretGeneratorSettings) SetSettingFields(value domain.SecretGeneratorSettingsAttributes) database.Change {
changes := make([]db_json.JsonUpdate, 0, 11)
if value.ClientSecret != nil {
changes = append(changes, s.SetClientSecret(*value.ClientSecret))
}
if value.InitializeUserCode != nil {
changes = append(changes, s.SetInitializeUserCode(*value.InitializeUserCode))
}
if value.EmailVerificationCode != nil {
changes = append(changes, s.SetEmailVerificationCode(*value.EmailVerificationCode))
}
if value.PhoneVerificationCode != nil {
changes = append(changes, s.SetPhoneVerificationCode(*value.PhoneVerificationCode))
}
if value.PasswordVerificationCode != nil {
changes = append(changes, s.SetPasswordVerificationCode(*value.PasswordVerificationCode))
}
if value.PasswordlessInitCode != nil {
changes = append(changes, s.SetPasswordlessInitCode(*value.PasswordlessInitCode))
}
if value.DomainVerification != nil {
changes = append(changes, s.SetDomainVerification(*value.DomainVerification))
}
if value.OTPSMS != nil {
changes = append(changes, s.SetOTPSMS(*value.OTPSMS))
}
if value.OTPEmail != nil {
changes = append(changes, s.SetOTPEmail(*value.OTPEmail))
}
return db_json.NewJsonChanges(s.SettingsColumn(), changes...)
}
func (s secretGeneratorSettings) SetClientSecret(value domain.ClientSecretAttributes) db_json.JsonUpdate {
return db_json.NewFieldChange([]string{"clientSecret"}, value)
}
func (s secretGeneratorSettings) SetInitializeUserCode(value domain.InitializeUserCodeAttributes) db_json.JsonUpdate {
return db_json.NewFieldChange([]string{"initializeUserCode"}, value)
}
func (s secretGeneratorSettings) SetEmailVerificationCode(value domain.EmailVerificationCodeAttributes) db_json.JsonUpdate {
return db_json.NewFieldChange([]string{"emailVerificationCode"}, value)
}
func (s secretGeneratorSettings) SetPhoneVerificationCode(value domain.PhoneVerificationCodeAttributes) db_json.JsonUpdate {
return db_json.NewFieldChange([]string{"phoneVerificationCode"}, value)
}
func (s secretGeneratorSettings) SetPasswordVerificationCode(value domain.PasswordVerificationCodeAttributes) db_json.JsonUpdate {
return db_json.NewFieldChange([]string{"passwordVerificationCode"}, value)
}
func (s secretGeneratorSettings) SetPasswordlessInitCode(value domain.PasswordlessInitCodeAttributes) db_json.JsonUpdate {
return db_json.NewFieldChange([]string{"passwordlessInitCode"}, value)
}
func (s secretGeneratorSettings) SetDomainVerification(value domain.DomainVerificationAttributes) db_json.JsonUpdate {
return db_json.NewFieldChange([]string{"domainVerification"}, value)
}
func (s secretGeneratorSettings) SetOTPSMS(value domain.OTPSMSAttributes) db_json.JsonUpdate {
return db_json.NewFieldChange([]string{"otpSms"}, value)
}
func (s secretGeneratorSettings) SetOTPEmail(value domain.OTPEmailAttributes) db_json.JsonUpdate {
return db_json.NewFieldChange([]string{"otpEmail"}, value)
}
func SecretGeneratorSettingsRepository() domain.SecretGeneratorSettingsRepository {
return &secretGeneratorSettings{
settings{},
}
}
var _ domain.SecretGeneratorSettingsRepository = (*secretGeneratorSettings)(nil)
func (s secretGeneratorSettings) Set(ctx context.Context, client database.QueryExecutor, settings *domain.SecretGeneratorSettings) (err error) {
if settings == nil {
return database.ErrInvalidChangeTarget
}
settings.Type = domain.SettingTypeSecretGenerator
settings.State = domain.SettingStateActive
settingsJSON, err := json.Marshal(settings.SecretGeneratorSettingsAttributes)
if err != nil {
return err
}
settings.Settings.Settings = settingsJSON
if err := s.set(ctx, client, &settings.Settings, s.SetSettingFields(settings.SecretGeneratorSettingsAttributes)); err != nil {
return err
}
settings.Settings.Settings = nil
return nil
}
func (s secretGeneratorSettings) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.SecretGeneratorSettings, error) {
settings, err := s.list(ctx, client, opts...)
if err != nil {
return nil, err
}
list := make([]*domain.SecretGeneratorSettings, len(settings))
for i := range settings {
list[i] = &domain.SecretGeneratorSettings{Settings: *settings[i]}
if err := json.Unmarshal(list[i].Settings.Settings, &list[i].SecretGeneratorSettingsAttributes); err != nil {
return nil, err
}
list[i].Settings.Settings = nil
}
return list, nil
}
func (s secretGeneratorSettings) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.SecretGeneratorSettings, error) {
settings, err := s.get(ctx, client, opts...)
if err != nil {
return nil, err
}
item := &domain.SecretGeneratorSettings{Settings: *settings}
if err = json.Unmarshal(item.Settings.Settings, &item.SecretGeneratorSettingsAttributes); err != nil {
return nil, err
}
item.Settings.Settings = nil
return item, nil
}
// -------------------------------------------------------------
// helpers
// -------------------------------------------------------------
@@ -498,6 +498,11 @@ func TestSetLoginSettings(t *testing.T) {
},
wantErr: new(database.ForeignKeyError),
},
{
name: "nil settings",
settings: nil,
wantErr: database.ErrInvalidChangeTarget,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -1167,6 +1172,11 @@ func TestSetBrandingSettings(t *testing.T) {
},
wantErr: new(database.ForeignKeyError),
},
{
name: "nil settings",
settings: nil,
wantErr: database.ErrInvalidChangeTarget,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -1912,6 +1922,11 @@ func TestSetPasswordComplexitySettings(t *testing.T) {
},
wantErr: new(database.ForeignKeyError),
},
{
name: "nil settings",
settings: nil,
wantErr: database.ErrInvalidChangeTarget,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -2300,6 +2315,11 @@ func TestSetPasswordExpirySettings(t *testing.T) {
},
wantErr: new(database.ForeignKeyError),
},
{
name: "nil settings",
settings: nil,
wantErr: database.ErrInvalidChangeTarget,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -2695,6 +2715,11 @@ func TestSetLockoutSettings(t *testing.T) {
},
wantErr: new(database.ForeignKeyError),
},
{
name: "nil settings",
settings: nil,
wantErr: database.ErrInvalidChangeTarget,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -3092,6 +3117,11 @@ func TestSetSecuritySettings(t *testing.T) {
},
wantErr: new(database.ForeignKeyError),
},
{
name: "nil settings",
settings: nil,
wantErr: database.ErrInvalidChangeTarget,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -3489,6 +3519,11 @@ func TestSetDomainSettings(t *testing.T) {
},
wantErr: new(database.ForeignKeyError),
},
{
name: "nil settings",
settings: nil,
wantErr: database.ErrInvalidChangeTarget,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -3860,6 +3895,11 @@ func TestSetOrganizationSettings(t *testing.T) {
},
wantErr: new(database.ForeignKeyError),
},
{
name: "nil settings",
settings: nil,
wantErr: database.ErrInvalidChangeTarget,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -4227,6 +4267,11 @@ func TestSetNotificationSettings(t *testing.T) {
},
wantErr: new(database.ForeignKeyError),
},
{
name: "nil settings",
settings: nil,
wantErr: database.ErrInvalidChangeTarget,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -4672,6 +4717,11 @@ func TestSetLegalAndSupportSettings(t *testing.T) {
},
wantErr: new(database.ForeignKeyError),
},
{
name: "nil settings",
settings: nil,
wantErr: database.ErrInvalidChangeTarget,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -4774,3 +4824,325 @@ func TestDeleteLegalAndSupportSettings(t *testing.T) {
})
}
}
func TestGetSecretGeneratorSettings(t *testing.T) {
tx, rollback := transactionForRollback(t)
defer rollback()
firstInstanceID := createInstance(t, tx)
secondInstanceID := createInstance(t, tx)
firstOrgID := createOrganization(t, tx, firstInstanceID)
secondOrgID := createOrganization(t, tx, secondInstanceID)
repo := repository.SecretGeneratorSettingsRepository()
settings := []*domain.SecretGeneratorSettings{
createSecretGeneratorSettings(firstInstanceID, nil),
createSecretGeneratorSettings(secondInstanceID, nil),
createSecretGeneratorSettings(firstInstanceID, gu.Ptr(firstOrgID)),
createSecretGeneratorSettings(secondInstanceID, gu.Ptr(secondOrgID)),
}
for _, setting := range settings {
err := repo.Set(t.Context(), tx, setting)
require.NoError(t, err)
}
tests := []struct {
name string
condition database.Condition
want *domain.SecretGeneratorSettings
wantErr error
}{
{
name: "incomplete condition",
condition: repo.IDCondition(settings[0].ID),
wantErr: database.NewMissingConditionError(repo.IDColumn()),
},
{
name: "not found",
condition: repo.PrimaryKeyCondition(firstInstanceID, "nix"),
wantErr: database.NewNoRowFoundError(nil),
},
{
name: "too many",
condition: repo.InstanceIDCondition(firstInstanceID),
wantErr: database.NewMultipleRowsFoundError(nil),
},
{
name: "ok, instance",
condition: repo.PrimaryKeyCondition(firstInstanceID, settings[0].ID),
want: settings[0],
},
{
name: "ok, organization",
condition: repo.PrimaryKeyCondition(secondInstanceID, settings[3].ID),
want: settings[3],
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := repo.Get(t.Context(), tx, database.WithCondition(tt.condition))
require.ErrorIs(t, err, tt.wantErr)
assert.EqualExportedValues(t, tt.want, got)
})
}
}
func TestListSecretGeneratorSettings(t *testing.T) {
tx, rollback := transactionForRollback(t)
defer rollback()
firstInstanceID := createInstance(t, tx)
secondInstanceID := createInstance(t, tx)
firstOrgID := createOrganization(t, tx, firstInstanceID)
secondOrgID := createOrganization(t, tx, secondInstanceID)
repo := repository.SecretGeneratorSettingsRepository()
settings := []*domain.SecretGeneratorSettings{
createSecretGeneratorSettings(firstInstanceID, nil),
createSecretGeneratorSettings(secondInstanceID, nil),
createSecretGeneratorSettings(firstInstanceID, gu.Ptr(firstOrgID)),
createSecretGeneratorSettings(secondInstanceID, gu.Ptr(secondOrgID)),
}
for _, setting := range settings {
err := repo.Set(t.Context(), tx, setting)
require.NoError(t, err)
}
tests := []struct {
name string
condition database.Condition
want []*domain.SecretGeneratorSettings
wantErr error
}{
{
name: "incomplete condition",
condition: repo.OrganizationIDCondition(gu.Ptr(firstOrgID)),
wantErr: database.NewMissingConditionError(repo.IDColumn()),
},
{
name: "no results, ok",
condition: repo.PrimaryKeyCondition(firstInstanceID, "nix"),
want: []*domain.SecretGeneratorSettings{},
},
{
name: "all from instance",
condition: repo.InstanceIDCondition(firstInstanceID),
want: []*domain.SecretGeneratorSettings{settings[2], settings[0]},
},
{
name: "only from instance",
condition: database.And(
repo.InstanceIDCondition(firstInstanceID),
repo.OrganizationIDCondition(nil),
),
want: []*domain.SecretGeneratorSettings{settings[0]},
},
{
name: "all from first org",
condition: database.And(
repo.InstanceIDCondition(firstInstanceID),
repo.OrganizationIDCondition(gu.Ptr(firstOrgID)),
),
want: []*domain.SecretGeneratorSettings{settings[2]},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := repo.List(t.Context(), tx,
database.WithCondition(tt.condition),
database.WithOrderByAscending(repo.InstanceIDColumn(), repo.OrganizationIDColumn()),
)
require.ErrorIs(t, err, tt.wantErr)
assert.EqualExportedValues(t, tt.want, got)
})
}
}
func TestSetSecretGeneratorSettings(t *testing.T) {
tx, rollback := transactionForRollback(t)
defer rollback()
instanceID := createInstance(t, tx)
orgID := createOrganization(t, tx, instanceID)
repo := repository.SecretGeneratorSettingsRepository()
existingSettings := createSecretGeneratorSettings(instanceID, gu.Ptr(orgID))
err := repo.Set(t.Context(), tx, existingSettings)
require.NoError(t, err)
tests := []struct {
name string
settings *domain.SecretGeneratorSettings
wantErr error
}{
{
name: "create instance",
settings: createSecretGeneratorSettings(instanceID, nil),
},
{
name: "update organization",
settings: createSecretGeneratorSettings(instanceID, gu.Ptr(orgID)),
},
{
name: "non-existing instance",
settings: createSecretGeneratorSettings("foo", nil),
wantErr: new(database.ForeignKeyError),
},
{
name: "non-existing org",
settings: createSecretGeneratorSettings(instanceID, gu.Ptr("foo")),
wantErr: new(database.ForeignKeyError),
},
{
name: "nil settings",
settings: nil,
wantErr: database.ErrInvalidChangeTarget,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
savepoint, rollback := savepointForRollback(t, tx)
defer rollback()
err := repo.Set(t.Context(), savepoint, tt.settings)
require.ErrorIs(t, err, tt.wantErr)
})
}
}
func TestDeleteSecretGeneratorSettings(t *testing.T) {
tx, rollback := transactionForRollback(t)
defer rollback()
instanceID := createInstance(t, tx)
orgID := createOrganization(t, tx, instanceID)
repo := repository.SecretGeneratorSettingsRepository()
existingInstanceSettings := createSecretGeneratorSettings(instanceID, nil)
err := repo.Set(t.Context(), tx, existingInstanceSettings)
require.NoError(t, err)
existingOrganizationSettings := createSecretGeneratorSettings(instanceID, gu.Ptr(orgID))
err = repo.Set(t.Context(), tx, existingOrganizationSettings)
require.NoError(t, err)
tests := []struct {
name string
condition database.Condition
wantRowsAffected int64
wantErr error
}{
{
name: "incomplete condition",
condition: repo.InstanceIDCondition(instanceID),
wantRowsAffected: 0,
wantErr: database.NewMissingConditionError(repo.IDColumn()),
},
{
name: "not found",
condition: repo.UniqueCondition(existingInstanceSettings.InstanceID, gu.Ptr("foo"), domain.SettingTypeSecretGenerator, domain.SettingStateActive),
wantRowsAffected: 0,
},
{
name: "delete instance",
condition: repo.UniqueCondition(existingInstanceSettings.InstanceID, existingInstanceSettings.OrganizationID, domain.SettingTypeSecretGenerator, domain.SettingStateActive),
wantRowsAffected: 1,
},
{
name: "delete instance twice",
condition: repo.UniqueCondition(existingInstanceSettings.InstanceID, existingInstanceSettings.OrganizationID, domain.SettingTypeSecretGenerator, domain.SettingStateActive),
wantRowsAffected: 0,
},
{
name: "delete organization",
condition: repo.UniqueCondition(existingOrganizationSettings.InstanceID, existingOrganizationSettings.OrganizationID, domain.SettingTypeSecretGenerator, domain.SettingStateActive),
wantRowsAffected: 1,
},
{
name: "delete organization twice",
condition: repo.UniqueCondition(existingOrganizationSettings.InstanceID, existingOrganizationSettings.OrganizationID, domain.SettingTypeSecretGenerator, domain.SettingStateActive),
wantRowsAffected: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rowsAffected, err := repo.Delete(t.Context(), tx, tt.condition)
require.ErrorIs(t, err, tt.wantErr)
assert.Equal(t, tt.wantRowsAffected, rowsAffected)
})
}
}
func createDefaultSecretGeneratorAttrs() domain.SecretGeneratorAttrs {
return domain.SecretGeneratorAttrs{
Length: gu.Ptr(uint(32)),
IncludeUpperLetters: gu.Ptr(true),
IncludeLowerLetters: gu.Ptr(true),
IncludeDigits: gu.Ptr(true),
IncludeSymbols: gu.Ptr(false),
}
}
func createDefaultSecretGeneratorAttrsWithExpiry(expiry time.Duration) domain.SecretGeneratorAttrsWithExpiry {
return domain.SecretGeneratorAttrsWithExpiry{
Expiry: gu.Ptr(expiry),
SecretGeneratorAttrs: domain.SecretGeneratorAttrs{
Length: gu.Ptr(uint(32)),
IncludeUpperLetters: gu.Ptr(true),
IncludeLowerLetters: gu.Ptr(true),
IncludeDigits: gu.Ptr(true),
IncludeSymbols: gu.Ptr(false),
},
}
}
func createDefaultSecretGeneratorSettingsAttributes() domain.SecretGeneratorSettingsAttributes {
attrs := createDefaultSecretGeneratorAttrs()
oneDay := 24 * time.Hour
thirtyMinutes := 30 * time.Minute
tenMinutes := 10 * time.Minute
return domain.SecretGeneratorSettingsAttributes{
ClientSecret: &domain.ClientSecretAttributes{
SecretGeneratorAttrs: attrs,
},
InitializeUserCode: &domain.InitializeUserCodeAttributes{
SecretGeneratorAttrsWithExpiry: createDefaultSecretGeneratorAttrsWithExpiry(oneDay),
},
EmailVerificationCode: &domain.EmailVerificationCodeAttributes{
SecretGeneratorAttrsWithExpiry: createDefaultSecretGeneratorAttrsWithExpiry(thirtyMinutes),
},
PhoneVerificationCode: &domain.PhoneVerificationCodeAttributes{
SecretGeneratorAttrsWithExpiry: createDefaultSecretGeneratorAttrsWithExpiry(thirtyMinutes),
},
PasswordVerificationCode: &domain.PasswordVerificationCodeAttributes{
SecretGeneratorAttrsWithExpiry: createDefaultSecretGeneratorAttrsWithExpiry(thirtyMinutes),
},
PasswordlessInitCode: &domain.PasswordlessInitCodeAttributes{
SecretGeneratorAttrsWithExpiry: createDefaultSecretGeneratorAttrsWithExpiry(tenMinutes),
},
DomainVerification: &domain.DomainVerificationAttributes{
SecretGeneratorAttrs: attrs,
},
OTPSMS: &domain.OTPSMSAttributes{
SecretGeneratorAttrsWithExpiry: createDefaultSecretGeneratorAttrsWithExpiry(thirtyMinutes),
},
OTPEmail: &domain.OTPEmailAttributes{
SecretGeneratorAttrsWithExpiry: createDefaultSecretGeneratorAttrsWithExpiry(thirtyMinutes),
},
}
}
func createSecretGeneratorSettings(instanceID string, organizationID *string) *domain.SecretGeneratorSettings {
return &domain.SecretGeneratorSettings{
Settings: domain.Settings{
InstanceID: instanceID,
OrganizationID: organizationID,
},
SecretGeneratorSettingsAttributes: createDefaultSecretGeneratorSettingsAttributes(),
}
}