chore: move the go code into a subfolder

This commit is contained in:
Florian Forster
2025-08-05 15:20:32 -07:00
parent 4ad22ba456
commit cd2921de26
2978 changed files with 373 additions and 300 deletions

View File

@@ -0,0 +1,56 @@
package model
import (
"encoding/json"
"github.com/zitadel/logging"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/zerrors"
)
type Address struct {
es_models.ObjectRoot
Country string `json:"country,omitempty"`
Locality string `json:"locality,omitempty"`
PostalCode string `json:"postalCode,omitempty"`
Region string `json:"region,omitempty"`
StreetAddress string `json:"streetAddress,omitempty"`
}
func (a *Address) Changes(changed *Address) map[string]interface{} {
changes := make(map[string]interface{}, 1)
if a.Country != changed.Country {
changes["country"] = changed.Country
}
if a.Locality != changed.Locality {
changes["locality"] = changed.Locality
}
if a.PostalCode != changed.PostalCode {
changes["postalCode"] = changed.PostalCode
}
if a.Region != changed.Region {
changes["region"] = changed.Region
}
if a.StreetAddress != changed.StreetAddress {
changes["streetAddress"] = changed.StreetAddress
}
return changes
}
func (u *Human) appendUserAddressChangedEvent(event *es_models.Event) error {
if u.Address == nil {
u.Address = new(Address)
}
return u.Address.setData(event)
}
func (a *Address) setData(event *es_models.Event) error {
a.ObjectRoot.AppendEvent(event)
if err := json.Unmarshal(event.Data, a); err != nil {
logging.Log("EVEN-clos0").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-so92s", "could not unmarshal event")
}
return nil
}

View File

@@ -0,0 +1,93 @@
package model
import (
"encoding/json"
"testing"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
func TestAddressChanges(t *testing.T) {
type args struct {
existingAddress *Address
newAddress *Address
}
type res struct {
changesLen int
}
tests := []struct {
name string
args args
res res
}{
{
name: "all fields changed",
args: args{
existingAddress: &Address{Country: "Country", Locality: "Locality", PostalCode: "PostalCode", Region: "Region", StreetAddress: "StreetAddress"},
newAddress: &Address{Country: "CountryChanged", Locality: "LocalityChanged", PostalCode: "PostalCodeChanged", Region: "RegionChanged", StreetAddress: "StreetAddressChanged"},
},
res: res{
changesLen: 5,
},
},
{
name: "no fields changed",
args: args{
existingAddress: &Address{Country: "Country", Locality: "Locality", PostalCode: "PostalCode", Region: "Region", StreetAddress: "StreetAddress"},
newAddress: &Address{Country: "Country", Locality: "Locality", PostalCode: "PostalCode", Region: "Region", StreetAddress: "StreetAddress"},
},
res: res{
changesLen: 0,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
changes := tt.args.existingAddress.Changes(tt.args.newAddress)
if len(changes) != tt.res.changesLen {
t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes))
}
})
}
}
func TestAppendUserAddressChangedEvent(t *testing.T) {
type args struct {
user *Human
address *Address
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append user address event",
args: args{
user: &Human{Address: &Address{Locality: "Locality", Country: "Country"}},
address: &Address{Locality: "LocalityChanged", PostalCode: "PostalCode"},
event: &es_models.Event{},
},
result: &Human{Address: &Address{Locality: "LocalityChanged", Country: "Country", PostalCode: "PostalCode"}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.address != nil {
data, _ := json.Marshal(tt.args.address)
tt.args.event.Data = data
}
tt.args.user.appendUserAddressChangedEvent(tt.args.event)
if tt.args.user.Address.Locality != tt.result.Address.Locality {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
}
if tt.args.user.Address.Country != tt.result.Address.Country {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
}
if tt.args.user.Address.PostalCode != tt.result.Address.PostalCode {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
}
})
}
}

View File

@@ -0,0 +1,31 @@
package model
import (
"net"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/zerrors"
)
type AuthRequest struct {
ID string `json:"id,omitempty"`
UserAgentID string `json:"userAgentID,omitempty"`
SelectedIDPConfigID string `json:"selectedIDPConfigID,omitempty"`
*BrowserInfo
}
type BrowserInfo struct {
UserAgent string `json:"userAgent,omitempty"`
AcceptLanguage string `json:"acceptLanguage,omitempty"`
RemoteIP net.IP `json:"remoteIP,omitempty"`
}
func (a *AuthRequest) SetData(event eventstore.Event) error {
if err := event.Unmarshal(a); err != nil {
logging.Log("EVEN-T5df6").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-yGmhh", "could not unmarshal event")
}
return nil
}

View File

@@ -0,0 +1,67 @@
package model
import (
"encoding/json"
"time"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/crypto"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/zerrors"
)
type Email struct {
es_models.ObjectRoot
EmailAddress string `json:"email,omitempty"`
IsEmailVerified bool `json:"-"`
}
type EmailCode struct {
es_models.ObjectRoot
Code *crypto.CryptoValue `json:"code,omitempty"`
Expiry time.Duration `json:"expiry,omitempty"`
}
func (e *Email) Changes(changed *Email) map[string]interface{} {
changes := make(map[string]interface{}, 1)
if changed.EmailAddress != "" && e.EmailAddress != changed.EmailAddress {
changes["email"] = changed.EmailAddress
}
return changes
}
func (u *Human) appendUserEmailChangedEvent(event *es_models.Event) error {
u.Email = new(Email)
return u.Email.setData(event)
}
func (u *Human) appendUserEmailCodeAddedEvent(event *es_models.Event) error {
u.EmailCode = new(EmailCode)
return u.EmailCode.SetData(event)
}
func (u *Human) appendUserEmailVerifiedEvent() {
u.IsEmailVerified = true
}
func (a *Email) setData(event *es_models.Event) error {
a.ObjectRoot.AppendEvent(event)
if err := json.Unmarshal(event.Data, a); err != nil {
logging.Log("EVEN-dlo9s").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-sl9xw", "could not unmarshal event")
}
return nil
}
func (a *EmailCode) SetData(event *es_models.Event) error {
a.ObjectRoot.AppendEvent(event)
a.CreationDate = event.CreationDate
if err := json.Unmarshal(event.Data, a); err != nil {
logging.Log("EVEN-lo9s").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-s8uws", "could not unmarshal event")
}
return nil
}

View File

