mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 19:17:32 +00:00
fix: use domain models for v2 eventstore (#1151)
* fix: use domain models for v2 eventstore * fix: user domain model * Update internal/api/grpc/admin/login_policy_converter.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * fix: converter Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
@@ -1,5 +1,40 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
caos_errors "github.com/caos/zitadel/internal/errors"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Human struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
*Password
|
||||
*Profile
|
||||
*Email
|
||||
*Phone
|
||||
*Address
|
||||
ExternalIDPs []*ExternalIDP
|
||||
InitCode *InitUserCode
|
||||
EmailCode *EmailCode
|
||||
PhoneCode *PhoneCode
|
||||
PasswordCode *PasswordCode
|
||||
OTP *OTP
|
||||
U2FTokens []*WebAuthNToken
|
||||
PasswordlessTokens []*WebAuthNToken
|
||||
U2FLogins []*WebAuthNLogin
|
||||
PasswordlessLogins []*WebAuthNLogin
|
||||
}
|
||||
|
||||
type InitUserCode struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
Code *crypto.CryptoValue
|
||||
Expiry time.Duration
|
||||
}
|
||||
|
||||
type Gender int32
|
||||
|
||||
const (
|
||||
@@ -14,3 +49,33 @@ const (
|
||||
func (f Gender) Valid() bool {
|
||||
return f >= 0 && f < genderCount
|
||||
}
|
||||
|
||||
func (u *Human) IsValid() bool {
|
||||
return u.Profile != nil && u.FirstName != "" && u.LastName != "" && u.Email != nil && u.Email.IsValid() && u.Phone == nil || (u.Phone != nil && u.Phone.PhoneNumber != "" && u.Phone.IsValid())
|
||||
}
|
||||
|
||||
func (u *Human) CheckOrgIAMPolicy(userName string, policy *OrgIAMPolicy) error {
|
||||
if policy == nil {
|
||||
return caos_errors.ThrowPreconditionFailed(nil, "DOMAIN-zSH7j", "Errors.Users.OrgIamPolicyNil")
|
||||
}
|
||||
if policy.UserLoginMustBeDomain && strings.Contains(userName, "@") {
|
||||
return caos_errors.ThrowPreconditionFailed(nil, "DOMAIN-se4sJ", "Errors.User.EmailAsUsernameNotAllowed")
|
||||
}
|
||||
if !policy.UserLoginMustBeDomain && u.Profile != nil && userName == "" && u.Email != nil {
|
||||
userName = u.EmailAddress
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Human) SetNamesAsDisplayname() {
|
||||
if u.Profile != nil && u.DisplayName == "" && u.FirstName != "" && u.LastName != "" {
|
||||
u.DisplayName = u.FirstName + " " + u.LastName
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Human) HashPasswordIfExisting(policy *PasswordComplexityPolicy, passwordAlg crypto.HashAlgorithm, onetime bool) error {
|
||||
if u.Password != nil {
|
||||
return u.Password.HashPasswordIfExisting(policy, passwordAlg, onetime)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
13
internal/v2/domain/human_address.go
Normal file
13
internal/v2/domain/human_address.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package domain
|
||||
|
||||
import es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
|
||||
type Address struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
Country string
|
||||
Locality string
|
||||
PostalCode string
|
||||
Region string
|
||||
StreetAddress string
|
||||
}
|
25
internal/v2/domain/human_email.go
Normal file
25
internal/v2/domain/human_email.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Email struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
EmailAddress string
|
||||
IsEmailVerified bool
|
||||
}
|
||||
|
||||
type EmailCode struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
Code *crypto.CryptoValue
|
||||
Expiry time.Duration
|
||||
}
|
||||
|
||||
func (e *Email) IsValid() bool {
|
||||
return e.EmailAddress != ""
|
||||
}
|
11
internal/v2/domain/human_external_idp.go
Normal file
11
internal/v2/domain/human_external_idp.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package domain
|
||||
|
||||
import es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
|
||||
type ExternalIDP struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
IDPConfigID string
|
||||
UserID string
|
||||
DisplayName string
|
||||
}
|
15
internal/v2/domain/human_otp.go
Normal file
15
internal/v2/domain/human_otp.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
)
|
||||
|
||||
type OTP struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
Secret *crypto.CryptoValue
|
||||
SecretString string
|
||||
Url string
|
||||
State MFAState
|
||||
}
|
43
internal/v2/domain/human_password.go
Normal file
43
internal/v2/domain/human_password.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Password struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
SecretString string
|
||||
SecretCrypto *crypto.CryptoValue
|
||||
ChangeRequired bool
|
||||
}
|
||||
|
||||
type PasswordCode struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
Code *crypto.CryptoValue
|
||||
Expiry time.Duration
|
||||
NotificationType NotificationType
|
||||
}
|
||||
|
||||
func (p *Password) HashPasswordIfExisting(policy *PasswordComplexityPolicy, passwordAlg crypto.HashAlgorithm, onetime bool) error {
|
||||
if p.SecretString == "" {
|
||||
return nil
|
||||
}
|
||||
if policy == nil {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "DOMAIN-s8ifS", "Errors.User.PasswordComplexityPolicy.NotFound")
|
||||
}
|
||||
if err := policy.Check(p.SecretString); err != nil {
|
||||
return err
|
||||
}
|
||||
secret, err := crypto.Hash([]byte(p.SecretString), passwordAlg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.SecretCrypto = secret
|
||||
p.ChangeRequired = onetime
|
||||
return nil
|
||||
}
|
41
internal/v2/domain/human_phone.go
Normal file
41
internal/v2/domain/human_phone.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/ttacon/libphonenumber"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRegion = "CH"
|
||||
)
|
||||
|
||||
type Phone struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
PhoneNumber string
|
||||
IsPhoneVerified bool
|
||||
}
|
||||
|
||||
type PhoneCode struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
Code *crypto.CryptoValue
|
||||
Expiry time.Duration
|
||||
}
|
||||
|
||||
func (p *Phone) IsValid() bool {
|
||||
err := p.formatPhone()
|
||||
return p.PhoneNumber != "" && err == nil
|
||||
}
|
||||
|
||||
func (p *Phone) formatPhone() error {
|
||||
phoneNr, err := libphonenumber.Parse(p.PhoneNumber, defaultRegion)
|
||||
if err != nil {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-so0wa", "Errors.User.Phone.Invalid")
|
||||
}
|
||||
p.PhoneNumber = libphonenumber.Format(phoneNr, libphonenumber.E164)
|
||||
return nil
|
||||
}
|
19
internal/v2/domain/human_profile.go
Normal file
19
internal/v2/domain/human_profile.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type Profile struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
FirstName string
|
||||
LastName string
|
||||
NickName string
|
||||
DisplayName string
|
||||
PreferredLanguage language.Tag
|
||||
Gender Gender
|
||||
PreferredLoginName string
|
||||
LoginNames []string
|
||||
}
|
40
internal/v2/domain/human_web_auth_n.go
Normal file
40
internal/v2/domain/human_web_auth_n.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package domain
|
||||
|
||||
import es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
|
||||
type WebAuthNToken struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
WebAuthNTokenID string
|
||||
CredentialCreationData []byte
|
||||
State MFAState
|
||||
Challenge string
|
||||
AllowedCredentialIDs [][]byte
|
||||
UserVerification UserVerificationRequirement
|
||||
KeyID []byte
|
||||
PublicKey []byte
|
||||
AttestationType string
|
||||
AAGUID []byte
|
||||
SignCount uint32
|
||||
WebAuthNTokenName string
|
||||
}
|
||||
|
||||
type WebAuthNLogin struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
CredentialAssertionData []byte
|
||||
Challenge string
|
||||
AllowedCredentialIDs [][]byte
|
||||
UserVerification UserVerificationRequirement
|
||||
//TODO: Add Auth Request
|
||||
//*model.AuthRequest
|
||||
}
|
||||
|
||||
type UserVerificationRequirement int32
|
||||
|
||||
const (
|
||||
UserVerificationRequirementUnspecified UserVerificationRequirement = iota
|
||||
UserVerificationRequirementRequired
|
||||
UserVerificationRequirementPreferred
|
||||
UserVerificationRequirementDiscouraged
|
||||
)
|
16
internal/v2/domain/iam_member.go
Normal file
16
internal/v2/domain/iam_member.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
)
|
||||
|
||||
type IAMMember struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
UserID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (i *IAMMember) IsValid() bool {
|
||||
return i.AggregateID != "" && i.UserID != "" && len(i.Roles) != 0
|
||||
}
|
@@ -1,5 +1,53 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
type IDPConfig struct {
|
||||
es_models.ObjectRoot
|
||||
IDPConfigID string
|
||||
Type IDPConfigType
|
||||
Name string
|
||||
StylingType IDPConfigStylingType
|
||||
State IDPConfigState
|
||||
OIDCConfig *OIDCIDPConfig
|
||||
}
|
||||
|
||||
type IDPConfigView struct {
|
||||
AggregateID string
|
||||
IDPConfigID string
|
||||
Name string
|
||||
StylingType IDPConfigStylingType
|
||||
State IDPConfigState
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
Sequence uint64
|
||||
IDPProviderType IdentityProviderType
|
||||
|
||||
IsOIDC bool
|
||||
OIDCClientID string
|
||||
OIDCClientSecret *crypto.CryptoValue
|
||||
OIDCIssuer string
|
||||
OIDCScopes []string
|
||||
OIDCIDPDisplayNameMapping OIDCMappingField
|
||||
OIDCUsernameMapping OIDCMappingField
|
||||
}
|
||||
|
||||
type OIDCIDPConfig struct {
|
||||
es_models.ObjectRoot
|
||||
IDPConfigID string
|
||||
ClientID string
|
||||
ClientSecret *crypto.CryptoValue
|
||||
ClientSecretString string
|
||||
Issuer string
|
||||
Scopes []string
|
||||
IDPDisplayNameMapping OIDCMappingField
|
||||
UsernameMapping OIDCMappingField
|
||||
}
|
||||
|
||||
type IDPConfigType int32
|
||||
|
||||
const (
|
||||
@@ -32,7 +80,8 @@ func (f IDPConfigState) Valid() bool {
|
||||
type IDPConfigStylingType int32
|
||||
|
||||
const (
|
||||
IDPConfigStylingTypeGoogle IDPConfigStylingType = iota + 1
|
||||
IDPConfigStylingTypeUnspecified IDPConfigStylingType = iota
|
||||
IDPConfigStylingTypeGoogle
|
||||
|
||||
idpConfigStylingTypeCount
|
||||
)
|
||||
|
14
internal/v2/domain/machine.go
Normal file
14
internal/v2/domain/machine.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package domain
|
||||
|
||||
import "github.com/caos/zitadel/internal/eventstore/models"
|
||||
|
||||
type Machine struct {
|
||||
models.ObjectRoot
|
||||
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
|
||||
func (sa *Machine) IsValid() bool {
|
||||
return sa.Name != ""
|
||||
}
|
13
internal/v2/domain/policy_label.go
Normal file
13
internal/v2/domain/policy_label.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
)
|
||||
|
||||
type LabelPolicy struct {
|
||||
models.ObjectRoot
|
||||
|
||||
Default bool
|
||||
PrimaryColor string
|
||||
SecondaryColor string
|
||||
}
|
@@ -1,5 +1,27 @@
|
||||
package domain
|
||||
|
||||
import "github.com/caos/zitadel/internal/eventstore/models"
|
||||
|
||||
type LoginPolicy struct {
|
||||
models.ObjectRoot
|
||||
|
||||
Default bool
|
||||
AllowUsernamePassword bool
|
||||
AllowRegister bool
|
||||
AllowExternalIdp bool
|
||||
IDPProviders []*IDPProvider
|
||||
ForceMFA bool
|
||||
SecondFactors []SecondFactorType
|
||||
MultiFactors []MultiFactorType
|
||||
PasswordlessType PasswordlessType
|
||||
}
|
||||
|
||||
type IDPProvider struct {
|
||||
models.ObjectRoot
|
||||
Type IdentityProviderType
|
||||
IDPConfigID string
|
||||
}
|
||||
|
||||
type PasswordlessType int32
|
||||
|
||||
const (
|
||||
|
12
internal/v2/domain/policy_org_iam.go
Normal file
12
internal/v2/domain/policy_org_iam.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
)
|
||||
|
||||
type OrgIAMPolicy struct {
|
||||
models.ObjectRoot
|
||||
|
||||
UserLoginMustBeDomain bool
|
||||
Default bool
|
||||
}
|
12
internal/v2/domain/policy_password_age.go
Normal file
12
internal/v2/domain/policy_password_age.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
)
|
||||
|
||||
type PasswordAgePolicy struct {
|
||||
models.ObjectRoot
|
||||
|
||||
MaxAgeDays uint64
|
||||
ExpireWarnDays uint64
|
||||
}
|
56
internal/v2/domain/policy_password_complexity.go
Normal file
56
internal/v2/domain/policy_password_complexity.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
hasStringLowerCase = regexp.MustCompile(`[a-z]`).MatchString
|
||||
hasStringUpperCase = regexp.MustCompile(`[A-Z]`).MatchString
|
||||
hasNumber = regexp.MustCompile(`[0-9]`).MatchString
|
||||
hasSymbol = regexp.MustCompile(`[^A-Za-z0-9]`).MatchString
|
||||
)
|
||||
|
||||
type PasswordComplexityPolicy struct {
|
||||
models.ObjectRoot
|
||||
|
||||
MinLength uint64
|
||||
HasLowercase bool
|
||||
HasUppercase bool
|
||||
HasNumber bool
|
||||
HasSymbol bool
|
||||
|
||||
Default bool
|
||||
}
|
||||
|
||||
func (p *PasswordComplexityPolicy) IsValid() error {
|
||||
if p.MinLength == 0 || p.MinLength > 72 {
|
||||
return caos_errs.ThrowInvalidArgument(nil, "MODEL-Lsp0e", "Errors.User.PasswordComplexityPolicy.MinLengthNotAllowed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PasswordComplexityPolicy) Check(password string) error {
|
||||
if p.MinLength != 0 && uint64(len(password)) < p.MinLength {
|
||||
return caos_errs.ThrowInvalidArgument(nil, "DOMAIN-HuJf6", "Errors.User.PasswordComplexityPolicy.MinLength")
|
||||
}
|
||||
|
||||
if p.HasLowercase && !hasStringLowerCase(password) {
|
||||
return caos_errs.ThrowInvalidArgument(nil, "DOMAIN-co3Xw", "Errors.User.PasswordComplexityPolicy.HasLower")
|
||||
}
|
||||
|
||||
if p.HasUppercase && !hasStringUpperCase(password) {
|
||||
return caos_errs.ThrowInvalidArgument(nil, "DOMAIN-VoaRj", "Errors.User.PasswordComplexityPolicy.HasUpper")
|
||||
}
|
||||
|
||||
if p.HasNumber && !hasNumber(password) {
|
||||
return caos_errs.ThrowInvalidArgument(nil, "DOMAIN-ZBv4H", "Errors.User.PasswordComplexityPolicy.HasNumber")
|
||||
}
|
||||
|
||||
if p.HasSymbol && !hasSymbol(password) {
|
||||
return caos_errs.ThrowInvalidArgument(nil, "DOMAIN-ZDLwA", "Errors.User.PasswordComplexityPolicy.HasSymbol")
|
||||
}
|
||||
return nil
|
||||
}
|
12
internal/v2/domain/policy_password_lockout.go
Normal file
12
internal/v2/domain/policy_password_lockout.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
)
|
||||
|
||||
type PasswordLockoutPolicy struct {
|
||||
models.ObjectRoot
|
||||
|
||||
MaxAttempts uint64
|
||||
ShowLockOutFailures bool
|
||||
}
|
@@ -1,5 +1,16 @@
|
||||
package domain
|
||||
|
||||
import es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
|
||||
type User struct {
|
||||
es_models.ObjectRoot
|
||||
State UserState
|
||||
UserName string
|
||||
|
||||
*Human
|
||||
*Machine
|
||||
}
|
||||
|
||||
type UserState int32
|
||||
|
||||
const (
|
||||
@@ -17,3 +28,13 @@ const (
|
||||
func (f UserState) Valid() bool {
|
||||
return f >= 0 && f < userStateCount
|
||||
}
|
||||
|
||||
func (u *User) IsValid() bool {
|
||||
if u.Human == nil && u.Machine == nil || u.UserName == "" {
|
||||
return false
|
||||
}
|
||||
if u.Human != nil {
|
||||
return u.Human.IsValid()
|
||||
}
|
||||
return u.Machine.IsValid()
|
||||
}
|
||||
|
Reference in New Issue
Block a user