feat: new user auth api (#1168)

* fix: correct selectors for extended writemodel

* fix: no previous checks in eventstore

* start check previous

* feat: auth user commands

* feat: auth user commands

* feat: auth user commands

* feat: otp

* feat: corrections from pr merge

* feat: webauthn

* feat: comment old webauthn

* feat: refactor user, human, machine

* feat: webauth command side

* feat: command and query side in login

* feat: fix user writemodel append events

* fix: remove creation dates on command side

* fix: remove previous sequence

* previous sequence

* fix: external idps

* Update internal/api/grpc/management/user.go

Co-authored-by: Livio Amstutz <livio.a@gmail.com>

* Update internal/v2/command/user_human_email.go

Co-authored-by: Livio Amstutz <livio.a@gmail.com>

* fix: pr changes

* fix: phone verification

Co-authored-by: adlerhurst <silvan.reusser@gmail.com>
Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Fabi 2021-01-15 09:32:59 +01:00 committed by GitHub
parent e5731b0d3b
commit 959530ddad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 1554 additions and 1519 deletions

View File

@ -119,7 +119,7 @@ func startZitadel(configPaths []string) {
} }
startAPI(ctx, conf, authZRepo, authRepo, command, query) startAPI(ctx, conf, authZRepo, authRepo, command, query)
startUI(ctx, conf, authRepo) startUI(ctx, conf, authRepo, command, query)
if *notificationEnabled { if *notificationEnabled {
notification.Start(ctx, conf.Notification, conf.SystemDefaults) notification.Start(ctx, conf.Notification, conf.SystemDefaults)
@ -129,10 +129,10 @@ func startZitadel(configPaths []string) {
logging.Log("MAIN-s8d2h").Info("stopping zitadel") logging.Log("MAIN-s8d2h").Info("stopping zitadel")
} }
func startUI(ctx context.Context, conf *Config, authRepo *auth_es.EsRepository) { func startUI(ctx context.Context, conf *Config, authRepo *auth_es.EsRepository, command *command.CommandSide, query *query.QuerySide) {
uis := ui.Create(conf.UI) uis := ui.Create(conf.UI)
if *loginEnabled { if *loginEnabled {
login, prefix := login.Start(conf.UI.Login, authRepo, conf.SystemDefaults, *localDevMode) login, prefix := login.Start(conf.UI.Login, command, query, authRepo, conf.SystemDefaults, *localDevMode)
uis.RegisterHandler(prefix, login.Handler()) uis.RegisterHandler(prefix, login.Handler())
} }
if *consoleEnabled { if *consoleEnabled {

View File

@ -2,6 +2,7 @@ package admin
import ( import (
"context" "context"
"github.com/caos/zitadel/internal/errors"
"github.com/golang/protobuf/ptypes/empty" "github.com/golang/protobuf/ptypes/empty"
@ -31,10 +32,11 @@ func (s *Server) IsOrgUnique(ctx context.Context, request *admin.UniqueOrgReques
} }
func (s *Server) SetUpOrg(ctx context.Context, orgSetUp *admin.OrgSetUpRequest) (_ *empty.Empty, err error) { func (s *Server) SetUpOrg(ctx context.Context, orgSetUp *admin.OrgSetUpRequest) (_ *empty.Empty, err error) {
err = s.command.SetUpOrg(ctx, orgCreateRequestToDomain(orgSetUp.Org), userCreateRequestToDomain(orgSetUp.User)) human, _ := userCreateRequestToDomain(orgSetUp.User)
if err != nil { if human == nil {
return nil, err return &empty.Empty{}, errors.ThrowPreconditionFailed(nil, "ADMIN-4nd9f", "Errors.User.NotHuman")
} }
err = s.command.SetUpOrg(ctx, orgCreateRequestToDomain(orgSetUp.Org), human)
return &empty.Empty{}, nil return &empty.Empty{}, nil
} }

View File

@ -9,22 +9,18 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
) )
func userCreateRequestToDomain(user *admin.CreateUserRequest) *domain.User { func userCreateRequestToDomain(user *admin.CreateUserRequest) (*domain.Human, *domain.Machine) {
var human *domain.Human
var machine *domain.Machine
if h := user.GetHuman(); h != nil { if h := user.GetHuman(); h != nil {
human = humanCreateToDomain(h) human := humanCreateToDomain(h)
human.Username = user.UserName
return human, nil
} }
if m := user.GetMachine(); m != nil { if m := user.GetMachine(); m != nil {
machine = machineCreateToDomain(m) machine := machineCreateToDomain(m)
} machine.Username = user.UserName
return nil, machine
return &domain.User{
UserName: user.UserName,
Human: human,
Machine: machine,
} }
return nil, nil
} }
func humanCreateToDomain(u *admin.CreateHumanRequest) *domain.Human { func humanCreateToDomain(u *admin.CreateHumanRequest) *domain.Human {

View File

@ -42,7 +42,8 @@ func (s *Server) GetMyUserPhone(ctx context.Context, _ *empty.Empty) (*auth.User
} }
func (s *Server) RemoveMyUserPhone(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) { func (s *Server) RemoveMyUserPhone(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) {
err := s.repo.RemoveMyPhone(ctx) ctxData := authz.GetCtxData(ctx)
err := s.command.RemoveHumanPhone(ctx, ctxData.UserID, ctxData.ResourceOwner)
return &empty.Empty{}, err return &empty.Empty{}, err
} }
@ -84,12 +85,14 @@ func (s *Server) ChangeMyUserEmail(ctx context.Context, request *auth.UpdateUser
} }
func (s *Server) VerifyMyUserEmail(ctx context.Context, request *auth.VerifyMyUserEmailRequest) (*empty.Empty, error) { func (s *Server) VerifyMyUserEmail(ctx context.Context, request *auth.VerifyMyUserEmailRequest) (*empty.Empty, error) {
err := s.repo.VerifyMyEmail(ctx, request.Code) ctxData := authz.GetCtxData(ctx)
err := s.command.VerifyHumanEmail(ctx, ctxData.UserID, request.Code, ctxData.OrgID)
return &empty.Empty{}, err return &empty.Empty{}, err
} }
func (s *Server) ResendMyEmailVerificationMail(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) { func (s *Server) ResendMyEmailVerificationMail(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) {
err := s.repo.ResendMyEmailVerificationMail(ctx) ctxData := authz.GetCtxData(ctx)
err := s.command.CreateHumanEmailVerificationCode(ctx, ctxData.UserID, ctxData.ResourceOwner)
return &empty.Empty{}, err return &empty.Empty{}, err
} }
@ -102,25 +105,28 @@ func (s *Server) ChangeMyUserPhone(ctx context.Context, request *auth.UpdateUser
} }
func (s *Server) VerifyMyUserPhone(ctx context.Context, request *auth.VerifyUserPhoneRequest) (*empty.Empty, error) { func (s *Server) VerifyMyUserPhone(ctx context.Context, request *auth.VerifyUserPhoneRequest) (*empty.Empty, error) {
err := s.repo.VerifyMyPhone(ctx, request.Code) ctxData := authz.GetCtxData(ctx)
err := s.command.VerifyHumanPhone(ctx, ctxData.UserID, request.Code, ctxData.ResourceOwner)
return &empty.Empty{}, err return &empty.Empty{}, err
} }
func (s *Server) ResendMyPhoneVerificationCode(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) { func (s *Server) ResendMyPhoneVerificationCode(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) {
err := s.repo.ResendMyPhoneVerificationCode(ctx) ctxData := authz.GetCtxData(ctx)
err := s.command.CreateHumanPhoneVerificationCode(ctx, ctxData.UserID, ctxData.ResourceOwner)
return &empty.Empty{}, err return &empty.Empty{}, err
} }
func (s *Server) UpdateMyUserAddress(ctx context.Context, request *auth.UpdateUserAddressRequest) (*auth.UserAddress, error) { func (s *Server) UpdateMyUserAddress(ctx context.Context, request *auth.UpdateUserAddressRequest) (*auth.UserAddress, error) {
address, err := s.repo.ChangeMyAddress(ctx, updateAddressToModel(ctx, request)) address, err := s.command.ChangeHumanAddress(ctx, updateAddressToDomain(ctx, request))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return addressFromModel(address), nil return addressFromDomain(address), nil
} }
func (s *Server) ChangeMyPassword(ctx context.Context, request *auth.PasswordChange) (*empty.Empty, error) { func (s *Server) ChangeMyPassword(ctx context.Context, request *auth.PasswordChange) (*empty.Empty, error) {
err := s.repo.ChangeMyPassword(ctx, request.OldPassword, request.NewPassword) ctxData := authz.GetCtxData(ctx)
err := s.command.ChangePassword(ctx, ctxData.OrgID, ctxData.UserID, request.OldPassword, request.NewPassword, "")
return &empty.Empty{}, err return &empty.Empty{}, err
} }
@ -133,7 +139,7 @@ func (s *Server) SearchMyExternalIDPs(ctx context.Context, request *auth.Externa
} }
func (s *Server) RemoveMyExternalIDP(ctx context.Context, request *auth.ExternalIDPRemoveRequest) (*empty.Empty, error) { func (s *Server) RemoveMyExternalIDP(ctx context.Context, request *auth.ExternalIDPRemoveRequest) (*empty.Empty, error) {
err := s.repo.RemoveMyExternalIDP(ctx, externalIDPRemoveToModel(ctx, request)) err := s.command.RemoveHumanExternalIDP(ctx, externalIDPRemoveToDomain(ctx, request))
return &empty.Empty{}, err return &empty.Empty{}, err
} }
@ -146,38 +152,44 @@ func (s *Server) GetMyPasswordComplexityPolicy(ctx context.Context, _ *empty.Emp
} }
func (s *Server) AddMfaOTP(ctx context.Context, _ *empty.Empty) (_ *auth.MfaOtpResponse, err error) { func (s *Server) AddMfaOTP(ctx context.Context, _ *empty.Empty) (_ *auth.MfaOtpResponse, err error) {
otp, err := s.repo.AddMyMFAOTP(ctx) ctxData := authz.GetCtxData(ctx)
otp, err := s.command.AddHumanOTP(ctx, ctxData.UserID, ctxData.OrgID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return otpFromModel(otp), nil return otpFromDomain(otp), nil
} }
func (s *Server) VerifyMfaOTP(ctx context.Context, request *auth.VerifyMfaOtp) (*empty.Empty, error) { func (s *Server) VerifyMfaOTP(ctx context.Context, request *auth.VerifyMfaOtp) (*empty.Empty, error) {
err := s.repo.VerifyMyMFAOTPSetup(ctx, request.Code) ctxData := authz.GetCtxData(ctx)
err := s.command.CheckMFAOTPSetup(ctx, ctxData.UserID, request.Code, "", ctxData.ResourceOwner)
return &empty.Empty{}, err return &empty.Empty{}, err
} }
func (s *Server) RemoveMfaOTP(ctx context.Context, _ *empty.Empty) (_ *empty.Empty, err error) { func (s *Server) RemoveMfaOTP(ctx context.Context, _ *empty.Empty) (_ *empty.Empty, err error) {
err = s.repo.RemoveMyMFAOTP(ctx) ctxData := authz.GetCtxData(ctx)
err = s.command.RemoveHumanOTP(ctx, ctxData.UserID, ctxData.OrgID)
return &empty.Empty{}, err return &empty.Empty{}, err
} }
func (s *Server) AddMyMfaU2F(ctx context.Context, _ *empty.Empty) (_ *auth.WebAuthNResponse, err error) { func (s *Server) AddMyMfaU2F(ctx context.Context, _ *empty.Empty) (_ *auth.WebAuthNResponse, err error) {
u2f, err := s.repo.AddMyMFAU2F(ctx) ctxData := authz.GetCtxData(ctx)
u2f, err := s.command.AddHumanU2F(ctx, ctxData.UserID, ctxData.ResourceOwner, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return verifyWebAuthNFromModel(u2f), err return verifyWebAuthNFromDomain(u2f), err
} }
func (s *Server) VerifyMyMfaU2F(ctx context.Context, request *auth.VerifyWebAuthN) (*empty.Empty, error) { func (s *Server) VerifyMyMfaU2F(ctx context.Context, request *auth.VerifyWebAuthN) (*empty.Empty, error) {
err := s.repo.VerifyMyMFAU2FSetup(ctx, request.TokenName, request.PublicKeyCredential) ctxData := authz.GetCtxData(ctx)
err := s.command.VerifyHumanU2F(ctx, ctxData.UserID, ctxData.OrgID, request.TokenName, "", request.PublicKeyCredential)
return &empty.Empty{}, err return &empty.Empty{}, err
} }
func (s *Server) RemoveMyMfaU2F(ctx context.Context, id *auth.WebAuthNTokenID) (*empty.Empty, error) { func (s *Server) RemoveMyMfaU2F(ctx context.Context, id *auth.WebAuthNTokenID) (*empty.Empty, error) {
err := s.repo.RemoveMyMFAU2F(ctx, id.Id) ctxData := authz.GetCtxData(ctx)
err := s.command.RemoveHumanU2F(ctx, ctxData.UserID, id.Id, ctxData.OrgID)
return &empty.Empty{}, err return &empty.Empty{}, err
} }
@ -190,20 +202,23 @@ func (s *Server) GetMyPasswordless(ctx context.Context, _ *empty.Empty) (_ *auth
} }
func (s *Server) AddMyPasswordless(ctx context.Context, _ *empty.Empty) (_ *auth.WebAuthNResponse, err error) { func (s *Server) AddMyPasswordless(ctx context.Context, _ *empty.Empty) (_ *auth.WebAuthNResponse, err error) {
u2f, err := s.repo.AddMyPasswordless(ctx) ctxData := authz.GetCtxData(ctx)
u2f, err := s.command.AddHumanPasswordless(ctx, ctxData.UserID, ctxData.ResourceOwner, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return verifyWebAuthNFromModel(u2f), err return verifyWebAuthNFromDomain(u2f), err
} }
func (s *Server) VerifyMyPasswordless(ctx context.Context, request *auth.VerifyWebAuthN) (*empty.Empty, error) { func (s *Server) VerifyMyPasswordless(ctx context.Context, request *auth.VerifyWebAuthN) (*empty.Empty, error) {
err := s.repo.VerifyMyPasswordlessSetup(ctx, request.TokenName, request.PublicKeyCredential) ctxData := authz.GetCtxData(ctx)
err := s.command.VerifyHumanPasswordless(ctx, ctxData.UserID, ctxData.OrgID, request.TokenName, "", request.PublicKeyCredential)
return &empty.Empty{}, err return &empty.Empty{}, err
} }
func (s *Server) RemoveMyPasswordless(ctx context.Context, id *auth.WebAuthNTokenID) (*empty.Empty, error) { func (s *Server) RemoveMyPasswordless(ctx context.Context, id *auth.WebAuthNTokenID) (*empty.Empty, error) {
err := s.repo.RemoveMyPasswordless(ctx, id.Id) ctxData := authz.GetCtxData(ctx)
err := s.command.RemoveHumanPasswordless(ctx, ctxData.UserID, id.Id, ctxData.ResourceOwner)
return &empty.Empty{}, err return &empty.Empty{}, err
} }

View File

@ -194,7 +194,7 @@ func updatePhoneToDomain(ctx context.Context, e *auth.UpdateUserPhoneRequest) *d
} }
} }
func addressFromModel(address *usr_model.Address) *auth.UserAddress { func addressFromDomain(address *domain.Address) *auth.UserAddress {
creationDate, err := ptypes.TimestampProto(address.CreationDate) creationDate, err := ptypes.TimestampProto(address.CreationDate)
logging.Log("GRPC-65FRs").OnError(err).Debug("unable to parse timestamp") logging.Log("GRPC-65FRs").OnError(err).Debug("unable to parse timestamp")
@ -234,8 +234,8 @@ func addressViewFromModel(address *usr_model.Address) *auth.UserAddressView {
} }
} }
func updateAddressToModel(ctx context.Context, address *auth.UpdateUserAddressRequest) *usr_model.Address { func updateAddressToDomain(ctx context.Context, address *auth.UpdateUserAddressRequest) *domain.Address {
return &usr_model.Address{ return &domain.Address{
ObjectRoot: ctxToObjectRoot(ctx), ObjectRoot: ctxToObjectRoot(ctx),
Country: address.Country, Country: address.Country,
StreetAddress: address.StreetAddress, StreetAddress: address.StreetAddress,
@ -252,11 +252,11 @@ func externalIDPSearchRequestToModel(request *auth.ExternalIDPSearchRequest) *us
} }
} }
func externalIDPRemoveToModel(ctx context.Context, idp *auth.ExternalIDPRemoveRequest) *usr_model.ExternalIDP { func externalIDPRemoveToDomain(ctx context.Context, idp *auth.ExternalIDPRemoveRequest) *domain.ExternalIDP {
return &usr_model.ExternalIDP{ return &domain.ExternalIDP{
ObjectRoot: ctxToObjectRoot(ctx), ObjectRoot: ctxToObjectRoot(ctx),
IDPConfigID: idp.IdpConfigId, IDPConfigID: idp.IdpConfigId,
UserID: idp.ExternalUserId, ExternalUserID: idp.ExternalUserId,
} }
} }
@ -308,12 +308,12 @@ func externalIDPViewFromModel(externalIDP *usr_model.ExternalIDPView) *auth.Exte
} }
} }
func otpFromModel(otp *usr_model.OTP) *auth.MfaOtpResponse { func otpFromDomain(otp *domain.OTP) *auth.MfaOtpResponse {
return &auth.MfaOtpResponse{ return &auth.MfaOtpResponse{
UserId: otp.AggregateID, UserId: otp.AggregateID,
Url: otp.Url, Url: otp.Url,
Secret: otp.SecretString, Secret: otp.SecretString,
State: mfaStateFromModel(otp.State), State: mfaStateFromDomain(otp.State),
} }
} }
@ -360,11 +360,11 @@ func genderToDomain(gender auth.Gender) domain.Gender {
} }
} }
func mfaStateFromModel(state usr_model.MFAState) auth.MFAState { func mfaStateFromDomain(state domain.MFAState) auth.MFAState {
switch state { switch state {
case usr_model.MFAStateReady: case domain.MFAStateReady:
return auth.MFAState_MFASTATE_READY return auth.MFAState_MFASTATE_READY
case usr_model.MFAStateNotReady: case domain.MFAStateNotReady:
return auth.MFAState_MFASTATE_NOT_READY return auth.MFAState_MFASTATE_NOT_READY
default: default:
return auth.MFAState_MFASTATE_UNSPECIFIED return auth.MFAState_MFASTATE_UNSPECIFIED
@ -381,7 +381,7 @@ func mfasFromModel(mfas []*usr_model.MultiFactor) []*auth.MultiFactor {
func mfaFromModel(mfa *usr_model.MultiFactor) *auth.MultiFactor { func mfaFromModel(mfa *usr_model.MultiFactor) *auth.MultiFactor {
return &auth.MultiFactor{ return &auth.MultiFactor{
State: mfaStateFromModel(mfa.State), State: auth.MFAState(mfa.State),
Type: mfaTypeFromModel(mfa.Type), Type: mfaTypeFromModel(mfa.Type),
Attribute: mfa.Attribute, Attribute: mfa.Attribute,
Id: mfa.ID, Id: mfa.ID,
@ -431,11 +431,11 @@ func userChangesToAPI(changes *usr_model.UserChanges) (_ []*auth.Change) {
return result return result
} }
func verifyWebAuthNFromModel(u2f *usr_model.WebAuthNToken) *auth.WebAuthNResponse { func verifyWebAuthNFromDomain(u2f *domain.WebAuthNToken) *auth.WebAuthNResponse {
return &auth.WebAuthNResponse{ return &auth.WebAuthNResponse{
Id: u2f.WebAuthNTokenID, Id: u2f.WebAuthNTokenID,
PublicKey: u2f.CredentialCreationData, PublicKey: u2f.CredentialCreationData,
State: mfaStateFromModel(u2f.State), State: mfaStateFromDomain(u2f.State),
} }
} }
@ -451,7 +451,7 @@ func webAuthNTokenFromModel(token *usr_model.WebAuthNToken) *auth.WebAuthNToken
return &auth.WebAuthNToken{ return &auth.WebAuthNToken{
Id: token.WebAuthNTokenID, Id: token.WebAuthNTokenID,
Name: token.WebAuthNTokenName, Name: token.WebAuthNTokenName,
State: mfaStateFromModel(token.State), State: auth.MFAState(token.State),
} }
} }

View File

@ -17,20 +17,16 @@ import (
) )
func appFromModel(app *proj_model.Application) *management.Application { func appFromModel(app *proj_model.Application) *management.Application {
creationDate, err := ptypes.TimestampProto(app.CreationDate)
logging.Log("GRPC-iejs3").OnError(err).Debug("unable to parse timestamp")
changeDate, err := ptypes.TimestampProto(app.ChangeDate) changeDate, err := ptypes.TimestampProto(app.ChangeDate)
logging.Log("GRPC-di7rw").OnError(err).Debug("unable to parse timestamp") logging.Log("GRPC-di7rw").OnError(err).Debug("unable to parse timestamp")
return &management.Application{ return &management.Application{
Id: app.AppID, Id: app.AppID,
State: appStateFromModel(app.State), State: appStateFromModel(app.State),
CreationDate: creationDate, ChangeDate: changeDate,
ChangeDate: changeDate, Name: app.Name,
Name: app.Name, Sequence: app.Sequence,
Sequence: app.Sequence, AppConfig: appConfigFromModel(app),
AppConfig: appConfigFromModel(app),
} }
} }

View File

@ -52,43 +52,39 @@ func (s *Server) IsUserUnique(ctx context.Context, request *management.UniqueUse
} }
func (s *Server) CreateUser(ctx context.Context, in *management.CreateUserRequest) (*management.UserResponse, error) { func (s *Server) CreateUser(ctx context.Context, in *management.CreateUserRequest) (*management.UserResponse, error) {
user, err := s.command.AddUser(ctx, authz.GetCtxData(ctx).OrgID, userCreateToDomain(in)) human, machine := userCreateToDomain(in)
if human != nil {
h, err := s.command.AddHuman(ctx, authz.GetCtxData(ctx).OrgID, human)
if err != nil {
return nil, err
}
return userHumanFromDomain(h), nil
}
m, err := s.command.AddMachine(ctx, authz.GetCtxData(ctx).OrgID, machine)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return userFromDomain(user), nil return userMachineFromDomain(m), nil
} }
func (s *Server) DeactivateUser(ctx context.Context, in *management.UserID) (*management.UserResponse, error) { func (s *Server) DeactivateUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) {
user, err := s.command.DeactivateUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID) err := s.command.DeactivateUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID)
if err != nil { return &empty.Empty{}, err
return nil, err
}
return userFromDomain(user), nil
} }
func (s *Server) ReactivateUser(ctx context.Context, in *management.UserID) (*management.UserResponse, error) { func (s *Server) ReactivateUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) {
user, err := s.command.ReactivateUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID) err := s.command.ReactivateUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID)
if err != nil { return &empty.Empty{}, err
return nil, err
}
return userFromDomain(user), nil
} }
func (s *Server) LockUser(ctx context.Context, in *management.UserID) (*management.UserResponse, error) { func (s *Server) LockUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) {
user, err := s.command.LockUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID) err := s.command.LockUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID)
if err != nil { return &empty.Empty{}, err
return nil, err
}
return userFromDomain(user), nil
} }
func (s *Server) UnlockUser(ctx context.Context, in *management.UserID) (*management.UserResponse, error) { func (s *Server) UnlockUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) {
user, err := s.command.UnlockUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID) err := s.command.UnlockUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID)
if err != nil { return &empty.Empty{}, err
return nil, err
}
return userFromDomain(user), nil
} }
func (s *Server) DeleteUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) { func (s *Server) DeleteUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) {

View File

@ -19,48 +19,48 @@ import (
"github.com/caos/zitadel/pkg/grpc/message" "github.com/caos/zitadel/pkg/grpc/message"
) )
func userFromDomain(user *domain.User) *management.UserResponse { func userMachineFromDomain(machine *domain.Machine) *management.UserResponse {
creationDate, err := ptypes.TimestampProto(user.CreationDate) changeDate, err := ptypes.TimestampProto(machine.ChangeDate)
logging.Log("GRPC-8duwe").OnError(err).Debug("unable to parse timestamp")
changeDate, err := ptypes.TimestampProto(user.ChangeDate)
logging.Log("GRPC-ckoe3d").OnError(err).Debug("unable to parse timestamp") logging.Log("GRPC-ckoe3d").OnError(err).Debug("unable to parse timestamp")
userResp := &management.UserResponse{ userResp := &management.UserResponse{
Id: user.AggregateID, Id: machine.AggregateID,
State: userStateFromDomain(user.State), State: userStateFromDomain(machine.GetState()),
CreationDate: creationDate, ChangeDate: changeDate,
ChangeDate: changeDate, Sequence: machine.Sequence,
Sequence: user.Sequence, UserName: machine.GetUsername(),
UserName: user.UserName,
} }
userResp.User = &management.UserResponse_Machine{Machine: machineFromDomain(machine)}
if user.Machine != nil {
userResp.User = &management.UserResponse_Machine{Machine: machineFromDomain(user.Machine)}
}
if user.Human != nil {
userResp.User = &management.UserResponse_Human{Human: humanFromDomain(user.Human)}
}
return userResp return userResp
} }
func userCreateToDomain(user *management.CreateUserRequest) *domain.User { func userHumanFromDomain(human *domain.Human) *management.UserResponse {
var human *domain.Human changeDate, err := ptypes.TimestampProto(human.ChangeDate)
var machine *domain.Machine logging.Log("GRPC-ckoe3d").OnError(err).Debug("unable to parse timestamp")
userResp := &management.UserResponse{
Id: human.AggregateID,
State: userStateFromDomain(human.GetState()),
ChangeDate: changeDate,
Sequence: human.Sequence,
UserName: human.GetUsername(),
}
userResp.User = &management.UserResponse_Human{Human: humanFromDomain(human)}
return userResp
}
func userCreateToDomain(user *management.CreateUserRequest) (*domain.Human, *domain.Machine) {
if h := user.GetHuman(); h != nil { if h := user.GetHuman(); h != nil {
human = humanCreateToDomain(h) human := humanCreateToDomain(h)
human.Username = user.UserName
return human, nil
} }
if m := user.GetMachine(); m != nil { if m := user.GetMachine(); m != nil {
machine = machineCreateToDomain(m) machine := machineCreateToDomain(m)
} machine.Username = user.UserName
return nil, machine
return &domain.User{
UserName: user.UserName,
Human: human,
Machine: machine,
} }
return nil, nil
} }
func passwordRequestToModel(r *management.PasswordRequest) *usr_model.Password { func passwordRequestToModel(r *management.PasswordRequest) *usr_model.Password {
@ -212,15 +212,11 @@ func userMembershipSearchKeyToModel(key management.UserMembershipSearchKey) usr_
} }
func profileFromDomain(profile *domain.Profile) *management.UserProfile { func profileFromDomain(profile *domain.Profile) *management.UserProfile {
creationDate, err := ptypes.TimestampProto(profile.CreationDate)
logging.Log("GRPC-dkso3").OnError(err).Debug("unable to parse timestamp")
changeDate, err := ptypes.TimestampProto(profile.ChangeDate) changeDate, err := ptypes.TimestampProto(profile.ChangeDate)
logging.Log("GRPC-ski8d").OnError(err).Debug("unable to parse timestamp") logging.Log("GRPC-ski8d").OnError(err).Debug("unable to parse timestamp")
return &management.UserProfile{ return &management.UserProfile{
Id: profile.AggregateID, Id: profile.AggregateID,
CreationDate: creationDate,
ChangeDate: changeDate, ChangeDate: changeDate,
Sequence: profile.Sequence, Sequence: profile.Sequence,
FirstName: profile.FirstName, FirstName: profile.FirstName,
@ -270,15 +266,11 @@ func updateProfileToDomain(u *management.UpdateUserProfileRequest) *domain.Profi
} }
func emailFromDomain(email *domain.Email) *management.UserEmail { func emailFromDomain(email *domain.Email) *management.UserEmail {
creationDate, err := ptypes.TimestampProto(email.CreationDate)
logging.Log("GRPC-d9ow2").OnError(err).Debug("unable to parse timestamp")
changeDate, err := ptypes.TimestampProto(email.ChangeDate) changeDate, err := ptypes.TimestampProto(email.ChangeDate)
logging.Log("GRPC-s0dkw").OnError(err).Debug("unable to parse timestamp") logging.Log("GRPC-s0dkw").OnError(err).Debug("unable to parse timestamp")
return &management.UserEmail{ return &management.UserEmail{
Id: email.AggregateID, Id: email.AggregateID,
CreationDate: creationDate,
ChangeDate: changeDate, ChangeDate: changeDate,
Sequence: email.Sequence, Sequence: email.Sequence,
Email: email.EmailAddress, Email: email.EmailAddress,
@ -312,15 +304,11 @@ func updateEmailToDomain(e *management.UpdateUserEmailRequest) *domain.Email {
} }
func phoneFromDomain(phone *domain.Phone) *management.UserPhone { func phoneFromDomain(phone *domain.Phone) *management.UserPhone {
creationDate, err := ptypes.TimestampProto(phone.CreationDate)
logging.Log("GRPC-ps9ws").OnError(err).Debug("unable to parse timestamp")
changeDate, err := ptypes.TimestampProto(phone.ChangeDate) changeDate, err := ptypes.TimestampProto(phone.ChangeDate)
logging.Log("GRPC-09ewq").OnError(err).Debug("unable to parse timestamp") logging.Log("GRPC-09ewq").OnError(err).Debug("unable to parse timestamp")
return &management.UserPhone{ return &management.UserPhone{
Id: phone.AggregateID, Id: phone.AggregateID,
CreationDate: creationDate,
ChangeDate: changeDate, ChangeDate: changeDate,
Sequence: phone.Sequence, Sequence: phone.Sequence,
Phone: phone.PhoneNumber, Phone: phone.PhoneNumber,
@ -353,15 +341,11 @@ func updatePhoneToDomain(e *management.UpdateUserPhoneRequest) *domain.Phone {
} }
func addressFromDomain(address *domain.Address) *management.UserAddress { func addressFromDomain(address *domain.Address) *management.UserAddress {
creationDate, err := ptypes.TimestampProto(address.CreationDate)
logging.Log("GRPC-ud8w7").OnError(err).Debug("unable to parse timestamp")
changeDate, err := ptypes.TimestampProto(address.ChangeDate) changeDate, err := ptypes.TimestampProto(address.ChangeDate)
logging.Log("GRPC-si9ws").OnError(err).Debug("unable to parse timestamp") logging.Log("GRPC-si9ws").OnError(err).Debug("unable to parse timestamp")
return &management.UserAddress{ return &management.UserAddress{
Id: address.AggregateID, Id: address.AggregateID,
CreationDate: creationDate,
ChangeDate: changeDate, ChangeDate: changeDate,
Sequence: address.Sequence, Sequence: address.Sequence,
Country: address.Country, Country: address.Country,

View File

@ -122,20 +122,6 @@ func (repo *UserRepo) SearchMyExternalIDPs(ctx context.Context, request *model.E
return result, nil return result, nil
} }
func (repo *UserRepo) AddMyExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) (*model.ExternalIDP, error) {
if err := checkIDs(ctx, externalIDP.ObjectRoot); err != nil {
return nil, err
}
return repo.UserEvents.AddExternalIDP(ctx, externalIDP)
}
func (repo *UserRepo) RemoveMyExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) error {
if err := checkIDs(ctx, externalIDP.ObjectRoot); err != nil {
return err
}
return repo.UserEvents.RemoveExternalIDP(ctx, externalIDP)
}
func (repo *UserRepo) MyEmail(ctx context.Context) (*model.Email, error) { func (repo *UserRepo) MyEmail(ctx context.Context) (*model.Email, error) {
user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID) user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID)
if err != nil { if err != nil {
@ -159,10 +145,6 @@ func (repo *UserRepo) ResendEmailVerificationMail(ctx context.Context, userID st
return repo.UserEvents.CreateEmailVerificationCode(ctx, userID) return repo.UserEvents.CreateEmailVerificationCode(ctx, userID)
} }
func (repo *UserRepo) ResendMyEmailVerificationMail(ctx context.Context) error {
return repo.UserEvents.CreateEmailVerificationCode(ctx, authz.GetCtxData(ctx).UserID)
}
func (repo *UserRepo) MyPhone(ctx context.Context) (*model.Phone, error) { func (repo *UserRepo) MyPhone(ctx context.Context) (*model.Phone, error) {
user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID) user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID)
if err != nil { if err != nil {
@ -189,10 +171,6 @@ func (repo *UserRepo) VerifyMyPhone(ctx context.Context, code string) error {
return repo.UserEvents.VerifyPhone(ctx, authz.GetCtxData(ctx).UserID, code) return repo.UserEvents.VerifyPhone(ctx, authz.GetCtxData(ctx).UserID, code)
} }
func (repo *UserRepo) ResendMyPhoneVerificationCode(ctx context.Context) error {
return repo.UserEvents.CreatePhoneVerificationCode(ctx, authz.GetCtxData(ctx).UserID)
}
func (repo *UserRepo) MyAddress(ctx context.Context) (*model.Address, error) { func (repo *UserRepo) MyAddress(ctx context.Context) (*model.Address, error) {
user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID) user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID)
if err != nil { if err != nil {
@ -204,13 +182,6 @@ func (repo *UserRepo) MyAddress(ctx context.Context) (*model.Address, error) {
return user.GetAddress() return user.GetAddress()
} }
func (repo *UserRepo) ChangeMyAddress(ctx context.Context, address *model.Address) (*model.Address, error) {
if err := checkIDs(ctx, address.ObjectRoot); err != nil {
return nil, err
}
return repo.UserEvents.ChangeAddress(ctx, address)
}
func (repo *UserRepo) ChangeMyPassword(ctx context.Context, old, new string) error { func (repo *UserRepo) ChangeMyPassword(ctx context.Context, old, new string) error {
policy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) policy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID)
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
@ -265,25 +236,10 @@ func (repo *UserRepo) AddMFAOTP(ctx context.Context, userID string) (*model.OTP,
return repo.UserEvents.AddOTP(ctx, userID, accountName) return repo.UserEvents.AddOTP(ctx, userID, accountName)
} }
func (repo *UserRepo) AddMyMFAOTP(ctx context.Context) (*model.OTP, error) {
accountName := ""
user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID)
if err != nil {
logging.Log("EVENT-Ml0sd").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get user for loginname")
} else {
accountName = user.PreferredLoginName
}
return repo.UserEvents.AddOTP(ctx, authz.GetCtxData(ctx).UserID, accountName)
}
func (repo *UserRepo) VerifyMFAOTPSetup(ctx context.Context, userID, code, userAgentID string) error { func (repo *UserRepo) VerifyMFAOTPSetup(ctx context.Context, userID, code, userAgentID string) error {
return repo.UserEvents.CheckMFAOTPSetup(ctx, userID, code, userAgentID) return repo.UserEvents.CheckMFAOTPSetup(ctx, userID, code, userAgentID)
} }
func (repo *UserRepo) VerifyMyMFAOTPSetup(ctx context.Context, code string) error {
return repo.UserEvents.CheckMFAOTPSetup(ctx, authz.GetCtxData(ctx).UserID, code, "")
}
func (repo *UserRepo) RemoveMyMFAOTP(ctx context.Context) error { func (repo *UserRepo) RemoveMyMFAOTP(ctx context.Context) error {
return repo.UserEvents.RemoveOTP(ctx, authz.GetCtxData(ctx).UserID) return repo.UserEvents.RemoveOTP(ctx, authz.GetCtxData(ctx).UserID)
} }
@ -315,18 +271,6 @@ func (repo *UserRepo) VerifyMFAU2FSetup(ctx context.Context, userID, tokenName,
return repo.UserEvents.VerifyU2FSetup(ctx, userID, tokenName, userAgentID, credentialData) return repo.UserEvents.VerifyU2FSetup(ctx, userID, tokenName, userAgentID, credentialData)
} }
func (repo *UserRepo) VerifyMyMFAU2FSetup(ctx context.Context, tokenName string, credentialData []byte) error {
return repo.UserEvents.VerifyU2FSetup(ctx, authz.GetCtxData(ctx).UserID, tokenName, "", credentialData)
}
func (repo *UserRepo) RemoveMFAU2F(ctx context.Context, userID, webAuthNTokenID string) error {
return repo.UserEvents.RemoveU2FToken(ctx, userID, webAuthNTokenID)
}
func (repo *UserRepo) RemoveMyMFAU2F(ctx context.Context, webAuthNTokenID string) error {
return repo.UserEvents.RemoveU2FToken(ctx, authz.GetCtxData(ctx).UserID, webAuthNTokenID)
}
func (repo *UserRepo) GetPasswordless(ctx context.Context, userID string) ([]*model.WebAuthNToken, error) { func (repo *UserRepo) GetPasswordless(ctx context.Context, userID string) ([]*model.WebAuthNToken, error) {
return repo.UserEvents.GetPasswordless(ctx, userID) return repo.UserEvents.GetPasswordless(ctx, userID)
} }
@ -346,34 +290,26 @@ func (repo *UserRepo) GetMyPasswordless(ctx context.Context) ([]*model.WebAuthNT
return repo.UserEvents.GetPasswordless(ctx, authz.GetCtxData(ctx).UserID) return repo.UserEvents.GetPasswordless(ctx, authz.GetCtxData(ctx).UserID)
} }
func (repo *UserRepo) AddMyPasswordless(ctx context.Context) (*model.WebAuthNToken, error) {
userID := authz.GetCtxData(ctx).UserID
accountName := ""
user, err := repo.UserByID(ctx, userID)
if err != nil {
logging.Log("EVENT-AEq21").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get user for loginname")
} else {
accountName = user.PreferredLoginName
}
return repo.UserEvents.AddPasswordless(ctx, authz.GetCtxData(ctx).UserID, accountName, false)
}
func (repo *UserRepo) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error { func (repo *UserRepo) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error {
return repo.UserEvents.VerifyPasswordlessSetup(ctx, userID, tokenName, userAgentID, credentialData) return repo.UserEvents.VerifyPasswordlessSetup(ctx, userID, tokenName, userAgentID, credentialData)
} }
func (repo *UserRepo) VerifyMyPasswordlessSetup(ctx context.Context, tokenName string, credentialData []byte) error {
return repo.UserEvents.VerifyPasswordlessSetup(ctx, authz.GetCtxData(ctx).UserID, tokenName, "", credentialData)
}
func (repo *UserRepo) RemovePasswordless(ctx context.Context, userID, webAuthNTokenID string) error {
return repo.UserEvents.RemovePasswordlessToken(ctx, userID, webAuthNTokenID)
}
func (repo *UserRepo) RemoveMyPasswordless(ctx context.Context, webAuthNTokenID string) error { func (repo *UserRepo) RemoveMyPasswordless(ctx context.Context, webAuthNTokenID string) error {
return repo.UserEvents.RemovePasswordlessToken(ctx, authz.GetCtxData(ctx).UserID, webAuthNTokenID) return repo.UserEvents.RemovePasswordlessToken(ctx, authz.GetCtxData(ctx).UserID, webAuthNTokenID)
} }
func (repo *UserRepo) ChangeMyUsername(ctx context.Context, username string) error {
ctxData := authz.GetCtxData(ctx)
orgPolicy, err := repo.View.OrgIAMPolicyByAggregateID(ctxData.OrgID)
if errors.IsNotFound(err) {
orgPolicy, err = repo.View.OrgIAMPolicyByAggregateID(repo.SystemDefaults.IamID)
}
if err != nil {
return err
}
orgPolicyView := iam_es_model.OrgIAMViewToModel(orgPolicy)
return repo.UserEvents.ChangeUsername(ctx, ctxData.UserID, username, orgPolicyView)
}
func (repo *UserRepo) ResendInitVerificationMail(ctx context.Context, userID string) error { func (repo *UserRepo) ResendInitVerificationMail(ctx context.Context, userID string) error {
_, err := repo.UserEvents.CreateInitializeUserCodeByID(ctx, userID) _, err := repo.UserEvents.CreateInitializeUserCodeByID(ctx, userID)
return err return err

View File

@ -30,12 +30,10 @@ type UserRepository interface {
AddMFAU2F(ctx context.Context, id string) (*model.WebAuthNToken, error) AddMFAU2F(ctx context.Context, id string) (*model.WebAuthNToken, error)
VerifyMFAU2FSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error VerifyMFAU2FSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error
RemoveMFAU2F(ctx context.Context, userID, webAuthNTokenID string) error
GetPasswordless(ctx context.Context, id string) ([]*model.WebAuthNToken, error) GetPasswordless(ctx context.Context, id string) ([]*model.WebAuthNToken, error)
AddPasswordless(ctx context.Context, id string) (*model.WebAuthNToken, error) AddPasswordless(ctx context.Context, id string) (*model.WebAuthNToken, error)
VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error
RemovePasswordless(ctx context.Context, userID, webAuthNTokenID string) error
ChangeUsername(ctx context.Context, userID, username string) error ChangeUsername(ctx context.Context, userID, username string) error
@ -52,37 +50,18 @@ type myUserRepo interface {
MyProfile(ctx context.Context) (*model.Profile, error) MyProfile(ctx context.Context) (*model.Profile, error)
MyEmail(ctx context.Context) (*model.Email, error) MyEmail(ctx context.Context) (*model.Email, error)
VerifyMyEmail(ctx context.Context, code string) error
ResendMyEmailVerificationMail(ctx context.Context) error
MyPhone(ctx context.Context) (*model.Phone, error) MyPhone(ctx context.Context) (*model.Phone, error)
ChangeMyPhone(ctx context.Context, phone *model.Phone) (*model.Phone, error)
RemoveMyPhone(ctx context.Context) error
VerifyMyPhone(ctx context.Context, code string) error
ResendMyPhoneVerificationCode(ctx context.Context) error
MyAddress(ctx context.Context) (*model.Address, error) MyAddress(ctx context.Context) (*model.Address, error)
ChangeMyAddress(ctx context.Context, address *model.Address) (*model.Address, error)
ChangeMyPassword(ctx context.Context, old, new string) error
SearchMyExternalIDPs(ctx context.Context, request *model.ExternalIDPSearchRequest) (*model.ExternalIDPSearchResponse, error) SearchMyExternalIDPs(ctx context.Context, request *model.ExternalIDPSearchRequest) (*model.ExternalIDPSearchResponse, error)
AddMyExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) (*model.ExternalIDP, error)
RemoveMyExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) error
MyUserMFAs(ctx context.Context) ([]*model.MultiFactor, error) MyUserMFAs(ctx context.Context) ([]*model.MultiFactor, error)
AddMyMFAOTP(ctx context.Context) (*model.OTP, error)
VerifyMyMFAOTPSetup(ctx context.Context, code string) error
RemoveMyMFAOTP(ctx context.Context) error
AddMyMFAU2F(ctx context.Context) (*model.WebAuthNToken, error) AddMyMFAU2F(ctx context.Context) (*model.WebAuthNToken, error)
VerifyMyMFAU2FSetup(ctx context.Context, tokenName string, data []byte) error
RemoveMyMFAU2F(ctx context.Context, webAuthNTokenID string) error
GetMyPasswordless(ctx context.Context) ([]*model.WebAuthNToken, error) GetMyPasswordless(ctx context.Context) ([]*model.WebAuthNToken, error)
AddMyPasswordless(ctx context.Context) (*model.WebAuthNToken, error)
VerifyMyPasswordlessSetup(ctx context.Context, tokenName string, data []byte) error
RemoveMyPasswordless(ctx context.Context, webAuthNTokenID string) error
MyUserChanges(ctx context.Context, lastSequence uint64, limit uint64, sortAscending bool) (*model.UserChanges, error) MyUserChanges(ctx context.Context, lastSequence uint64, limit uint64, sortAscending bool) (*model.UserChanges, error)
} }

View File

@ -74,7 +74,7 @@ func insertEvents(stmt *sql.Stmt, previousSequence Sequence, events []*models.Ev
event.AggregateType, event.AggregateID, previousSequence, previousSequence).Scan(&previousSequence, &event.CreationDate) event.AggregateType, event.AggregateID, previousSequence, previousSequence).Scan(&previousSequence, &event.CreationDate)
if err != nil { if err != nil {
logging.LogWithFields("SQL-IP3js", logging.LogWithFields("SQL-5M0sd",
"aggregate", event.AggregateType, "aggregate", event.AggregateType,
"previousSequence", previousSequence, "previousSequence", previousSequence,
"aggregateId", event.AggregateID, "aggregateId", event.AggregateID,

View File

@ -13,11 +13,6 @@ type Aggregater interface {
ResourceOwner() string ResourceOwner() string
//Version represents the semantic version of the aggregate //Version represents the semantic version of the aggregate
Version() Version Version() Version
//PreviouseSequence should return the sequence of the latest event of this aggregate
// stored in the eventstore
// it's set to the first event of this push transaction,
// later events consume the sequence of the previously pushed event of the aggregate
PreviousSequence() uint64
} }
func NewAggregate( func NewAggregate(
@ -25,15 +20,13 @@ func NewAggregate(
typ AggregateType, typ AggregateType,
resourceOwner string, resourceOwner string,
version Version, version Version,
previousSequence uint64,
) *Aggregate { ) *Aggregate {
return &Aggregate{ return &Aggregate{
id: id, id: id,
typ: typ, typ: typ,
resourceOwner: resourceOwner, resourceOwner: resourceOwner,
version: version, version: version,
previousSequence: previousSequence, events: []EventPusher{},
events: []EventPusher{},
} }
} }
@ -43,23 +36,21 @@ func AggregateFromWriteModel(
version Version, version Version,
) *Aggregate { ) *Aggregate {
return &Aggregate{ return &Aggregate{
id: wm.AggregateID, id: wm.AggregateID,
typ: typ, typ: typ,
resourceOwner: wm.ResourceOwner, resourceOwner: wm.ResourceOwner,
version: version, version: version,
previousSequence: wm.ProcessedSequence, events: []EventPusher{},
events: []EventPusher{},
} }
} }
//Aggregate is the basic implementation of Aggregater //Aggregate is the basic implementation of Aggregater
type Aggregate struct { type Aggregate struct {
id string `json:"-"` id string `json:"-"`
typ AggregateType `json:"-"` typ AggregateType `json:"-"`
events []EventPusher `json:"-"` events []EventPusher `json:"-"`
resourceOwner string `json:"-"` resourceOwner string `json:"-"`
version Version `json:"-"` version Version `json:"-"`
previousSequence uint64 `json:"-"`
} }
//PushEvents adds all the events to the aggregate. //PushEvents adds all the events to the aggregate.
@ -93,8 +84,3 @@ func (a *Aggregate) ResourceOwner() string {
func (a *Aggregate) Version() Version { func (a *Aggregate) Version() Version {
return a.version return a.version
} }
//PreviousSequence implements Aggregater
func (a *Aggregate) PreviousSequence() uint64 {
return a.previousSequence
}

View File

@ -33,6 +33,5 @@ type EventReader interface {
ResourceOwner() string ResourceOwner() string
AggregateVersion() Version AggregateVersion() Version
Sequence() uint64 Sequence() uint64
PreviousSequence() uint64
CreationDate() time.Time CreationDate() time.Time
} }

View File

@ -14,11 +14,10 @@ type BaseEvent struct {
aggregateType AggregateType `json:"-"` aggregateType AggregateType `json:"-"`
EventType EventType `json:"-"` EventType EventType `json:"-"`
resourceOwner string `json:"-"` resourceOwner string `json:"-"`
aggregateVersion Version `json:"-"` aggregateVersion Version `json:"-"`
sequence uint64 `json:"-"` sequence uint64 `json:"-"`
previouseSequence uint64 `json:"-"` creationDate time.Time `json:"-"`
creationDate time.Time `json:"-"`
//User is the user who created the event //User is the user who created the event
User string `json:"-"` User string `json:"-"`
@ -56,9 +55,6 @@ func (e *BaseEvent) AggregateVersion() Version {
func (e *BaseEvent) Sequence() uint64 { func (e *BaseEvent) Sequence() uint64 {
return e.sequence return e.sequence
} }
func (e *BaseEvent) PreviousSequence() uint64 {
return e.previouseSequence
}
func (e *BaseEvent) CreationDate() time.Time { func (e *BaseEvent) CreationDate() time.Time {
return e.creationDate return e.creationDate
} }

View File

@ -66,25 +66,21 @@ func (es *Eventstore) PushAggregates(ctx context.Context, aggregates ...Aggregat
func (es *Eventstore) aggregatesToEvents(aggregates []Aggregater) ([]*repository.Event, error) { func (es *Eventstore) aggregatesToEvents(aggregates []Aggregater) ([]*repository.Event, error) {
events := make([]*repository.Event, 0, len(aggregates)) events := make([]*repository.Event, 0, len(aggregates))
for _, aggregate := range aggregates { for _, aggregate := range aggregates {
var previousEvent *repository.Event
for _, event := range aggregate.Events() { for _, event := range aggregate.Events() {
data, err := eventData(event) data, err := eventData(event)
if err != nil { if err != nil {
return nil, err return nil, err
} }
events = append(events, &repository.Event{ events = append(events, &repository.Event{
AggregateID: aggregate.ID(), AggregateID: aggregate.ID(),
AggregateType: repository.AggregateType(aggregate.Type()), AggregateType: repository.AggregateType(aggregate.Type()),
ResourceOwner: aggregate.ResourceOwner(), ResourceOwner: aggregate.ResourceOwner(),
EditorService: event.EditorService(), EditorService: event.EditorService(),
EditorUser: event.EditorUser(), EditorUser: event.EditorUser(),
Type: repository.EventType(event.Type()), Type: repository.EventType(event.Type()),
Version: repository.Version(aggregate.Version()), Version: repository.Version(aggregate.Version()),
PreviousEvent: previousEvent, Data: data,
PreviousSequence: aggregate.PreviousSequence(),
Data: data,
}) })
previousEvent = events[len(events)-1]
} }
} }
return events, nil return events, nil

View File

@ -14,9 +14,8 @@ import (
) )
type testAggregate struct { type testAggregate struct {
id string id string
events []EventPusher events []EventPusher
previousSequence uint64
} }
func (a *testAggregate) ID() string { func (a *testAggregate) ID() string {
@ -39,10 +38,6 @@ func (a *testAggregate) Version() Version {
return "v1" return "v1"
} }
func (a *testAggregate) PreviousSequence() uint64 {
return a.previousSequence
}
// testEvent implements the Event interface // testEvent implements the Event interface
type testEvent struct { type testEvent struct {
BaseEvent BaseEvent
@ -425,8 +420,8 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
}, },
res: res{ res: res{
wantErr: false, wantErr: false,
events: linkEvents( events: []*repository.Event{
&repository.Event{ {
AggregateID: "1", AggregateID: "1",
AggregateType: "test.aggregate", AggregateType: "test.aggregate",
Data: []byte(nil), Data: []byte(nil),
@ -436,7 +431,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
Type: "test.event", Type: "test.event",
Version: "v1", Version: "v1",
}, },
&repository.Event{ {
AggregateID: "1", AggregateID: "1",
AggregateType: "test.aggregate", AggregateType: "test.aggregate",
Data: []byte(nil), Data: []byte(nil),
@ -446,7 +441,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
Type: "test.event", Type: "test.event",
Version: "v1", Version: "v1",
}, },
), },
}, },
}, },
{ {
@ -507,8 +502,8 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
res: res{ res: res{
wantErr: false, wantErr: false,
events: combineEventLists( events: combineEventLists(
linkEvents( []*repository.Event{
&repository.Event{ {
AggregateID: "1", AggregateID: "1",
AggregateType: "test.aggregate", AggregateType: "test.aggregate",
Data: []byte(nil), Data: []byte(nil),
@ -518,7 +513,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
Type: "test.event", Type: "test.event",
Version: "v1", Version: "v1",
}, },
&repository.Event{ {
AggregateID: "1", AggregateID: "1",
AggregateType: "test.aggregate", AggregateType: "test.aggregate",
Data: []byte(nil), Data: []byte(nil),
@ -528,7 +523,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
Type: "test.event", Type: "test.event",
Version: "v1", Version: "v1",
}, },
), },
[]*repository.Event{ []*repository.Event{
{ {
AggregateID: "2", AggregateID: "2",
@ -695,8 +690,8 @@ func TestEventstore_Push(t *testing.T) {
fields: fields{ fields: fields{
repo: &testRepo{ repo: &testRepo{
t: t, t: t,
events: linkEvents( events: []*repository.Event{
&repository.Event{ {
AggregateID: "1", AggregateID: "1",
AggregateType: "test.aggregate", AggregateType: "test.aggregate",
Data: []byte(nil), Data: []byte(nil),
@ -706,7 +701,7 @@ func TestEventstore_Push(t *testing.T) {
Type: "test.event", Type: "test.event",
Version: "v1", Version: "v1",
}, },
&repository.Event{ {
AggregateID: "1", AggregateID: "1",
AggregateType: "test.aggregate", AggregateType: "test.aggregate",
Data: []byte(nil), Data: []byte(nil),
@ -716,7 +711,7 @@ func TestEventstore_Push(t *testing.T) {
Type: "test.event", Type: "test.event",
Version: "v1", Version: "v1",
}, },
), },
}, },
eventMapper: map[EventType]func(*repository.Event) (EventReader, error){ eventMapper: map[EventType]func(*repository.Event) (EventReader, error){
"test.event": func(e *repository.Event) (EventReader, error) { "test.event": func(e *repository.Event) (EventReader, error) {
@ -766,8 +761,8 @@ func TestEventstore_Push(t *testing.T) {
repo: &testRepo{ repo: &testRepo{
t: t, t: t,
events: combineEventLists( events: combineEventLists(
linkEvents( []*repository.Event{
&repository.Event{ {
AggregateID: "1", AggregateID: "1",
AggregateType: "test.aggregate", AggregateType: "test.aggregate",
Data: []byte(nil), Data: []byte(nil),
@ -777,7 +772,7 @@ func TestEventstore_Push(t *testing.T) {
Type: "test.event", Type: "test.event",
Version: "v1", Version: "v1",
}, },
&repository.Event{ {
AggregateID: "1", AggregateID: "1",
AggregateType: "test.aggregate", AggregateType: "test.aggregate",
Data: []byte(nil), Data: []byte(nil),
@ -787,7 +782,7 @@ func TestEventstore_Push(t *testing.T) {
Type: "test.event", Type: "test.event",
Version: "v1", Version: "v1",
}, },
), },
[]*repository.Event{ []*repository.Event{
{ {
AggregateID: "2", AggregateID: "2",
@ -1305,7 +1300,7 @@ func combineEventLists(lists ...[]*repository.Event) []*repository.Event {
func linkEvents(events ...*repository.Event) []*repository.Event { func linkEvents(events ...*repository.Event) []*repository.Event {
for i := 1; i < len(events); i++ { for i := 1; i < len(events); i++ {
events[i].PreviousEvent = events[i-1] // events[i].PreviousEvent = events[i-1]
} }
return events return events
} }
@ -1337,9 +1332,6 @@ func compareEvents(t *testing.T, want, got *repository.Event) {
if want.Version != got.Version { if want.Version != got.Version {
t.Errorf("wrong version got %q want %q", got.Version, want.Version) t.Errorf("wrong version got %q want %q", got.Version, want.Version)
} }
if (want.PreviousEvent == nil) != (got.PreviousEvent == nil) {
t.Errorf("linking failed got was linked: %v want was linked: %v", (got.PreviousEvent != nil), (want.PreviousEvent != nil))
}
if want.PreviousSequence != got.PreviousSequence { if want.PreviousSequence != got.PreviousSequence {
t.Errorf("wrong previous sequence got %d want %d", got.PreviousSequence, want.PreviousSequence) t.Errorf("wrong previous sequence got %d want %d", got.PreviousSequence, want.PreviousSequence)
} }

View File

@ -16,17 +16,13 @@ type Event struct {
// if it's 0 then it's the first event of this aggregate // if it's 0 then it's the first event of this aggregate
PreviousSequence uint64 PreviousSequence uint64
//PreviousEvent is needed in push to update PreviousSequence
// it implements a linked list
PreviousEvent *Event
//CreationDate is the time the event is created //CreationDate is the time the event is created
// it's used for human readability. // it's used for human readability.
// Don't use it for event ordering, // Don't use it for event ordering,
// time drifts in different services could cause integrity problems // time drifts in different services could cause integrity problems
CreationDate time.Time CreationDate time.Time
//KeyType describes the cause of the event (e.g. user.added) //Type describes the cause of the event (e.g. user.added)
// it should always be in past-form // it should always be in past-form
Type EventType Type EventType

View File

@ -17,7 +17,10 @@ import (
) )
const ( const (
crdbInsert = "WITH input_event ( " + //as soon as stored procedures are possible in crdb
// we could move the code to migrations and coll the procedure
// traking issue: https://github.com/cockroachdb/cockroach/issues/17511
crdbInsert = "WITH data ( " +
" event_type, " + " event_type, " +
" aggregate_type, " + " aggregate_type, " +
" aggregate_id, " + " aggregate_id, " +
@ -27,47 +30,35 @@ const (
" editor_user, " + " editor_user, " +
" editor_service, " + " editor_service, " +
" resource_owner, " + " resource_owner, " +
" previous_sequence, " +
// variables below are calculated // variables below are calculated
" max_event_seq " + " previous_sequence" +
") AS ( " + ") AS (" +
" ( " + //previous_data selects the needed data of the latest event of the aggregate
//the following select will return no row if no previous event defined // and buffers it (crdb inmemory)
" SELECT " + " WITH previous_data AS (" +
" $1::VARCHAR, " + " SELECT MAX(event_sequence) AS seq, resource_owner " +
" $2::VARCHAR, " + " FROM eventstore.events " +
" $3::VARCHAR, " + //TODO: remove LIMIT 1 as soon as data cleaned up (only 1 resource_owner per aggregate)
" $4::VARCHAR, " + " WHERE aggregate_type = $2 AND aggregate_id = $3 GROUP BY resource_owner LIMIT 1" +
" COALESCE($5::TIMESTAMPTZ, NOW()), " + " )" +
" $6::JSONB, " + // defines the data to be inserted
" $7::VARCHAR, " + " SELECT " +
" $8::VARCHAR, " + " $1::VARCHAR AS event_type, " +
" resource_owner, " + " $2::VARCHAR AS aggregate_type, " +
" $10::BIGINT, " + " $3::VARCHAR AS aggregate_id, " +
" MAX(event_sequence) AS max_event_seq " + " $4::VARCHAR AS aggregate_version, " +
" FROM eventstore.events " + " NOW() AS creation_date, " +
" WHERE " + " $5::JSONB AS event_data, " +
" aggregate_type = $2::VARCHAR " + " $6::VARCHAR AS editor_user, " +
" AND aggregate_id = $3::VARCHAR " + " $7::VARCHAR AS editor_service, " +
" GROUP BY resource_owner " + " CASE WHEN EXISTS (SELECT * FROM previous_data) " +
" ) UNION (" + " THEN (SELECT resource_owner FROM previous_data) " +
// if no previous event we use the given data " ELSE $8::VARCHAR " +
" VALUES (" + " end AS resource_owner, " +
" $1::VARCHAR, " + " CASE WHEN EXISTS (SELECT * FROM previous_data) " +
" $2::VARCHAR, " + " THEN (SELECT seq FROM previous_data) " +
" $3::VARCHAR, " + " ELSE NULL " +
" $4::VARCHAR, " + " end AS previous_sequence" +
" COALESCE($5::TIMESTAMPTZ, NOW()), " +
" $6::JSONB, " +
" $7::VARCHAR, " +
" $8::VARCHAR, " +
" $9::VARCHAR, " +
" $10::BIGINT, " +
" NULL::BIGINT " +
" ) " +
" ) " +
// ensure only 1 row in input_event
" LIMIT 1 " +
") " + ") " +
"INSERT INTO eventstore.events " + "INSERT INTO eventstore.events " +
" ( " + " ( " +
@ -94,9 +85,9 @@ const (
" editor_service, " + " editor_service, " +
" resource_owner, " + " resource_owner, " +
" previous_sequence " + " previous_sequence " +
" FROM input_event " + " FROM data " +
" ) " + " ) " +
"RETURNING id, event_sequence, previous_sequence, creation_date, resource_owner " "RETURNING id, event_sequence, previous_sequence, creation_date, resource_owner"
) )
type CRDB struct { type CRDB struct {
@ -119,28 +110,17 @@ func (db *CRDB) Push(ctx context.Context, events ...*repository.Event) error {
return caos_errs.ThrowInternal(err, "SQL-OdXRE", "prepare failed") return caos_errs.ThrowInternal(err, "SQL-OdXRE", "prepare failed")
} }
var previousSequence Sequence
for _, event := range events { for _, event := range events {
previousSequence := Sequence(event.PreviousSequence)
if event.PreviousEvent != nil {
if event.PreviousEvent.AggregateType != event.AggregateType || event.PreviousEvent.AggregateID != event.AggregateID {
return caos_errs.ThrowPreconditionFailed(nil, "SQL-J55uR", "aggregate of linked events unequal")
}
previousSequence = Sequence(event.PreviousEvent.Sequence)
}
err = stmt.QueryRowContext(ctx, err = stmt.QueryRowContext(ctx,
event.Type, event.Type,
event.AggregateType, event.AggregateType,
event.AggregateID, event.AggregateID,
event.Version, event.Version,
&sql.NullTime{
Time: event.CreationDate,
Valid: !event.CreationDate.IsZero(),
},
Data(event.Data), Data(event.Data),
event.EditorUser, event.EditorUser,
event.EditorService, event.EditorService,
event.ResourceOwner, event.ResourceOwner,
previousSequence,
).Scan(&event.ID, &event.Sequence, &previousSequence, &event.CreationDate, &event.ResourceOwner) ).Scan(&event.ID, &event.Sequence, &previousSequence, &event.CreationDate, &event.ResourceOwner)
event.PreviousSequence = uint64(previousSequence) event.PreviousSequence = uint64(previousSequence)

View File

@ -284,11 +284,11 @@ func TestCRDB_Push_OneAggregate(t *testing.T) {
res res res res
}{ }{
{ {
name: "push 1 event with check previous", name: "push 1 event",
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
events: []*repository.Event{ events: []*repository.Event{
generateEvent(t, "1", true, 0), generateEvent(t, "1"),
}, },
}, },
res: res{ res: res{
@ -300,83 +300,14 @@ func TestCRDB_Push_OneAggregate(t *testing.T) {
}}, }},
}, },
{ {
name: "fail push 1 event with check previous wrong sequence", name: "push two events on agg",
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
events: []*repository.Event{ events: []*repository.Event{
generateEvent(t, "2", true, 5), generateEvent(t, "6"),
generateEvent(t, "6"),
}, },
}, },
res: res{
wantErr: true,
eventsRes: eventsRes{
pushedEventsCount: 0,
aggID: []string{"2"},
aggType: repository.AggregateType(t.Name()),
},
},
},
{
name: "push 1 event without check previous",
args: args{
ctx: context.Background(),
events: []*repository.Event{
generateEvent(t, "3", false, 0),
},
},
res: res{
wantErr: false,
eventsRes: eventsRes{
pushedEventsCount: 1,
aggID: []string{"3"},
aggType: repository.AggregateType(t.Name()),
},
},
},
{
name: "push 1 event without check previous wrong sequence",
args: args{
ctx: context.Background(),
events: []*repository.Event{
generateEvent(t, "4", false, 5),
},
},
res: res{
wantErr: false,
eventsRes: eventsRes{
pushedEventsCount: 1,
aggID: []string{"4"},
aggType: repository.AggregateType(t.Name()),
},
},
},
{
name: "fail on push two events on agg without linking",
args: args{
ctx: context.Background(),
events: []*repository.Event{
generateEvent(t, "5", true, 0),
generateEvent(t, "5", true, 0),
},
},
res: res{
wantErr: true,
eventsRes: eventsRes{
pushedEventsCount: 0,
aggID: []string{"5"},
aggType: repository.AggregateType(t.Name()),
},
},
},
{
name: "push two events on agg with linking",
args: args{
ctx: context.Background(),
events: linkEvents(
generateEvent(t, "6", true, 0),
generateEvent(t, "6", true, 0),
),
},
res: res{ res: res{
wantErr: false, wantErr: false,
eventsRes: eventsRes{ eventsRes: eventsRes{
@ -386,51 +317,12 @@ func TestCRDB_Push_OneAggregate(t *testing.T) {
}, },
}, },
}, },
{
name: "push two events on agg with linking without check previous",
args: args{
ctx: context.Background(),
events: linkEvents(
generateEvent(t, "7", false, 0),
generateEvent(t, "7", false, 0),
),
},
res: res{
wantErr: false,
eventsRes: eventsRes{
pushedEventsCount: 2,
aggID: []string{"7"},
aggType: repository.AggregateType(t.Name()),
},
},
},
{
name: "push two events on agg with linking mixed check previous",
args: args{
ctx: context.Background(),
events: linkEvents(
generateEvent(t, "8", false, 0),
generateEvent(t, "8", true, 0),
generateEvent(t, "8", false, 0),
generateEvent(t, "8", true, 0),
generateEvent(t, "8", true, 0),
),
},
res: res{
wantErr: false,
eventsRes: eventsRes{
pushedEventsCount: 5,
aggID: []string{"8"},
aggType: repository.AggregateType(t.Name()),
},
},
},
{ {
name: "failed push because context canceled", name: "failed push because context canceled",
args: args{ args: args{
ctx: canceledCtx(), ctx: canceledCtx(),
events: []*repository.Event{ events: []*repository.Event{
generateEvent(t, "9", true, 0), generateEvent(t, "9"),
}, },
}, },
res: res{ res: res{
@ -485,11 +377,11 @@ func TestCRDB_Push_MultipleAggregate(t *testing.T) {
res res res res
}{ }{
{ {
name: "push two aggregates both check previous", name: "push two aggregates",
args: args{ args: args{
events: []*repository.Event{ events: []*repository.Event{
generateEvent(t, "100", true, 0), generateEvent(t, "100"),
generateEvent(t, "101", true, 0), generateEvent(t, "101"),
}, },
}, },
res: res{ res: res{
@ -502,18 +394,14 @@ func TestCRDB_Push_MultipleAggregate(t *testing.T) {
}, },
}, },
{ {
name: "push two aggregates both check previous multiple events", name: "push two aggregates both multiple events",
args: args{ args: args{
events: combineEventLists( events: []*repository.Event{
linkEvents( generateEvent(t, "102"),
generateEvent(t, "102", true, 0), generateEvent(t, "102"),
generateEvent(t, "102", true, 0), generateEvent(t, "103"),
), generateEvent(t, "103"),
linkEvents( },
generateEvent(t, "103", true, 0),
generateEvent(t, "103", true, 0),
),
),
}, },
res: res{ res: res{
wantErr: false, wantErr: false,
@ -525,64 +413,28 @@ func TestCRDB_Push_MultipleAggregate(t *testing.T) {
}, },
}, },
{ {
name: "fail push linked events of different aggregates", name: "push two aggregates mixed multiple events",
args: args{ args: args{
events: linkEvents( events: []*repository.Event{
generateEvent(t, "104", false, 0), generateEvent(t, "106"),
generateEvent(t, "105", false, 0), generateEvent(t, "106"),
), generateEvent(t, "106"),
}, generateEvent(t, "106"),
res: res{ generateEvent(t, "107"),
wantErr: true, generateEvent(t, "107"),
eventsRes: eventsRes{ generateEvent(t, "107"),
pushedEventsCount: 0, generateEvent(t, "107"),
aggID: []string{"104", "105"}, generateEvent(t, "108"),
aggType: []repository.AggregateType{repository.AggregateType(t.Name())}, generateEvent(t, "108"),
generateEvent(t, "108"),
generateEvent(t, "108"),
}, },
}, },
},
{
name: "push two aggregates mixed check previous multiple events",
args: args{
events: combineEventLists(
linkEvents(
generateEvent(t, "106", true, 0),
generateEvent(t, "106", false, 0),
generateEvent(t, "106", false, 0),
generateEvent(t, "106", true, 0),
),
linkEvents(
generateEvent(t, "107", false, 0),
generateEvent(t, "107", true, 0),
generateEvent(t, "107", false, 0),
generateEvent(t, "107", true, 0),
),
linkEvents(
generateEvent(t, "108", true, 0),
generateEvent(t, "108", false, 0),
generateEvent(t, "108", false, 0),
generateEvent(t, "108", true, 0),
),
),
},
},
{
name: "failed push same aggregate in two transactions",
args: args{
events: combineEventLists(
linkEvents(
generateEvent(t, "109", true, 0),
),
linkEvents(
generateEvent(t, "109", true, 0),
),
),
},
res: res{ res: res{
wantErr: true, wantErr: false,
eventsRes: eventsRes{ eventsRes: eventsRes{
pushedEventsCount: 0, pushedEventsCount: 12,
aggID: []string{"109"}, aggID: []string{"106", "107", "108"},
aggType: []repository.AggregateType{repository.AggregateType(t.Name())}, aggType: []repository.AggregateType{repository.AggregateType(t.Name())},
}, },
}, },
@ -633,25 +485,19 @@ func TestCRDB_Push_Parallel(t *testing.T) {
name: "clients push different aggregates", name: "clients push different aggregates",
args: args{ args: args{
events: [][]*repository.Event{ events: [][]*repository.Event{
linkEvents( {
generateEvent(t, "200", false, 0), generateEvent(t, "200"),
generateEvent(t, "200", true, 0), generateEvent(t, "200"),
generateEvent(t, "200", false, 0), generateEvent(t, "200"),
), generateEvent(t, "201"),
linkEvents( generateEvent(t, "201"),
generateEvent(t, "201", false, 0), generateEvent(t, "201"),
generateEvent(t, "201", true, 0), },
generateEvent(t, "201", false, 0), {
), generateEvent(t, "202"),
combineEventLists( generateEvent(t, "203"),
linkEvents( generateEvent(t, "203"),
generateEvent(t, "202", false, 0), },
),
linkEvents(
generateEvent(t, "203", true, 0),
generateEvent(t, "203", false, 0),
),
),
}, },
}, },
res: res{ res: res{
@ -664,41 +510,31 @@ func TestCRDB_Push_Parallel(t *testing.T) {
}, },
}, },
{ {
name: "clients push same aggregates no check previous", name: "clients push same aggregates",
args: args{ args: args{
events: [][]*repository.Event{ events: [][]*repository.Event{
linkEvents( {
generateEvent(t, "204", false, 0), generateEvent(t, "204"),
generateEvent(t, "204", false, 0), generateEvent(t, "204"),
), },
linkEvents( {
generateEvent(t, "204", false, 0), generateEvent(t, "204"),
generateEvent(t, "204", false, 0), generateEvent(t, "204"),
), },
combineEventLists( {
linkEvents( generateEvent(t, "205"),
generateEvent(t, "205", false, 0), generateEvent(t, "205"),
generateEvent(t, "205", false, 0), generateEvent(t, "205"),
generateEvent(t, "205", false, 0), generateEvent(t, "206"),
), generateEvent(t, "206"),
linkEvents( generateEvent(t, "206"),
generateEvent(t, "206", false, 0), },
generateEvent(t, "206", false, 0), {
generateEvent(t, "206", false, 0), generateEvent(t, "204"),
), generateEvent(t, "205"),
), generateEvent(t, "205"),
combineEventLists( generateEvent(t, "206"),
linkEvents( },
generateEvent(t, "204", false, 0),
),
linkEvents(
generateEvent(t, "205", false, 0),
generateEvent(t, "205", false, 0),
),
linkEvents(
generateEvent(t, "206", false, 0),
),
),
}, },
}, },
res: res{ res: res{
@ -711,24 +547,24 @@ func TestCRDB_Push_Parallel(t *testing.T) {
}, },
}, },
{ {
name: "clients push different aggregates one with check previous", name: "clients push different aggregates",
args: args{ args: args{
events: [][]*repository.Event{ events: [][]*repository.Event{
linkEvents( {
generateEvent(t, "207", false, 0), generateEvent(t, "207"),
generateEvent(t, "207", false, 0), generateEvent(t, "207"),
generateEvent(t, "207", false, 0), generateEvent(t, "207"),
generateEvent(t, "207", false, 0), generateEvent(t, "207"),
generateEvent(t, "207", false, 0), generateEvent(t, "207"),
generateEvent(t, "207", false, 0), generateEvent(t, "207"),
), },
linkEvents( {
generateEvent(t, "208", true, 0), generateEvent(t, "208"),
generateEvent(t, "208", true, 0), generateEvent(t, "208"),
generateEvent(t, "208", true, 0), generateEvent(t, "208"),
generateEvent(t, "208", true, 0), generateEvent(t, "208"),
generateEvent(t, "208", true, 0), generateEvent(t, "208"),
), },
}, },
}, },
res: res{ res: res{
@ -741,21 +577,21 @@ func TestCRDB_Push_Parallel(t *testing.T) {
}, },
}, },
{ {
name: "clients push different aggregates all with check previous on first event fail", name: "clients push same aggregates",
args: args{ args: args{
events: [][]*repository.Event{ events: [][]*repository.Event{
linkEvents( {
generateEventWithData(t, "210", true, 0, []byte(`{ "transaction": 1 }`)), generateEventWithData(t, "210", []byte(`{ "transaction": 1 }`)),
generateEventWithData(t, "210", false, 0, []byte(`{ "transaction": 1.1 }`)), generateEventWithData(t, "210", []byte(`{ "transaction": 1.1 }`)),
), },
linkEvents( {
generateEventWithData(t, "210", true, 0, []byte(`{ "transaction": 2 }`)), generateEventWithData(t, "210", []byte(`{ "transaction": 2 }`)),
generateEventWithData(t, "210", false, 0, []byte(`{ "transaction": 2.1 }`)), generateEventWithData(t, "210", []byte(`{ "transaction": 2.1 }`)),
), },
linkEvents( {
generateEventWithData(t, "210", true, 0, []byte(`{ "transaction": 3 }`)), generateEventWithData(t, "210", []byte(`{ "transaction": 3 }`)),
generateEventWithData(t, "210", false, 0, []byte(`{ "transaction": 30.1 }`)), generateEventWithData(t, "210", []byte(`{ "transaction": 30.1 }`)),
), },
}, },
}, },
res: res{ res: res{
@ -850,9 +686,9 @@ func TestCRDB_Filter(t *testing.T) {
}, },
fields: fields{ fields: fields{
existingEvents: []*repository.Event{ existingEvents: []*repository.Event{
generateEvent(t, "300", false, 0), generateEvent(t, "300"),
generateEvent(t, "300", false, 0), generateEvent(t, "300"),
generateEvent(t, "300", false, 0), generateEvent(t, "300"),
}, },
}, },
res: res{ res: res{
@ -873,10 +709,10 @@ func TestCRDB_Filter(t *testing.T) {
}, },
fields: fields{ fields: fields{
existingEvents: []*repository.Event{ existingEvents: []*repository.Event{
generateEvent(t, "303", false, 0), generateEvent(t, "303"),
generateEvent(t, "303", false, 0), generateEvent(t, "303"),
generateEvent(t, "303", false, 0), generateEvent(t, "303"),
generateEvent(t, "305", false, 0), generateEvent(t, "305"),
}, },
}, },
res: res{ res: res{
@ -938,9 +774,9 @@ func TestCRDB_LatestSequence(t *testing.T) {
}, },
fields: fields{ fields: fields{
existingEvents: []*repository.Event{ existingEvents: []*repository.Event{
generateEvent(t, "400", false, 0), generateEvent(t, "400"),
generateEvent(t, "400", false, 0), generateEvent(t, "400"),
generateEvent(t, "400", false, 0), generateEvent(t, "400"),
}, },
}, },
res: res{ res: res{
@ -960,9 +796,9 @@ func TestCRDB_LatestSequence(t *testing.T) {
}, },
fields: fields{ fields: fields{
existingEvents: []*repository.Event{ existingEvents: []*repository.Event{
generateEvent(t, "401", false, 0), generateEvent(t, "401"),
generateEvent(t, "401", false, 0), generateEvent(t, "401"),
generateEvent(t, "401", false, 0), generateEvent(t, "401"),
}, },
}, },
res: res{ res: res{
@ -1016,8 +852,8 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) {
name: "two events of same aggregate same resource owner", name: "two events of same aggregate same resource owner",
args: args{ args: args{
events: []*repository.Event{ events: []*repository.Event{
generateEvent(t, "500", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), generateEvent(t, "500", func(e *repository.Event) { e.ResourceOwner = "caos" }),
generateEvent(t, "500", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), generateEvent(t, "500", func(e *repository.Event) { e.ResourceOwner = "caos" }),
}, },
}, },
fields: fields{ fields: fields{
@ -1032,8 +868,8 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) {
name: "two events of different aggregate same resource owner", name: "two events of different aggregate same resource owner",
args: args{ args: args{
events: []*repository.Event{ events: []*repository.Event{
generateEvent(t, "501", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), generateEvent(t, "501", func(e *repository.Event) { e.ResourceOwner = "caos" }),
generateEvent(t, "502", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), generateEvent(t, "502", func(e *repository.Event) { e.ResourceOwner = "caos" }),
}, },
}, },
fields: fields{ fields: fields{
@ -1048,8 +884,8 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) {
name: "two events of different aggregate different resource owner", name: "two events of different aggregate different resource owner",
args: args{ args: args{
events: []*repository.Event{ events: []*repository.Event{
generateEvent(t, "503", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), generateEvent(t, "503", func(e *repository.Event) { e.ResourceOwner = "caos" }),
generateEvent(t, "504", false, 0, func(e *repository.Event) { e.ResourceOwner = "zitadel" }), generateEvent(t, "504", func(e *repository.Event) { e.ResourceOwner = "zitadel" }),
}, },
}, },
fields: fields{ fields: fields{
@ -1063,16 +899,12 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) {
{ {
name: "events of different aggregate different resource owner", name: "events of different aggregate different resource owner",
args: args{ args: args{
events: combineEventLists( events: []*repository.Event{
linkEvents( generateEvent(t, "505", func(e *repository.Event) { e.ResourceOwner = "caos" }),
generateEvent(t, "505", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), generateEvent(t, "505", func(e *repository.Event) { e.ResourceOwner = "caos" }),
generateEvent(t, "505", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), generateEvent(t, "506", func(e *repository.Event) { e.ResourceOwner = "zitadel" }),
), generateEvent(t, "506", func(e *repository.Event) { e.ResourceOwner = "zitadel" }),
linkEvents( },
generateEvent(t, "506", false, 0, func(e *repository.Event) { e.ResourceOwner = "zitadel" }),
generateEvent(t, "506", false, 0, func(e *repository.Event) { e.ResourceOwner = "zitadel" }),
),
),
}, },
fields: fields{ fields: fields{
aggregateIDs: []string{"505", "506"}, aggregateIDs: []string{"505", "506"},
@ -1085,16 +917,12 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) {
{ {
name: "events of different aggregate different resource owner per event", name: "events of different aggregate different resource owner per event",
args: args{ args: args{
events: combineEventLists( events: []*repository.Event{
linkEvents( generateEvent(t, "507", func(e *repository.Event) { e.ResourceOwner = "caos" }),
generateEvent(t, "507", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), generateEvent(t, "507", func(e *repository.Event) { e.ResourceOwner = "ignored" }),
generateEvent(t, "507", false, 0, func(e *repository.Event) { e.ResourceOwner = "ignored" }), generateEvent(t, "508", func(e *repository.Event) { e.ResourceOwner = "zitadel" }),
), generateEvent(t, "508", func(e *repository.Event) { e.ResourceOwner = "ignored" }),
linkEvents( },
generateEvent(t, "508", false, 0, func(e *repository.Event) { e.ResourceOwner = "zitadel" }),
generateEvent(t, "508", false, 0, func(e *repository.Event) { e.ResourceOwner = "ignored" }),
),
),
}, },
fields: fields{ fields: fields{
aggregateIDs: []string{"507", "508"}, aggregateIDs: []string{"507", "508"},
@ -1107,16 +935,12 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) {
{ {
name: "events of one aggregate different resource owner per event", name: "events of one aggregate different resource owner per event",
args: args{ args: args{
events: combineEventLists( events: []*repository.Event{
linkEvents( generateEvent(t, "509", func(e *repository.Event) { e.ResourceOwner = "caos" }),
generateEvent(t, "509", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), generateEvent(t, "509", func(e *repository.Event) { e.ResourceOwner = "ignored" }),
generateEvent(t, "509", false, 0, func(e *repository.Event) { e.ResourceOwner = "ignored" }), generateEvent(t, "509", func(e *repository.Event) { e.ResourceOwner = "ignored" }),
), generateEvent(t, "509", func(e *repository.Event) { e.ResourceOwner = "ignored" }),
linkEvents( },
generateEvent(t, "509", false, 0, func(e *repository.Event) { e.ResourceOwner = "ignored" }),
generateEvent(t, "509", false, 0, func(e *repository.Event) { e.ResourceOwner = "ignored" }),
),
),
}, },
fields: fields{ fields: fields{
aggregateIDs: []string{"509"}, aggregateIDs: []string{"509"},
@ -1180,34 +1004,16 @@ func canceledCtx() context.Context {
return ctx return ctx
} }
func combineEventLists(lists ...[]*repository.Event) []*repository.Event { func generateEvent(t *testing.T, aggregateID string, opts ...func(*repository.Event)) *repository.Event {
combined := make([]*repository.Event, 0)
for _, list := range lists {
combined = append(combined, list...)
}
return combined
}
func linkEvents(events ...*repository.Event) []*repository.Event {
for i := 1; i < len(events); i++ {
events[i].PreviousEvent = events[i-1]
}
return events
}
func generateEvent(t *testing.T, aggregateID string, checkPrevious bool, previousSeq uint64, opts ...func(*repository.Event)) *repository.Event {
t.Helper() t.Helper()
e := &repository.Event{ e := &repository.Event{
AggregateID: aggregateID, AggregateID: aggregateID,
AggregateType: repository.AggregateType(t.Name()), AggregateType: repository.AggregateType(t.Name()),
CheckPreviousSequence: checkPrevious, EditorService: "svc",
EditorService: "svc", EditorUser: "user",
EditorUser: "user", ResourceOwner: "ro",
PreviousEvent: nil, Type: "test.created",
PreviousSequence: previousSeq, Version: "v1",
ResourceOwner: "ro",
Type: "test.created",
Version: "v1",
} }
for _, opt := range opts { for _, opt := range opts {
@ -1217,19 +1023,16 @@ func generateEvent(t *testing.T, aggregateID string, checkPrevious bool, previou
return e return e
} }
func generateEventWithData(t *testing.T, aggregateID string, checkPrevious bool, previousSeq uint64, data []byte) *repository.Event { func generateEventWithData(t *testing.T, aggregateID string, data []byte) *repository.Event {
t.Helper() t.Helper()
return &repository.Event{ return &repository.Event{
AggregateID: aggregateID, AggregateID: aggregateID,
AggregateType: repository.AggregateType(t.Name()), AggregateType: repository.AggregateType(t.Name()),
CheckPreviousSequence: checkPrevious, EditorService: "svc",
EditorService: "svc", EditorUser: "user",
EditorUser: "user", ResourceOwner: "ro",
PreviousEvent: nil, Type: "test.created",
PreviousSequence: previousSeq, Version: "v1",
ResourceOwner: "ro", Data: data,
Type: "test.created",
Version: "v1",
Data: data,
} }
} }

View File

@ -27,9 +27,13 @@ func TestMain(m *testing.M) {
} }
testCRDBClient, err = sql.Open("postgres", ts.PGURL().String()) testCRDBClient, err = sql.Open("postgres", ts.PGURL().String())
if err != nil { if err != nil {
logging.LogWithFields("REPOS-CF6dQ", "error", err).Fatal("unable to connect to db") logging.LogWithFields("REPOS-CF6dQ", "error", err).Fatal("unable to connect to db")
} }
if err = testCRDBClient.Ping(); err != nil {
logging.LogWithFields("REPOS-CF6dQ", "error", err).Fatal("unable to ping db")
}
defer func() { defer func() {
testCRDBClient.Close() testCRDBClient.Close()

View File

@ -112,12 +112,11 @@ func eventsScanner(scanner scan, dest interface{}) (err error) {
) )
if err != nil { if err != nil {
logging.Log("SQL-kn1Sw").WithError(err).Warn("unable to scan row") logging.Log("SQL-3mofs").WithError(err).Warn("unable to scan row")
return z_errors.ThrowInternal(err, "SQL-J0hFS", "unable to scan row") return z_errors.ThrowInternal(err, "SQL-M0dsf", "unable to scan row")
} }
event.PreviousSequence = uint64(previousSequence) event.PreviousSequence = uint64(previousSequence)
event.Data = make([]byte, len(data)) event.Data = make([]byte, len(data))
copy(event.Data, data) copy(event.Data, data)

View File

@ -329,9 +329,9 @@ func Test_query_events_with_crdb(t *testing.T) {
fields: fields{ fields: fields{
client: testCRDBClient, client: testCRDBClient,
existingEvents: []*repository.Event{ existingEvents: []*repository.Event{
generateEvent(t, "300", false, 0), generateEvent(t, "300"),
generateEvent(t, "300", false, 0), generateEvent(t, "300"),
generateEvent(t, "300", false, 0), generateEvent(t, "300"),
}, },
}, },
res: res{ res: res{
@ -352,10 +352,10 @@ func Test_query_events_with_crdb(t *testing.T) {
fields: fields{ fields: fields{
client: testCRDBClient, client: testCRDBClient,
existingEvents: []*repository.Event{ existingEvents: []*repository.Event{
generateEvent(t, "301", false, 0), generateEvent(t, "301"),
generateEvent(t, "302", false, 0), generateEvent(t, "302"),
generateEvent(t, "302", false, 0), generateEvent(t, "302"),
generateEvent(t, "303", false, 0, func(e *repository.Event) { e.AggregateType = "not in list" }), generateEvent(t, "303", func(e *repository.Event) { e.AggregateType = "not in list" }),
}, },
}, },
res: res{ res: res{
@ -377,11 +377,11 @@ func Test_query_events_with_crdb(t *testing.T) {
fields: fields{ fields: fields{
client: testCRDBClient, client: testCRDBClient,
existingEvents: []*repository.Event{ existingEvents: []*repository.Event{
generateEvent(t, "303", false, 0), generateEvent(t, "303"),
generateEvent(t, "303", false, 0), generateEvent(t, "303"),
generateEvent(t, "303", false, 0), generateEvent(t, "303"),
generateEvent(t, "304", false, 0, func(e *repository.Event) { e.AggregateType = "not in list" }), generateEvent(t, "304", func(e *repository.Event) { e.AggregateType = "not in list" }),
generateEvent(t, "305", false, 0), generateEvent(t, "305"),
}, },
}, },
res: res{ res: res{
@ -402,11 +402,11 @@ func Test_query_events_with_crdb(t *testing.T) {
fields: fields{ fields: fields{
client: testCRDBClient, client: testCRDBClient,
existingEvents: []*repository.Event{ existingEvents: []*repository.Event{
generateEvent(t, "306", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), generateEvent(t, "306", func(e *repository.Event) { e.ResourceOwner = "caos" }),
generateEvent(t, "307", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), generateEvent(t, "307", func(e *repository.Event) { e.ResourceOwner = "caos" }),
generateEvent(t, "308", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), generateEvent(t, "308", func(e *repository.Event) { e.ResourceOwner = "caos" }),
generateEvent(t, "309", false, 0, func(e *repository.Event) { e.ResourceOwner = "orgID" }), generateEvent(t, "309", func(e *repository.Event) { e.ResourceOwner = "orgID" }),
generateEvent(t, "309", false, 0, func(e *repository.Event) { e.ResourceOwner = "orgID" }), generateEvent(t, "309", func(e *repository.Event) { e.ResourceOwner = "orgID" }),
}, },
}, },
res: res{ res: res{
@ -428,11 +428,11 @@ func Test_query_events_with_crdb(t *testing.T) {
fields: fields{ fields: fields{
client: testCRDBClient, client: testCRDBClient,
existingEvents: []*repository.Event{ existingEvents: []*repository.Event{
generateEvent(t, "307", false, 0, func(e *repository.Event) { e.EditorService = "MANAGEMENT-API" }), generateEvent(t, "307", func(e *repository.Event) { e.EditorService = "MANAGEMENT-API" }),
generateEvent(t, "307", false, 0, func(e *repository.Event) { e.EditorService = "MANAGEMENT-API" }), generateEvent(t, "307", func(e *repository.Event) { e.EditorService = "MANAGEMENT-API" }),
generateEvent(t, "308", false, 0, func(e *repository.Event) { e.EditorService = "ADMIN-API" }), generateEvent(t, "308", func(e *repository.Event) { e.EditorService = "ADMIN-API" }),
generateEvent(t, "309", false, 0, func(e *repository.Event) { e.EditorService = "AUTHAPI" }), generateEvent(t, "309", func(e *repository.Event) { e.EditorService = "AUTHAPI" }),
generateEvent(t, "309", false, 0, func(e *repository.Event) { e.EditorService = "AUTHAPI" }), generateEvent(t, "309", func(e *repository.Event) { e.EditorService = "AUTHAPI" }),
}, },
}, },
res: res{ res: res{
@ -455,13 +455,13 @@ func Test_query_events_with_crdb(t *testing.T) {
fields: fields{ fields: fields{
client: testCRDBClient, client: testCRDBClient,
existingEvents: []*repository.Event{ existingEvents: []*repository.Event{
generateEvent(t, "310", false, 0, func(e *repository.Event) { e.EditorUser = "adlerhurst" }), generateEvent(t, "310", func(e *repository.Event) { e.EditorUser = "adlerhurst" }),
generateEvent(t, "310", false, 0, func(e *repository.Event) { e.EditorUser = "adlerhurst" }), generateEvent(t, "310", func(e *repository.Event) { e.EditorUser = "adlerhurst" }),
generateEvent(t, "310", false, 0, func(e *repository.Event) { e.EditorUser = "nobody" }), generateEvent(t, "310", func(e *repository.Event) { e.EditorUser = "nobody" }),
generateEvent(t, "311", false, 0, func(e *repository.Event) { e.EditorUser = "" }), generateEvent(t, "311", func(e *repository.Event) { e.EditorUser = "" }),
generateEvent(t, "311", false, 0, func(e *repository.Event) { e.EditorUser = "" }), generateEvent(t, "311", func(e *repository.Event) { e.EditorUser = "" }),
generateEvent(t, "312", false, 0, func(e *repository.Event) { e.EditorUser = "fforootd" }), generateEvent(t, "312", func(e *repository.Event) { e.EditorUser = "fforootd" }),
generateEvent(t, "312", false, 0, func(e *repository.Event) { e.EditorUser = "fforootd" }), generateEvent(t, "312", func(e *repository.Event) { e.EditorUser = "fforootd" }),
}, },
}, },
res: res{ res: res{
@ -483,15 +483,15 @@ func Test_query_events_with_crdb(t *testing.T) {
fields: fields{ fields: fields{
client: testCRDBClient, client: testCRDBClient,
existingEvents: []*repository.Event{ existingEvents: []*repository.Event{
generateEvent(t, "311", false, 0, func(e *repository.Event) { e.Type = "user.created" }), generateEvent(t, "311", func(e *repository.Event) { e.Type = "user.created" }),
generateEvent(t, "311", false, 0, func(e *repository.Event) { e.Type = "user.updated" }), generateEvent(t, "311", func(e *repository.Event) { e.Type = "user.updated" }),
generateEvent(t, "311", false, 0, func(e *repository.Event) { e.Type = "user.deactivated" }), generateEvent(t, "311", func(e *repository.Event) { e.Type = "user.deactivated" }),
generateEvent(t, "311", false, 0, func(e *repository.Event) { e.Type = "user.locked" }), generateEvent(t, "311", func(e *repository.Event) { e.Type = "user.locked" }),
generateEvent(t, "312", false, 0, func(e *repository.Event) { e.Type = "user.created" }), generateEvent(t, "312", func(e *repository.Event) { e.Type = "user.created" }),
generateEvent(t, "312", false, 0, func(e *repository.Event) { e.Type = "user.updated" }), generateEvent(t, "312", func(e *repository.Event) { e.Type = "user.updated" }),
generateEvent(t, "312", false, 0, func(e *repository.Event) { e.Type = "user.deactivated" }), generateEvent(t, "312", func(e *repository.Event) { e.Type = "user.deactivated" }),
generateEvent(t, "312", false, 0, func(e *repository.Event) { e.Type = "user.reactivated" }), generateEvent(t, "312", func(e *repository.Event) { e.Type = "user.reactivated" }),
generateEvent(t, "313", false, 0, func(e *repository.Event) { e.Type = "user.locked" }), generateEvent(t, "313", func(e *repository.Event) { e.Type = "user.locked" }),
}, },
}, },
res: res{ res: res{

View File

@ -2,6 +2,8 @@ package handler
import ( import (
"context" "context"
"github.com/caos/zitadel/internal/v2/command"
"github.com/caos/zitadel/internal/v2/query"
"net" "net"
"net/http" "net/http"
@ -27,6 +29,8 @@ type Login struct {
router http.Handler router http.Handler
renderer *Renderer renderer *Renderer
parser *form.Parser parser *form.Parser
command *command.CommandSide
query *query.QuerySide
authRepo auth_repository.Repository authRepo auth_repository.Repository
baseURL string baseURL string
zitadelURL string zitadelURL string
@ -56,7 +60,7 @@ const (
handlerPrefix = "/login" handlerPrefix = "/login"
) )
func CreateLogin(config Config, authRepo *eventsourcing.EsRepository, systemDefaults systemdefaults.SystemDefaults, localDevMode bool) (*Login, string) { func CreateLogin(config Config, command *command.CommandSide, query *query.QuerySide, authRepo *eventsourcing.EsRepository, systemDefaults systemdefaults.SystemDefaults, localDevMode bool) (*Login, string) {
aesCrypto, err := crypto.NewAESCrypto(systemDefaults.IDPConfigVerificationKey) aesCrypto, err := crypto.NewAESCrypto(systemDefaults.IDPConfigVerificationKey)
if err != nil { if err != nil {
logging.Log("HANDL-s90ew").WithError(err).Debug("error create new aes crypto") logging.Log("HANDL-s90ew").WithError(err).Debug("error create new aes crypto")
@ -65,6 +69,8 @@ func CreateLogin(config Config, authRepo *eventsourcing.EsRepository, systemDefa
oidcAuthCallbackURL: config.OidcAuthCallbackURL, oidcAuthCallbackURL: config.OidcAuthCallbackURL,
baseURL: config.BaseURL, baseURL: config.BaseURL,
zitadelURL: config.ZitadelURL, zitadelURL: config.ZitadelURL,
command: command,
query: query,
authRepo: authRepo, authRepo: authRepo,
IDPConfigAesCrypto: aesCrypto, IDPConfigAesCrypto: aesCrypto,
} }

View File

@ -4,12 +4,14 @@ import (
"github.com/caos/zitadel/internal/auth/repository/eventsourcing" "github.com/caos/zitadel/internal/auth/repository/eventsourcing"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/ui/login/handler" "github.com/caos/zitadel/internal/ui/login/handler"
"github.com/caos/zitadel/internal/v2/command"
"github.com/caos/zitadel/internal/v2/query"
) )
type Config struct { type Config struct {
Handler handler.Config Handler handler.Config
} }
func Start(config Config, authRepo *eventsourcing.EsRepository, systemdefaults systemdefaults.SystemDefaults, localDevMode bool) (*handler.Login, string) { func Start(config Config, command *command.CommandSide, query *query.QuerySide, authRepo *eventsourcing.EsRepository, systemdefaults systemdefaults.SystemDefaults, localDevMode bool) (*handler.Login, string) {
return handler.CreateLogin(config.Handler, authRepo, systemdefaults, localDevMode) return handler.CreateLogin(config.Handler, command, query, authRepo, systemdefaults, localDevMode)
} }

View File

@ -1303,47 +1303,49 @@ func (es *UserEventstore) verifyMFAOTP(otp *usr_model.OTP, code string) error {
} }
func (es *UserEventstore) AddU2F(ctx context.Context, userID string, accountName string, isLoginUI bool) (*usr_model.WebAuthNToken, error) { func (es *UserEventstore) AddU2F(ctx context.Context, userID string, accountName string, isLoginUI bool) (*usr_model.WebAuthNToken, error) {
user, err := es.HumanByID(ctx, userID) //user, err := es.HumanByID(ctx, userID)
if err != nil { //if err != nil {
return nil, err // return nil, err
} //}
webAuthN, err := es.webauthn.BeginRegistration(user, accountName, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementDiscouraged, isLoginUI, user.U2FTokens...) //webAuthN, err := es.webauthn.BeginRegistration(user, accountName, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementDiscouraged, isLoginUI, user.U2FTokens...)
if err != nil { //if err != nil {
return nil, err // return nil, err
} //}
tokenID, err := es.idGenerator.Next() //tokenID, err := es.idGenerator.Next()
if err != nil { //if err != nil {
return nil, err // return nil, err
} //}
webAuthN.WebAuthNTokenID = tokenID //webAuthN.WebAuthNTokenID = tokenID
webAuthN.State = usr_model.MFAStateNotReady //webAuthN.State = usr_model.MFAStateNotReady
repoUser := model.UserFromModel(user) //repoUser := model.UserFromModel(user)
repoWebAuthN := model.WebAuthNFromModel(webAuthN) //repoWebAuthN := model.WebAuthNFromModel(webAuthN)
//
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FAddAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) //err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FAddAggregate(es.AggregateCreator(), repoUser, repoWebAuthN))
if err != nil { //if err != nil {
return nil, err // return nil, err
} //}
return webAuthN, nil //return webAuthN, nil
return nil, nil
} }
func (es *UserEventstore) VerifyU2FSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error { func (es *UserEventstore) VerifyU2FSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error {
user, err := es.HumanByID(ctx, userID) //user, err := es.HumanByID(ctx, userID)
if err != nil { //if err != nil {
return err // return err
} //}
_, token := user.Human.GetU2FToVerify() //_, token := user.Human.GetU2FToVerify()
webAuthN, err := es.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "") //webAuthN, err := es.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "")
if err != nil { //if err != nil {
return err // return err
} //}
repoUser := model.UserFromModel(user) //repoUser := model.UserFromModel(user)
repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN, userAgentID) //repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN, userAgentID)
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) //err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN))
if err != nil { //if err != nil {
return err // return err
} //}
es.userCache.cacheUser(repoUser) //es.userCache.cacheUser(repoUser)
//return nil
return nil return nil
} }
@ -1365,49 +1367,51 @@ func (es *UserEventstore) RemoveU2FToken(ctx context.Context, userID, webAuthNTo
} }
func (es *UserEventstore) BeginU2FLogin(ctx context.Context, userID string, authRequest *req_model.AuthRequest, isLoginUI bool) (*usr_model.WebAuthNLogin, error) { func (es *UserEventstore) BeginU2FLogin(ctx context.Context, userID string, authRequest *req_model.AuthRequest, isLoginUI bool) (*usr_model.WebAuthNLogin, error) {
user, err := es.HumanByID(ctx, userID) //user, err := es.HumanByID(ctx, userID)
if err != nil { //if err != nil {
return nil, err // return nil, err
} //}
if user.U2FTokens == nil { //if user.U2FTokens == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-5Mk8s", "Errors.User.MFA.U2F.NotExisting") // return nil, errors.ThrowPreconditionFailed(nil, "EVENT-5Mk8s", "Errors.User.MFA.U2F.NotExisting")
} //}
//
webAuthNLogin, err := es.webauthn.BeginLogin(user, usr_model.UserVerificationRequirementDiscouraged, isLoginUI, user.U2FTokens...) //webAuthNLogin, err := es.webauthn.BeginLogin(user, usr_model.UserVerificationRequirementDiscouraged, isLoginUI, user.U2FTokens...)
if err != nil { //if err != nil {
return nil, err // return nil, err
} //}
webAuthNLogin.AuthRequest = authRequest //webAuthNLogin.AuthRequest = authRequest
repoUser := model.UserFromModel(user) //repoUser := model.UserFromModel(user)
repoWebAuthNLogin := model.WebAuthNLoginFromModel(webAuthNLogin) //repoWebAuthNLogin := model.WebAuthNLoginFromModel(webAuthNLogin)
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FBeginLoginAggregate(es.AggregateCreator(), repoUser, repoWebAuthNLogin)) //err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FBeginLoginAggregate(es.AggregateCreator(), repoUser, repoWebAuthNLogin))
if err != nil { //if err != nil {
return nil, err // return nil, err
} //}
return webAuthNLogin, nil //return webAuthNLogin, nil
return nil, nil
} }
func (es *UserEventstore) VerifyMFAU2F(ctx context.Context, userID string, credentialData []byte, authRequest *req_model.AuthRequest, isLoginUI bool) error { func (es *UserEventstore) VerifyMFAU2F(ctx context.Context, userID string, credentialData []byte, authRequest *req_model.AuthRequest, isLoginUI bool) error {
user, err := es.HumanByID(ctx, userID) //user, err := es.HumanByID(ctx, userID)
if err != nil { //if err != nil {
return err // return err
} //}
_, u2f := user.GetU2FLogin(authRequest.ID) //_, u2f := user.GetU2FLogin(authRequest.ID)
keyID, signCount, finishErr := es.webauthn.FinishLogin(user, u2f, credentialData, isLoginUI, user.U2FTokens...) //keyID, signCount, finishErr := es.webauthn.FinishLogin(user, u2f, credentialData, isLoginUI, user.U2FTokens...)
if finishErr != nil && keyID == nil { //if finishErr != nil && keyID == nil {
return finishErr // return finishErr
} //}
//
_, token := user.GetU2FByKeyID(keyID) //_, token := user.GetU2FByKeyID(keyID)
repoUser := model.UserFromModel(user) //repoUser := model.UserFromModel(user)
repoAuthRequest := model.AuthRequestFromModel(authRequest) //repoAuthRequest := model.AuthRequestFromModel(authRequest)
//
signAgg := MFAU2FSignCountAggregate(es.AggregateCreator(), repoUser, &model.WebAuthNSignCount{WebauthNTokenID: token.WebAuthNTokenID, SignCount: signCount}, repoAuthRequest, finishErr == nil) //signAgg := MFAU2FSignCountAggregate(es.AggregateCreator(), repoUser, &model.WebAuthNSignCount{WebauthNTokenID: token.WebAuthNTokenID, SignCount: signCount}, repoAuthRequest, finishErr == nil)
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, signAgg) //err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, signAgg)
if err != nil { //if err != nil {
return err // return err
} //}
return finishErr //return finishErr
return nil
} }
func (es *UserEventstore) GetPasswordless(ctx context.Context, userID string) ([]*usr_model.WebAuthNToken, error) { func (es *UserEventstore) GetPasswordless(ctx context.Context, userID string) ([]*usr_model.WebAuthNToken, error) {
@ -1419,45 +1423,47 @@ func (es *UserEventstore) GetPasswordless(ctx context.Context, userID string) ([
} }
func (es *UserEventstore) AddPasswordless(ctx context.Context, userID, accountName string, isLoginUI bool) (*usr_model.WebAuthNToken, error) { func (es *UserEventstore) AddPasswordless(ctx context.Context, userID, accountName string, isLoginUI bool) (*usr_model.WebAuthNToken, error) {
user, err := es.HumanByID(ctx, userID) //user, err := es.HumanByID(ctx, userID)
if err != nil { //if err != nil {
return nil, err // return nil, err
} //}
webAuthN, err := es.webauthn.BeginRegistration(user, accountName, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementRequired, isLoginUI, user.PasswordlessTokens...) //webAuthN, err := es.webauthn.BeginRegistration(user, accountName, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementRequired, isLoginUI, user.PasswordlessTokens...)
if err != nil { //if err != nil {
return nil, err // return nil, err
} //}
tokenID, err := es.idGenerator.Next() //tokenID, err := es.idGenerator.Next()
if err != nil { //if err != nil {
return nil, err // return nil, err
} //}
webAuthN.WebAuthNTokenID = tokenID //webAuthN.WebAuthNTokenID = tokenID
repoUser := model.UserFromModel(user) //repoUser := model.UserFromModel(user)
repoWebAuthN := model.WebAuthNFromModel(webAuthN) //repoWebAuthN := model.WebAuthNFromModel(webAuthN)
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessAddAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) //err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessAddAggregate(es.AggregateCreator(), repoUser, repoWebAuthN))
if err != nil { //if err != nil {
return nil, err // return nil, err
} //}
return webAuthN, nil //return webAuthN, nil
return nil, nil
} }
func (es *UserEventstore) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error { func (es *UserEventstore) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error {
user, err := es.HumanByID(ctx, userID) //user, err := es.HumanByID(ctx, userID)
if err != nil { //if err != nil {
return err // return err
} //}
_, token := user.Human.GetPasswordlessToVerify() //_, token := user.Human.GetPasswordlessToVerify()
webAuthN, err := es.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "") //webAuthN, err := es.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "")
if err != nil { //if err != nil {
return err // return err
} //}
repoUser := model.UserFromModel(user) //repoUser := model.UserFromModel(user)
repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN, userAgentID) //repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN, userAgentID)
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) //err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN))
if err != nil { //if err != nil {
return err // return err
} //}
es.userCache.cacheUser(repoUser) //es.userCache.cacheUser(repoUser)
//return nil
return nil return nil
} }
@ -1479,47 +1485,49 @@ func (es *UserEventstore) RemovePasswordlessToken(ctx context.Context, userID, w
} }
func (es *UserEventstore) BeginPasswordlessLogin(ctx context.Context, userID string, authRequest *req_model.AuthRequest, isLoginUI bool) (*usr_model.WebAuthNLogin, error) { func (es *UserEventstore) BeginPasswordlessLogin(ctx context.Context, userID string, authRequest *req_model.AuthRequest, isLoginUI bool) (*usr_model.WebAuthNLogin, error) {
user, err := es.HumanByID(ctx, userID) //user, err := es.HumanByID(ctx, userID)
if err != nil { //if err != nil {
return nil, err // return nil, err
} //}
if user.PasswordlessTokens == nil { //if user.PasswordlessTokens == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-5M9sd", "Errors.User.MFA.Passwordless.NotExisting") // return nil, errors.ThrowPreconditionFailed(nil, "EVENT-5M9sd", "Errors.User.MFA.Passwordless.NotExisting")
} //}
webAuthNLogin, err := es.webauthn.BeginLogin(user, usr_model.UserVerificationRequirementRequired, isLoginUI, user.PasswordlessTokens...) //webAuthNLogin, err := es.webauthn.BeginLogin(user, usr_model.UserVerificationRequirementRequired, isLoginUI, user.PasswordlessTokens...)
if err != nil { //if err != nil {
return nil, err // return nil, err
} //}
webAuthNLogin.AuthRequest = authRequest //webAuthNLogin.AuthRequest = authRequest
repoUser := model.UserFromModel(user) //repoUser := model.UserFromModel(user)
repoWebAuthNLogin := model.WebAuthNLoginFromModel(webAuthNLogin) //repoWebAuthNLogin := model.WebAuthNLoginFromModel(webAuthNLogin)
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessBeginLoginAggregate(es.AggregateCreator(), repoUser, repoWebAuthNLogin)) //err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessBeginLoginAggregate(es.AggregateCreator(), repoUser, repoWebAuthNLogin))
if err != nil { //if err != nil {
return nil, err // return nil, err
} //}
return webAuthNLogin, nil //return webAuthNLogin, nil
return nil, nil
} }
func (es *UserEventstore) VerifyPasswordless(ctx context.Context, userID string, credentialData []byte, authRequest *req_model.AuthRequest, isLoginUI bool) error { func (es *UserEventstore) VerifyPasswordless(ctx context.Context, userID string, credentialData []byte, authRequest *req_model.AuthRequest, isLoginUI bool) error {
user, err := es.HumanByID(ctx, userID) //user, err := es.HumanByID(ctx, userID)
if err != nil { //if err != nil {
return err // return err
} //}
_, passwordless := user.GetPasswordlessLogin(authRequest.ID) //_, passwordless := user.GetPasswordlessLogin(authRequest.ID)
keyID, signCount, finishErr := es.webauthn.FinishLogin(user, passwordless, credentialData, isLoginUI, user.PasswordlessTokens...) //keyID, signCount, finishErr := es.webauthn.FinishLogin(user, passwordless, credentialData, isLoginUI, user.PasswordlessTokens...)
if finishErr != nil && keyID == nil { //if finishErr != nil && keyID == nil {
return finishErr // return finishErr
} //}
_, token := user.GetPasswordlessByKeyID(keyID) //_, token := user.GetPasswordlessByKeyID(keyID)
repoUser := model.UserFromModel(user) //repoUser := model.UserFromModel(user)
repoAuthRequest := model.AuthRequestFromModel(authRequest) //repoAuthRequest := model.AuthRequestFromModel(authRequest)
//
signAgg := MFAPasswordlessSignCountAggregate(es.AggregateCreator(), repoUser, &model.WebAuthNSignCount{WebauthNTokenID: token.WebAuthNTokenID, SignCount: signCount}, repoAuthRequest, finishErr == nil) //signAgg := MFAPasswordlessSignCountAggregate(es.AggregateCreator(), repoUser, &model.WebAuthNSignCount{WebauthNTokenID: token.WebAuthNTokenID, SignCount: signCount}, repoAuthRequest, finishErr == nil)
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, signAgg) //err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, signAgg)
if err != nil { //if err != nil {
return err // return err
} //}
return finishErr //return finishErr
return nil
} }
func (es *UserEventstore) SignOut(ctx context.Context, agentID string, userIDs []string) error { func (es *UserEventstore) SignOut(ctx context.Context, agentID string, userIDs []string) error {

View File

@ -2,6 +2,8 @@ package command
import ( import (
"context" "context"
global_model "github.com/caos/zitadel/internal/model"
webauthn_helper "github.com/caos/zitadel/internal/webauthn"
sd "github.com/caos/zitadel/internal/config/systemdefaults" sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
@ -9,6 +11,7 @@ import (
"github.com/caos/zitadel/internal/id" "github.com/caos/zitadel/internal/id"
"github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/telemetry/tracing"
iam_repo "github.com/caos/zitadel/internal/v2/repository/iam" iam_repo "github.com/caos/zitadel/internal/v2/repository/iam"
usr_repo "github.com/caos/zitadel/internal/v2/repository/user"
) )
type CommandSide struct { type CommandSide struct {
@ -18,14 +21,18 @@ type CommandSide struct {
idpConfigSecretCrypto crypto.Crypto idpConfigSecretCrypto crypto.Crypto
userPasswordAlg crypto.HashAlgorithm userPasswordAlg crypto.HashAlgorithm
initializeUserCode crypto.Generator initializeUserCode crypto.Generator
emailVerificationCode crypto.Generator emailVerificationCode crypto.Generator
phoneVerificationCode crypto.Generator phoneVerificationCode crypto.Generator
passwordVerificationCode crypto.Generator passwordVerificationCode crypto.Generator
machineKeyAlg crypto.EncryptionAlgorithm machineKeyAlg crypto.EncryptionAlgorithm
machineKeySize int machineKeySize int
//TODO: remove global model, or move to domain
multifactors global_model.Multifactors
applicationSecretGenerator crypto.Generator applicationSecretGenerator crypto.Generator
webauthn *webauthn_helper.WebAuthN
} }
type Config struct { type Config struct {
@ -40,6 +47,7 @@ func StartCommandSide(config *Config) (repo *CommandSide, err error) {
iamDomain: config.SystemDefaults.Domain, iamDomain: config.SystemDefaults.Domain,
} }
iam_repo.RegisterEventMappers(repo.eventstore) iam_repo.RegisterEventMappers(repo.eventstore)
usr_repo.RegisterEventMappers(repo.eventstore)
//TODO: simplify!!!! //TODO: simplify!!!!
repo.idpConfigSecretCrypto, err = crypto.NewAESCrypto(config.SystemDefaults.IDPConfigVerificationKey) repo.idpConfigSecretCrypto, err = crypto.NewAESCrypto(config.SystemDefaults.IDPConfigVerificationKey)
@ -58,8 +66,23 @@ func StartCommandSide(config *Config) (repo *CommandSide, err error) {
repo.machineKeyAlg = userEncryptionAlgorithm repo.machineKeyAlg = userEncryptionAlgorithm
repo.machineKeySize = int(config.SystemDefaults.SecretGenerators.MachineKeySize) repo.machineKeySize = int(config.SystemDefaults.SecretGenerators.MachineKeySize)
aesOTPCrypto, err := crypto.NewAESCrypto(config.SystemDefaults.Multifactors.OTP.VerificationKey)
if err != nil {
return nil, err
}
repo.multifactors = global_model.Multifactors{
OTP: global_model.OTP{
CryptoMFA: aesOTPCrypto,
Issuer: config.SystemDefaults.Multifactors.OTP.Issuer,
},
}
passwordAlg := crypto.NewBCrypt(config.SystemDefaults.SecretGenerators.PasswordSaltCost) passwordAlg := crypto.NewBCrypt(config.SystemDefaults.SecretGenerators.PasswordSaltCost)
repo.applicationSecretGenerator = crypto.NewHashGenerator(config.SystemDefaults.SecretGenerators.ClientSecretGenerator, passwordAlg) repo.applicationSecretGenerator = crypto.NewHashGenerator(config.SystemDefaults.SecretGenerators.ClientSecretGenerator, passwordAlg)
web, err := webauthn_helper.StartServer(config.SystemDefaults.WebAuthN)
if err != nil {
return nil, err
}
repo.webauthn = web
return repo, nil return repo, nil
} }

View File

@ -9,7 +9,18 @@ import (
"github.com/caos/zitadel/internal/v2/repository/user" "github.com/caos/zitadel/internal/v2/repository/user"
) )
func (r *CommandSide) SetUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.User) error { func (r *CommandSide) getOrg(ctx context.Context, orgID string) (*domain.Org, error) {
writeModel, err := r.getOrgWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if writeModel.State == domain.OrgStateActive {
return nil, caos_errs.ThrowInternal(err, "COMMAND-4M9sf", "Errors.Org.NotFound")
}
return orgWriteModelToOrg(writeModel), nil
}
func (r *CommandSide) SetUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.Human) error {
orgAgg, userAgg, orgMemberAgg, err := r.setUpOrg(ctx, organisation, admin) orgAgg, userAgg, orgMemberAgg, err := r.setUpOrg(ctx, organisation, admin)
if err != nil { if err != nil {
return err return err
@ -19,13 +30,13 @@ func (r *CommandSide) SetUpOrg(ctx context.Context, organisation *domain.Org, ad
return err return err
} }
func (r *CommandSide) setUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.User) (*org.Aggregate, *user.Aggregate, *org.Aggregate, error) { func (r *CommandSide) setUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.Human) (*org.Aggregate, *user.Aggregate, *org.Aggregate, error) {
orgAgg, _, err := r.addOrg(ctx, organisation) orgAgg, _, err := r.addOrg(ctx, organisation)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
userAgg, _, err := r.addHuman(ctx, orgAgg.ID(), admin.UserName, admin.Human) userAgg, _, err := r.addHuman(ctx, orgAgg.ID(), admin)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }

View File

@ -6,9 +6,10 @@ import (
func orgWriteModelToOrg(wm *OrgWriteModel) *domain.Org { func orgWriteModelToOrg(wm *OrgWriteModel) *domain.Org {
return &domain.Org{ return &domain.Org{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel), ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
Name: wm.Name, Name: wm.Name,
State: wm.State, State: wm.State,
PrimaryDomain: wm.PrimaryDomain,
} }
} }

View File

@ -10,8 +10,9 @@ import (
type OrgWriteModel struct { type OrgWriteModel struct {
eventstore.WriteModel eventstore.WriteModel
Name string Name string
State domain.OrgState State domain.OrgState
PrimaryDomain string
} }
func NewOrgWriteModel(orgID string) *OrgWriteModel { func NewOrgWriteModel(orgID string) *OrgWriteModel {
@ -30,6 +31,8 @@ func (wm *OrgWriteModel) AppendEvents(events ...eventstore.EventReader) {
case *org.OrgAddedEvent, case *org.OrgAddedEvent,
*iam.LabelPolicyChangedEvent: *iam.LabelPolicyChangedEvent:
wm.WriteModel.AppendEvents(e) wm.WriteModel.AppendEvents(e)
case *org.DomainPrimarySetEvent:
wm.WriteModel.AppendEvents(e)
} }
} }
} }
@ -42,6 +45,8 @@ func (wm *OrgWriteModel) Reduce() error {
wm.State = domain.OrgStateActive wm.State = domain.OrgStateActive
case *org.OrgChangedEvent: case *org.OrgChangedEvent:
wm.Name = e.Name wm.Name = e.Name
case *org.DomainPrimarySetEvent:
wm.PrimaryDomain = e.Domain
} }
} }
return nil return nil

View File

@ -34,7 +34,7 @@ func (r *CommandSide) addOrgIAMPolicy(ctx context.Context, orgAgg *org.Aggregate
return err return err
} }
if addedPolicy.State == domain.PolicyStateActive { if addedPolicy.State == domain.PolicyStateActive {
return caos_errs.ThrowAlreadyExists(nil, "ORG-5M0ds", "Errors.Org.OrgIAMPolicy.AlreadyExists") return caos_errs.ThrowAlreadyExists(nil, "ORG-1M8ds", "Errors.Org.OrgIAMPolicy.AlreadyExists")
} }
orgAgg.PushEvents(org.NewOrgIAMPolicyAddedEvent(ctx, policy.UserLoginMustBeDomain)) orgAgg.PushEvents(org.NewOrgIAMPolicyAddedEvent(ctx, policy.UserLoginMustBeDomain))
return nil return nil

View File

@ -105,20 +105,18 @@ func (r *CommandSide) SetupStep1(ctx context.Context, step1 *Step1) error {
Name: organisation.Name, Name: organisation.Name,
Domains: []*domain.OrgDomain{{Domain: organisation.Domain}}, Domains: []*domain.OrgDomain{{Domain: organisation.Domain}},
}, },
&domain.User{ &domain.Human{
UserName: organisation.Owner.UserName, Username: organisation.Owner.UserName,
Human: &domain.Human{ Profile: &domain.Profile{
Profile: &domain.Profile{ FirstName: organisation.Owner.FirstName,
FirstName: organisation.Owner.FirstName, LastName: organisation.Owner.LastName,
LastName: organisation.Owner.LastName, },
}, Password: &domain.Password{
Password: &domain.Password{ SecretString: organisation.Owner.Password,
SecretString: organisation.Owner.Password, },
}, Email: &domain.Email{
Email: &domain.Email{ EmailAddress: organisation.Owner.Email,
EmailAddress: organisation.Owner.Email, IsEmailVerified: true,
IsEmailVerified: true,
},
}, },
}) })
if err != nil { if err != nil {

View File

@ -9,42 +9,6 @@ import (
"github.com/caos/zitadel/internal/v2/repository/user" "github.com/caos/zitadel/internal/v2/repository/user"
) )
func (r *CommandSide) AddUser(ctx context.Context, orgID string, user *domain.User) (*domain.User, error) {
if !user.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2N9fs", "Errors.User.Invalid")
}
if user.Human != nil {
human, err := r.AddHuman(ctx, orgID, user.UserName, user.Human)
if err != nil {
return nil, err
}
return &domain.User{UserName: user.UserName, Human: human}, nil
} else if user.Machine != nil {
machine, err := r.AddMachine(ctx, orgID, user.UserName, user.Machine)
if err != nil {
return nil, err
}
return &domain.User{UserName: user.UserName, Machine: machine}, nil
}
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-8K0df", "Errors.User.TypeUndefined")
}
func (r *CommandSide) RegisterUser(ctx context.Context, orgID string, user *domain.User) (*domain.User, error) {
if !user.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2N9fs", "Errors.User.Invalid")
}
if user.Human != nil {
human, err := r.RegisterHuman(ctx, orgID, user.UserName, user.Human, nil)
if err != nil {
return nil, err
}
return &domain.User{UserName: user.UserName, Human: human}, nil
}
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-8K0df", "Errors.User.TypeUndefined")
}
func (r *CommandSide) ChangeUsername(ctx context.Context, orgID, userID, userName string) error { func (r *CommandSide) ChangeUsername(ctx context.Context, orgID, userID, userName string) error {
if orgID == "" || userID == "" || userName == "" { if orgID == "" || userID == "" || userName == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2N9fs", "Errors.IDMissing") return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2N9fs", "Errors.IDMissing")
@ -75,100 +39,84 @@ func (r *CommandSide) ChangeUsername(ctx context.Context, orgID, userID, userNam
return r.eventstore.PushAggregate(ctx, existingUser, userAgg) return r.eventstore.PushAggregate(ctx, existingUser, userAgg)
} }
func (r *CommandSide) DeactivateUser(ctx context.Context, userID, resourceOwner string) (*domain.User, error) { func (r *CommandSide) DeactivateUser(ctx context.Context, userID, resourceOwner string) error {
if userID == "" { if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-m0gDf", "Errors.User.UserIDMissing") return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-m0gDf", "Errors.User.UserIDMissing")
} }
existingUser, err := r.userWriteModelByID(ctx, userID, resourceOwner) existingUser, err := r.userWriteModelByID(ctx, userID, resourceOwner)
if err != nil { if err != nil {
return nil, err return err
} }
if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3M9ds", "Errors.User.NotFound") return caos_errs.ThrowNotFound(nil, "COMMAND-3M9ds", "Errors.User.NotFound")
} }
if existingUser.UserState == domain.UserStateInactive { if existingUser.UserState == domain.UserStateInactive {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5M0sf", "Errors.User.AlreadyInactive") return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5M0sf", "Errors.User.AlreadyInactive")
} }
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
userAgg.PushEvents(user.NewUserDeactivatedEvent(ctx)) userAgg.PushEvents(user.NewUserDeactivatedEvent(ctx))
err = r.eventstore.PushAggregate(ctx, existingUser, userAgg) return r.eventstore.PushAggregate(ctx, existingUser, userAgg)
if err != nil {
return nil, err
}
return writeModelToUser(existingUser), nil
} }
func (r *CommandSide) ReactivateUser(ctx context.Context, userID, resourceOwner string) (*domain.User, error) { func (r *CommandSide) ReactivateUser(ctx context.Context, userID, resourceOwner string) error {
if userID == "" { if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M9ds", "Errors.User.UserIDMissing") return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M9ds", "Errors.User.UserIDMissing")
} }
existingUser, err := r.userWriteModelByID(ctx, userID, resourceOwner) existingUser, err := r.userWriteModelByID(ctx, userID, resourceOwner)
if err != nil { if err != nil {
return nil, err return err
} }
if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-4M0sd", "Errors.User.NotFound") return caos_errs.ThrowNotFound(nil, "COMMAND-4M0sd", "Errors.User.NotFound")
} }
if existingUser.UserState != domain.UserStateInactive { if existingUser.UserState != domain.UserStateInactive {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0sf", "Errors.User.NotInactive") return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0sf", "Errors.User.NotInactive")
} }
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
userAgg.PushEvents(user.NewUserReactivatedEvent(ctx)) userAgg.PushEvents(user.NewUserReactivatedEvent(ctx))
err = r.eventstore.PushAggregate(ctx, existingUser, userAgg) return r.eventstore.PushAggregate(ctx, existingUser, userAgg)
if err != nil {
return nil, err
}
return writeModelToUser(existingUser), nil
} }
func (r *CommandSide) LockUser(ctx context.Context, userID, resourceOwner string) (*domain.User, error) { func (r *CommandSide) LockUser(ctx context.Context, userID, resourceOwner string) error {
if userID == "" { if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0sd", "Errors.User.UserIDMissing") return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0sd", "Errors.User.UserIDMissing")
} }
existingUser, err := r.userWriteModelByID(ctx, userID, resourceOwner) existingUser, err := r.userWriteModelByID(ctx, userID, resourceOwner)
if err != nil { if err != nil {
return nil, err return err
} }
if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-5M9fs", "Errors.User.NotFound") return caos_errs.ThrowNotFound(nil, "COMMAND-5M9fs", "Errors.User.NotFound")
} }
if existingUser.UserState != domain.UserStateActive && existingUser.UserState != domain.UserStateInitial { if existingUser.UserState != domain.UserStateActive && existingUser.UserState != domain.UserStateInitial {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.ShouldBeActiveOrInitial") return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.ShouldBeActiveOrInitial")
} }
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
userAgg.PushEvents(user.NewUserLockedEvent(ctx)) userAgg.PushEvents(user.NewUserLockedEvent(ctx))
err = r.eventstore.PushAggregate(ctx, existingUser, userAgg) return r.eventstore.PushAggregate(ctx, existingUser, userAgg)
if err != nil {
return nil, err
}
return writeModelToUser(existingUser), nil
} }
func (r *CommandSide) UnlockUser(ctx context.Context, userID, resourceOwner string) (*domain.User, error) { func (r *CommandSide) UnlockUser(ctx context.Context, userID, resourceOwner string) error {
if userID == "" { if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-M0dse", "Errors.User.UserIDMissing") return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-M0dse", "Errors.User.UserIDMissing")
} }
existingUser, err := r.userWriteModelByID(ctx, userID, resourceOwner) existingUser, err := r.userWriteModelByID(ctx, userID, resourceOwner)
if err != nil { if err != nil {
return nil, err return err
} }
if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-M0dos", "Errors.User.NotFound") return caos_errs.ThrowNotFound(nil, "COMMAND-M0dos", "Errors.User.NotFound")
} }
if existingUser.UserState != domain.UserStateLocked { if existingUser.UserState != domain.UserStateLocked {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.NotLocked") return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.NotLocked")
} }
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
userAgg.PushEvents(user.NewUserUnlockedEvent(ctx)) userAgg.PushEvents(user.NewUserUnlockedEvent(ctx))
err = r.eventstore.PushAggregate(ctx, existingUser, userAgg) return r.eventstore.PushAggregate(ctx, existingUser, userAgg)
if err != nil {
return nil, err
}
return writeModelToUser(existingUser), nil
} }
func (r *CommandSide) RemoveUser(ctx context.Context, userID, resourceOwner string) error { func (r *CommandSide) RemoveUser(ctx context.Context, userID, resourceOwner string) error {
@ -201,3 +149,15 @@ func (r *CommandSide) userWriteModelByID(ctx context.Context, userID, resourceOw
} }
return writeModel, nil return writeModel, nil
} }
func (r *CommandSide) userReadModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *UserWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel = NewUserWriteModel(userID, resourceOwner)
err = r.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}

View File

@ -4,17 +4,11 @@ import (
"github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/domain"
) )
func writeModelToUser(wm *UserWriteModel) *domain.User {
return &domain.User{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
UserName: wm.UserName,
State: wm.UserState,
}
}
func writeModelToHuman(wm *HumanWriteModel) *domain.Human { func writeModelToHuman(wm *HumanWriteModel) *domain.Human {
return &domain.Human{ return &domain.Human{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel), ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
Username: wm.UserName,
State: wm.UserState,
Profile: &domain.Profile{ Profile: &domain.Profile{
FirstName: wm.FirstName, FirstName: wm.FirstName,
LastName: wm.LastName, LastName: wm.LastName,
@ -82,3 +76,34 @@ func writeModelToMachine(wm *MachineWriteModel) *domain.Machine {
Description: wm.Description, Description: wm.Description,
} }
} }
func readModelToU2FTokens(wm *HumanU2FTokensReadModel) []*domain.WebAuthNToken {
tokens := make([]*domain.WebAuthNToken, len(wm.WebAuthNTokens))
for i, token := range wm.WebAuthNTokens {
tokens[i] = writeModelToWebAuthN(token)
}
return tokens
}
func readModelToPasswordlessTokens(wm *HumanPasswordlessTokensReadModel) []*domain.WebAuthNToken {
tokens := make([]*domain.WebAuthNToken, len(wm.WebAuthNTokens))
for i, token := range wm.WebAuthNTokens {
tokens[i] = writeModelToWebAuthN(token)
}
return tokens
}
func writeModelToWebAuthN(wm *HumanWebAuthNWriteModel) *domain.WebAuthNToken {
return &domain.WebAuthNToken{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
WebAuthNTokenID: wm.WebauthNTokenID,
Challenge: wm.Challenge,
KeyID: wm.KeyID,
PublicKey: wm.PublicKey,
AttestationType: wm.AttestationType,
AAGUID: wm.AAGUID,
SignCount: wm.SignCount,
WebAuthNTokenName: wm.WebAuthNTokenName,
State: wm.State,
}
}

View File

@ -2,14 +2,26 @@ package command
import ( import (
"context" "context"
"github.com/caos/zitadel/internal/eventstore/v2"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/domain"
"github.com/caos/zitadel/internal/v2/repository/user" "github.com/caos/zitadel/internal/v2/repository/user"
) )
func (r *CommandSide) AddHuman(ctx context.Context, orgID, username string, human *domain.Human) (*domain.Human, error) { func (r *CommandSide) getHuman(ctx context.Context, userID, resourceowner string) (*domain.Human, error) {
userAgg, addedHuman, err := r.addHuman(ctx, orgID, username, human) writeModel, err := r.getHumanWriteModelByID(ctx, userID, resourceowner)
if err != nil {
return nil, err
}
if writeModel.UserState == domain.UserStateUnspecified || writeModel.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-M9dsd", "Errors.User.NotFound")
}
return writeModelToHuman(writeModel), nil
}
func (r *CommandSide) AddHuman(ctx context.Context, orgID string, human *domain.Human) (*domain.Human, error) {
userAgg, addedHuman, err := r.addHuman(ctx, orgID, human)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -21,10 +33,34 @@ func (r *CommandSide) AddHuman(ctx context.Context, orgID, username string, huma
return writeModelToHuman(addedHuman), nil return writeModelToHuman(addedHuman), nil
} }
func (r *CommandSide) addHuman(ctx context.Context, orgID, username string, human *domain.Human) (*user.Aggregate, *HumanWriteModel, error) { func (r *CommandSide) addHuman(ctx context.Context, orgID string, human *domain.Human) (*user.Aggregate, *HumanWriteModel, error) {
if !human.IsValid() { if !human.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M90d", "Errors.User.Invalid") return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M90d", "Errors.User.Invalid")
} }
return r.createHuman(ctx, orgID, human, nil, false)
}
func (r *CommandSide) RegisterHuman(ctx context.Context, orgID string, human *domain.Human, externalIDP *domain.ExternalIDP) (*domain.Human, error) {
userAgg, addedHuman, err := r.registerHuman(ctx, orgID, human, externalIDP)
if err != nil {
return nil, err
}
err = r.eventstore.PushAggregate(ctx, addedHuman, userAgg)
if err != nil {
return nil, err
}
return writeModelToHuman(addedHuman), nil
}
func (r *CommandSide) registerHuman(ctx context.Context, orgID string, human *domain.Human, externalIDP *domain.ExternalIDP) (*user.Aggregate, *HumanWriteModel, error) {
if !human.IsValid() || externalIDP == nil && (human.Password == nil || human.SecretString == "") {
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-9dk45", "Errors.User.Invalid")
}
return r.createHuman(ctx, orgID, human, externalIDP, true)
}
func (r *CommandSide) createHuman(ctx context.Context, orgID string, human *domain.Human, externalIDP *domain.ExternalIDP, selfregister bool) (*user.Aggregate, *HumanWriteModel, error) {
userID, err := r.idGenerator.Next() userID, err := r.idGenerator.Next()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -40,8 +76,8 @@ func (r *CommandSide) addHuman(ctx context.Context, orgID, username string, huma
} }
addedHuman := NewHumanWriteModel(human.AggregateID, orgID) addedHuman := NewHumanWriteModel(human.AggregateID, orgID)
//TODO: Check Unique Username //TODO: Check Unique Username or unique external idp
if err := human.CheckOrgIAMPolicy(username, orgIAMPolicy); err != nil { if err := human.CheckOrgIAMPolicy(human.Username, orgIAMPolicy); err != nil {
return nil, nil, err return nil, nil, err
} }
human.SetNamesAsDisplayname() human.SetNamesAsDisplayname()
@ -50,6 +86,73 @@ func (r *CommandSide) addHuman(ctx context.Context, orgID, username string, huma
} }
userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel) userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel)
var createEvent eventstore.EventPusher
if selfregister {
createEvent = createRegisterHumanEvent(ctx, human.Username, human)
} else {
createEvent = createAddHumanEvent(ctx, human.Username, human)
}
userAgg.PushEvents(createEvent)
if externalIDP != nil {
if !externalIDP.IsValid() {
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4Dj9s", "Errors.User.ExternalIDP.Invalid")
}
//TODO: check if idpconfig exists
userAgg.PushEvents(user.NewHumanExternalIDPAddedEvent(ctx, externalIDP.IDPConfigID, externalIDP.DisplayName))
}
if human.IsInitialState() {
initCode, err := domain.NewInitUserCode(r.initializeUserCode)
if err != nil {
return nil, nil, err
}
userAgg.PushEvents(user.NewHumanInitialCodeAddedEvent(ctx, initCode.Code, initCode.Expiry))
}
if human.Email != nil && human.EmailAddress != "" && human.IsEmailVerified {
userAgg.PushEvents(user.NewHumanEmailVerifiedEvent(ctx))
}
if human.Phone != nil && human.PhoneNumber != "" && !human.IsPhoneVerified {
phoneCode, err := domain.NewPhoneCode(r.phoneVerificationCode)
if err != nil {
return nil, nil, err
}
user.NewHumanPhoneCodeAddedEvent(ctx, phoneCode.Code, phoneCode.Expiry)
} else if human.Phone != nil && human.PhoneNumber != "" && human.IsPhoneVerified {
userAgg.PushEvents(user.NewHumanPhoneVerifiedEvent(ctx))
}
return userAgg, addedHuman, nil
}
func (r *CommandSide) ResendInitialMail(ctx context.Context, userID, email, resourceowner string) (err error) {
if userID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.UserIDMissing")
}
existingEmail, err := r.emailWriteModel(ctx, userID, resourceowner)
if err != nil {
return err
}
if existingEmail.UserState == domain.UserStateUnspecified || existingEmail.UserState == domain.UserStateDeleted {
return caos_errs.ThrowNotFound(nil, "COMMAND-2M9df", "Errors.User.NotFound")
}
if existingEmail.UserState != domain.UserStateInitial {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9sd", "Errors.User.AlreadyInitialised")
}
userAgg := UserAggregateFromWriteModel(&existingEmail.WriteModel)
if email != "" && existingEmail.Email != email {
changedEvent, _ := existingEmail.NewChangedEvent(ctx, email)
userAgg.PushEvents(changedEvent)
}
initCode, err := domain.NewInitUserCode(r.initializeUserCode)
if err != nil {
return err
}
userAgg.PushEvents(user.NewHumanInitialCodeAddedEvent(ctx, initCode.Code, initCode.Expiry))
return r.eventstore.PushAggregate(ctx, existingEmail, userAgg)
}
func createAddHumanEvent(ctx context.Context, username string, human *domain.Human) *user.HumanAddedEvent {
addEvent := user.NewHumanAddedEvent( addEvent := user.NewHumanAddedEvent(
ctx, ctx,
username, username,
@ -75,60 +178,10 @@ func (r *CommandSide) addHuman(ctx context.Context, orgID, username string, huma
if human.Password != nil { if human.Password != nil {
addEvent.AddPasswordData(human.SecretCrypto, human.ChangeRequired) addEvent.AddPasswordData(human.SecretCrypto, human.ChangeRequired)
} }
userAgg.PushEvents(addEvent) return addEvent
if human.IsInitialState() {
initCode, err := domain.NewInitUserCode(r.initializeUserCode)
if err != nil {
return nil, nil, err
}
user.NewHumanInitialCodeAddedEvent(ctx, initCode.Code, initCode.Expiry)
}
if human.Email != nil && human.EmailAddress != "" && human.IsEmailVerified {
userAgg.PushEvents(user.NewHumanEmailVerifiedEvent(ctx))
}
if human.Phone != nil && human.PhoneNumber != "" && !human.IsPhoneVerified {
phoneCode, err := domain.NewPhoneCode(r.phoneVerificationCode)
if err != nil {
return nil, nil, err
}
user.NewHumanPhoneCodeAddedEvent(ctx, phoneCode.Code, phoneCode.Expiry)
} else if human.Phone != nil && human.PhoneNumber != "" && human.IsPhoneVerified {
userAgg.PushEvents(user.NewHumanPhoneVerifiedEvent(ctx))
}
return userAgg, addedHuman, nil
} }
func (r *CommandSide) RegisterHuman(ctx context.Context, orgID, username string, human *domain.Human, externalIDP *domain.ExternalIDP) (*domain.Human, error) { func createRegisterHumanEvent(ctx context.Context, username string, human *domain.Human) *user.HumanRegisteredEvent {
if !human.IsValid() || externalIDP == nil && (human.Password == nil || human.SecretString == "") {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-9dk45", "Errors.User.Invalid")
}
userID, err := r.idGenerator.Next()
if err != nil {
return nil, err
}
human.AggregateID = userID
orgIAMPolicy, err := r.getOrgIAMPolicy(ctx, orgID)
if err != nil {
return nil, err
}
pwPolicy, err := r.GetOrgPasswordComplexityPolicy(ctx, orgID)
if err != nil {
return nil, err
}
addedHuman := NewHumanWriteModel(human.AggregateID, orgID)
//TODO: Check Unique Username or unique external idp
if err := human.CheckOrgIAMPolicy(username, orgIAMPolicy); err != nil {
return nil, err
}
human.SetNamesAsDisplayname()
if err := human.HashPasswordIfExisting(pwPolicy, r.userPasswordAlg, true); err != nil {
return nil, err
}
userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel)
addEvent := user.NewHumanRegisteredEvent( addEvent := user.NewHumanRegisteredEvent(
ctx, ctx,
username, username,
@ -154,61 +207,14 @@ func (r *CommandSide) RegisterHuman(ctx context.Context, orgID, username string,
if human.Password != nil { if human.Password != nil {
addEvent.AddPasswordData(human.SecretCrypto, human.ChangeRequired) addEvent.AddPasswordData(human.SecretCrypto, human.ChangeRequired)
} }
userAgg.PushEvents(addEvent) return addEvent
//TODO: Add External IDP Event }
if human.IsInitialState() {
initCode, err := domain.NewInitUserCode(r.initializeUserCode)
if err != nil {
return nil, err
}
userAgg.PushEvents(user.NewHumanInitialCodeAddedEvent(ctx, initCode.Code, initCode.Expiry))
}
if human.Email != nil && human.EmailAddress != "" && human.IsEmailVerified { func (r *CommandSide) getHumanWriteModelByID(ctx context.Context, userID, resourceowner string) (*HumanWriteModel, error) {
userAgg.PushEvents(user.NewHumanEmailVerifiedEvent(ctx)) humanWriteModel := NewHumanWriteModel(userID, resourceowner)
} err := r.eventstore.FilterToQueryReducer(ctx, humanWriteModel)
if human.Phone != nil && human.PhoneNumber != "" && !human.IsPhoneVerified {
phoneCode, err := domain.NewPhoneCode(r.phoneVerificationCode)
if err != nil {
return nil, err
}
userAgg.PushEvents(user.NewHumanPhoneCodeAddedEvent(ctx, phoneCode.Code, phoneCode.Expiry))
} else if human.Phone != nil && human.PhoneNumber != "" && human.IsPhoneVerified {
userAgg.PushEvents(user.NewHumanPhoneVerifiedEvent(ctx))
}
err = r.eventstore.PushAggregate(ctx, addedHuman, userAgg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return humanWriteModel, nil
return writeModelToHuman(addedHuman), nil
}
func (r *CommandSide) ResendInitialMail(ctx context.Context, userID, email, resourceOwner string) (err error) {
if userID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.UserIDMissing")
}
existingEmail, err := r.emailWriteModel(ctx, userID, resourceOwner)
if err != nil {
return err
}
if existingEmail.UserState == domain.UserStateUnspecified || existingEmail.UserState == domain.UserStateDeleted {
return caos_errs.ThrowNotFound(nil, "COMMAND-2M9df", "Errors.User.NotFound")
}
if existingEmail.UserState != domain.UserStateInitial {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9sd", "Errors.User.AlreadyInitialised")
}
userAgg := UserAggregateFromWriteModel(&existingEmail.WriteModel)
if email != "" && existingEmail.Email != email {
changedEvent, _ := existingEmail.NewChangedEvent(ctx, email)
userAgg.PushEvents(changedEvent)
}
initCode, err := domain.NewInitUserCode(r.initializeUserCode)
if err != nil {
return err
}
userAgg.PushEvents(user.NewHumanInitialCodeAddedEvent(ctx, initCode.Code, initCode.Expiry))
return r.eventstore.PushAggregate(ctx, existingEmail, userAgg)
} }

View File

@ -30,16 +30,7 @@ func NewHumanAddressWriteModel(userID, resourceOwner string) *HumanAddressWriteM
} }
func (wm *HumanAddressWriteModel) AppendEvents(events ...eventstore.EventReader) { func (wm *HumanAddressWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events { wm.WriteModel.AppendEvents(events...)
switch e := event.(type) {
case *user.HumanAddressChangedEvent:
wm.AppendEvents(e)
case *user.HumanAddedEvent, *user.HumanRegisteredEvent:
wm.AppendEvents(e)
case *user.UserRemovedEvent:
wm.AppendEvents(e)
}
}
} }
func (wm *HumanAddressWriteModel) Reduce() error { func (wm *HumanAddressWriteModel) Reduce() error {

View File

@ -2,6 +2,8 @@ package command
import ( import (
"context" "context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/crypto"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/telemetry/tracing"
"github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/domain"
@ -45,6 +47,34 @@ func (r *CommandSide) ChangeHumanEmail(ctx context.Context, email *domain.Email)
return writeModelToEmail(existingEmail), nil return writeModelToEmail(existingEmail), nil
} }
func (r *CommandSide) VerifyHumanEmail(ctx context.Context, userID, code, resourceowner string) error {
if userID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
}
if code == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-çm0ds", "Errors.User.Code.Empty")
}
existingCode, err := r.emailWriteModel(ctx, userID, resourceowner)
if err != nil {
return err
}
if existingCode.Code == nil || existingCode.UserState == domain.UserStateUnspecified || existingCode.UserState == domain.UserStateDeleted {
return caos_errs.ThrowNotFound(nil, "COMMAND-2M9fs", "Errors.User.Code.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel)
err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, r.emailVerificationCode)
if err == nil {
userAgg.PushEvents(user.NewHumanEmailVerifiedEvent(ctx))
return r.eventstore.PushAggregate(ctx, existingCode, userAgg)
}
userAgg.PushEvents(user.NewHumanEmailVerificationFailedEvent(ctx))
err = r.eventstore.PushAggregate(ctx, existingCode, userAgg)
logging.LogWithFields("COMMAND-Dg2z5", "userID", userAgg.ID()).OnError(err).Error("NewHumanEmailVerificationFailedEvent push failed")
return caos_errs.ThrowInvalidArgument(err, "COMMAND-Gdsgs", "Errors.User.Code.Invalid")
}
func (r *CommandSide) CreateHumanEmailVerificationCode(ctx context.Context, userID, resourceOwner string) error { func (r *CommandSide) CreateHumanEmailVerificationCode(ctx context.Context, userID, resourceOwner string) error {
if userID == "" { if userID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing") return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")

View File

@ -2,10 +2,11 @@ package command
import ( import (
"context" "context"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/eventstore/v2" "github.com/caos/zitadel/internal/eventstore/v2"
"github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/domain"
"github.com/caos/zitadel/internal/v2/repository/user" "github.com/caos/zitadel/internal/v2/repository/user"
"time"
) )
type HumanEmailWriteModel struct { type HumanEmailWriteModel struct {
@ -14,6 +15,10 @@ type HumanEmailWriteModel struct {
Email string Email string
IsEmailVerified bool IsEmailVerified bool
Code *crypto.CryptoValue
CodeCreationDate time.Time
CodeExpiry time.Duration
UserState domain.UserState UserState domain.UserState
} }
@ -27,18 +32,7 @@ func NewHumanEmailWriteModel(userID, resourceOwner string) *HumanEmailWriteModel
} }
func (wm *HumanEmailWriteModel) AppendEvents(events ...eventstore.EventReader) { func (wm *HumanEmailWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events { wm.WriteModel.AppendEvents(events...)
switch e := event.(type) {
case *user.HumanEmailChangedEvent:
wm.AppendEvents(e)
case *user.HumanEmailVerifiedEvent:
wm.AppendEvents(e)
case *user.HumanAddedEvent, *user.HumanRegisteredEvent:
wm.AppendEvents(e)
case *user.UserRemovedEvent:
wm.AppendEvents(e)
}
}
} }
func (wm *HumanEmailWriteModel) Reduce() error { func (wm *HumanEmailWriteModel) Reduce() error {
@ -53,8 +47,14 @@ func (wm *HumanEmailWriteModel) Reduce() error {
case *user.HumanEmailChangedEvent: case *user.HumanEmailChangedEvent:
wm.Email = e.EmailAddress wm.Email = e.EmailAddress
wm.IsEmailVerified = false wm.IsEmailVerified = false
wm.Code = nil
case *user.HumanEmailCodeAddedEvent:
wm.Code = e.Code
wm.CodeCreationDate = e.CreationDate()
wm.CodeExpiry = e.Expiry
case *user.HumanEmailVerifiedEvent: case *user.HumanEmailVerifiedEvent:
wm.IsEmailVerified = true wm.IsEmailVerified = true
wm.Code = nil
if wm.UserState == domain.UserStateInitial { if wm.UserState == domain.UserStateInitial {
wm.UserState = domain.UserStateActive wm.UserState = domain.UserStateActive
} }

View File

@ -22,7 +22,7 @@ func (r *CommandSide) removeHumanExternalIDP(ctx context.Context, externalIDP *d
return err return err
} }
if existingExternalIDP.State == domain.ExternalIDPStateUnspecified || existingExternalIDP.State == domain.ExternalIDPStateRemoved { if existingExternalIDP.State == domain.ExternalIDPStateUnspecified || existingExternalIDP.State == domain.ExternalIDPStateRemoved {
return caos_errs.ThrowNotFound(nil, "COMMAND-5M0ds", "Errors.User.ExternalIDP.NotFound") return caos_errs.ThrowNotFound(nil, "COMMAND-1M9xR", "Errors.User.ExternalIDP.NotFound")
} }
userAgg := UserAggregateFromWriteModel(&existingExternalIDP.WriteModel) userAgg := UserAggregateFromWriteModel(&existingExternalIDP.WriteModel)
if !cascade { if !cascade {

View File

@ -28,24 +28,7 @@ func NewHumanExternalIDPWriteModel(userID, idpConfigID, externalUserID, resource
} }
func (wm *HumanExternalIDPWriteModel) AppendEvents(events ...eventstore.EventReader) { func (wm *HumanExternalIDPWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events { wm.WriteModel.AppendEvents(events...)
switch e := event.(type) {
case *user.HumanExternalIDPAddedEvent:
if wm.IDPConfigID == e.IDPConfigID && wm.ExternalUserID == e.UserID {
wm.AppendEvents(e)
}
case *user.HumanExternalIDPRemovedEvent:
if wm.IDPConfigID == e.IDPConfigID && wm.ExternalUserID == e.UserID {
wm.AppendEvents(e)
}
case *user.HumanExternalIDPCascadeRemovedEvent:
if wm.IDPConfigID == e.IDPConfigID && wm.ExternalUserID == e.UserID {
wm.AppendEvents(e)
}
case *user.UserRemovedEvent:
wm.AppendEvents(e)
}
}
} }
func (wm *HumanExternalIDPWriteModel) Reduce() error { func (wm *HumanExternalIDPWriteModel) Reduce() error {

View File

@ -48,28 +48,10 @@ func NewHumanWriteModel(userID, resourceOwner string) *HumanWriteModel {
} }
func (wm *HumanWriteModel) AppendEvents(events ...eventstore.EventReader) { func (wm *HumanWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events { wm.WriteModel.AppendEvents(events...)
switch e := event.(type) {
case *user.HumanAddedEvent,
*user.HumanRegisteredEvent,
*user.HumanProfileChangedEvent,
*user.HumanEmailChangedEvent,
*user.HumanEmailVerifiedEvent,
*user.HumanPhoneChangedEvent,
*user.HumanPhoneVerifiedEvent,
*user.HumanAddressChangedEvent,
*user.HumanPasswordChangedEvent,
*user.UserDeactivatedEvent,
*user.UserReactivatedEvent,
*user.UserLockedEvent,
*user.UserUnlockedEvent,
*user.UserRemovedEvent:
wm.AppendEvents(e)
}
}
} }
//TODO: Compute State? initial/active //TODO: Compute OTPState? initial/active
func (wm *HumanWriteModel) Reduce() error { func (wm *HumanWriteModel) Reduce() error {
for _, event := range wm.Events { for _, event := range wm.Events {
switch e := event.(type) { switch e := event.(type) {

View File

@ -3,11 +3,87 @@ package command
import ( import (
"context" "context"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/telemetry/tracing"
"github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/domain"
"github.com/caos/zitadel/internal/v2/repository/user" "github.com/caos/zitadel/internal/v2/repository/user"
) )
func (r *CommandSide) AddHumanOTP(ctx context.Context, userID, resourceowner string) (*domain.OTP, error) {
if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing")
}
human, err := r.getHuman(ctx, userID, resourceowner)
if err != nil {
return nil, err
}
org, err := r.getOrg(ctx, human.ResourceOwner)
if err != nil {
return nil, err
}
orgPolicy, err := r.getOrgIAMPolicy(ctx, org.AggregateID)
if err != nil {
return nil, err
}
otpWriteModel, err := r.otpWriteModelByID(ctx, userID, resourceowner)
if err != nil {
return nil, err
}
if otpWriteModel.State == domain.MFAStateReady {
return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-do9se", "Errors.User.MFA.OTP.AlreadyReady")
}
userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel)
accountName := domain.GenerateLoginName(human.GetUsername(), org.PrimaryDomain, orgPolicy.UserLoginMustBeDomain)
if accountName == "" {
accountName = human.EmailAddress
}
key, secret, err := domain.NewOTPKey(r.multifactors.OTP.Issuer, accountName, r.multifactors.OTP.CryptoMFA)
if err != nil {
return nil, err
}
userAgg.PushEvents(
user.NewHumanOTPAddedEvent(ctx, secret),
)
err = r.eventstore.PushAggregate(ctx, otpWriteModel, userAgg)
if err != nil {
return nil, err
}
return &domain.OTP{
ObjectRoot: models.ObjectRoot{
AggregateID: human.AggregateID,
},
SecretString: key.Secret(),
Url: key.URL(),
}, nil
}
func (r *CommandSide) CheckMFAOTPSetup(ctx context.Context, userID, code, userAgentID, resourceowner string) error {
if userID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing")
}
existingOTP, err := r.otpWriteModelByID(ctx, userID, resourceowner)
if err != nil {
return err
}
if existingOTP.State == domain.MFAStateUnspecified || existingOTP.State == domain.MFAStateRemoved {
return caos_errs.ThrowNotFound(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotExisting")
}
if existingOTP.State == domain.MFAStateReady {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-qx4ls", "Errors.Users.MFA.OTP.AlreadyReady")
}
if err := domain.VerifyMFAOTP(code, existingOTP.Secret, r.multifactors.OTP.CryptoMFA); err != nil {
return err
}
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
userAgg.PushEvents(
user.NewHumanOTPVerifiedEvent(ctx, userAgentID),
)
return r.eventstore.PushAggregate(ctx, existingOTP, userAgg)
}
func (r *CommandSide) RemoveHumanOTP(ctx context.Context, userID, resourceOwner string) error { func (r *CommandSide) RemoveHumanOTP(ctx context.Context, userID, resourceOwner string) error {
if userID == "" { if userID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing") return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing")
@ -17,8 +93,8 @@ func (r *CommandSide) RemoveHumanOTP(ctx context.Context, userID, resourceOwner
if err != nil { if err != nil {
return err return err
} }
if existingOTP.State == domain.OTPStateUnspecified || existingOTP.State == domain.OTPStateRemoved { if existingOTP.State == domain.MFAStateUnspecified || existingOTP.State == domain.MFAStateRemoved {
return caos_errs.ThrowNotFound(nil, "COMMAND-5M0ds", "Errors.User.OTP.NotFound") return caos_errs.ThrowNotFound(nil, "COMMAND-Hd9sd", "Errors.User.MFA.OTP.NotExisting")
} }
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel) userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
userAgg.PushEvents( userAgg.PushEvents(

View File

@ -10,9 +10,8 @@ import (
type HumanOTPWriteModel struct { type HumanOTPWriteModel struct {
eventstore.WriteModel eventstore.WriteModel
State domain.MFAState
Secret *crypto.CryptoValue Secret *crypto.CryptoValue
State domain.OTPState
} }
func NewHumanOTPWriteModel(userID, resourceOwner string) *HumanOTPWriteModel { func NewHumanOTPWriteModel(userID, resourceOwner string) *HumanOTPWriteModel {
@ -25,16 +24,7 @@ func NewHumanOTPWriteModel(userID, resourceOwner string) *HumanOTPWriteModel {
} }
func (wm *HumanOTPWriteModel) AppendEvents(events ...eventstore.EventReader) { func (wm *HumanOTPWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events { wm.WriteModel.AppendEvents(events...)
switch e := event.(type) {
case *user.HumanOTPAddedEvent:
wm.AppendEvents(e)
case *user.HumanOTPRemovedEvent:
wm.AppendEvents(e)
case *user.UserRemovedEvent:
wm.AppendEvents(e)
}
}
} }
func (wm *HumanOTPWriteModel) Reduce() error { func (wm *HumanOTPWriteModel) Reduce() error {
@ -42,11 +32,13 @@ func (wm *HumanOTPWriteModel) Reduce() error {
switch e := event.(type) { switch e := event.(type) {
case *user.HumanOTPAddedEvent: case *user.HumanOTPAddedEvent:
wm.Secret = e.Secret wm.Secret = e.Secret
wm.State = domain.OTPStateActive wm.State = domain.MFAStateNotReady
case *user.HumanOTPVerifiedEvent:
wm.State = domain.MFAStateReady
case *user.HumanOTPRemovedEvent: case *user.HumanOTPRemovedEvent:
wm.State = domain.OTPStateRemoved wm.State = domain.MFAStateRemoved
case *user.UserRemovedEvent: case *user.UserRemovedEvent:
wm.State = domain.OTPStateRemoved wm.State = domain.MFAStateRemoved
} }
} }
return wm.WriteModel.Reduce() return wm.WriteModel.Reduce()

View File

@ -24,11 +24,11 @@ func (r *CommandSide) SetOneTimePassword(ctx context.Context, orgID, userID, pas
return r.changePassword(ctx, orgID, userID, "", password, existingPassword) return r.changePassword(ctx, orgID, userID, "", password, existingPassword)
} }
func (r *CommandSide) ChangePassword(ctx context.Context, orgID, userID, oldPassword, newPassword, userAgentID, resourceOwner string) (err error) { func (r *CommandSide) ChangePassword(ctx context.Context, orgID, userID, oldPassword, newPassword, userAgentID string) (err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()
existingPassword, err := r.passwordWriteModel(ctx, userID, resourceOwner) existingPassword, err := r.passwordWriteModel(ctx, userID, orgID)
if err != nil { if err != nil {
return err return err
} }

View File

@ -26,18 +26,7 @@ func NewHumanPasswordWriteModel(userID, resourceOwner string) *HumanPasswordWrit
} }
func (wm *HumanPasswordWriteModel) AppendEvents(events ...eventstore.EventReader) { func (wm *HumanPasswordWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events { wm.WriteModel.AppendEvents(events...)
switch e := event.(type) {
case *user.HumanPasswordChangedEvent:
wm.AppendEvents(e)
case *user.HumanAddedEvent, *user.HumanRegisteredEvent:
wm.AppendEvents(e)
case *user.HumanEmailVerifiedEvent:
wm.AppendEvents(e)
case *user.UserRemovedEvent:
wm.AppendEvents(e)
}
}
} }
func (wm *HumanPasswordWriteModel) Reduce() error { func (wm *HumanPasswordWriteModel) Reduce() error {

View File

@ -2,7 +2,8 @@ package command
import ( import (
"context" "context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/crypto"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/telemetry/tracing"
"github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/domain"
@ -14,12 +15,12 @@ func (r *CommandSide) ChangeHumanPhone(ctx context.Context, phone *domain.Phone)
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0ds", "Errors.Phone.Invalid") return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0ds", "Errors.Phone.Invalid")
} }
existingPhone, err := r.phoneWriteModel(ctx, phone.AggregateID, phone.ResourceOwner) existingPhone, err := r.phoneWriteModelByID(ctx, phone.AggregateID, phone.ResourceOwner)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if existingPhone.State == domain.PhoneStateUnspecified || existingPhone.State == domain.PhoneStateRemoved { if existingPhone.State == domain.PhoneStateUnspecified || existingPhone.State == domain.PhoneStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-5M0ds", "Errors.User.Phone.NotFound") return nil, caos_errs.ThrowNotFound(nil, "COMMAND-aM9cs", "Errors.User.Phone.NotFound")
} }
changedEvent, hasChanged := existingPhone.NewChangedEvent(ctx, phone.PhoneNumber) changedEvent, hasChanged := existingPhone.NewChangedEvent(ctx, phone.PhoneNumber)
if !hasChanged { if !hasChanged {
@ -46,12 +47,48 @@ func (r *CommandSide) ChangeHumanPhone(ctx context.Context, phone *domain.Phone)
return writeModelToPhone(existingPhone), nil return writeModelToPhone(existingPhone), nil
} }
func (r *CommandSide) CreateHumanPhoneVerificationCode(ctx context.Context, userID, resourceOwner string) error { func (r *CommandSide) VerifyHumanPhone(ctx context.Context, userID, code, resourceowner string) error {
if userID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Km9ds", "Errors.User.UserIDMissing")
}
if code == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-wMe9f", "Errors.User.Code.Empty")
}
existingCode, err := r.phoneWriteModelByID(ctx, userID, resourceowner)
if err != nil {
return err
}
if existingCode.Code == nil || existingCode.State == domain.PhoneStateUnspecified || existingCode.State == domain.PhoneStateRemoved {
return caos_errs.ThrowNotFound(nil, "COMMAND-Rsj8c", "Errors.User.Code.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel)
err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, r.emailVerificationCode)
if err == nil {
userAgg.PushEvents(user.NewHumanPhoneVerifiedEvent(ctx))
return r.eventstore.PushAggregate(ctx, existingCode, userAgg)
}
userAgg.PushEvents(user.NewHumanPhoneVerificationFailedEvent(ctx))
err = r.eventstore.PushAggregate(ctx, existingCode, userAgg)
err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, r.emailVerificationCode)
if err == nil {
userAgg.PushEvents(user.NewHumanEmailVerifiedEvent(ctx))
return r.eventstore.PushAggregate(ctx, existingCode, userAgg)
}
userAgg.PushEvents(user.NewHumanEmailVerificationFailedEvent(ctx))
err = r.eventstore.PushAggregate(ctx, existingCode, userAgg)
logging.LogWithFields("COMMAND-5M9ds", "userID", userAgg.ID()).OnError(err).Error("NewHumanEmailVerificationFailedEvent push failed")
return caos_errs.ThrowInvalidArgument(err, "COMMAND-sM0cs", "Errors.User.Code.Invalid")
}
func (r *CommandSide) CreateHumanPhoneVerificationCode(ctx context.Context, userID, resourceowner string) error {
if userID == "" { if userID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing") return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
} }
existingPhone, err := r.phoneWriteModel(ctx, userID, resourceOwner) existingPhone, err := r.phoneWriteModelByID(ctx, userID, resourceowner)
if err != nil { if err != nil {
return err return err
} }
@ -75,12 +112,12 @@ func (r *CommandSide) RemoveHumanPhone(ctx context.Context, userID, resourceOwne
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0ds", "Errors.User.UserIDMissing") return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0ds", "Errors.User.UserIDMissing")
} }
existingPhone, err := r.phoneWriteModel(ctx, userID, resourceOwner) existingPhone, err := r.phoneWriteModelByID(ctx, userID, resourceOwner)
if err != nil { if err != nil {
return err return err
} }
if existingPhone.State == domain.PhoneStateUnspecified || existingPhone.State == domain.PhoneStateRemoved { if existingPhone.State == domain.PhoneStateUnspecified || existingPhone.State == domain.PhoneStateRemoved {
return caos_errs.ThrowNotFound(nil, "COMMAND-5M0ds", "Errors.User.Phone.NotFound") return caos_errs.ThrowNotFound(nil, "COMMAND-p6rsc", "Errors.User.Phone.NotFound")
} }
userAgg := UserAggregateFromWriteModel(&existingPhone.WriteModel) userAgg := UserAggregateFromWriteModel(&existingPhone.WriteModel)
userAgg.PushEvents( userAgg.PushEvents(
@ -89,7 +126,7 @@ func (r *CommandSide) RemoveHumanPhone(ctx context.Context, userID, resourceOwne
return r.eventstore.PushAggregate(ctx, existingPhone, userAgg) return r.eventstore.PushAggregate(ctx, existingPhone, userAgg)
} }
func (r *CommandSide) phoneWriteModel(ctx context.Context, userID, resourceOwner string) (writeModel *HumanPhoneWriteModel, err error) { func (r *CommandSide) phoneWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanPhoneWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()

View File

@ -2,10 +2,11 @@ package command
import ( import (
"context" "context"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/eventstore/v2" "github.com/caos/zitadel/internal/eventstore/v2"
"github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/domain"
"github.com/caos/zitadel/internal/v2/repository/user" "github.com/caos/zitadel/internal/v2/repository/user"
"time"
) )
type HumanPhoneWriteModel struct { type HumanPhoneWriteModel struct {
@ -14,6 +15,10 @@ type HumanPhoneWriteModel struct {
Phone string Phone string
IsPhoneVerified bool IsPhoneVerified bool
Code *crypto.CryptoValue
CodeCreationDate time.Time
CodeExpiry time.Duration
State domain.PhoneState State domain.PhoneState
} }
@ -27,20 +32,7 @@ func NewHumanPhoneWriteModel(userID, resourceOwner string) *HumanPhoneWriteModel
} }
func (wm *HumanPhoneWriteModel) AppendEvents(events ...eventstore.EventReader) { func (wm *HumanPhoneWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events { wm.WriteModel.AppendEvents(events...)
switch e := event.(type) {
case *user.HumanAddedEvent, *user.HumanRegisteredEvent:
wm.AppendEvents(e)
case *user.HumanPhoneChangedEvent:
wm.AppendEvents(e)
case *user.HumanPhoneVerifiedEvent:
wm.AppendEvents(e)
case *user.HumanPhoneRemovedEvent:
wm.AppendEvents(e)
case *user.UserRemovedEvent:
wm.AppendEvents(e)
}
}
} }
func (wm *HumanPhoneWriteModel) Reduce() error { func (wm *HumanPhoneWriteModel) Reduce() error {
@ -49,8 +41,8 @@ func (wm *HumanPhoneWriteModel) Reduce() error {
case *user.HumanAddedEvent: case *user.HumanAddedEvent:
if e.PhoneNumber != "" { if e.PhoneNumber != "" {
wm.Phone = e.PhoneNumber wm.Phone = e.PhoneNumber
wm.State = domain.PhoneStateActive
} }
wm.State = domain.PhoneStateActive
case *user.HumanRegisteredEvent: case *user.HumanRegisteredEvent:
if e.PhoneNumber != "" { if e.PhoneNumber != "" {
wm.Phone = e.PhoneNumber wm.Phone = e.PhoneNumber
@ -60,8 +52,14 @@ func (wm *HumanPhoneWriteModel) Reduce() error {
wm.Phone = e.PhoneNumber wm.Phone = e.PhoneNumber
wm.IsPhoneVerified = false wm.IsPhoneVerified = false
wm.State = domain.PhoneStateActive wm.State = domain.PhoneStateActive
wm.Code = nil
case *user.HumanPhoneVerifiedEvent: case *user.HumanPhoneVerifiedEvent:
wm.IsPhoneVerified = true wm.IsPhoneVerified = true
wm.Code = nil
case *user.HumanPhoneCodeAddedEvent:
wm.Code = e.Code
wm.CodeCreationDate = e.CreationDate()
wm.CodeExpiry = e.Expiry
case *user.HumanPhoneRemovedEvent: case *user.HumanPhoneRemovedEvent:
wm.State = domain.PhoneStateRemoved wm.State = domain.PhoneStateRemoved
case *user.UserRemovedEvent: case *user.UserRemovedEvent:

View File

@ -19,7 +19,7 @@ func (r *CommandSide) ChangeHumanProfile(ctx context.Context, profile *domain.Pr
if existingProfile.UserState == domain.UserStateUnspecified || existingProfile.UserState == domain.UserStateDeleted { if existingProfile.UserState == domain.UserStateUnspecified || existingProfile.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.User.Profile.NotFound") return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.User.Profile.NotFound")
} }
changedEvent, hasChanged := existingProfile.NewChangedEvent(ctx, profile.FirstName, profile.LastName, profile.NickName, profile.DisplayName, profile.PreferredLanguage, domain.Gender(profile.Gender)) changedEvent, hasChanged := existingProfile.NewChangedEvent(ctx, profile.FirstName, profile.LastName, profile.NickName, profile.DisplayName, profile.PreferredLanguage, profile.Gender)
if !hasChanged { if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0fs", "Errors.User.Profile.NotChanged") return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0fs", "Errors.User.Profile.NotChanged")
} }

View File

@ -33,16 +33,7 @@ func NewHumanProfileWriteModel(userID, resourceOwner string) *HumanProfileWriteM
} }
func (wm *HumanProfileWriteModel) AppendEvents(events ...eventstore.EventReader) { func (wm *HumanProfileWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events { wm.WriteModel.AppendEvents(events...)
switch e := event.(type) {
case *user.HumanProfileChangedEvent:
wm.AppendEvents(e)
case *user.HumanAddedEvent, *user.HumanRegisteredEvent:
wm.AppendEvents(e)
case *user.UserRemovedEvent:
wm.AppendEvents(e)
}
}
} }
func (wm *HumanProfileWriteModel) Reduce() error { func (wm *HumanProfileWriteModel) Reduce() error {

View File

@ -6,16 +6,193 @@ import (
"github.com/caos/zitadel/internal/eventstore/v2" "github.com/caos/zitadel/internal/eventstore/v2"
"github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/telemetry/tracing"
"github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/domain"
"github.com/caos/zitadel/internal/v2/repository/user" usr_repo "github.com/caos/zitadel/internal/v2/repository/user"
) )
func (r *CommandSide) getHumanU2FTokens(ctx context.Context, userID, resourceowner string) ([]*domain.WebAuthNToken, error) {
tokenReadModel := NewHumanU2FTokensReadModel(userID, resourceowner)
err := r.eventstore.FilterToQueryReducer(ctx, tokenReadModel)
if err != nil {
return nil, err
}
if tokenReadModel.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-4M0ds", "Errors.User.NotFound")
}
return readModelToU2FTokens(tokenReadModel), nil
}
func (r *CommandSide) getHumanPasswordlessTokens(ctx context.Context, userID, resourceowner string) ([]*domain.WebAuthNToken, error) {
tokenReadModel := NewHumanPasswordlessTokensReadModel(userID, resourceowner)
err := r.eventstore.FilterToQueryReducer(ctx, tokenReadModel)
if err != nil {
return nil, err
}
if tokenReadModel.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Mv9sd", "Errors.User.NotFound")
}
return readModelToPasswordlessTokens(tokenReadModel), nil
}
func (r *CommandSide) AddHumanU2F(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) {
u2fTokens, err := r.getHumanU2FTokens(ctx, userID, resourceowner)
if err != nil {
return nil, err
}
addWebAuthN, userAgg, webAuthN, err := r.addHumanWebAuthN(ctx, userID, resourceowner, isLoginUI, u2fTokens)
if err != nil {
return nil, err
}
userAgg.PushEvents(usr_repo.NewHumanU2FAddedEvent(ctx, addWebAuthN.WebauthNTokenID, webAuthN.Challenge))
err = r.eventstore.PushAggregate(ctx, addWebAuthN, userAgg)
if err != nil {
return nil, err
}
createdWebAuthN := writeModelToWebAuthN(addWebAuthN)
createdWebAuthN.CredentialCreationData = webAuthN.CredentialCreationData
createdWebAuthN.AllowedCredentialIDs = webAuthN.AllowedCredentialIDs
createdWebAuthN.UserVerification = webAuthN.UserVerification
return createdWebAuthN, nil
}
func (r *CommandSide) AddHumanPasswordless(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) {
passwordlessTokens, err := r.getHumanPasswordlessTokens(ctx, userID, resourceowner)
if err != nil {
return nil, err
}
addWebAuthN, userAgg, webAuthN, err := r.addHumanWebAuthN(ctx, userID, resourceowner, isLoginUI, passwordlessTokens)
if err != nil {
return nil, err
}
userAgg.PushEvents(usr_repo.NewHumanU2FAddedEvent(ctx, addWebAuthN.WebauthNTokenID, webAuthN.Challenge))
err = r.eventstore.PushAggregate(ctx, addWebAuthN, userAgg)
if err != nil {
return nil, err
}
createdWebAuthN := writeModelToWebAuthN(addWebAuthN)
createdWebAuthN.CredentialCreationData = webAuthN.CredentialCreationData
createdWebAuthN.AllowedCredentialIDs = webAuthN.AllowedCredentialIDs
createdWebAuthN.UserVerification = webAuthN.UserVerification
return createdWebAuthN, nil
}
func (r *CommandSide) addHumanWebAuthN(ctx context.Context, userID, resourceowner string, isLoginUI bool, tokens []*domain.WebAuthNToken) (*HumanWebAuthNWriteModel, *usr_repo.Aggregate, *domain.WebAuthNToken, error) {
if userID == "" || resourceowner == "" {
return nil, nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0od", "Errors.IDMissing")
}
user, err := r.getHuman(ctx, userID, resourceowner)
if err != nil {
return nil, nil, nil, err
}
org, err := r.getOrg(ctx, user.ResourceOwner)
if err != nil {
return nil, nil, nil, err
}
orgPolicy, err := r.getOrgIAMPolicy(ctx, org.AggregateID)
if err != nil {
return nil, nil, nil, err
}
accountName := domain.GenerateLoginName(user.GetUsername(), org.PrimaryDomain, orgPolicy.UserLoginMustBeDomain)
if accountName == "" {
accountName = user.EmailAddress
}
webAuthN, err := r.webauthn.BeginRegistration(user, accountName, domain.AuthenticatorAttachmentUnspecified, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
if err != nil {
return nil, nil, nil, err
}
tokenID, err := r.idGenerator.Next()
if err != nil {
return nil, nil, nil, err
}
addWebAuthN, err := r.webauthNWriteModelByID(ctx, userID, tokenID, resourceowner)
if err != nil {
return nil, nil, nil, err
}
userAgg := UserAggregateFromWriteModel(&addWebAuthN.WriteModel)
return addWebAuthN, userAgg, webAuthN, nil
}
func (r *CommandSide) VerifyHumanU2F(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) error {
u2fTokens, err := r.getHumanU2FTokens(ctx, userID, resourceowner)
if err != nil {
return err
}
verifyWebAuthN, userAgg, webAuthN, err := r.verifyHumanWebAuthN(ctx, userID, resourceowner, tokenName, userAgentID, credentialData, u2fTokens)
if err != nil {
return err
}
userAgg.PushEvents(
usr_repo.NewHumanU2FVerifiedEvent(
ctx,
verifyWebAuthN.WebauthNTokenID,
webAuthN.WebAuthNTokenName,
webAuthN.AttestationType,
webAuthN.KeyID,
webAuthN.PublicKey,
webAuthN.AAGUID,
webAuthN.SignCount,
),
)
return r.eventstore.PushAggregate(ctx, verifyWebAuthN, userAgg)
}
func (r *CommandSide) VerifyHumanPasswordless(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) error {
u2fTokens, err := r.getHumanPasswordlessTokens(ctx, userID, resourceowner)
if err != nil {
return err
}
verifyWebAuthN, userAgg, webAuthN, err := r.verifyHumanWebAuthN(ctx, userID, resourceowner, tokenName, userAgentID, credentialData, u2fTokens)
if err != nil {
return err
}
userAgg.PushEvents(
usr_repo.NewHumanU2FVerifiedEvent(
ctx,
verifyWebAuthN.WebauthNTokenID,
webAuthN.WebAuthNTokenName,
webAuthN.AttestationType,
webAuthN.KeyID,
webAuthN.PublicKey,
webAuthN.AAGUID,
webAuthN.SignCount,
),
)
return r.eventstore.PushAggregate(ctx, verifyWebAuthN, userAgg)
}
func (r *CommandSide) verifyHumanWebAuthN(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte, tokens []*domain.WebAuthNToken) (*HumanWebAuthNWriteModel, *usr_repo.Aggregate, *domain.WebAuthNToken, error) {
if userID == "" || resourceowner == "" {
return nil, nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0od", "Errors.IDMissing")
}
user, err := r.getHuman(ctx, userID, resourceowner)
if err != nil {
return nil, nil, nil, err
}
_, token := domain.GetTokenToVerify(tokens)
webAuthN, err := r.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "")
if err != nil {
return nil, nil, nil, err
}
verifyWebAuthN, err := r.webauthNWriteModelByID(ctx, userID, token.WebAuthNTokenID, resourceowner)
if err != nil {
return nil, nil, nil, err
}
userAgg := UserAggregateFromWriteModel(&verifyWebAuthN.WriteModel)
return verifyWebAuthN, userAgg, webAuthN, nil
}
func (r *CommandSide) RemoveHumanU2F(ctx context.Context, userID, webAuthNID, resourceOwner string) error { func (r *CommandSide) RemoveHumanU2F(ctx context.Context, userID, webAuthNID, resourceOwner string) error {
event := user.NewHumanU2FRemovedEvent(ctx, webAuthNID) event := usr_repo.NewHumanU2FRemovedEvent(ctx, webAuthNID)
return r.removeHumanWebAuthN(ctx, userID, webAuthNID, resourceOwner, event) return r.removeHumanWebAuthN(ctx, userID, webAuthNID, resourceOwner, event)
} }
func (r *CommandSide) RemoveHumanPasswordless(ctx context.Context, userID, webAuthNID, resourceOwner string) error { func (r *CommandSide) RemoveHumanPasswordless(ctx context.Context, userID, webAuthNID, resourceOwner string) error {
event := user.NewHumanPasswordlessRemovedEvent(ctx, webAuthNID) event := usr_repo.NewHumanPasswordlessRemovedEvent(ctx, webAuthNID)
return r.removeHumanWebAuthN(ctx, userID, webAuthNID, resourceOwner, event) return r.removeHumanWebAuthN(ctx, userID, webAuthNID, resourceOwner, event)
} }
@ -28,8 +205,8 @@ func (r *CommandSide) removeHumanWebAuthN(ctx context.Context, userID, webAuthNI
if err != nil { if err != nil {
return err return err
} }
if existingWebAuthN.State == domain.WebAuthNStateUnspecified || existingWebAuthN.State == domain.WebAuthNStateRemoved { if existingWebAuthN.State == domain.MFAStateUnspecified || existingWebAuthN.State == domain.MFAStateRemoved {
return caos_errs.ThrowNotFound(nil, "COMMAND-5M0ds", "Errors.User.ExternalIDP.NotFound") return caos_errs.ThrowNotFound(nil, "COMMAND-2M9ds", "Errors.User.ExternalIDP.NotFound")
} }
userAgg := UserAggregateFromWriteModel(&existingWebAuthN.WriteModel) userAgg := UserAggregateFromWriteModel(&existingWebAuthN.WriteModel)
userAgg.PushEvents(event) userAgg.PushEvents(event)

View File

@ -10,8 +10,16 @@ type HumanWebAuthNWriteModel struct {
eventstore.WriteModel eventstore.WriteModel
WebauthNTokenID string WebauthNTokenID string
Challenge string
State domain.WebAuthNState KeyID []byte
PublicKey []byte
AttestationType string
AAGUID []byte
SignCount uint32
WebAuthNTokenName string
State domain.MFAState
} }
func NewHumanWebAuthNWriteModel(userID, wbAuthNTokenID, resourceOwner string) *HumanWebAuthNWriteModel { func NewHumanWebAuthNWriteModel(userID, wbAuthNTokenID, resourceOwner string) *HumanWebAuthNWriteModel {
@ -29,14 +37,14 @@ func (wm *HumanWebAuthNWriteModel) AppendEvents(events ...eventstore.EventReader
switch e := event.(type) { switch e := event.(type) {
case *user.HumanWebAuthNAddedEvent: case *user.HumanWebAuthNAddedEvent:
if wm.WebauthNTokenID == e.WebAuthNTokenID { if wm.WebauthNTokenID == e.WebAuthNTokenID {
wm.AppendEvents(e) wm.WriteModel.AppendEvents(e)
} }
case *user.HumanWebAuthNRemovedEvent: case *user.HumanWebAuthNRemovedEvent:
if wm.WebauthNTokenID == e.WebAuthNTokenID { if wm.WebauthNTokenID == e.WebAuthNTokenID {
wm.AppendEvents(e) wm.WriteModel.AppendEvents(e)
} }
case *user.UserRemovedEvent: case *user.UserRemovedEvent:
wm.AppendEvents(e) wm.WriteModel.AppendEvents(e)
} }
} }
} }
@ -45,19 +53,185 @@ func (wm *HumanWebAuthNWriteModel) Reduce() error {
for _, event := range wm.Events { for _, event := range wm.Events {
switch e := event.(type) { switch e := event.(type) {
case *user.HumanWebAuthNAddedEvent: case *user.HumanWebAuthNAddedEvent:
wm.WebauthNTokenID = e.WebAuthNTokenID wm.appendAddedEvent(e)
wm.State = domain.WebAuthNStateActive case *user.HumanWebAuthNVerifiedEvent:
wm.appendVerifiedEvent(e)
case *user.HumanWebAuthNRemovedEvent: case *user.HumanWebAuthNRemovedEvent:
wm.State = domain.WebAuthNStateRemoved wm.State = domain.MFAStateRemoved
case *user.UserRemovedEvent: case *user.UserRemovedEvent:
wm.State = domain.WebAuthNStateRemoved wm.State = domain.MFAStateRemoved
} }
} }
return wm.WriteModel.Reduce() return wm.WriteModel.Reduce()
} }
func (wm *HumanWebAuthNWriteModel) appendAddedEvent(e *user.HumanWebAuthNAddedEvent) {
wm.WebauthNTokenID = e.WebAuthNTokenID
wm.Challenge = e.Challenge
wm.State = domain.MFAStateNotReady
}
func (wm *HumanWebAuthNWriteModel) appendVerifiedEvent(e *user.HumanWebAuthNVerifiedEvent) {
wm.KeyID = e.KeyID
wm.PublicKey = e.PublicKey
wm.AttestationType = e.AttestationType
wm.AAGUID = e.AAGUID
wm.SignCount = e.SignCount
wm.WebAuthNTokenName = e.WebAuthNTokenName
wm.State = domain.MFAStateReady
}
func (wm *HumanWebAuthNWriteModel) Query() *eventstore.SearchQueryBuilder { func (wm *HumanWebAuthNWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType).
AggregateIDs(wm.AggregateID). AggregateIDs(wm.AggregateID).
ResourceOwner(wm.ResourceOwner) ResourceOwner(wm.ResourceOwner)
} }
type HumanU2FTokensReadModel struct {
eventstore.WriteModel
WebAuthNTokens []*HumanWebAuthNWriteModel
UserState domain.UserState
}
func NewHumanU2FTokensReadModel(userID, resourceOwner string) *HumanU2FTokensReadModel {
return &HumanU2FTokensReadModel{
WriteModel: eventstore.WriteModel{
AggregateID: userID,
ResourceOwner: resourceOwner,
},
}
}
func (wm *HumanU2FTokensReadModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *user.HumanWebAuthNAddedEvent:
wm.WriteModel.AppendEvents(e)
case *user.HumanWebAuthNVerifiedEvent:
wm.WriteModel.AppendEvents(e)
case *user.HumanWebAuthNRemovedEvent:
wm.WriteModel.AppendEvents(e)
case *user.UserRemovedEvent:
wm.WriteModel.AppendEvents(e)
}
}
}
func (wm *HumanU2FTokensReadModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *user.HumanWebAuthNAddedEvent:
token := &HumanWebAuthNWriteModel{}
token.appendAddedEvent(e)
wm.WebAuthNTokens = append(wm.WebAuthNTokens, token)
case *user.HumanWebAuthNVerifiedEvent:
idx, token := wm.WebAuthNTokenByID(e.WebAuthNTokenID)
if idx < 0 {
continue
}
token.appendVerifiedEvent(e)
case *user.HumanWebAuthNRemovedEvent:
idx, _ := wm.WebAuthNTokenByID(e.WebAuthNTokenID)
if idx < 0 {
continue
}
copy(wm.WebAuthNTokens[idx:], wm.WebAuthNTokens[idx+1:])
wm.WebAuthNTokens[len(wm.WebAuthNTokens)-1] = nil
wm.WebAuthNTokens = wm.WebAuthNTokens[:len(wm.WebAuthNTokens)-1]
case *user.UserRemovedEvent:
wm.UserState = domain.UserStateDeleted
}
}
return wm.WriteModel.Reduce()
}
func (rm *HumanU2FTokensReadModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType).
AggregateIDs(rm.AggregateID).
ResourceOwner(rm.ResourceOwner).
EventTypes(
user.HumanU2FTokenAddedType,
user.HumanU2FTokenVerifiedType,
user.HumanU2FTokenRemovedType,
user.UserV1MFAOTPRemovedType)
}
func (wm *HumanU2FTokensReadModel) WebAuthNTokenByID(id string) (idx int, token *HumanWebAuthNWriteModel) {
for idx, token = range wm.WebAuthNTokens {
if token.WebauthNTokenID == id {
return idx, token
}
}
return -1, nil
}
type HumanPasswordlessTokensReadModel struct {
eventstore.WriteModel
WebAuthNTokens []*HumanWebAuthNWriteModel
UserState domain.UserState
}
func NewHumanPasswordlessTokensReadModel(userID, resourceOwner string) *HumanPasswordlessTokensReadModel {
return &HumanPasswordlessTokensReadModel{
WriteModel: eventstore.WriteModel{
AggregateID: userID,
ResourceOwner: resourceOwner,
},
}
}
func (wm *HumanPasswordlessTokensReadModel) AppendEvents(events ...eventstore.EventReader) {
wm.WriteModel.AppendEvents(events...)
}
func (wm *HumanPasswordlessTokensReadModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *user.HumanWebAuthNAddedEvent:
token := &HumanWebAuthNWriteModel{}
token.appendAddedEvent(e)
wm.WebAuthNTokens = append(wm.WebAuthNTokens, token)
case *user.HumanWebAuthNVerifiedEvent:
idx, token := wm.WebAuthNTokenByID(e.WebAuthNTokenID)
if idx < 0 {
continue
}
token.appendVerifiedEvent(e)
case *user.HumanWebAuthNRemovedEvent:
idx, _ := wm.WebAuthNTokenByID(e.WebAuthNTokenID)
if idx < 0 {
continue
}
copy(wm.WebAuthNTokens[idx:], wm.WebAuthNTokens[idx+1:])
wm.WebAuthNTokens[len(wm.WebAuthNTokens)-1] = nil
wm.WebAuthNTokens = wm.WebAuthNTokens[:len(wm.WebAuthNTokens)-1]
case *user.UserRemovedEvent:
wm.UserState = domain.UserStateDeleted
}
}
return wm.WriteModel.Reduce()
}
func (rm *HumanPasswordlessTokensReadModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType).
AggregateIDs(rm.AggregateID).
ResourceOwner(rm.ResourceOwner).
EventTypes(
user.HumanPasswordlessTokenAddedType,
user.HumanPasswordlessTokenVerifiedType,
user.HumanPasswordlessTokenRemovedType,
user.UserV1MFAOTPRemovedType)
}
func (wm *HumanPasswordlessTokensReadModel) WebAuthNTokenByID(id string) (idx int, token *HumanWebAuthNWriteModel) {
for idx, token = range wm.WebAuthNTokens {
if token.WebauthNTokenID == id {
return idx, token
}
}
return -1, nil
}

View File

@ -8,9 +8,9 @@ import (
"github.com/caos/zitadel/internal/v2/repository/user" "github.com/caos/zitadel/internal/v2/repository/user"
) )
func (r *CommandSide) AddMachine(ctx context.Context, orgID, username string, machine *domain.Machine) (*domain.Machine, error) { func (r *CommandSide) AddMachine(ctx context.Context, orgID string, machine *domain.Machine) (*domain.Machine, error) {
if !machine.IsValid() { if !machine.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0ds", "Errors.User.Invalid") return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-bm9Ds", "Errors.User.Invalid")
} }
userID, err := r.idGenerator.Next() userID, err := r.idGenerator.Next()
if err != nil { if err != nil {
@ -31,11 +31,12 @@ func (r *CommandSide) AddMachine(ctx context.Context, orgID, username string, ma
userAgg.PushEvents( userAgg.PushEvents(
user.NewMachineAddedEvent( user.NewMachineAddedEvent(
ctx, ctx,
username, machine.Username,
machine.Name, machine.Name,
machine.Description, machine.Description,
), ),
) )
err = r.eventstore.PushAggregate(ctx, addedMachine, userAgg)
return writeModelToMachine(addedMachine), nil return writeModelToMachine(addedMachine), nil
} }
@ -64,7 +65,7 @@ func (r *CommandSide) ChangeMachine(ctx context.Context, machine *domain.Machine
func (r *CommandSide) machineWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *MachineWriteModel, err error) { func (r *CommandSide) machineWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *MachineWriteModel, err error) {
if userID == "" { if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0ds", "Errors.User.UserIDMissing") return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-0Plof", "Errors.User.UserIDMissing")
} }
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()

View File

@ -28,29 +28,10 @@ func NewMachineWriteModel(userID, resourceOwner string) *MachineWriteModel {
} }
func (wm *MachineWriteModel) AppendEvents(events ...eventstore.EventReader) { func (wm *MachineWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events { wm.WriteModel.AppendEvents(events...)
switch e := event.(type) {
case *user.MachineAddedEvent:
wm.AppendEvents(e)
case *user.UsernameChangedEvent:
wm.AppendEvents(e)
case *user.MachineChangedEvent:
wm.AppendEvents(e)
case *user.UserDeactivatedEvent:
wm.AppendEvents(e)
case *user.UserReactivatedEvent:
wm.AppendEvents(e)
case *user.UserLockedEvent:
wm.AppendEvents(e)
case *user.UserUnlockedEvent:
wm.AppendEvents(e)
case *user.UserRemovedEvent:
wm.AppendEvents(e)
}
}
} }
//TODO: Compute State? initial/active //TODO: Compute OTPState? initial/active
func (wm *MachineWriteModel) Reduce() error { func (wm *MachineWriteModel) Reduce() error {
for _, event := range wm.Events { for _, event := range wm.Events {
switch e := event.(type) { switch e := event.(type) {

View File

@ -26,29 +26,10 @@ func NewUserWriteModel(userID, resourceOwner string) *UserWriteModel {
} }
func (wm *UserWriteModel) AppendEvents(events ...eventstore.EventReader) { func (wm *UserWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events { wm.WriteModel.AppendEvents(events...)
switch e := event.(type) {
case *user.HumanAddedEvent, *user.HumanRegisteredEvent:
wm.AppendEvents(e)
case *user.MachineAddedEvent:
wm.AppendEvents(e)
case *user.UsernameChangedEvent:
wm.AppendEvents(e)
case *user.UserDeactivatedEvent:
wm.AppendEvents(e)
case *user.UserReactivatedEvent:
wm.AppendEvents(e)
case *user.UserLockedEvent:
wm.AppendEvents(e)
case *user.UserUnlockedEvent:
wm.AppendEvents(e)
case *user.UserRemovedEvent:
wm.AppendEvents(e)
}
}
} }
//TODO: Compute State? initial/active //TODO: Compute OTPState? initial/active
func (wm *UserWriteModel) Reduce() error { func (wm *UserWriteModel) Reduce() error {
for _, event := range wm.Events { for _, event := range wm.Events {
switch e := event.(type) { switch e := event.(type) {

View File

@ -11,6 +11,8 @@ import (
type Human struct { type Human struct {
es_models.ObjectRoot es_models.ObjectRoot
Username string
State UserState
*Password *Password
*Profile *Profile
*Email *Email
@ -24,6 +26,14 @@ type Human struct {
PasswordlessLogins []*WebAuthNLogin PasswordlessLogins []*WebAuthNLogin
} }
func (h Human) GetUsername() string {
return h.Username
}
func (h Human) GetState() UserState {
return h.State
}
type InitUserCode struct { type InitUserCode struct {
es_models.ObjectRoot es_models.ObjectRoot
@ -91,3 +101,10 @@ func NewInitUserCode(generator crypto.Generator) (*InitUserCode, error) {
Expiry: generator.Expiry(), Expiry: generator.Expiry(),
}, nil }, nil
} }
func GenerateLoginName(username, domain string, appendDomain bool) string {
if !appendDomain {
return username
}
return username + "@" + domain
}

View File

@ -2,7 +2,10 @@ package domain
import ( import (
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
caos_errs "github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models" es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
) )
type OTP struct { type OTP struct {
@ -14,16 +17,27 @@ type OTP struct {
State MFAState State MFAState
} }
type OTPState int32 func NewOTPKey(issuer, accountName string, cryptoAlg crypto.EncryptionAlgorithm) (*otp.Key, *crypto.CryptoValue, error) {
key, err := totp.Generate(totp.GenerateOpts{Issuer: issuer, AccountName: accountName})
const ( if err != nil {
OTPStateUnspecified OTPState = iota return nil, nil, err
OTPStateActive }
OTPStateRemoved encryptedSecret, err := crypto.Encrypt([]byte(key.Secret()), cryptoAlg)
if err != nil {
otpStateCount return nil, nil, err
) }
return key, encryptedSecret, nil
func (s OTPState) Valid() bool { }
return s >= 0 && s < otpStateCount
func VerifyMFAOTP(code string, secret *crypto.CryptoValue, cryptoAlg crypto.EncryptionAlgorithm) error {
decrypt, err := crypto.DecryptString(secret, cryptoAlg)
if err != nil {
return err
}
valid := totp.Validate(code, decrypt)
if !valid {
return caos_errs.ThrowInvalidArgument(nil, "EVENT-8isk2", "Errors.User.MFA.OTP.InvalidCode")
}
return nil
} }

View File

@ -39,16 +39,19 @@ const (
UserVerificationRequirementDiscouraged UserVerificationRequirementDiscouraged
) )
type WebAuthNState int32 type AuthenticatorAttachment int32
const ( const (
WebAuthNStateUnspecified WebAuthNState = iota AuthenticatorAttachmentUnspecified AuthenticatorAttachment = iota
WebAuthNStateActive AuthenticatorAttachmentPlattform
WebAuthNStateRemoved AuthenticatorAttachmentCrossPlattform
webAuthNStateCount
) )
func (s WebAuthNState) Valid() bool { func GetTokenToVerify(tokens []*WebAuthNToken) (int, *WebAuthNToken) {
return s >= 0 && s < webAuthNStateCount for i, u2f := range tokens {
if u2f.State == MFAStateNotReady {
return i, u2f
}
}
return -1, nil
} }

View File

@ -5,10 +5,20 @@ import "github.com/caos/zitadel/internal/eventstore/models"
type Machine struct { type Machine struct {
models.ObjectRoot models.ObjectRoot
Username string
State UserState
Name string Name string
Description string Description string
} }
func (m Machine) GetUsername() string {
return m.Username
}
func (m Machine) GetState() UserState {
return m.State
}
func (sa *Machine) IsValid() bool { func (sa *Machine) IsValid() bool {
return sa.Name != "" return sa.Name != ""
} }

View File

@ -6,6 +6,7 @@ const (
MFAStateUnspecified MFAState = iota MFAStateUnspecified MFAState = iota
MFAStateNotReady MFAStateNotReady
MFAStateReady MFAStateReady
MFAStateRemoved
stateCount stateCount
) )

View File

@ -12,6 +12,7 @@ type Org struct {
State OrgState State OrgState
Name string Name string
PrimaryDomain string
Domains []*OrgDomain Domains []*OrgDomain
Members []*Member Members []*Member
OrgIamPolicy *OrgIAMPolicy OrgIamPolicy *OrgIAMPolicy
@ -38,6 +39,7 @@ func (o *Org) nameForDomain(iamDomain string) string {
type OrgState int32 type OrgState int32
const ( const (
OrgStateActive OrgState = iota OrgStateUnspecified OrgState = iota
OrgStateActive
OrgStateInactive OrgStateInactive
) )

View File

@ -1,14 +1,8 @@
package domain package domain
import es_models "github.com/caos/zitadel/internal/eventstore/models" type User interface {
GetUsername() string
type User struct { GetState() UserState
es_models.ObjectRoot
State UserState
UserName string
*Human
*Machine
} }
type UserState int32 type UserState int32
@ -28,13 +22,3 @@ const (
func (f UserState) Valid() bool { func (f UserState) Valid() bool {
return f >= 0 && f < userStateCount 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()
}

View File

@ -77,7 +77,7 @@ func readModelToLabelPolicy(readModel *IAMLabelPolicyReadModel) *model.LabelPoli
PrimaryColor: readModel.PrimaryColor, PrimaryColor: readModel.PrimaryColor,
SecondaryColor: readModel.SecondaryColor, SecondaryColor: readModel.SecondaryColor,
Default: true, Default: true,
//TODO: State: int32, //TODO: OTPState: int32,
} }
} }
@ -89,7 +89,7 @@ func readModelToLoginPolicy(readModel *IAMLoginPolicyReadModel) *model.LoginPoli
AllowUsernamePassword: readModel.AllowUserNamePassword, AllowUsernamePassword: readModel.AllowUserNamePassword,
Default: true, Default: true,
//TODO: IDPProviders: []*model.IDPProvider, //TODO: IDPProviders: []*model.IDPProvider,
//TODO: State: int32, //TODO: OTPState: int32,
} }
} }
func readModelToOrgIAMPolicy(readModel *IAMOrgIAMPolicyReadModel) *model.OrgIAMPolicy { func readModelToOrgIAMPolicy(readModel *IAMOrgIAMPolicyReadModel) *model.OrgIAMPolicy {
@ -97,7 +97,7 @@ func readModelToOrgIAMPolicy(readModel *IAMOrgIAMPolicyReadModel) *model.OrgIAMP
ObjectRoot: readModelToObjectRoot(readModel.OrgIAMPolicyReadModel.ReadModel), ObjectRoot: readModelToObjectRoot(readModel.OrgIAMPolicyReadModel.ReadModel),
UserLoginMustBeDomain: readModel.UserLoginMustBeDomain, UserLoginMustBeDomain: readModel.UserLoginMustBeDomain,
Default: true, Default: true,
//TODO: State: int32, //TODO: OTPState: int32,
} }
} }
func readModelToPasswordAgePolicy(readModel *IAMPasswordAgePolicyReadModel) *model.PasswordAgePolicy { func readModelToPasswordAgePolicy(readModel *IAMPasswordAgePolicyReadModel) *model.PasswordAgePolicy {
@ -105,7 +105,7 @@ func readModelToPasswordAgePolicy(readModel *IAMPasswordAgePolicyReadModel) *mod
ObjectRoot: readModelToObjectRoot(readModel.PasswordAgePolicyReadModel.ReadModel), ObjectRoot: readModelToObjectRoot(readModel.PasswordAgePolicyReadModel.ReadModel),
ExpireWarnDays: uint64(readModel.ExpireWarnDays), ExpireWarnDays: uint64(readModel.ExpireWarnDays),
MaxAgeDays: uint64(readModel.MaxAgeDays), MaxAgeDays: uint64(readModel.MaxAgeDays),
//TODO: State: int32, //TODO: OTPState: int32,
} }
} }
func readModelToPasswordComplexityPolicy(readModel *IAMPasswordComplexityPolicyReadModel) *model.PasswordComplexityPolicy { func readModelToPasswordComplexityPolicy(readModel *IAMPasswordComplexityPolicyReadModel) *model.PasswordComplexityPolicy {
@ -116,7 +116,7 @@ func readModelToPasswordComplexityPolicy(readModel *IAMPasswordComplexityPolicyR
HasSymbol: readModel.HasSymbol, HasSymbol: readModel.HasSymbol,
HasUppercase: readModel.HasUpperCase, HasUppercase: readModel.HasUpperCase,
MinLength: uint64(readModel.MinLength), MinLength: uint64(readModel.MinLength),
//TODO: State: int32, //TODO: OTPState: int32,
} }
} }
func readModelToPasswordLockoutPolicy(readModel *IAMPasswordLockoutPolicyReadModel) *model.PasswordLockoutPolicy { func readModelToPasswordLockoutPolicy(readModel *IAMPasswordLockoutPolicyReadModel) *model.PasswordLockoutPolicy {
@ -124,7 +124,7 @@ func readModelToPasswordLockoutPolicy(readModel *IAMPasswordLockoutPolicyReadMod
ObjectRoot: readModelToObjectRoot(readModel.PasswordLockoutPolicyReadModel.ReadModel), ObjectRoot: readModelToObjectRoot(readModel.PasswordLockoutPolicyReadModel.ReadModel),
MaxAttempts: uint64(readModel.MaxAttempts), MaxAttempts: uint64(readModel.MaxAttempts),
ShowLockOutFailures: readModel.ShowLockOutFailures, ShowLockOutFailures: readModel.ShowLockOutFailures,
//TODO: State: int32, //TODO: OTPState: int32,
} }
} }

View File

@ -138,7 +138,6 @@ func IAMAggregateFromReadModel(rm *ReadModel) *iam.Aggregate {
iam.AggregateType, iam.AggregateType,
rm.ResourceOwner, rm.ResourceOwner,
iam.AggregateVersion, iam.AggregateVersion,
rm.ProcessedSequence,
), ),
} }
} }

View File

@ -59,7 +59,6 @@ func UserAggregateFromReadModel(rm *UserReadModel) *user.Aggregate {
user.AggregateType, user.AggregateType,
rm.ResourceOwner, rm.ResourceOwner,
user.AggregateVersion, user.AggregateVersion,
rm.ProcessedSequence,
), ),
} }
} }

View File

@ -19,23 +19,6 @@ type Aggregate struct {
eventstore.Aggregate eventstore.Aggregate
} }
func NewAggregate(
id,
resourceOwner string,
previousSequence uint64,
) *Aggregate {
return &Aggregate{
Aggregate: *eventstore.NewAggregate(
id,
AggregateType,
resourceOwner,
AggregateVersion,
previousSequence,
),
}
}
func (a *Aggregate) PushStepStarted(ctx context.Context, step domain.Step) *Aggregate { func (a *Aggregate) PushStepStarted(ctx context.Context, step domain.Step) *Aggregate {
a.Aggregate = *a.PushEvents(NewSetupStepStartedEvent(ctx, step)) a.Aggregate = *a.PushEvents(NewSetupStepStartedEvent(ctx, step))
return a return a

View File

@ -27,7 +27,7 @@ func NewIAMProjectSetEvent(ctx context.Context, projectID string) *ProjectSetEve
return &ProjectSetEvent{ return &ProjectSetEvent{
BaseEvent: *eventstore.NewBaseEventForPush( BaseEvent: *eventstore.NewBaseEventForPush(
ctx, ctx,
SetupDoneEventType, ProjectSetEventType,
), ),
ProjectID: projectID, ProjectID: projectID,
} }

View File

@ -16,20 +16,3 @@ const (
type Aggregate struct { type Aggregate struct {
eventstore.Aggregate eventstore.Aggregate
} }
func NewAggregate(
id,
resourceOwner string,
previousSequence uint64,
) *Aggregate {
return &Aggregate{
Aggregate: *eventstore.NewAggregate(
id,
AggregateType,
resourceOwner,
AggregateVersion,
previousSequence,
),
}
}

View File

@ -12,20 +12,3 @@ const (
type Aggregate struct { type Aggregate struct {
eventstore.Aggregate eventstore.Aggregate
} }
func NewAggregate(
id,
resourceOwner string,
previousSequence uint64,
) *Aggregate {
return &Aggregate{
Aggregate: *eventstore.NewAggregate(
id,
AggregateType,
resourceOwner,
AggregateVersion,
previousSequence,
),
}
}

View File

@ -12,20 +12,3 @@ const (
type Aggregate struct { type Aggregate struct {
eventstore.Aggregate eventstore.Aggregate
} }
func NewAggregate(
id,
resourceOwner string,
previousSequence uint64,
) *Aggregate {
return &Aggregate{
Aggregate: *eventstore.NewAggregate(
id,
AggregateType,
resourceOwner,
AggregateVersion,
previousSequence,
),
}
}

View File

@ -37,7 +37,7 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(UserV1MFAOTPCheckSucceededType, HumanOTPCheckSucceededEventMapper). RegisterFilterEventMapper(UserV1MFAOTPCheckSucceededType, HumanOTPCheckSucceededEventMapper).
RegisterFilterEventMapper(UserV1MFAOTPCheckFailedType, HumanOTPCheckFailedEventMapper). RegisterFilterEventMapper(UserV1MFAOTPCheckFailedType, HumanOTPCheckFailedEventMapper).
RegisterFilterEventMapper(UserLockedType, UserLockedEventMapper). RegisterFilterEventMapper(UserLockedType, UserLockedEventMapper).
RegisterFilterEventMapper(UserUnlockedType, UserLockedEventMapper). RegisterFilterEventMapper(UserUnlockedType, UserUnlockedEventMapper).
RegisterFilterEventMapper(UserDeactivatedType, UserDeactivatedEventMapper). RegisterFilterEventMapper(UserDeactivatedType, UserDeactivatedEventMapper).
RegisterFilterEventMapper(UserReactivatedType, UserReactivatedEventMapper). RegisterFilterEventMapper(UserReactivatedType, UserReactivatedEventMapper).
RegisterFilterEventMapper(UserRemovedType, UserRemovedEventMapper). RegisterFilterEventMapper(UserRemovedType, UserRemovedEventMapper).

View File

@ -52,18 +52,20 @@ func HumanOTPAddedEventMapper(event *repository.Event) (eventstore.EventReader,
type HumanOTPVerifiedEvent struct { type HumanOTPVerifiedEvent struct {
eventstore.BaseEvent `json:"-"` eventstore.BaseEvent `json:"-"`
UserAgentID string `json:"userAgentID,omitempty"`
} }
func (e *HumanOTPVerifiedEvent) Data() interface{} { func (e *HumanOTPVerifiedEvent) Data() interface{} {
return nil return nil
} }
func NewHumanOTPVerifiedEvent(ctx context.Context) *HumanOTPVerifiedEvent { func NewHumanOTPVerifiedEvent(ctx context.Context, userAgentID string) *HumanOTPVerifiedEvent {
return &HumanOTPVerifiedEvent{ return &HumanOTPVerifiedEvent{
BaseEvent: *eventstore.NewBaseEventForPush( BaseEvent: *eventstore.NewBaseEventForPush(
ctx, ctx,
HumanMFAOTPVerifiedType, HumanMFAOTPVerifiedType,
), ),
UserAgentID: userAgentID,
} }
} }

View File

@ -32,9 +32,8 @@ const (
type HumanWebAuthNAddedEvent struct { type HumanWebAuthNAddedEvent struct {
eventstore.BaseEvent `json:"-"` eventstore.BaseEvent `json:"-"`
WebAuthNTokenID string `json:"webAuthNTokenId"` WebAuthNTokenID string `json:"webAuthNTokenId"`
Challenge string `json:"challenge"` Challenge string `json:"challenge"`
State domain.MFAState `json:"-"`
} }
func (e *HumanWebAuthNAddedEvent) Data() interface{} { func (e *HumanWebAuthNAddedEvent) Data() interface{} {
@ -74,7 +73,6 @@ func NewHumanPasswordlessAddedEvent(
func WebAuthNAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) { func WebAuthNAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
webAuthNAdded := &HumanWebAuthNAddedEvent{ webAuthNAdded := &HumanWebAuthNAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event), BaseEvent: *eventstore.BaseEventFromRepo(event),
State: domain.MFAStateNotReady,
} }
err := json.Unmarshal(event.Data, webAuthNAdded) err := json.Unmarshal(event.Data, webAuthNAdded)
if err != nil { if err != nil {
@ -86,14 +84,13 @@ func WebAuthNAddedEventMapper(event *repository.Event) (eventstore.EventReader,
type HumanWebAuthNVerifiedEvent struct { type HumanWebAuthNVerifiedEvent struct {
eventstore.BaseEvent `json:"-"` eventstore.BaseEvent `json:"-"`
WebAuthNTokenID string `json:"webAuthNTokenId"` WebAuthNTokenID string `json:"webAuthNTokenId"`
KeyID []byte `json:"keyId"` KeyID []byte `json:"keyId"`
PublicKey []byte `json:"publicKey"` PublicKey []byte `json:"publicKey"`
AttestationType string `json:"attestationType"` AttestationType string `json:"attestationType"`
AAGUID []byte `json:"aaguid"` AAGUID []byte `json:"aaguid"`
SignCount uint32 `json:"signCount"` SignCount uint32 `json:"signCount"`
WebAuthNTokenName string `json:"webAuthNTokenName"` WebAuthNTokenName string `json:"webAuthNTokenName"`
State domain.MFAState `json:"-"`
} }
func (e *HumanWebAuthNVerifiedEvent) Data() interface{} { func (e *HumanWebAuthNVerifiedEvent) Data() interface{} {
@ -153,7 +150,6 @@ func NewHumanPasswordlessVerifiedEvent(
func HumanWebAuthNVerifiedEventMapper(event *repository.Event) (eventstore.EventReader, error) { func HumanWebAuthNVerifiedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
webauthNVerified := &HumanWebAuthNVerifiedEvent{ webauthNVerified := &HumanWebAuthNVerifiedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event), BaseEvent: *eventstore.BaseEventFromRepo(event),
State: domain.MFAStateReady,
} }
err := json.Unmarshal(event.Data, webauthNVerified) err := json.Unmarshal(event.Data, webauthNVerified)
if err != nil { if err != nil {
@ -165,9 +161,8 @@ func HumanWebAuthNVerifiedEventMapper(event *repository.Event) (eventstore.Event
type HumanWebAuthNSignCountChangedEvent struct { type HumanWebAuthNSignCountChangedEvent struct {
eventstore.BaseEvent `json:"-"` eventstore.BaseEvent `json:"-"`
WebAuthNTokenID string `json:"webAuthNTokenId"` WebAuthNTokenID string `json:"webAuthNTokenId"`
SignCount uint32 `json:"signCount"` SignCount uint32 `json:"signCount"`
State domain.MFAState `json:"-"`
} }
func (e *HumanWebAuthNSignCountChangedEvent) Data() interface{} { func (e *HumanWebAuthNSignCountChangedEvent) Data() interface{} {

View File

@ -1,15 +1,15 @@
package webauthn package webauthn
import ( import (
"github.com/caos/zitadel/internal/user/model" "github.com/caos/zitadel/internal/v2/domain"
"github.com/duo-labs/webauthn/protocol" "github.com/duo-labs/webauthn/protocol"
"github.com/duo-labs/webauthn/webauthn" "github.com/duo-labs/webauthn/webauthn"
) )
func WebAuthNsToCredentials(webAuthNs []*model.WebAuthNToken) []webauthn.Credential { func WebAuthNsToCredentials(webAuthNs []*domain.WebAuthNToken) []webauthn.Credential {
creds := make([]webauthn.Credential, 0) creds := make([]webauthn.Credential, 0)
for _, webAuthN := range webAuthNs { for _, webAuthN := range webAuthNs {
if webAuthN.State == model.MFAStateReady { if webAuthN.State == domain.MFAStateReady {
creds = append(creds, webauthn.Credential{ creds = append(creds, webauthn.Credential{
ID: webAuthN.KeyID, ID: webAuthN.KeyID,
PublicKey: webAuthN.PublicKey, PublicKey: webAuthN.PublicKey,
@ -24,55 +24,55 @@ func WebAuthNsToCredentials(webAuthNs []*model.WebAuthNToken) []webauthn.Credent
return creds return creds
} }
func WebAuthNToSessionData(webAuthN *model.WebAuthNToken) webauthn.SessionData { func WebAuthNToSessionData(webAuthN *domain.WebAuthNToken) webauthn.SessionData {
return webauthn.SessionData{ return webauthn.SessionData{
Challenge: webAuthN.Challenge, Challenge: webAuthN.Challenge,
UserID: []byte(webAuthN.AggregateID), UserID: []byte(webAuthN.AggregateID),
AllowedCredentialIDs: webAuthN.AllowedCredentialIDs, AllowedCredentialIDs: webAuthN.AllowedCredentialIDs,
UserVerification: UserVerificationFromModel(webAuthN.UserVerification), UserVerification: UserVerificationFromDomain(webAuthN.UserVerification),
} }
} }
func WebAuthNLoginToSessionData(webAuthN *model.WebAuthNLogin) webauthn.SessionData { func WebAuthNLoginToSessionData(webAuthN *domain.WebAuthNLogin) webauthn.SessionData {
return webauthn.SessionData{ return webauthn.SessionData{
Challenge: webAuthN.Challenge, Challenge: webAuthN.Challenge,
UserID: []byte(webAuthN.AggregateID), UserID: []byte(webAuthN.AggregateID),
AllowedCredentialIDs: webAuthN.AllowedCredentialIDs, AllowedCredentialIDs: webAuthN.AllowedCredentialIDs,
UserVerification: UserVerificationFromModel(webAuthN.UserVerification), UserVerification: UserVerificationFromDomain(webAuthN.UserVerification),
} }
} }
func UserVerificationToModel(verification protocol.UserVerificationRequirement) model.UserVerificationRequirement { func UserVerificationToDomain(verification protocol.UserVerificationRequirement) domain.UserVerificationRequirement {
switch verification { switch verification {
case protocol.VerificationRequired: case protocol.VerificationRequired:
return model.UserVerificationRequirementRequired return domain.UserVerificationRequirementRequired
case protocol.VerificationPreferred: case protocol.VerificationPreferred:
return model.UserVerificationRequirementPreferred return domain.UserVerificationRequirementPreferred
case protocol.VerificationDiscouraged: case protocol.VerificationDiscouraged:
return model.UserVerificationRequirementDiscouraged return domain.UserVerificationRequirementDiscouraged
default: default:
return model.UserVerificationRequirementUnspecified return domain.UserVerificationRequirementUnspecified
} }
} }
func UserVerificationFromModel(verification model.UserVerificationRequirement) protocol.UserVerificationRequirement { func UserVerificationFromDomain(verification domain.UserVerificationRequirement) protocol.UserVerificationRequirement {
switch verification { switch verification {
case model.UserVerificationRequirementRequired: case domain.UserVerificationRequirementRequired:
return protocol.VerificationRequired return protocol.VerificationRequired
case model.UserVerificationRequirementPreferred: case domain.UserVerificationRequirementPreferred:
return protocol.VerificationPreferred return protocol.VerificationPreferred
case model.UserVerificationRequirementDiscouraged: case domain.UserVerificationRequirementDiscouraged:
return protocol.VerificationDiscouraged return protocol.VerificationDiscouraged
default: default:
return protocol.VerificationDiscouraged return protocol.VerificationDiscouraged
} }
} }
func AuthenticatorAttachmentFromModel(authType model.AuthenticatorAttachment) protocol.AuthenticatorAttachment { func AuthenticatorAttachmentFromDomain(authType domain.AuthenticatorAttachment) protocol.AuthenticatorAttachment {
switch authType { switch authType {
case model.AuthenticatorAttachmentPlattform: case domain.AuthenticatorAttachmentPlattform:
return protocol.Platform return protocol.Platform
case model.AuthenticatorAttachmentCrossPlattform: case domain.AuthenticatorAttachmentCrossPlattform:
return protocol.CrossPlatform return protocol.CrossPlatform
default: default:
return "" return ""

View File

@ -3,13 +3,13 @@ package webauthn
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"github.com/caos/zitadel/internal/v2/domain"
"github.com/duo-labs/webauthn/protocol" "github.com/duo-labs/webauthn/protocol"
"github.com/duo-labs/webauthn/webauthn" "github.com/duo-labs/webauthn/webauthn"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
usr_model "github.com/caos/zitadel/internal/user/model"
) )
type WebAuthN struct { type WebAuthN struct {
@ -41,7 +41,7 @@ func StartServer(sd systemdefaults.WebAuthN) (*WebAuthN, error) {
} }
type webUser struct { type webUser struct {
*usr_model.User *domain.Human
accountName string accountName string
credentials []webauthn.Credential credentials []webauthn.Credential
} }
@ -54,7 +54,7 @@ func (u *webUser) WebAuthnName() string {
if u.accountName != "" { if u.accountName != "" {
return u.accountName return u.accountName
} }
return u.UserName return u.GetUsername()
} }
func (u *webUser) WebAuthnDisplayName() string { func (u *webUser) WebAuthnDisplayName() string {
@ -69,7 +69,7 @@ func (u *webUser) WebAuthnCredentials() []webauthn.Credential {
return u.credentials return u.credentials
} }
func (w *WebAuthN) BeginRegistration(user *usr_model.User, accountName string, authType usr_model.AuthenticatorAttachment, userVerification usr_model.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*usr_model.WebAuthNToken) (*usr_model.WebAuthNToken, error) { func (w *WebAuthN) BeginRegistration(user *domain.Human, accountName string, authType domain.AuthenticatorAttachment, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNToken, error) {
creds := WebAuthNsToCredentials(webAuthNs) creds := WebAuthNsToCredentials(webAuthNs)
existing := make([]protocol.CredentialDescriptor, len(creds)) existing := make([]protocol.CredentialDescriptor, len(creds))
for i, cred := range creds { for i, cred := range creds {
@ -80,13 +80,13 @@ func (w *WebAuthN) BeginRegistration(user *usr_model.User, accountName string, a
} }
credentialOptions, sessionData, err := w.web(isLoginUI).BeginRegistration( credentialOptions, sessionData, err := w.web(isLoginUI).BeginRegistration(
&webUser{ &webUser{
User: user, Human: user,
accountName: accountName, accountName: accountName,
credentials: creds, credentials: creds,
}, },
webauthn.WithAuthenticatorSelection(protocol.AuthenticatorSelection{ webauthn.WithAuthenticatorSelection(protocol.AuthenticatorSelection{
UserVerification: UserVerificationFromModel(userVerification), UserVerification: UserVerificationFromDomain(userVerification),
AuthenticatorAttachment: AuthenticatorAttachmentFromModel(authType), AuthenticatorAttachment: AuthenticatorAttachmentFromDomain(authType),
}), }),
webauthn.WithConveyancePreference(protocol.PreferNoAttestation), webauthn.WithConveyancePreference(protocol.PreferNoAttestation),
webauthn.WithExclusions(existing), webauthn.WithExclusions(existing),
@ -98,15 +98,15 @@ func (w *WebAuthN) BeginRegistration(user *usr_model.User, accountName string, a
if err != nil { if err != nil {
return nil, caos_errs.ThrowInternal(err, "WEBAU-D7cus", "Errors.User.WebAuthN.MarshalError") return nil, caos_errs.ThrowInternal(err, "WEBAU-D7cus", "Errors.User.WebAuthN.MarshalError")
} }
return &usr_model.WebAuthNToken{ return &domain.WebAuthNToken{
Challenge: sessionData.Challenge, Challenge: sessionData.Challenge,
CredentialCreationData: cred, CredentialCreationData: cred,
AllowedCredentialIDs: sessionData.AllowedCredentialIDs, AllowedCredentialIDs: sessionData.AllowedCredentialIDs,
UserVerification: UserVerificationToModel(sessionData.UserVerification), UserVerification: UserVerificationToDomain(sessionData.UserVerification),
}, nil }, nil
} }
func (w *WebAuthN) FinishRegistration(user *usr_model.User, webAuthN *usr_model.WebAuthNToken, tokenName string, credData []byte, isLoginUI bool) (*usr_model.WebAuthNToken, error) { func (w *WebAuthN) FinishRegistration(user *domain.Human, webAuthN *domain.WebAuthNToken, tokenName string, credData []byte, isLoginUI bool) (*domain.WebAuthNToken, error) {
if webAuthN == nil { if webAuthN == nil {
return nil, caos_errs.ThrowInternal(nil, "WEBAU-5M9so", "Errors.User.WebAuthN.NotFound") return nil, caos_errs.ThrowInternal(nil, "WEBAU-5M9so", "Errors.User.WebAuthN.NotFound")
} }
@ -117,7 +117,7 @@ func (w *WebAuthN) FinishRegistration(user *usr_model.User, webAuthN *usr_model.
sessionData := WebAuthNToSessionData(webAuthN) sessionData := WebAuthNToSessionData(webAuthN)
credential, err := w.web(isLoginUI).CreateCredential( credential, err := w.web(isLoginUI).CreateCredential(
&webUser{ &webUser{
User: user, Human: user,
}, },
sessionData, credentialData) sessionData, credentialData)
if err != nil { if err != nil {
@ -133,11 +133,11 @@ func (w *WebAuthN) FinishRegistration(user *usr_model.User, webAuthN *usr_model.
return webAuthN, nil return webAuthN, nil
} }
func (w *WebAuthN) BeginLogin(user *usr_model.User, userVerification usr_model.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*usr_model.WebAuthNToken) (*usr_model.WebAuthNLogin, error) { func (w *WebAuthN) BeginLogin(user *domain.Human, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNLogin, error) {
assertion, sessionData, err := w.web(isLoginUI).BeginLogin(&webUser{ assertion, sessionData, err := w.web(isLoginUI).BeginLogin(&webUser{
User: user, Human: user,
credentials: WebAuthNsToCredentials(webAuthNs), credentials: WebAuthNsToCredentials(webAuthNs),
}, webauthn.WithUserVerification(UserVerificationFromModel(userVerification))) }, webauthn.WithUserVerification(UserVerificationFromDomain(userVerification)))
if err != nil { if err != nil {
return nil, caos_errs.ThrowInternal(err, "WEBAU-4G8sw", "Errors.User.WebAuthN.BeginLoginFailed") return nil, caos_errs.ThrowInternal(err, "WEBAU-4G8sw", "Errors.User.WebAuthN.BeginLoginFailed")
} }
@ -145,7 +145,7 @@ func (w *WebAuthN) BeginLogin(user *usr_model.User, userVerification usr_model.U
if err != nil { if err != nil {
return nil, caos_errs.ThrowInternal(err, "WEBAU-2M0s9", "Errors.User.WebAuthN.MarshalError") return nil, caos_errs.ThrowInternal(err, "WEBAU-2M0s9", "Errors.User.WebAuthN.MarshalError")
} }
return &usr_model.WebAuthNLogin{ return &domain.WebAuthNLogin{
Challenge: sessionData.Challenge, Challenge: sessionData.Challenge,
CredentialAssertionData: cred, CredentialAssertionData: cred,
AllowedCredentialIDs: sessionData.AllowedCredentialIDs, AllowedCredentialIDs: sessionData.AllowedCredentialIDs,
@ -153,13 +153,13 @@ func (w *WebAuthN) BeginLogin(user *usr_model.User, userVerification usr_model.U
}, nil }, nil
} }
func (w *WebAuthN) FinishLogin(user *usr_model.User, webAuthN *usr_model.WebAuthNLogin, credData []byte, isLoginUI bool, webAuthNs ...*usr_model.WebAuthNToken) ([]byte, uint32, error) { func (w *WebAuthN) FinishLogin(user *domain.Human, webAuthN *domain.WebAuthNLogin, credData []byte, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) ([]byte, uint32, error) {
assertionData, err := protocol.ParseCredentialRequestResponseBody(bytes.NewReader(credData)) assertionData, err := protocol.ParseCredentialRequestResponseBody(bytes.NewReader(credData))
if err != nil { if err != nil {
return nil, 0, caos_errs.ThrowInternal(err, "WEBAU-ADgv4", "Errors.User.WebAuthN.ValidateLoginFailed") return nil, 0, caos_errs.ThrowInternal(err, "WEBAU-ADgv4", "Errors.User.WebAuthN.ValidateLoginFailed")
} }
webUser := &webUser{ webUser := &webUser{
User: user, Human: user,
credentials: WebAuthNsToCredentials(webAuthNs), credentials: WebAuthNsToCredentials(webAuthNs),
} }
credential, err := w.web(isLoginUI).ValidateLogin(webUser, WebAuthNLoginToSessionData(webAuthN), assertionData) credential, err := w.web(isLoginUI).ValidateLogin(webUser, WebAuthNLoginToSessionData(webAuthN), assertionData)

View File

@ -114,7 +114,7 @@ service ManagementService {
}; };
} }
rpc DeactivateUser(UserID) returns (UserResponse) { rpc DeactivateUser(UserID) returns (google.protobuf.Empty) {
option (google.api.http) = { option (google.api.http) = {
put: "/users/{id}/_deactivate" put: "/users/{id}/_deactivate"
body: "*" body: "*"
@ -125,7 +125,7 @@ service ManagementService {
}; };
} }
rpc ReactivateUser(UserID) returns (UserResponse) { rpc ReactivateUser(UserID) returns (google.protobuf.Empty) {
option (google.api.http) = { option (google.api.http) = {
put: "/users/{id}/_reactivate" put: "/users/{id}/_reactivate"
body: "*" body: "*"
@ -136,7 +136,7 @@ service ManagementService {
}; };
} }
rpc LockUser(UserID) returns (UserResponse) { rpc LockUser(UserID) returns (google.protobuf.Empty) {
option (google.api.http) = { option (google.api.http) = {
put: "/users/{id}/_lock" put: "/users/{id}/_lock"
body: "*" body: "*"
@ -147,7 +147,7 @@ service ManagementService {
}; };
} }
rpc UnlockUser(UserID) returns (UserResponse) { rpc UnlockUser(UserID) returns (google.protobuf.Empty) {
option (google.api.http) = { option (google.api.http) = {
put: "/users/{id}/_unlock" put: "/users/{id}/_unlock"
body: "*" body: "*"
@ -1741,16 +1741,15 @@ message CreateMachineRequest {
message UserResponse { message UserResponse {
string id = 1; string id = 1;
UserState state = 2; UserState state = 2;
google.protobuf.Timestamp creation_date = 3; google.protobuf.Timestamp change_date = 3;
google.protobuf.Timestamp change_date = 4; uint64 sequence = 4;
uint64 sequence = 5; string user_name = 5;
string user_name = 6;
oneof user { oneof user {
option (validate.required) = true; option (validate.required) = true;
HumanResponse human = 7; HumanResponse human = 6;
MachineResponse machine = 8; MachineResponse machine = 7;
} }
} }
@ -1954,8 +1953,7 @@ message UserProfile {
string preferred_language = 6; string preferred_language = 6;
Gender gender = 7; Gender gender = 7;
uint64 sequence = 8; uint64 sequence = 8;
google.protobuf.Timestamp creation_date = 9; google.protobuf.Timestamp change_date = 9;
google.protobuf.Timestamp change_date = 10;
} }
message UserProfileView { message UserProfileView {
@ -1992,8 +1990,7 @@ message UserEmail {
string email = 2; string email = 2;
bool is_email_verified = 3; bool is_email_verified = 3;
uint64 sequence = 4; uint64 sequence = 4;
google.protobuf.Timestamp creation_date = 5; google.protobuf.Timestamp change_date = 5;
google.protobuf.Timestamp change_date = 6;
} }
message UserEmailView { message UserEmailView {
@ -2016,8 +2013,7 @@ message UserPhone {
string phone = 2; string phone = 2;
bool is_phone_verified = 3; bool is_phone_verified = 3;
uint64 sequence = 5; uint64 sequence = 5;
google.protobuf.Timestamp creation_date = 6; google.protobuf.Timestamp change_date = 6;
google.protobuf.Timestamp change_date = 7;
} }
message UserPhoneView { message UserPhoneView {
@ -2043,8 +2039,7 @@ message UserAddress {
string region = 5; string region = 5;
string street_address = 6; string street_address = 6;
uint64 sequence = 7; uint64 sequence = 7;
google.protobuf.Timestamp creation_date = 8; google.protobuf.Timestamp change_date = 8;
google.protobuf.Timestamp change_date = 9;
} }
message UserAddressView { message UserAddressView {
@ -2528,7 +2523,6 @@ enum AppState {
message Application { message Application {
string id = 1; string id = 1;
AppState state = 2; AppState state = 2;
google.protobuf.Timestamp creation_date = 3;
google.protobuf.Timestamp change_date = 4; google.protobuf.Timestamp change_date = 4;
string name = 5; string name = 5;
oneof app_config { oneof app_config {