@@ -0,0 +1,153 @@
package model
import (
"encoding/json"
"testing"
"time"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
func TestEmailChanges(t *testing.T) {
type args struct {
existingEmail *Email
new *Email
}
type res struct {
changesLen int
}
tests := []struct {
name string
args args
res res
}{
{
name: "all fields changed",
args: args{
existingEmail: &Email{EmailAddress: "Email", IsEmailVerified: true},
new: &Email{EmailAddress: "EmailChanged", IsEmailVerified: false},
},
res: res{
changesLen: 1,
},
},
{
name: "no fields changed",
args: args{
existingEmail: &Email{EmailAddress: "Email", IsEmailVerified: true},
new: &Email{EmailAddress: "Email", IsEmailVerified: false},
},
res: res{
changesLen: 0,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
changes := tt.args.existingEmail.Changes(tt.args.new)
if len(changes) != tt.res.changesLen {
t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes))
}
})
}
}
func TestAppendUserEmailChangedEvent(t *testing.T) {
type args struct {
user *Human
email *Email
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append user email event",
args: args{
user: &Human{Email: &Email{EmailAddress: "EmailAddress"}},
email: &Email{EmailAddress: "EmailAddressChanged"},
event: &es_models.Event{},
},
result: &Human{Email: &Email{EmailAddress: "EmailAddressChanged"}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.email != nil {
data, _ := json.Marshal(tt.args.email)
tt.args.event.Data = data
}
tt.args.user.appendUserEmailChangedEvent(tt.args.event)
if tt.args.user.Email.EmailAddress != tt.result.Email.EmailAddress {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
}
})
}
}
func TestAppendUserEmailCodeAddedEvent(t *testing.T) {
type args struct {
user *Human
code *EmailCode
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append user email code added event",
args: args{
user: &Human{Email: &Email{EmailAddress: "EmailAddress"}},
code: &EmailCode{Expiry: time.Hour * 1},
event: &es_models.Event{},
},
result: &Human{EmailCode: &EmailCode{Expiry: time.Hour * 1}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.code != nil {
data, _ := json.Marshal(tt.args.code)
tt.args.event.Data = data
}
tt.args.user.appendUserEmailCodeAddedEvent(tt.args.event)
if tt.args.user.EmailCode.Expiry != tt.result.EmailCode.Expiry {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
}
})
}
}
func TestAppendUserEmailVerifiedEvent(t *testing.T) {
type args struct {
user *Human
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append user email event",
args: args{
user: &Human{Email: &Email{EmailAddress: "EmailAddress"}},
event: &es_models.Event{},
},
result: &Human{Email: &Email{EmailAddress: "EmailAddress", IsEmailVerified: true}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.args.user.appendUserEmailVerifiedEvent()
if tt.args.user.Email.IsEmailVerified != tt.result.Email.IsEmailVerified {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
}
})
}
}

View File

@@ -0,0 +1,60 @@
package model
import (
"encoding/json"
"github.com/zitadel/logging"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/zerrors"
)
type ExternalIDP struct {
es_models.ObjectRoot
IDPConfigID string `json:"idpConfigId,omitempty"`
UserID string `json:"userId,omitempty"`
DisplayName string `json:"displayName,omitempty"`
}
func GetExternalIDP(idps []*ExternalIDP, id string) (int, *ExternalIDP) {
for i, idp := range idps {
if idp.UserID == id {
return i, idp
}
}
return -1, nil
}
func (u *Human) appendExternalIDPAddedEvent(event *es_models.Event) error {
idp := new(ExternalIDP)
err := idp.setData(event)
if err != nil {
return err
}
idp.ObjectRoot.CreationDate = event.CreationDate
u.ExternalIDPs = append(u.ExternalIDPs, idp)
return nil
}
func (u *Human) appendExternalIDPRemovedEvent(event *es_models.Event) error {
idp := new(ExternalIDP)
err := idp.setData(event)
if err != nil {
return err
}
if i, externalIdp := GetExternalIDP(u.ExternalIDPs, idp.UserID); externalIdp != nil {
u.ExternalIDPs[i] = u.ExternalIDPs[len(u.ExternalIDPs)-1]
u.ExternalIDPs[len(u.ExternalIDPs)-1] = nil
u.ExternalIDPs = u.ExternalIDPs[:len(u.ExternalIDPs)-1]
}
return nil
}
func (pw *ExternalIDP) setData(event *es_models.Event) error {
pw.ObjectRoot.AppendEvent(event)
if err := json.Unmarshal(event.Data, pw); err != nil {
logging.Log("EVEN-Msi9d").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-A9osf", "could not unmarshal event")
}
return nil
}

View File

@@ -0,0 +1,90 @@
package model
import (
"encoding/json"
"testing"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
func TestAppendExternalIDPAddedEvent(t *testing.T) {
type args struct {
user *Human
externalIDP *ExternalIDP
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append external idp added event",
args: args{
user: &Human{},
externalIDP: &ExternalIDP{IDPConfigID: "IDPConfigID", UserID: "UserID", DisplayName: "DisplayName"},
event: &es_models.Event{},
},
result: &Human{ExternalIDPs: []*ExternalIDP{{IDPConfigID: "IDPConfigID", UserID: "UserID", DisplayName: "DisplayName"}}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.externalIDP != nil {
data, _ := json.Marshal(tt.args.externalIDP)
tt.args.event.Data = data
}
tt.args.user.appendExternalIDPAddedEvent(tt.args.event)
if len(tt.args.user.ExternalIDPs) == 0 {
t.Error("got wrong result expected external idps on user ")
}
if tt.args.user.ExternalIDPs[0].UserID != tt.result.ExternalIDPs[0].UserID {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.ExternalIDPs[0].UserID, tt.args.user.ExternalIDPs[0].UserID)
}
if tt.args.user.ExternalIDPs[0].IDPConfigID != tt.result.ExternalIDPs[0].IDPConfigID {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.ExternalIDPs[0].IDPConfigID, tt.args.user.ExternalIDPs[0].IDPConfigID)
}
if tt.args.user.ExternalIDPs[0].DisplayName != tt.result.ExternalIDPs[0].DisplayName {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.ExternalIDPs[0].DisplayName, tt.args.user.ExternalIDPs[0].IDPConfigID)
}
})
}
}
func TestAppendExternalIDPRemovedEvent(t *testing.T) {
type args struct {
user *Human
externalIDP *ExternalIDP
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append external idp removed event",
args: args{
user: &Human{
ExternalIDPs: []*ExternalIDP{
{IDPConfigID: "IDPConfigID", UserID: "UserID", DisplayName: "DisplayName"},
}},
externalIDP: &ExternalIDP{UserID: "UserID"},
event: &es_models.Event{},
},
result: &Human{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.externalIDP != nil {
data, _ := json.Marshal(tt.args.externalIDP)
tt.args.event.Data = data
}
tt.args.user.appendExternalIDPRemovedEvent(tt.args.event)
if len(tt.args.user.ExternalIDPs) != 0 {
t.Error("got wrong result expected 0 external idps on user ")
}
})
}
}

View File

