Files
zitadel/backend/v3/domain/settings.go
T
Gayathri VijayanandGitHub f99142bc4a feat: implement otp sms session challenge (#11919)
# Which Problems Are Solved

This PR adds the OTP SMS Challenge needed for CreateSession API
https://github.com/zitadel/zitadel/issues/11035

# How the Problems Are Solved

- Introduce `OTPSMSChallengeCommand` (validate/execute/events) for
session creation checks
- Introduce `GetOTPCryptoGeneratorConfigWithDefault` to fetch
per-instance secret generator settings
- Add unit tests

# Additional Changes

Refactor tests in `session_check_recovery_code_test.go` to make the
helper functions reusable in `session_challenge_otp_sms_test.go`

# Additional Context
Related to https://github.com/zitadel/zitadel/issues/11035
2026-03-26 11:33:44 +00:00

529 lines
19 KiB
Go

package domain
import (
"context"
"net/url"
"time"
"github.com/zitadel/zitadel/backend/v3/storage/database"
db_json "github.com/zitadel/zitadel/backend/v3/storage/database/json"
)
//go:generate enumer -type SettingType -transform snake -trimprefix SettingType -sql
type SettingType uint8
const (
SettingTypeLogin SettingType = iota
SettingTypeBranding
SettingTypePasswordComplexity
SettingTypePasswordExpiry
SettingTypeDomain
SettingTypeLockout
SettingTypeSecurity
SettingTypeOrganization
SettingTypeNotification
SettingTypeLegalAndSupport
SettingTypeSecretGenerator
)
//go:generate enumer -type SettingState -transform snake -trimprefix SettingState -sql
type SettingState int32
const (
SettingStateActive SettingState = iota
SettingStatePreview
)
type Settings struct {
ID string `json:"id,omitempty" db:"id"`
InstanceID string `json:"instanceId,omitempty" db:"instance_id"`
OrganizationID *string `json:"organizationId,omitempty" db:"organization_id"`
Type SettingType `json:"type,omitempty" db:"type"`
State SettingState `json:"state,omitempty" db:"state"`
Settings []byte `json:"settings,omitempty" db:"settings"`
CreatedAt time.Time `json:"createdAt,omitzero" db:"created_at"`
UpdatedAt time.Time `json:"updatedAt,omitzero" db:"updated_at"`
}
type settingsColumns interface {
IDColumn() database.Column
InstanceIDColumn() database.Column
OrganizationIDColumn() database.Column
TypeColumn() database.Column
StateColumn() database.Column
SettingsColumn() database.Column
CreatedAtColumn() database.Column
UpdatedAtColumn() database.Column
PrimaryKeyColumns() []database.Column
UniqueColumns() []database.Column
}
type settingsConditions interface {
InstanceIDCondition(id string) database.Condition
OrganizationIDCondition(id *string) database.Condition
IDCondition(id string) database.Condition
PrimaryKeyCondition(instanceID, id string) database.Condition
UniqueCondition(instanceID string, orgID *string, typ SettingType, state SettingState) database.Condition
TypeCondition(typ SettingType) database.Condition
}
type settingsChanges interface {
// SetSettings(settings string) database.Change
SetUpdatedAt(updatedAt *time.Time) database.Change
}
type settingsRepository[T any] interface {
settingsColumns
settingsConditions
settingsChanges
Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*T, error)
List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*T, error)
Set(ctx context.Context, client database.QueryExecutor, settings *T) error
Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error)
}
type PasswordlessType int32
const (
PasswordlessTypeNotAllowed PasswordlessType = iota
PasswordlessTypeAllowed
)
type MultiFactorType int32
const (
MultiFactorTypeUnspecified MultiFactorType = iota
MultiFactorTypeU2FWithPIN
)
type SecondFactorType int32
const (
SecondFactorTypeUnspecified SecondFactorType = iota
SecondFactorTypeTOTP
SecondFactorTypeU2F
SecondFactorTypeOTPEmail
SecondFactorTypeOTPSMS
)
type loginSettingsJSONChanges interface {
SetSettingFields(value LoginSettingsAttributes) database.Change
SetAllowUserNamePassword(value bool) db_json.JsonUpdate
SetAllowRegister(value bool) db_json.JsonUpdate
SetAllowExternalIDP(value bool) db_json.JsonUpdate
SetForceMFA(value bool) db_json.JsonUpdate
SetForceMFALocalOnly(value bool) db_json.JsonUpdate
SetHidePasswordReset(value bool) db_json.JsonUpdate
SetIgnoreUnknownUsernames(value bool) db_json.JsonUpdate
SetAllowDomainDiscovery(value bool) db_json.JsonUpdate
SetDisableLoginWithEmail(value bool) db_json.JsonUpdate
SetDisableLoginWithPhone(value bool) db_json.JsonUpdate
SetPasswordlessType(value PasswordlessType) db_json.JsonUpdate
SetDefaultRedirectURI(value string) db_json.JsonUpdate
SetPasswordCheckLifetime(value time.Duration) db_json.JsonUpdate
SetExternalLoginCheckLifetime(value time.Duration) db_json.JsonUpdate
SetMFAInitSkipLifetime(value time.Duration) db_json.JsonUpdate
SetSecondFactorCheckLifetime(value time.Duration) db_json.JsonUpdate
SetMultiFactorCheckLifetime(value time.Duration) db_json.JsonUpdate
AddMFAType(value MultiFactorType) database.Change
RemoveMFAType(value MultiFactorType) database.Change
AddSecondFactorTypes(value SecondFactorType) database.Change
RemoveSecondFactorTypes(value SecondFactorType) database.Change
}
type LoginSettings struct {
Settings
LoginSettingsAttributes
}
type LoginSettingsAttributes struct {
AllowUserNamePassword *bool `json:"allowUsernamePassword,omitempty"`
AllowRegister *bool `json:"allowRegister,omitempty"`
AllowExternalIDP *bool `json:"allowExternalIdp,omitempty"`
ForceMFA *bool `json:"forceMfa,omitempty"`
ForceMFALocalOnly *bool `json:"forceMfaLocalOnly,omitempty"`
HidePasswordReset *bool `json:"hidePasswordReset,omitempty"`
IgnoreUnknownUsernames *bool `json:"ignoreUnknownUsernames,omitempty"`
AllowDomainDiscovery *bool `json:"allowDomainDiscovery,omitempty"`
DisableLoginWithEmail *bool `json:"disableLoginWithEmail,omitempty"`
DisableLoginWithPhone *bool `json:"disableLoginWithPhone,omitempty"`
PasswordlessType *PasswordlessType `json:"passwordlessType,omitempty"`
DefaultRedirectURI *string `json:"defaultRedirectUri,omitempty"`
PasswordCheckLifetime *time.Duration `json:"passwordCheckLifetime,omitempty"`
ExternalLoginCheckLifetime *time.Duration `json:"externalLoginCheckLifetime,omitempty"`
MFAInitSkipLifetime *time.Duration `json:"mfaInitSkipLifetime,omitempty"`
SecondFactorCheckLifetime *time.Duration `json:"secondFactorCheckLifetime,omitempty"`
MultiFactorCheckLifetime *time.Duration `json:"multiFactorCheckLifetime,omitempty"`
MFAType []MultiFactorType `json:"mfaType,omitempty"`
SecondFactorTypes []SecondFactorType `json:"secondFactors,omitempty"`
}
//go:generate mockgen -typed -package domainmock -destination ./mock/login_settings.mock.go . LoginSettingsRepository
type LoginSettingsRepository interface {
settingsRepository[LoginSettings]
loginSettingsJSONChanges
SetColumns(ctx context.Context, client database.QueryExecutor, settings *Settings, changes ...database.Change) error
}
type BrandingPolicyThemeMode int32
const (
BrandingPolicyThemeAuto BrandingPolicyThemeMode = iota
BrandingPolicyThemeLight
BrandingPolicyThemeDark
)
type brandingSettingsJSONChanges interface {
SetSettingFields(value BrandingSettingsAttributes) database.Change
SetPrimaryColorLight(value string) db_json.JsonUpdate
SetBackgroundColorLight(value string) db_json.JsonUpdate
SetWarnColorLight(value string) db_json.JsonUpdate
SetFontColorLight(value string) db_json.JsonUpdate
SetLogoURLLight(value url.URL) db_json.JsonUpdate
SetIconURLLight(value url.URL) db_json.JsonUpdate
SetPrimaryColorDark(value string) db_json.JsonUpdate
SetBackgroundColorDark(value string) db_json.JsonUpdate
SetWarnColorDark(value string) db_json.JsonUpdate
SetFontColorDark(value string) db_json.JsonUpdate
SetLogoURLDark(value url.URL) db_json.JsonUpdate
SetIconURLDark(value url.URL) db_json.JsonUpdate
SetHideLoginNameSuffix(value bool) db_json.JsonUpdate
SetErrorMsgPopup(value bool) db_json.JsonUpdate
SetDisableWatermark(value bool) db_json.JsonUpdate
SetThemeMode(value BrandingPolicyThemeMode) db_json.JsonUpdate
SetFontURL(value url.URL) db_json.JsonUpdate
}
type BrandingSettings struct {
Settings
BrandingSettingsAttributes
}
type BrandingSettingsAttributes struct {
PrimaryColorLight *string `json:"primaryColorLight,omitempty"`
BackgroundColorLight *string `json:"backgroundColorLight,omitempty"`
WarnColorLight *string `json:"warnColorLight,omitempty"`
FontColorLight *string `json:"fontColorLight,omitempty"`
LogoURLLight *url.URL `json:"logoUrlLight,omitempty"`
IconURLLight *url.URL `json:"iconUrlLight,omitempty"`
PrimaryColorDark *string `json:"primaryColorDark,omitempty"`
BackgroundColorDark *string `json:"backgroundColorDark,omitempty"`
WarnColorDark *string `json:"warnColorDark,omitempty"`
FontColorDark *string `json:"fontColorDark,omitempty"`
LogoURLDark *url.URL `json:"logoUrlDark,omitempty"`
IconURLDark *url.URL `json:"iconUrlDark,omitempty"`
HideLoginNameSuffix *bool `json:"hideLoginNameSuffix,omitempty"`
ErrorMsgPopup *bool `json:"errorMsgPopup,omitempty"`
DisableWatermark *bool `json:"disableMsgPopup,omitempty"`
ThemeMode *BrandingPolicyThemeMode `json:"themeMode,omitempty"`
FontURL *url.URL `json:"fontUrl,omitempty"`
}
//go:generate mockgen -typed -package domainmock -destination ./mock/branding_settings.mock.go . BrandingSettingsRepository
type BrandingSettingsRepository interface {
settingsRepository[BrandingSettings]
brandingSettingsJSONChanges
StateCondition(typ SettingState) database.Condition
Activate(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error)
ActivateAt(ctx context.Context, client database.QueryExecutor, condition database.Condition, updatedAt time.Time) (int64, error)
SetColumns(ctx context.Context, client database.QueryExecutor, settings *Settings, changes ...database.Change) error
}
type passwordComplexitySettingsJSONChanges interface {
SetSettingFields(value PasswordComplexitySettingsAttributes) database.Change
SetMinLength(value uint64) db_json.JsonUpdate
SetHasLowercase(value bool) db_json.JsonUpdate
SetHasUppercase(value bool) db_json.JsonUpdate
SetHasNumber(value bool) db_json.JsonUpdate
SetHasSymbol(value bool) db_json.JsonUpdate
}
type PasswordComplexitySettings struct {
Settings
PasswordComplexitySettingsAttributes
}
type PasswordComplexitySettingsAttributes struct {
MinLength *uint64 `json:"minLength,omitempty"`
HasLowercase *bool `json:"hasLowercase,omitempty"`
HasUppercase *bool `json:"hasUppercase,omitempty"`
HasNumber *bool `json:"hasNumber,omitempty"`
HasSymbol *bool `json:"hasSymbol,omitempty"`
}
//go:generate mockgen -typed -package domainmock -destination ./mock/password_complexity_settings.mock.go . PasswordComplexitySettingsRepository
type PasswordComplexitySettingsRepository interface {
settingsRepository[PasswordComplexitySettings]
passwordComplexitySettingsJSONChanges
}
type passwordExpirySettingsJSONChanges interface {
SetSettingFields(value PasswordExpirySettingsAttributes) database.Change
SetExpireWarnDays(value uint64) db_json.JsonUpdate
SetMaxAgeDays(value uint64) db_json.JsonUpdate
}
type PasswordExpirySettings struct {
Settings
PasswordExpirySettingsAttributes
}
type PasswordExpirySettingsAttributes struct {
ExpireWarnDays *uint64 `json:"expireWarnDays,omitempty"`
MaxAgeDays *uint64 `json:"maxAgeDays,omitempty"`
}
//go:generate mockgen -typed -package domainmock -destination ./mock/password_expiry_settings.mock.go . PasswordExpirySettingsRepository
type PasswordExpirySettingsRepository interface {
settingsRepository[PasswordExpirySettings]
passwordExpirySettingsJSONChanges
}
type lockoutSettingsJSONChanges interface {
SetSettingFields(value LockoutSettingsAttributes) database.Change
SetMaxPasswordAttempts(value uint64) db_json.JsonUpdate
SetMaxOTPAttempts(value uint64) db_json.JsonUpdate
SetShowLockOutFailures(value bool) db_json.JsonUpdate
}
type LockoutSettings struct {
Settings
LockoutSettingsAttributes
}
type LockoutSettingsAttributes struct {
MaxPasswordAttempts *uint64 `json:"maxPasswordAttempts,omitempty"`
MaxOTPAttempts *uint64 `json:"maxOtpAttempts,omitempty"`
ShowLockOutFailures *bool `json:"showLockOutFailures,omitempty"`
}
//go:generate mockgen -typed -package domainmock -destination ./mock/lockout_settings.mock.go . LockoutSettingsRepository
type LockoutSettingsRepository interface {
settingsRepository[LockoutSettings]
lockoutSettingsJSONChanges
}
type securitySettingsJSONChanges interface {
SetSettingFields(value SecuritySettingsAttributes) database.Change
SetEnableIframeEmbedding(value bool) db_json.JsonUpdate
SetAllowedOrigins(values []string) db_json.JsonUpdate
SetEnableImpersonation(value bool) db_json.JsonUpdate
}
type SecuritySettings struct {
Settings
SecuritySettingsAttributes
}
type SecuritySettingsAttributes struct {
EnableIframeEmbedding *bool `json:"enableIframeEmbedding,omitempty"`
AllowedOrigins []string `json:"allowedOrigins,omitempty"`
EnableImpersonation *bool `json:"enableImpersonation,omitempty"`
}
//go:generate mockgen -typed -package domainmock -destination ./mock/security_settings.mock.go . SecuritySettingsRepository
type SecuritySettingsRepository interface {
settingsRepository[SecuritySettings]
securitySettingsJSONChanges
}
type domainSettingsJSONChanges interface {
SetSettingFields(value DomainSettingsAttributes) database.Change
SetLoginNameIncludesDomain(value bool) db_json.JsonUpdate
SetRequireOrgDomainVerification(value bool) db_json.JsonUpdate
SetSMTPSenderAddressMatchesInstanceDomain(value bool) db_json.JsonUpdate
}
type DomainSettings struct {
Settings
DomainSettingsAttributes
}
type DomainSettingsAttributes struct {
LoginNameIncludesDomain *bool `json:"loginNameIncludesDomain,omitempty"`
RequireOrgDomainVerification *bool `json:"requireOrgDomainVerification,omitempty"`
SMTPSenderAddressMatchesInstanceDomain *bool `json:"smtpSenderAddressMatchesInstanceDomain,omitempty"`
}
//go:generate mockgen -typed -package domainmock -destination ./mock/domain_settings.mock.go . DomainSettingsRepository
type DomainSettingsRepository interface {
settingsRepository[DomainSettings]
domainSettingsJSONChanges
}
type organizationSettingsJSONChanges interface {
SetSettingFields(value OrganizationSettingsAttributes) database.Change
SetOrganizationScopedUsernames(value bool) db_json.JsonUpdate
}
type OrganizationSettings struct {
Settings
OrganizationSettingsAttributes
}
type OrganizationSettingsAttributes struct {
OrganizationScopedUsernames *bool `json:"organizationScopedUsernames,omitempty"`
}
//go:generate mockgen -typed -package domainmock -destination ./mock/organization_settings.mock.go . OrganizationSettingsRepository
type OrganizationSettingsRepository interface {
settingsRepository[OrganizationSettings]
organizationSettingsJSONChanges
}
type notificationSettingsJSONChanges interface {
SetSettingFields(value NotificationSettingsAttributes) database.Change
SetPasswordChange(value bool) db_json.JsonUpdate
}
type NotificationSettings struct {
Settings
NotificationSettingsAttributes
}
type NotificationSettingsAttributes struct {
PasswordChange *bool `json:"passwordChange,omitempty"`
}
//go:generate mockgen -typed -package domainmock -destination ./mock/notification_settings.mock.go . NotificationSettingsRepository
type NotificationSettingsRepository interface {
settingsRepository[NotificationSettings]
notificationSettingsJSONChanges
}
type legalAndSupportSettingsJSONChanges interface {
SetSettingFields(value LegalAndSupportSettingsAttributes) database.Change
SetTOSLink(value string) db_json.JsonUpdate
SetPrivacyPolicyLink(value string) db_json.JsonUpdate
SetHelpLink(value string) db_json.JsonUpdate
SetSupportEmail(value string) db_json.JsonUpdate
SetDocsLink(value string) db_json.JsonUpdate
SetCustomLink(value string) db_json.JsonUpdate
SetCustomLinkText(value string) db_json.JsonUpdate
}
type LegalAndSupportSettings struct {
Settings
LegalAndSupportSettingsAttributes
}
type LegalAndSupportSettingsAttributes struct {
TOSLink *string `json:"tosLink,omitempty"`
PrivacyPolicyLink *string `json:"privacyPolicyLink,omitempty"`
HelpLink *string `json:"helpLink,omitempty"`
SupportEmail *string `json:"supportEmail,omitempty"`
DocsLink *string `json:"docsLink,omitempty"`
CustomLink *string `json:"customLink,omitempty"`
CustomLinkText *string `json:"customLinkText,omitempty"`
}
//go:generate mockgen -typed -package domainmock -destination ./mock/legal_and_support_settings.mock.go . LegalAndSupportSettingsRepository
type LegalAndSupportSettingsRepository interface {
settingsRepository[LegalAndSupportSettings]
legalAndSupportSettingsJSONChanges
}
type SecretGeneratorSettings struct {
Settings
SecretGeneratorSettingsAttributes
}
//go:generate mockgen -typed -package domainmock -destination ./mock/secret_generator_settings.mock.go . SecretGeneratorSettingsRepository
type SecretGeneratorSettingsRepository interface {
settingsRepository[SecretGeneratorSettings]
secretGeneratorSettingsJSONChanges
}
type secretGeneratorSettingsJSONChanges interface {
SetSettingFields(value SecretGeneratorSettingsAttributes) database.Change
SetClientSecret(value ClientSecretAttributes) db_json.JsonUpdate
SetInitializeUserCode(value InitializeUserCodeAttributes) db_json.JsonUpdate
SetEmailVerificationCode(value EmailVerificationCodeAttributes) db_json.JsonUpdate
SetPhoneVerificationCode(value PhoneVerificationCodeAttributes) db_json.JsonUpdate
SetPasswordVerificationCode(value PasswordVerificationCodeAttributes) db_json.JsonUpdate
SetPasswordlessInitCode(value PasswordlessInitCodeAttributes) db_json.JsonUpdate
SetDomainVerification(value DomainVerificationAttributes) db_json.JsonUpdate
SetOTPSMS(value OTPSMSAttributes) db_json.JsonUpdate
SetOTPEmail(value OTPEmailAttributes) db_json.JsonUpdate
}
type SecretGeneratorSettingsAttributes struct {
ClientSecret *ClientSecretAttributes `json:"clientSecret,omitempty"`
InitializeUserCode *InitializeUserCodeAttributes `json:"initializeUserCode,omitempty"`
EmailVerificationCode *EmailVerificationCodeAttributes `json:"emailVerificationCode,omitempty"`
PhoneVerificationCode *PhoneVerificationCodeAttributes `json:"phoneVerificationCode,omitempty"`
PasswordVerificationCode *PasswordVerificationCodeAttributes `json:"passwordVerificationCode,omitempty"`
PasswordlessInitCode *PasswordlessInitCodeAttributes `json:"passwordlessInitCode,omitempty"`
DomainVerification *DomainVerificationAttributes `json:"domainVerification,omitempty"`
OTPSMS *OTPSMSAttributes `json:"otpSms,omitempty"`
OTPEmail *OTPEmailAttributes `json:"otpEmail,omitempty"`
}
type ClientSecretAttributes struct {
SecretGeneratorAttrs
}
type InitializeUserCodeAttributes struct {
SecretGeneratorAttrsWithExpiry
}
type EmailVerificationCodeAttributes struct {
SecretGeneratorAttrsWithExpiry
}
type PhoneVerificationCodeAttributes struct {
SecretGeneratorAttrsWithExpiry
}
type PasswordVerificationCodeAttributes struct {
SecretGeneratorAttrsWithExpiry
}
type PasswordlessInitCodeAttributes struct {
SecretGeneratorAttrsWithExpiry
}
type DomainVerificationAttributes struct {
SecretGeneratorAttrs
}
type OTPSMSAttributes struct {
SecretGeneratorAttrsWithExpiry
}
type OTPEmailAttributes struct {
SecretGeneratorAttrsWithExpiry
}
type SecretGeneratorAttrs struct {
Length *uint `json:"length,omitempty"`
IncludeLowerLetters *bool `json:"includeLowerLetters,omitempty"`
IncludeUpperLetters *bool `json:"includeUpperLetters,omitempty"`
IncludeDigits *bool `json:"includeDigits,omitempty"`
IncludeSymbols *bool `json:"includeSymbols,omitempty"`
}
type SecretGeneratorAttrsWithExpiry struct {
Expiry *time.Duration `json:"expiry,omitempty"`
SecretGeneratorAttrs
}