@@ -0,0 +1,54 @@
package model
import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/user/model"
"github.com/zitadel/zitadel/internal/zerrors"
)
type OTP struct {
es_models.ObjectRoot
Secret *crypto.CryptoValue `json:"otpSecret,omitempty"`
State int32 `json:"-"`
}
type OTPVerified struct {
UserAgentID string `json:"userAgentID,omitempty"`
}
func (u *Human) appendOTPAddedEvent(event eventstore.Event) error {
u.OTP = &OTP{
State: int32(model.MFAStateNotReady),
}
return u.OTP.setData(event)
}
func (u *Human) appendOTPVerifiedEvent() {
u.OTP.State = int32(model.MFAStateReady)
}
func (u *Human) appendOTPRemovedEvent() {
u.OTP = nil
}
func (o *OTP) setData(event eventstore.Event) error {
o.ObjectRoot.AppendEvent(event)
if err := event.Unmarshal(o); err != nil {
logging.Log("EVEN-d9soe").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-lo023", "could not unmarshal event")
}
return nil
}
func (o *OTPVerified) SetData(event eventstore.Event) error {
if err := event.Unmarshal(o); err != nil {
logging.Log("EVEN-BF421").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-GB6hj", "could not unmarshal event")
}
return nil
}

View File

@@ -0,0 +1,110 @@
package model
import (
"encoding/json"
"testing"
"github.com/zitadel/zitadel/internal/crypto"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/user/model"
)
func TestAppendMFAOTPAddedEvent(t *testing.T) {
type args struct {
user *Human
otp *OTP
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append user otp event",
args: args{
user: &Human{},
otp: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}},
event: &es_models.Event{},
},
result: &Human{OTP: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}, State: int32(model.MFAStateNotReady)}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.otp != nil {
data, _ := json.Marshal(tt.args.otp)
tt.args.event.Data = data
}
tt.args.user.appendOTPAddedEvent(tt.args.event)
if tt.args.user.OTP.State != tt.result.OTP.State {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.OTP.State, tt.args.user.OTP.State)
}
})
}
}
func TestAppendMFAOTPVerifyEvent(t *testing.T) {
type args struct {
user *Human
otp *OTP
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append otp verify event",
args: args{
user: &Human{OTP: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}}},
otp: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}},
event: &es_models.Event{},
},
result: &Human{OTP: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}, State: int32(model.MFAStateReady)}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.otp != nil {
data, _ := json.Marshal(tt.args.otp)
tt.args.event.Data = data
}
tt.args.user.appendOTPVerifiedEvent()
if tt.args.user.OTP.State != tt.result.OTP.State {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.OTP.State, tt.args.user.OTP.State)
}
})
}
}
func TestAppendMFAOTPRemoveEvent(t *testing.T) {
type args struct {
user *Human
otp *OTP
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append otp verify event",
args: args{
user: &Human{OTP: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}}},
event: &es_models.Event{},
},
result: &Human{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.args.user.appendOTPRemovedEvent()
if tt.args.user.OTP != nil {
t.Errorf("got wrong result: actual: %v ", tt.result.OTP)
}
})
}
}

View File

@@ -0,0 +1,76 @@
package model
import (
"time"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/zerrors"
)
type Password struct {
es_models.ObjectRoot
Secret *crypto.CryptoValue `json:"secret,omitempty"`
EncodedHash string `json:"encodedHash,omitempty"`
ChangeRequired bool `json:"changeRequired,omitempty"`
}
type PasswordCode struct {
es_models.ObjectRoot
Code *crypto.CryptoValue `json:"code,omitempty"`
Expiry time.Duration `json:"expiry,omitempty"`
NotificationType int32 `json:"notificationType,omitempty"`
}
type PasswordChange struct {
Password
UserAgentID string `json:"userAgentID,omitempty"`
}
func (u *Human) appendUserPasswordChangedEvent(event eventstore.Event) error {
u.Password = new(Password)
err := u.Password.setData(event)
if err != nil {
return err
}
u.Password.ObjectRoot.CreationDate = event.CreatedAt()
return nil
}
func (u *Human) appendPasswordSetRequestedEvent(event eventstore.Event) error {
u.PasswordCode = new(PasswordCode)
return u.PasswordCode.SetData(event)
}
func (pw *Password) setData(event eventstore.Event) error {
pw.ObjectRoot.AppendEvent(event)
if err := event.Unmarshal(pw); err != nil {
logging.Log("EVEN-dks93").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-sl9xlo2rsw", "could not unmarshal event")
}
return nil
}
func (c *PasswordCode) SetData(event eventstore.Event) error {
c.ObjectRoot.AppendEvent(event)
c.CreationDate = event.CreatedAt()
if err := event.Unmarshal(c); err != nil {
logging.Log("EVEN-lo0y2").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-q21dr", "could not unmarshal event")
}
return nil
}
func (pw *PasswordChange) SetData(event eventstore.Event) error {
if err := event.Unmarshal(pw); err != nil {
logging.Log("EVEN-ADs31").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-BDd32", "could not unmarshal event")
}
pw.ObjectRoot.AppendEvent(event)
return nil
}

View File

@@ -0,0 +1,79 @@
package model
import (
"encoding/json"
"testing"
"time"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
func TestAppendUserPasswordChangedEvent(t *testing.T) {
type args struct {
user *Human
pw *Password
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append init user code event",
args: args{
user: &Human{},
pw: &Password{ChangeRequired: true},
event: &es_models.Event{},
},
result: &Human{Password: &Password{ChangeRequired: true}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.pw != nil {
data, _ := json.Marshal(tt.args.pw)
tt.args.event.Data = data
}
tt.args.user.appendUserPasswordChangedEvent(tt.args.event)
if tt.args.user.Password.ChangeRequired != tt.result.Password.ChangeRequired {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
}
})
}
}
func TestAppendPasswordSetRequestedEvent(t *testing.T) {
type args struct {
user *Human
code *PasswordCode
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append user phone code added event",
args: args{
user: &Human{Phone: &Phone{PhoneNumber: "PhoneNumber"}},
code: &PasswordCode{Expiry: time.Hour * 1},
event: &es_models.Event{},
},
result: &Human{PasswordCode: &PasswordCode{Expiry: time.Hour * 1}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.code != nil {
data, _ := json.Marshal(tt.args.code)
tt.args.event.Data = data
}
tt.args.user.appendPasswordSetRequestedEvent(tt.args.event)
if tt.args.user.PasswordCode.Expiry != tt.result.PasswordCode.Expiry {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
}
})
}
}

View File

@@ -0,0 +1,72 @@
package model
import (
"encoding/json"
"time"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/crypto"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/zerrors"
)
type Phone struct {
es_models.ObjectRoot
PhoneNumber string `json:"phone,omitempty"`
IsPhoneVerified bool `json:"-"`
}
type PhoneCode struct {
es_models.ObjectRoot
Code *crypto.CryptoValue `json:"code,omitempty"`
Expiry time.Duration `json:"expiry,omitempty"`
}
func (p *Phone) Changes(changed *Phone) map[string]interface{} {
changes := make(map[string]interface{}, 1)
if changed.PhoneNumber != "" && p.PhoneNumber != changed.PhoneNumber {
changes["phone"] = changed.PhoneNumber
}
return changes
}
func (u *Human) appendUserPhoneChangedEvent(event *es_models.Event) error {
u.Phone = new(Phone)
return u.Phone.setData(event)
}
func (u *Human) appendUserPhoneCodeAddedEvent(event *es_models.Event) error {
u.PhoneCode = new(PhoneCode)
return u.PhoneCode.SetData(event)
}
func (u *Human) appendUserPhoneVerifiedEvent() {
u.IsPhoneVerified = true
}
func (u *Human) appendUserPhoneRemovedEvent() {
u.Phone = nil
u.PhoneCode = nil
}
func (p *Phone) setData(event *es_models.Event) error {
p.ObjectRoot.AppendEvent(event)
if err := json.Unmarshal(event.Data, p); err != nil {
logging.Log("EVEN-lco9s").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-lre56", "could not unmarshal event")
}
return nil
}
func (c *PhoneCode) SetData(event *es_models.Event) error {
c.ObjectRoot.AppendEvent(event)
c.CreationDate = event.CreationDate
if err := json.Unmarshal(event.Data, c); err != nil {
logging.Log("EVEN-sk8ws").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-7hdj3", "could not unmarshal event")
}
return nil
}

View File

@@ -0,0 +1,153 @@
package model
import (
"encoding/json"
"testing"
"time"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
func TestPhoneChanges(t *testing.T) {
type args struct {
existingPhone *Phone
newPhone *Phone
}
type res struct {
changesLen int
}
tests := []struct {
name string
args args
res res
}{
{
name: "all fields changed",
args: args{
existingPhone: &Phone{PhoneNumber: "Phone", IsPhoneVerified: true},
newPhone: &Phone{PhoneNumber: "PhoneChanged", IsPhoneVerified: false},
},
res: res{
changesLen: 1,
},
},
{
name: "no fields changed",
args: args{
existingPhone: &Phone{PhoneNumber: "Phone", IsPhoneVerified: true},
newPhone: &Phone{PhoneNumber: "Phone", IsPhoneVerified: false},
},
res: res{
changesLen: 0,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
changes := tt.args.existingPhone.Changes(tt.args.newPhone)
if len(changes) != tt.res.changesLen {
t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes))
}
})
}
}
func TestAppendUserPhoneChangedEvent(t *testing.T) {
type args struct {
user *Human
phone *Phone
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append user phone event",
args: args{
user: &Human{Phone: &Phone{PhoneNumber: "PhoneNumber"}},
phone: &Phone{PhoneNumber: "PhoneNumberChanged"},
event: &es_models.Event{},
},
result: &Human{Phone: &Phone{PhoneNumber: "PhoneNumberChanged"}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.phone != nil {
data, _ := json.Marshal(tt.args.phone)
tt.args.event.Data = data
}
tt.args.user.appendUserPhoneChangedEvent(tt.args.event)
if tt.args.user.Phone.PhoneNumber != tt.result.Phone.PhoneNumber {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
}
})
}
}
func TestAppendUserPhoneCodeAddedEvent(t *testing.T) {
type args struct {
user *Human
code *PhoneCode
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append user phone code added event",
args: args{
user: &Human{Phone: &Phone{PhoneNumber: "PhoneNumber"}},
code: &PhoneCode{Expiry: time.Hour * 1},
event: &es_models.Event{},
},
result: &Human{PhoneCode: &PhoneCode{Expiry: time.Hour * 1}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.code != nil {
data, _ := json.Marshal(tt.args.code)
tt.args.event.Data = data
}
tt.args.user.appendUserPhoneCodeAddedEvent(tt.args.event)
if tt.args.user.PhoneCode.Expiry != tt.result.PhoneCode.Expiry {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
}
})
}
}
func TestAppendUserPhoneVerifiedEvent(t *testing.T) {
type args struct {
user *Human
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append user phone event",
args: args{
user: &Human{Phone: &Phone{PhoneNumber: "PhoneNumber"}},
event: &es_models.Event{},
},
result: &Human{Phone: &Phone{PhoneNumber: "PhoneNumber", IsPhoneVerified: true}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.args.user.appendUserPhoneVerifiedEvent()
if tt.args.user.Phone.IsPhoneVerified != tt.result.Phone.IsPhoneVerified {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
}
})
}
}

View File

@@ -0,0 +1,73 @@
package model
import (
"encoding/json"
"golang.org/x/text/language"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type Profile struct {
es_models.ObjectRoot
FirstName string `json:"firstName,omitempty"`
LastName string `json:"lastName,omitempty"`
NickName string `json:"nickName,omitempty"`
DisplayName string `json:"displayName,omitempty"`
PreferredLanguage LanguageTag `json:"preferredLanguage,omitempty"`
Gender int32 `json:"gender,omitempty"`
}
func (p *Profile) Changes(changed *Profile) map[string]interface{} {
changes := make(map[string]interface{}, 1)
if changed.FirstName != "" && p.FirstName != changed.FirstName {
changes["firstName"] = changed.FirstName
}
if changed.LastName != "" && p.LastName != changed.LastName {
changes["lastName"] = changed.LastName
}
if changed.NickName != p.NickName {
changes["nickName"] = changed.NickName
}
if changed.DisplayName != "" && p.DisplayName != changed.DisplayName {
changes["displayName"] = changed.DisplayName
}
if language.Tag(changed.PreferredLanguage) != language.Und && changed.PreferredLanguage != p.PreferredLanguage {
changes["preferredLanguage"] = changed.PreferredLanguage
}
if changed.Gender != p.Gender {
changes["gender"] = changed.Gender
}
return changes
}
type LanguageTag language.Tag
func (t *LanguageTag) UnmarshalJSON(data []byte) error {
var tag string
err := json.Unmarshal(data, &tag)
if err != nil {
return err
}
*t = LanguageTag(language.Make(tag))
return nil
}
func (t LanguageTag) MarshalJSON() ([]byte, error) {
return json.Marshal(language.Tag(t))
}
func (t *LanguageTag) MarshalBinary() ([]byte, error) {
if t == nil {
return nil, nil
}
return []byte(language.Tag(*t).String()), nil
}
// UnmarshalBinary modifies the receiver so it must take a pointer receiver.
func (t *LanguageTag) UnmarshalBinary(data []byte) error {
*t = LanguageTag(language.Make(string(data)))
return nil
}

View File

@@ -0,0 +1,63 @@
package model
import (
"testing"
"golang.org/x/text/language"
user_model "github.com/zitadel/zitadel/internal/user/model"
)
func TestProfileChanges(t *testing.T) {
type args struct {
existingProfile *Profile
newProfile *Profile
}
type res struct {
changesLen int
}
tests := []struct {
name string
args args
res res
}{
{
name: "all attributes changed",
args: args{
existingProfile: &Profile{FirstName: "FirstName", LastName: "LastName", NickName: "NickName", DisplayName: "DisplayName", PreferredLanguage: LanguageTag(language.German), Gender: int32(user_model.GenderFemale)},
newProfile: &Profile{FirstName: "FirstNameChanged", LastName: "LastNameChanged", NickName: "NickNameChanged", DisplayName: "DisplayNameChanged", PreferredLanguage: LanguageTag(language.English), Gender: int32(user_model.GenderMale)},
},
res: res{
changesLen: 6,
},
},
{
name: "no changes",
args: args{
existingProfile: &Profile{FirstName: "FirstName", LastName: "LastName", NickName: "NickName", DisplayName: "DisplayName", PreferredLanguage: LanguageTag(language.German), Gender: int32(user_model.GenderFemale)},
newProfile: &Profile{FirstName: "FirstName", LastName: "LastName", NickName: "NickName", DisplayName: "DisplayName", PreferredLanguage: LanguageTag(language.German), Gender: int32(user_model.GenderFemale)},
},
res: res{
changesLen: 0,
},
},
{
name: "empty names",
args: args{
existingProfile: &Profile{FirstName: "FirstName", LastName: "LastName", NickName: "NickName", DisplayName: "DisplayName", PreferredLanguage: LanguageTag(language.German), Gender: int32(user_model.GenderFemale)},
newProfile: &Profile{FirstName: "", LastName: "", NickName: "NickName", DisplayName: "DisplayName", PreferredLanguage: LanguageTag(language.German), Gender: int32(user_model.GenderFemale)},
},
res: res{
changesLen: 0,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
changes := tt.args.existingProfile.Changes(tt.args.newProfile)
if len(changes) != tt.res.changesLen {
t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes))
}
})
}
}

View File

@@ -0,0 +1,55 @@
package model
import (
"encoding/json"
"time"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
user_repo "github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/zerrors"
)
type Token struct {
es_models.ObjectRoot
TokenID string `json:"tokenId" gorm:"column:token_id"`
ApplicationID string `json:"applicationId" gorm:"column:application_id"`
UserAgentID string `json:"userAgentId" gorm:"column:user_agent_id"`
Audience database.TextArray[string] `json:"audience" gorm:"column:audience"`
Scopes database.TextArray[string] `json:"scopes" gorm:"column:scopes"`
Expiration time.Time `json:"expiration" gorm:"column:expiration"`
PreferredLanguage string `json:"preferredLanguage" gorm:"column:preferred_language"`
}
func (t *Token) AppendEvents(events ...*es_models.Event) error {
for _, event := range events {
if err := t.AppendEvent(event); err != nil {
return err
}
}
return nil
}
func (t *Token) AppendEvent(event *es_models.Event) error {
if event.Typ == user_repo.UserTokenAddedType {
err := t.setData(event)
if err != nil {
return err
}
t.CreationDate = event.CreationDate
}
return nil
}
func (t *Token) setData(event *es_models.Event) error {
t.ObjectRoot.AppendEvent(event)
if err := json.Unmarshal(event.Data, t); err != nil {
logging.Log("EVEN-4Fm9s").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-5Gms9", "could not unmarshal event")
}
return nil
}

View File

@@ -0,0 +1,111 @@
package model
import (
"encoding/json"
"strings"
"github.com/zitadel/logging"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/user/model"
"github.com/zitadel/zitadel/internal/zerrors"
)
const (
UserVersion = "v2"
)
type User struct {
es_models.ObjectRoot
State int32 `json:"-"`
UserName string `json:"userName"`
*Human
*Machine
}
func (u *User) AppendEvents(events ...*es_models.Event) error {
for _, event := range events {
if err := u.AppendEvent(event); err != nil {
return err
}
}
return nil
}
func (u *User) AppendEvent(event *es_models.Event) error {
u.ObjectRoot.AppendEvent(event)
switch event.Type() {
case user.UserV1AddedType,
user.HumanAddedType,
user.MachineAddedEventType,
user.UserV1RegisteredType,
user.HumanRegisteredType,
user.UserV1ProfileChangedType,
user.UserDomainClaimedType,
user.UserUserNameChangedType:
err := u.setData(event)
if err != nil {
return err
}
case user.UserDeactivatedType:
u.appendDeactivatedEvent()
case user.UserReactivatedType:
u.appendReactivatedEvent()
case user.UserLockedType:
u.appendLockedEvent()
case user.UserUnlockedType:
u.appendUnlockedEvent()
case user.UserRemovedType:
u.appendRemovedEvent()
}
if u.Human != nil {
u.Human.user = u
return u.Human.AppendEvent(event)
} else if u.Machine != nil {
u.Machine.user = u
return u.Machine.AppendEvent(event)
}
if strings.HasPrefix(string(event.Typ), "user.human") || event.AggregateVersion == "v1" {
u.Human = &Human{user: u}
return u.Human.AppendEvent(event)
}
if strings.HasPrefix(string(event.Typ), "user.machine") {
u.Machine = &Machine{user: u}
return u.Machine.AppendEvent(event)
}
return zerrors.ThrowNotFound(nil, "MODEL-x9TaX", "Errors.UserType.Undefined")
}
func (u *User) setData(event *es_models.Event) error {
if err := json.Unmarshal(event.Data, u); err != nil {
logging.Log("EVEN-ZDzQy").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-yGmhh", "could not unmarshal event")
}
return nil
}
func (u *User) appendDeactivatedEvent() {
u.State = int32(model.UserStateInactive)
}
func (u *User) appendReactivatedEvent() {
u.State = int32(model.UserStateActive)
}
func (u *User) appendLockedEvent() {
u.State = int32(model.UserStateLocked)
}
func (u *User) appendUnlockedEvent() {
u.State = int32(model.UserStateActive)
}
func (u *User) appendRemovedEvent() {
u.State = int32(model.UserStateDeleted)
}

View File

@@ -0,0 +1,185 @@
package model
import (
"encoding/json"
"time"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/crypto"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/user/model"
"github.com/zitadel/zitadel/internal/zerrors"
)
type Human struct {
user *User `json:"-"`
*Password
*Profile
*Email
*Phone
*Address
ExternalIDPs []*ExternalIDP `json:"-"`
InitCode *InitUserCode `json:"-"`
EmailCode *EmailCode `json:"-"`
PhoneCode *PhoneCode `json:"-"`
PasswordCode *PasswordCode `json:"-"`
OTP *OTP `json:"-"`
U2FTokens []*WebAuthNToken `json:"-"`
PasswordlessTokens []*WebAuthNToken `json:"-"`
U2FLogins []*WebAuthNLogin `json:"-"`
PasswordlessLogins []*WebAuthNLogin `json:"-"`
}
type InitUserCode struct {
es_models.ObjectRoot
Code *crypto.CryptoValue `json:"code,omitempty"`
Expiry time.Duration `json:"expiry,omitempty"`
}
func (p *Human) AppendEvents(events ...*es_models.Event) error {
for _, event := range events {
if err := p.AppendEvent(event); err != nil {
return err
}
}
return nil
}
func (h *Human) AppendEvent(event *es_models.Event) (err error) {
switch event.Type() {
case user.UserV1AddedType,
user.UserV1RegisteredType,
user.UserV1ProfileChangedType,
user.HumanAddedType,
user.HumanRegisteredType,
user.HumanProfileChangedType:
err = h.setData(event)
case user.UserV1InitialCodeAddedType,
user.HumanInitialCodeAddedType:
err = h.appendInitUsercodeCreatedEvent(event)
case user.UserV1PasswordChangedType,
user.HumanPasswordChangedType:
err = h.appendUserPasswordChangedEvent(event)
case user.UserV1PasswordCodeAddedType,
user.HumanPasswordCodeAddedType:
err = h.appendPasswordSetRequestedEvent(event)
case user.UserV1EmailChangedType,
user.HumanEmailChangedType:
err = h.appendUserEmailChangedEvent(event)
case user.UserV1EmailCodeAddedType,
user.HumanEmailCodeAddedType:
err = h.appendUserEmailCodeAddedEvent(event)
case user.UserV1EmailVerifiedType,
user.HumanEmailVerifiedType:
h.appendUserEmailVerifiedEvent()
case user.UserV1PhoneChangedType,
user.HumanPhoneChangedType:
err = h.appendUserPhoneChangedEvent(event)
case user.UserV1PhoneCodeAddedType,
user.HumanPhoneCodeAddedType:
err = h.appendUserPhoneCodeAddedEvent(event)
case user.UserV1PhoneVerifiedType,
user.HumanPhoneVerifiedType:
h.appendUserPhoneVerifiedEvent()
case user.UserV1PhoneRemovedType,
user.HumanPhoneRemovedType:
h.appendUserPhoneRemovedEvent()
case user.UserV1AddressChangedType,
user.HumanAddressChangedType:
err = h.appendUserAddressChangedEvent(event)
case user.UserV1MFAOTPAddedType,
user.HumanMFAOTPAddedType:
err = h.appendOTPAddedEvent(event)
case user.UserV1MFAOTPVerifiedType,
user.HumanMFAOTPVerifiedType:
h.appendOTPVerifiedEvent()
case user.UserV1MFAOTPRemovedType,
user.HumanMFAOTPRemovedType:
h.appendOTPRemovedEvent()
case user.UserIDPLinkAddedType:
err = h.appendExternalIDPAddedEvent(event)
case user.UserIDPLinkRemovedType, user.UserIDPLinkCascadeRemovedType:
err = h.appendExternalIDPRemovedEvent(event)
case user.HumanU2FTokenAddedType:
err = h.appendU2FAddedEvent(event)
case user.HumanU2FTokenVerifiedType:
err = h.appendU2FVerifiedEvent(event)
case user.HumanU2FTokenSignCountChangedType:
err = h.appendU2FChangeSignCountEvent(event)
case user.HumanU2FTokenRemovedType:
err = h.appendU2FRemovedEvent(event)
case user.HumanPasswordlessTokenAddedType:
err = h.appendPasswordlessAddedEvent(event)
case user.HumanPasswordlessTokenVerifiedType:
err = h.appendPasswordlessVerifiedEvent(event)
case user.HumanPasswordlessTokenSignCountChangedType:
err = h.appendPasswordlessChangeSignCountEvent(event)
case user.HumanPasswordlessTokenRemovedType:
err = h.appendPasswordlessRemovedEvent(event)
case user.HumanU2FTokenBeginLoginType:
err = h.appendU2FLoginEvent(event)
case user.HumanPasswordlessTokenBeginLoginType:
err = h.appendPasswordlessLoginEvent(event)
}
if err != nil {
return err
}
h.ComputeObject()
return nil
}
func (h *Human) ComputeObject() {
if h.user.State == int32(model.UserStateUnspecified) || h.user.State == int32(model.UserStateInitial) {
if h.Email != nil && h.IsEmailVerified {
h.user.State = int32(model.UserStateActive)
} else {
h.user.State = int32(model.UserStateInitial)
}
}
if h.Password != nil && h.Password.ObjectRoot.IsZero() {
h.Password.ObjectRoot = h.user.ObjectRoot
}
if h.Profile != nil && h.Profile.ObjectRoot.IsZero() {
h.Profile.ObjectRoot = h.user.ObjectRoot
}
if h.Email != nil && h.Email.ObjectRoot.IsZero() {
h.Email.ObjectRoot = h.user.ObjectRoot
}
if h.Phone != nil && h.Phone.ObjectRoot.IsZero() {
h.Phone.ObjectRoot = h.user.ObjectRoot
}
if h.Address != nil && h.Address.ObjectRoot.IsZero() {
h.Address.ObjectRoot = h.user.ObjectRoot
}
}
func (u *Human) setData(event *es_models.Event) error {
if err := json.Unmarshal(event.Data, u); err != nil {
logging.Log("EVEN-8ujgd").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-sj4jd", "could not unmarshal event")
}
return nil
}
func (u *Human) appendInitUsercodeCreatedEvent(event *es_models.Event) error {
initCode := new(InitUserCode)
err := initCode.SetData(event)
if err != nil {
return err
}
initCode.ObjectRoot.CreationDate = event.CreationDate
u.InitCode = initCode
return nil
}
func (c *InitUserCode) SetData(event *es_models.Event) error {
c.ObjectRoot.AppendEvent(event)
if err := json.Unmarshal(event.Data, c); err != nil {
logging.Log("EVEN-7duwe").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-lo34s", "could not unmarshal event")
}
return nil
}

View File

@@ -0,0 +1,78 @@
package model
import (
"encoding/json"
"time"
"github.com/zitadel/logging"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
user_repo "github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/zerrors"
)
type Machine struct {
user *User `json:"-"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
}
func (sa *Machine) AppendEvents(events ...*es_models.Event) error {
for _, event := range events {
if err := sa.AppendEvent(event); err != nil {
return err
}
}
return nil
}
func (sa *Machine) AppendEvent(event *es_models.Event) (err error) {
switch event.Type() {
case user_repo.MachineAddedEventType, user_repo.MachineChangedEventType:
err = sa.setData(event)
}
return err
}
func (sa *Machine) setData(event *es_models.Event) error {
if err := json.Unmarshal(event.Data, sa); err != nil {
logging.Log("EVEN-8ujgd").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-GwjY9", "could not unmarshal event")
}
return nil
}
type MachineKey struct {
es_models.ObjectRoot `json:"-"`
KeyID string `json:"keyId,omitempty"`
Type int32 `json:"type,omitempty"`
ExpirationDate time.Time `json:"expirationDate,omitempty"`
PublicKey []byte `json:"publicKey,omitempty"`
privateKey []byte
}
func (key *MachineKey) AppendEvents(events ...*es_models.Event) error {
for _, event := range events {
err := key.AppendEvent(event)
if err != nil {
return err
}
}
return nil
}
func (key *MachineKey) AppendEvent(event *es_models.Event) (err error) {
key.ObjectRoot.AppendEvent(event)
switch event.Type() {
case user_repo.MachineKeyAddedEventType:
err = json.Unmarshal(event.Data, key)
if err != nil {
return zerrors.ThrowInternal(err, "MODEL-SjI4S", "Errors.Internal")
}
case user_repo.MachineKeyRemovedEventType:
key.ExpirationDate = event.CreationDate
}
return err
}

View File

@@ -0,0 +1,257 @@
package model
import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/eventstore"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/user/model"
"github.com/zitadel/zitadel/internal/zerrors"
)
type WebAuthNToken struct {
es_models.ObjectRoot
WebauthNTokenID string `json:"webAuthNTokenId"`
Challenge string `json:"challenge"`
State int32 `json:"-"`
KeyID []byte `json:"keyId"`
PublicKey []byte `json:"publicKey"`
AttestationType string `json:"attestationType"`
AAGUID []byte `json:"aaguid"`
SignCount uint32 `json:"signCount"`
WebAuthNTokenName string `json:"webAuthNTokenName"`
}
type WebAuthNVerify struct {
WebAuthNTokenID string `json:"webAuthNTokenId"`
KeyID []byte `json:"keyId"`
PublicKey []byte `json:"publicKey"`
AttestationType string `json:"attestationType"`
AAGUID []byte `json:"aaguid"`
SignCount uint32 `json:"signCount"`
WebAuthNTokenName string `json:"webAuthNTokenName"`
UserAgentID string `json:"userAgentID,omitempty"`
}
type WebAuthNSignCount struct {
WebauthNTokenID string `json:"webAuthNTokenId"`
SignCount uint32 `json:"signCount"`
}
type WebAuthNTokenID struct {
WebauthNTokenID string `json:"webAuthNTokenId"`
}
type WebAuthNLogin struct {
es_models.ObjectRoot
WebauthNTokenID string `json:"webAuthNTokenId"`
Challenge string `json:"challenge"`
*AuthRequest
}
func GetWebauthn(webauthnTokens []*WebAuthNToken, id string) (int, *WebAuthNToken) {
for i, webauthn := range webauthnTokens {
if webauthn.WebauthNTokenID == id {
return i, webauthn
}
}
return -1, nil
}
func (w *WebAuthNVerify) SetData(event eventstore.Event) error {
if err := event.Unmarshal(w); err != nil {
logging.Log("EVEN-G342rf").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-B6641", "could not unmarshal event")
}
return nil
}
func (u *Human) appendU2FAddedEvent(event eventstore.Event) error {
webauthn := new(WebAuthNToken)
err := webauthn.setData(event)
if err != nil {
return err
}
webauthn.ObjectRoot.CreationDate = event.CreatedAt()
webauthn.State = int32(model.MFAStateNotReady)
for i, token := range u.U2FTokens {
if token.State == int32(model.MFAStateNotReady) {
u.U2FTokens[i] = webauthn
return nil
}
}
u.U2FTokens = append(u.U2FTokens, webauthn)
return nil
}
func (u *Human) appendU2FVerifiedEvent(event eventstore.Event) error {
webauthn := new(WebAuthNToken)
err := webauthn.setData(event)
if err != nil {
return err
}
if _, token := GetWebauthn(u.U2FTokens, webauthn.WebauthNTokenID); token != nil {
err := token.setData(event)
if err != nil {
return err
}
token.State = int32(model.MFAStateReady)
return nil
}
return zerrors.ThrowPreconditionFailed(nil, "MODEL-4hu9s", "Errors.Users.MFA.U2F.NotExisting")
}
func (u *Human) appendU2FChangeSignCountEvent(event eventstore.Event) error {
webauthn := new(WebAuthNToken)
err := webauthn.setData(event)
if err != nil {
return err
}
if _, token := GetWebauthn(u.U2FTokens, webauthn.WebauthNTokenID); token != nil {
token.setData(event)
return nil
}
return zerrors.ThrowPreconditionFailed(nil, "MODEL-5Ms8h", "Errors.Users.MFA.U2F.NotExisting")
}
func (u *Human) appendU2FRemovedEvent(event eventstore.Event) error {
webauthn := new(WebAuthNToken)
err := webauthn.setData(event)
if err != nil {
return err
}
for i := len(u.U2FTokens) - 1; i >= 0; i-- {
if u.U2FTokens[i].WebauthNTokenID == webauthn.WebauthNTokenID {
copy(u.U2FTokens[i:], u.U2FTokens[i+1:])
u.U2FTokens[len(u.U2FTokens)-1] = nil
u.U2FTokens = u.U2FTokens[:len(u.U2FTokens)-1]
return nil
}
}
return nil
}
func (u *Human) appendPasswordlessAddedEvent(event eventstore.Event) error {
webauthn := new(WebAuthNToken)
err := webauthn.setData(event)
if err != nil {
return err
}
webauthn.ObjectRoot.CreationDate = event.CreatedAt()
webauthn.State = int32(model.MFAStateNotReady)
for i, token := range u.PasswordlessTokens {
if token.State == int32(model.MFAStateNotReady) {
u.PasswordlessTokens[i] = webauthn
return nil
}
}
u.PasswordlessTokens = append(u.PasswordlessTokens, webauthn)
return nil
}
func (u *Human) appendPasswordlessVerifiedEvent(event eventstore.Event) error {
webauthn := new(WebAuthNToken)
err := webauthn.setData(event)
if err != nil {
return err
}
if _, token := GetWebauthn(u.PasswordlessTokens, webauthn.WebauthNTokenID); token != nil {
err := token.setData(event)
if err != nil {
return err
}
token.State = int32(model.MFAStateReady)
return nil
}
return zerrors.ThrowPreconditionFailed(nil, "MODEL-mKns8", "Errors.Users.MFA.Passwordless.NotExisting")
}
func (u *Human) appendPasswordlessChangeSignCountEvent(event eventstore.Event) error {
webauthn := new(WebAuthNToken)
err := webauthn.setData(event)
if err != nil {
return err
}
if _, token := GetWebauthn(u.PasswordlessTokens, webauthn.WebauthNTokenID); token != nil {
err := token.setData(event)
if err != nil {
return err
}
return nil
}
return zerrors.ThrowPreconditionFailed(nil, "MODEL-2Mv9s", "Errors.Users.MFA.Passwordless.NotExisting")
}
func (u *Human) appendPasswordlessRemovedEvent(event eventstore.Event) error {
webauthn := new(WebAuthNToken)
err := webauthn.setData(event)
if err != nil {
return err
}
for i := len(u.PasswordlessTokens) - 1; i >= 0; i-- {
if u.PasswordlessTokens[i].WebauthNTokenID == webauthn.WebauthNTokenID {
copy(u.PasswordlessTokens[i:], u.PasswordlessTokens[i+1:])
u.PasswordlessTokens[len(u.PasswordlessTokens)-1] = nil
u.PasswordlessTokens = u.PasswordlessTokens[:len(u.PasswordlessTokens)-1]
return nil
}
}
return nil
}
func (w *WebAuthNToken) setData(event eventstore.Event) error {
w.ObjectRoot.AppendEvent(event)
if err := event.Unmarshal(w); err != nil {
logging.Log("EVEN-4M9is").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-lo023", "could not unmarshal event")
}
return nil
}
func (u *Human) appendU2FLoginEvent(event eventstore.Event) error {
webauthn := new(WebAuthNLogin)
webauthn.ObjectRoot.AppendEvent(event)
err := webauthn.setData(event)
if err != nil {
return err
}
webauthn.ObjectRoot.CreationDate = event.CreatedAt()
for i, token := range u.U2FLogins {
if token.AuthRequest.ID == webauthn.AuthRequest.ID {
u.U2FLogins[i] = webauthn
return nil
}
}
u.U2FLogins = append(u.U2FLogins, webauthn)
return nil
}
func (u *Human) appendPasswordlessLoginEvent(event eventstore.Event) error {
webauthn := new(WebAuthNLogin)
webauthn.ObjectRoot.AppendEvent(event)
err := webauthn.setData(event)
if err != nil {
return err
}
webauthn.ObjectRoot.CreationDate = event.CreatedAt()
for i, token := range u.PasswordlessLogins {
if token.AuthRequest.ID == webauthn.AuthRequest.ID {
u.PasswordlessLogins[i] = webauthn
return nil
}
}
u.PasswordlessLogins = append(u.PasswordlessLogins, webauthn)
return nil
}
func (w *WebAuthNLogin) setData(event eventstore.Event) error {
w.ObjectRoot.AppendEvent(event)
if err := event.Unmarshal(w); err != nil {
logging.Log("EVEN-hmSlo").WithError(err).Error("could not unmarshal event data")
return zerrors.ThrowInternal(err, "MODEL-lo023", "could not unmarshal event")
}
return nil
}

View File

@@ -0,0 +1,151 @@
package model
import (
"encoding/json"
"testing"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/pkg/grpc/user"
)
func TestAppendMFAU2FAddedEvent(t *testing.T) {
type args struct {
user *Human
u2f *WebAuthNToken
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append user u2f event",
args: args{
user: &Human{},
u2f: &WebAuthNToken{WebauthNTokenID: "WebauthNTokenID", Challenge: "Challenge"},
event: &es_models.Event{},
},
result: &Human{
U2FTokens: []*WebAuthNToken{
{WebauthNTokenID: "WebauthNTokenID", Challenge: "Challenge", State: int32(user.AuthFactorState_AUTH_FACTOR_STATE_NOT_READY)},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.u2f != nil {
data, _ := json.Marshal(tt.args.u2f)
tt.args.event.Data = data
}
tt.args.user.appendU2FAddedEvent(tt.args.event)
if tt.args.user.U2FTokens[0].State != tt.result.U2FTokens[0].State {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.U2FTokens[0].State, tt.args.user.U2FTokens[0].State)
}
})
}
}
func TestAppendMFAU2FVerifyEvent(t *testing.T) {
type args struct {
user *Human
u2f *WebAuthNVerify
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append u2f verify event",
args: args{
user: &Human{
U2FTokens: []*WebAuthNToken{
{WebauthNTokenID: "WebauthNTokenID", Challenge: "Challenge", State: int32(user.AuthFactorState_AUTH_FACTOR_STATE_NOT_READY)},
},
},
u2f: &WebAuthNVerify{WebAuthNTokenID: "WebauthNTokenID", KeyID: []byte("KeyID"), PublicKey: []byte("PublicKey"), AttestationType: "AttestationType", AAGUID: []byte("AAGUID"), SignCount: 1},
event: &es_models.Event{},
},
result: &Human{
U2FTokens: []*WebAuthNToken{
{
WebauthNTokenID: "WebauthNTokenID",
Challenge: "Challenge",
State: int32(user.AuthFactorState_AUTH_FACTOR_STATE_READY),
KeyID: []byte("KeyID"),
PublicKey: []byte("PublicKey"),
AttestationType: "AttestationType",
AAGUID: []byte("AAGUID"),
SignCount: 1,
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.u2f != nil {
data, _ := json.Marshal(tt.args.u2f)
tt.args.event.Data = data
}
tt.args.user.appendU2FVerifiedEvent(tt.args.event)
if tt.args.user.U2FTokens[0].State != tt.result.U2FTokens[0].State {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.U2FTokens[0].State, tt.args.user.U2FTokens[0].State)
}
if tt.args.user.U2FTokens[0].AttestationType != tt.result.U2FTokens[0].AttestationType {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.U2FTokens[0].AttestationType, tt.args.user.U2FTokens[0].AttestationType)
}
})
}
}
func TestAppendMFAU2FRemoveEvent(t *testing.T) {
type args struct {
user *Human
u2f *WebAuthNTokenID
event *es_models.Event
}
tests := []struct {
name string
args args
result *Human
}{
{
name: "append u2f remove event",
args: args{
user: &Human{
U2FTokens: []*WebAuthNToken{
{
WebauthNTokenID: "WebauthNTokenID",
Challenge: "Challenge",
State: int32(user.AuthFactorState_AUTH_FACTOR_STATE_NOT_READY),
KeyID: []byte("KeyID"),
PublicKey: []byte("PublicKey"),
AttestationType: "AttestationType",
AAGUID: []byte("AAGUID"),
SignCount: 1,
},
},
},
u2f: &WebAuthNTokenID{WebauthNTokenID: "WebauthNTokenID"},
event: &es_models.Event{},
},
result: &Human{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.u2f != nil {
data, _ := json.Marshal(tt.args.u2f)
tt.args.event.Data = data
}
tt.args.user.appendU2FRemovedEvent(tt.args.event)
if len(tt.args.user.U2FTokens) != 0 {
t.Errorf("got wrong result: actual: %v ", tt.result.U2FTokens)
}
})
}
}