mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-04 23:45:07 +00:00
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:
parent
e5731b0d3b
commit
959530ddad
@ -119,7 +119,7 @@ func startZitadel(configPaths []string) {
|
||||
}
|
||||
|
||||
startAPI(ctx, conf, authZRepo, authRepo, command, query)
|
||||
startUI(ctx, conf, authRepo)
|
||||
startUI(ctx, conf, authRepo, command, query)
|
||||
|
||||
if *notificationEnabled {
|
||||
notification.Start(ctx, conf.Notification, conf.SystemDefaults)
|
||||
@ -129,10 +129,10 @@ func startZitadel(configPaths []string) {
|
||||
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)
|
||||
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())
|
||||
}
|
||||
if *consoleEnabled {
|
||||
|
@ -2,6 +2,7 @@ package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
|
||||
"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) {
|
||||
err = s.command.SetUpOrg(ctx, orgCreateRequestToDomain(orgSetUp.Org), userCreateRequestToDomain(orgSetUp.User))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
human, _ := userCreateRequestToDomain(orgSetUp.User)
|
||||
if human == nil {
|
||||
return &empty.Empty{}, errors.ThrowPreconditionFailed(nil, "ADMIN-4nd9f", "Errors.User.NotHuman")
|
||||
}
|
||||
err = s.command.SetUpOrg(ctx, orgCreateRequestToDomain(orgSetUp.Org), human)
|
||||
return &empty.Empty{}, nil
|
||||
}
|
||||
|
||||
|
@ -9,22 +9,18 @@ import (
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func userCreateRequestToDomain(user *admin.CreateUserRequest) *domain.User {
|
||||
var human *domain.Human
|
||||
var machine *domain.Machine
|
||||
|
||||
func userCreateRequestToDomain(user *admin.CreateUserRequest) (*domain.Human, *domain.Machine) {
|
||||
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 {
|
||||
machine = machineCreateToDomain(m)
|
||||
}
|
||||
|
||||
return &domain.User{
|
||||
UserName: user.UserName,
|
||||
Human: human,
|
||||
Machine: machine,
|
||||
machine := machineCreateToDomain(m)
|
||||
machine.Username = user.UserName
|
||||
return nil, machine
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func humanCreateToDomain(u *admin.CreateHumanRequest) *domain.Human {
|
||||
|
@ -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) {
|
||||
err := s.repo.RemoveMyPhone(ctx)
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
err := s.command.RemoveHumanPhone(ctx, ctxData.UserID, ctxData.ResourceOwner)
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
return addressFromModel(address), nil
|
||||
return addressFromDomain(address), nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
err := s.repo.RemoveMyExternalIDP(ctx, externalIDPRemoveToModel(ctx, request))
|
||||
err := s.command.RemoveHumanExternalIDP(ctx, externalIDPRemoveToDomain(ctx, request))
|
||||
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) {
|
||||
otp, err := s.repo.AddMyMFAOTP(ctx)
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
otp, err := s.command.AddHumanOTP(ctx, ctxData.UserID, ctxData.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return otpFromModel(otp), nil
|
||||
return otpFromDomain(otp), nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
return verifyWebAuthNFromModel(u2f), err
|
||||
return verifyWebAuthNFromDomain(u2f), err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
u2f, err := s.repo.AddMyPasswordless(ctx)
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
u2f, err := s.command.AddHumanPasswordless(ctx, ctxData.UserID, ctxData.ResourceOwner, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return verifyWebAuthNFromModel(u2f), err
|
||||
return verifyWebAuthNFromDomain(u2f), err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
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 {
|
||||
return &usr_model.Address{
|
||||
func updateAddressToDomain(ctx context.Context, address *auth.UpdateUserAddressRequest) *domain.Address {
|
||||
return &domain.Address{
|
||||
ObjectRoot: ctxToObjectRoot(ctx),
|
||||
Country: address.Country,
|
||||
StreetAddress: address.StreetAddress,
|
||||
@ -252,11 +252,11 @@ func externalIDPSearchRequestToModel(request *auth.ExternalIDPSearchRequest) *us
|
||||
}
|
||||
}
|
||||
|
||||
func externalIDPRemoveToModel(ctx context.Context, idp *auth.ExternalIDPRemoveRequest) *usr_model.ExternalIDP {
|
||||
return &usr_model.ExternalIDP{
|
||||
ObjectRoot: ctxToObjectRoot(ctx),
|
||||
IDPConfigID: idp.IdpConfigId,
|
||||
UserID: idp.ExternalUserId,
|
||||
func externalIDPRemoveToDomain(ctx context.Context, idp *auth.ExternalIDPRemoveRequest) *domain.ExternalIDP {
|
||||
return &domain.ExternalIDP{
|
||||
ObjectRoot: ctxToObjectRoot(ctx),
|
||||
IDPConfigID: idp.IdpConfigId,
|
||||
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{
|
||||
UserId: otp.AggregateID,
|
||||
Url: otp.Url,
|
||||
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 {
|
||||
case usr_model.MFAStateReady:
|
||||
case domain.MFAStateReady:
|
||||
return auth.MFAState_MFASTATE_READY
|
||||
case usr_model.MFAStateNotReady:
|
||||
case domain.MFAStateNotReady:
|
||||
return auth.MFAState_MFASTATE_NOT_READY
|
||||
default:
|
||||
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 {
|
||||
return &auth.MultiFactor{
|
||||
State: mfaStateFromModel(mfa.State),
|
||||
State: auth.MFAState(mfa.State),
|
||||
Type: mfaTypeFromModel(mfa.Type),
|
||||
Attribute: mfa.Attribute,
|
||||
Id: mfa.ID,
|
||||
@ -431,11 +431,11 @@ func userChangesToAPI(changes *usr_model.UserChanges) (_ []*auth.Change) {
|
||||
return result
|
||||
}
|
||||
|
||||
func verifyWebAuthNFromModel(u2f *usr_model.WebAuthNToken) *auth.WebAuthNResponse {
|
||||
func verifyWebAuthNFromDomain(u2f *domain.WebAuthNToken) *auth.WebAuthNResponse {
|
||||
return &auth.WebAuthNResponse{
|
||||
Id: u2f.WebAuthNTokenID,
|
||||
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{
|
||||
Id: token.WebAuthNTokenID,
|
||||
Name: token.WebAuthNTokenName,
|
||||
State: mfaStateFromModel(token.State),
|
||||
State: auth.MFAState(token.State),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,20 +17,16 @@ import (
|
||||
)
|
||||
|
||||
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)
|
||||
logging.Log("GRPC-di7rw").OnError(err).Debug("unable to parse timestamp")
|
||||
|
||||
return &management.Application{
|
||||
Id: app.AppID,
|
||||
State: appStateFromModel(app.State),
|
||||
CreationDate: creationDate,
|
||||
ChangeDate: changeDate,
|
||||
Name: app.Name,
|
||||
Sequence: app.Sequence,
|
||||
AppConfig: appConfigFromModel(app),
|
||||
Id: app.AppID,
|
||||
State: appStateFromModel(app.State),
|
||||
ChangeDate: changeDate,
|
||||
Name: app.Name,
|
||||
Sequence: app.Sequence,
|
||||
AppConfig: appConfigFromModel(app),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
return userFromDomain(user), nil
|
||||
return userMachineFromDomain(m), nil
|
||||
}
|
||||
|
||||
func (s *Server) DeactivateUser(ctx context.Context, in *management.UserID) (*management.UserResponse, error) {
|
||||
user, err := s.command.DeactivateUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return userFromDomain(user), nil
|
||||
func (s *Server) DeactivateUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) {
|
||||
err := s.command.DeactivateUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID)
|
||||
return &empty.Empty{}, err
|
||||
}
|
||||
|
||||
func (s *Server) ReactivateUser(ctx context.Context, in *management.UserID) (*management.UserResponse, error) {
|
||||
user, err := s.command.ReactivateUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return userFromDomain(user), nil
|
||||
func (s *Server) ReactivateUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) {
|
||||
err := s.command.ReactivateUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID)
|
||||
return &empty.Empty{}, err
|
||||
}
|
||||
|
||||
func (s *Server) LockUser(ctx context.Context, in *management.UserID) (*management.UserResponse, error) {
|
||||
user, err := s.command.LockUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return userFromDomain(user), nil
|
||||
func (s *Server) LockUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) {
|
||||
err := s.command.LockUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID)
|
||||
return &empty.Empty{}, err
|
||||
}
|
||||
|
||||
func (s *Server) UnlockUser(ctx context.Context, in *management.UserID) (*management.UserResponse, error) {
|
||||
user, err := s.command.UnlockUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return userFromDomain(user), nil
|
||||
func (s *Server) UnlockUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) {
|
||||
err := s.command.UnlockUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID)
|
||||
return &empty.Empty{}, err
|
||||
}
|
||||
|
||||
func (s *Server) DeleteUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) {
|
||||
|
@ -19,48 +19,48 @@ import (
|
||||
"github.com/caos/zitadel/pkg/grpc/message"
|
||||
)
|
||||
|
||||
func userFromDomain(user *domain.User) *management.UserResponse {
|
||||
creationDate, err := ptypes.TimestampProto(user.CreationDate)
|
||||
logging.Log("GRPC-8duwe").OnError(err).Debug("unable to parse timestamp")
|
||||
|
||||
changeDate, err := ptypes.TimestampProto(user.ChangeDate)
|
||||
func userMachineFromDomain(machine *domain.Machine) *management.UserResponse {
|
||||
changeDate, err := ptypes.TimestampProto(machine.ChangeDate)
|
||||
logging.Log("GRPC-ckoe3d").OnError(err).Debug("unable to parse timestamp")
|
||||
|
||||
userResp := &management.UserResponse{
|
||||
Id: user.AggregateID,
|
||||
State: userStateFromDomain(user.State),
|
||||
CreationDate: creationDate,
|
||||
ChangeDate: changeDate,
|
||||
Sequence: user.Sequence,
|
||||
UserName: user.UserName,
|
||||
Id: machine.AggregateID,
|
||||
State: userStateFromDomain(machine.GetState()),
|
||||
ChangeDate: changeDate,
|
||||
Sequence: machine.Sequence,
|
||||
UserName: machine.GetUsername(),
|
||||
}
|
||||
|
||||
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)}
|
||||
}
|
||||
|
||||
userResp.User = &management.UserResponse_Machine{Machine: machineFromDomain(machine)}
|
||||
return userResp
|
||||
}
|
||||
|
||||
func userCreateToDomain(user *management.CreateUserRequest) *domain.User {
|
||||
var human *domain.Human
|
||||
var machine *domain.Machine
|
||||
func userHumanFromDomain(human *domain.Human) *management.UserResponse {
|
||||
changeDate, err := ptypes.TimestampProto(human.ChangeDate)
|
||||
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 {
|
||||
human = humanCreateToDomain(h)
|
||||
human := humanCreateToDomain(h)
|
||||
human.Username = user.UserName
|
||||
return human, nil
|
||||
}
|
||||
if m := user.GetMachine(); m != nil {
|
||||
machine = machineCreateToDomain(m)
|
||||
}
|
||||
|
||||
return &domain.User{
|
||||
UserName: user.UserName,
|
||||
Human: human,
|
||||
Machine: machine,
|
||||
machine := machineCreateToDomain(m)
|
||||
machine.Username = user.UserName
|
||||
return nil, machine
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
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 {
|
||||
creationDate, err := ptypes.TimestampProto(profile.CreationDate)
|
||||
logging.Log("GRPC-dkso3").OnError(err).Debug("unable to parse timestamp")
|
||||
|
||||
changeDate, err := ptypes.TimestampProto(profile.ChangeDate)
|
||||
logging.Log("GRPC-ski8d").OnError(err).Debug("unable to parse timestamp")
|
||||
|
||||
return &management.UserProfile{
|
||||
Id: profile.AggregateID,
|
||||
CreationDate: creationDate,
|
||||
ChangeDate: changeDate,
|
||||
Sequence: profile.Sequence,
|
||||
FirstName: profile.FirstName,
|
||||
@ -270,15 +266,11 @@ func updateProfileToDomain(u *management.UpdateUserProfileRequest) *domain.Profi
|
||||
}
|
||||
|
||||
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)
|
||||
logging.Log("GRPC-s0dkw").OnError(err).Debug("unable to parse timestamp")
|
||||
|
||||
return &management.UserEmail{
|
||||
Id: email.AggregateID,
|
||||
CreationDate: creationDate,
|
||||
ChangeDate: changeDate,
|
||||
Sequence: email.Sequence,
|
||||
Email: email.EmailAddress,
|
||||
@ -312,15 +304,11 @@ func updateEmailToDomain(e *management.UpdateUserEmailRequest) *domain.Email {
|
||||
}
|
||||
|
||||
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)
|
||||
logging.Log("GRPC-09ewq").OnError(err).Debug("unable to parse timestamp")
|
||||
|
||||
return &management.UserPhone{
|
||||
Id: phone.AggregateID,
|
||||
CreationDate: creationDate,
|
||||
ChangeDate: changeDate,
|
||||
Sequence: phone.Sequence,
|
||||
Phone: phone.PhoneNumber,
|
||||
@ -353,15 +341,11 @@ func updatePhoneToDomain(e *management.UpdateUserPhoneRequest) *domain.Phone {
|
||||
}
|
||||
|
||||
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)
|
||||
logging.Log("GRPC-si9ws").OnError(err).Debug("unable to parse timestamp")
|
||||
|
||||
return &management.UserAddress{
|
||||
Id: address.AggregateID,
|
||||
CreationDate: creationDate,
|
||||
ChangeDate: changeDate,
|
||||
Sequence: address.Sequence,
|
||||
Country: address.Country,
|
||||
|
@ -122,20 +122,6 @@ func (repo *UserRepo) SearchMyExternalIDPs(ctx context.Context, request *model.E
|
||||
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) {
|
||||
user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID)
|
||||
if err != nil {
|
||||
@ -159,10 +145,6 @@ func (repo *UserRepo) ResendEmailVerificationMail(ctx context.Context, userID st
|
||||
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) {
|
||||
user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID)
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID)
|
||||
if err != nil {
|
||||
@ -204,13 +182,6 @@ func (repo *UserRepo) MyAddress(ctx context.Context) (*model.Address, error) {
|
||||
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 {
|
||||
policy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID)
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
_, err := repo.UserEvents.CreateInitializeUserCodeByID(ctx, userID)
|
||||
return err
|
||||
|
@ -30,12 +30,10 @@ type UserRepository interface {
|
||||
|
||||
AddMFAU2F(ctx context.Context, id string) (*model.WebAuthNToken, 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)
|
||||
AddPasswordless(ctx context.Context, id string) (*model.WebAuthNToken, 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
|
||||
|
||||
@ -52,37 +50,18 @@ type myUserRepo interface {
|
||||
MyProfile(ctx context.Context) (*model.Profile, 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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
VerifyMyMFAU2FSetup(ctx context.Context, tokenName string, data []byte) error
|
||||
RemoveMyMFAU2F(ctx context.Context, webAuthNTokenID string) 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)
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ func insertEvents(stmt *sql.Stmt, previousSequence Sequence, events []*models.Ev
|
||||
event.AggregateType, event.AggregateID, previousSequence, previousSequence).Scan(&previousSequence, &event.CreationDate)
|
||||
|
||||
if err != nil {
|
||||
logging.LogWithFields("SQL-IP3js",
|
||||
logging.LogWithFields("SQL-5M0sd",
|
||||
"aggregate", event.AggregateType,
|
||||
"previousSequence", previousSequence,
|
||||
"aggregateId", event.AggregateID,
|
||||
|
@ -13,11 +13,6 @@ type Aggregater interface {
|
||||
ResourceOwner() string
|
||||
//Version represents the semantic version of the aggregate
|
||||
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(
|
||||
@ -25,15 +20,13 @@ func NewAggregate(
|
||||
typ AggregateType,
|
||||
resourceOwner string,
|
||||
version Version,
|
||||
previousSequence uint64,
|
||||
) *Aggregate {
|
||||
return &Aggregate{
|
||||
id: id,
|
||||
typ: typ,
|
||||
resourceOwner: resourceOwner,
|
||||
version: version,
|
||||
previousSequence: previousSequence,
|
||||
events: []EventPusher{},
|
||||
id: id,
|
||||
typ: typ,
|
||||
resourceOwner: resourceOwner,
|
||||
version: version,
|
||||
events: []EventPusher{},
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,23 +36,21 @@ func AggregateFromWriteModel(
|
||||
version Version,
|
||||
) *Aggregate {
|
||||
return &Aggregate{
|
||||
id: wm.AggregateID,
|
||||
typ: typ,
|
||||
resourceOwner: wm.ResourceOwner,
|
||||
version: version,
|
||||
previousSequence: wm.ProcessedSequence,
|
||||
events: []EventPusher{},
|
||||
id: wm.AggregateID,
|
||||
typ: typ,
|
||||
resourceOwner: wm.ResourceOwner,
|
||||
version: version,
|
||||
events: []EventPusher{},
|
||||
}
|
||||
}
|
||||
|
||||
//Aggregate is the basic implementation of Aggregater
|
||||
type Aggregate struct {
|
||||
id string `json:"-"`
|
||||
typ AggregateType `json:"-"`
|
||||
events []EventPusher `json:"-"`
|
||||
resourceOwner string `json:"-"`
|
||||
version Version `json:"-"`
|
||||
previousSequence uint64 `json:"-"`
|
||||
id string `json:"-"`
|
||||
typ AggregateType `json:"-"`
|
||||
events []EventPusher `json:"-"`
|
||||
resourceOwner string `json:"-"`
|
||||
version Version `json:"-"`
|
||||
}
|
||||
|
||||
//PushEvents adds all the events to the aggregate.
|
||||
@ -93,8 +84,3 @@ func (a *Aggregate) ResourceOwner() string {
|
||||
func (a *Aggregate) Version() Version {
|
||||
return a.version
|
||||
}
|
||||
|
||||
//PreviousSequence implements Aggregater
|
||||
func (a *Aggregate) PreviousSequence() uint64 {
|
||||
return a.previousSequence
|
||||
}
|
||||
|
@ -33,6 +33,5 @@ type EventReader interface {
|
||||
ResourceOwner() string
|
||||
AggregateVersion() Version
|
||||
Sequence() uint64
|
||||
PreviousSequence() uint64
|
||||
CreationDate() time.Time
|
||||
}
|
||||
|
@ -14,11 +14,10 @@ type BaseEvent struct {
|
||||
aggregateType AggregateType `json:"-"`
|
||||
EventType EventType `json:"-"`
|
||||
|
||||
resourceOwner string `json:"-"`
|
||||
aggregateVersion Version `json:"-"`
|
||||
sequence uint64 `json:"-"`
|
||||
previouseSequence uint64 `json:"-"`
|
||||
creationDate time.Time `json:"-"`
|
||||
resourceOwner string `json:"-"`
|
||||
aggregateVersion Version `json:"-"`
|
||||
sequence uint64 `json:"-"`
|
||||
creationDate time.Time `json:"-"`
|
||||
|
||||
//User is the user who created the event
|
||||
User string `json:"-"`
|
||||
@ -56,9 +55,6 @@ func (e *BaseEvent) AggregateVersion() Version {
|
||||
func (e *BaseEvent) Sequence() uint64 {
|
||||
return e.sequence
|
||||
}
|
||||
func (e *BaseEvent) PreviousSequence() uint64 {
|
||||
return e.previouseSequence
|
||||
}
|
||||
func (e *BaseEvent) CreationDate() time.Time {
|
||||
return e.creationDate
|
||||
}
|
||||
|
@ -66,25 +66,21 @@ func (es *Eventstore) PushAggregates(ctx context.Context, aggregates ...Aggregat
|
||||
func (es *Eventstore) aggregatesToEvents(aggregates []Aggregater) ([]*repository.Event, error) {
|
||||
events := make([]*repository.Event, 0, len(aggregates))
|
||||
for _, aggregate := range aggregates {
|
||||
var previousEvent *repository.Event
|
||||
for _, event := range aggregate.Events() {
|
||||
data, err := eventData(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events = append(events, &repository.Event{
|
||||
AggregateID: aggregate.ID(),
|
||||
AggregateType: repository.AggregateType(aggregate.Type()),
|
||||
ResourceOwner: aggregate.ResourceOwner(),
|
||||
EditorService: event.EditorService(),
|
||||
EditorUser: event.EditorUser(),
|
||||
Type: repository.EventType(event.Type()),
|
||||
Version: repository.Version(aggregate.Version()),
|
||||
PreviousEvent: previousEvent,
|
||||
PreviousSequence: aggregate.PreviousSequence(),
|
||||
Data: data,
|
||||
AggregateID: aggregate.ID(),
|
||||
AggregateType: repository.AggregateType(aggregate.Type()),
|
||||
ResourceOwner: aggregate.ResourceOwner(),
|
||||
EditorService: event.EditorService(),
|
||||
EditorUser: event.EditorUser(),
|
||||
Type: repository.EventType(event.Type()),
|
||||
Version: repository.Version(aggregate.Version()),
|
||||
Data: data,
|
||||
})
|
||||
previousEvent = events[len(events)-1]
|
||||
}
|
||||
}
|
||||
return events, nil
|
||||
|
@ -14,9 +14,8 @@ import (
|
||||
)
|
||||
|
||||
type testAggregate struct {
|
||||
id string
|
||||
events []EventPusher
|
||||
previousSequence uint64
|
||||
id string
|
||||
events []EventPusher
|
||||
}
|
||||
|
||||
func (a *testAggregate) ID() string {
|
||||
@ -39,10 +38,6 @@ func (a *testAggregate) Version() Version {
|
||||
return "v1"
|
||||
}
|
||||
|
||||
func (a *testAggregate) PreviousSequence() uint64 {
|
||||
return a.previousSequence
|
||||
}
|
||||
|
||||
// testEvent implements the Event interface
|
||||
type testEvent struct {
|
||||
BaseEvent
|
||||
@ -425,8 +420,8 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
||||
},
|
||||
res: res{
|
||||
wantErr: false,
|
||||
events: linkEvents(
|
||||
&repository.Event{
|
||||
events: []*repository.Event{
|
||||
{
|
||||
AggregateID: "1",
|
||||
AggregateType: "test.aggregate",
|
||||
Data: []byte(nil),
|
||||
@ -436,7 +431,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
&repository.Event{
|
||||
{
|
||||
AggregateID: "1",
|
||||
AggregateType: "test.aggregate",
|
||||
Data: []byte(nil),
|
||||
@ -446,7 +441,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -507,8 +502,8 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
||||
res: res{
|
||||
wantErr: false,
|
||||
events: combineEventLists(
|
||||
linkEvents(
|
||||
&repository.Event{
|
||||
[]*repository.Event{
|
||||
{
|
||||
AggregateID: "1",
|
||||
AggregateType: "test.aggregate",
|
||||
Data: []byte(nil),
|
||||
@ -518,7 +513,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
&repository.Event{
|
||||
{
|
||||
AggregateID: "1",
|
||||
AggregateType: "test.aggregate",
|
||||
Data: []byte(nil),
|
||||
@ -528,7 +523,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
),
|
||||
},
|
||||
[]*repository.Event{
|
||||
{
|
||||
AggregateID: "2",
|
||||
@ -695,8 +690,8 @@ func TestEventstore_Push(t *testing.T) {
|
||||
fields: fields{
|
||||
repo: &testRepo{
|
||||
t: t,
|
||||
events: linkEvents(
|
||||
&repository.Event{
|
||||
events: []*repository.Event{
|
||||
{
|
||||
AggregateID: "1",
|
||||
AggregateType: "test.aggregate",
|
||||
Data: []byte(nil),
|
||||
@ -706,7 +701,7 @@ func TestEventstore_Push(t *testing.T) {
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
&repository.Event{
|
||||
{
|
||||
AggregateID: "1",
|
||||
AggregateType: "test.aggregate",
|
||||
Data: []byte(nil),
|
||||
@ -716,7 +711,7 @@ func TestEventstore_Push(t *testing.T) {
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
eventMapper: map[EventType]func(*repository.Event) (EventReader, error){
|
||||
"test.event": func(e *repository.Event) (EventReader, error) {
|
||||
@ -766,8 +761,8 @@ func TestEventstore_Push(t *testing.T) {
|
||||
repo: &testRepo{
|
||||
t: t,
|
||||
events: combineEventLists(
|
||||
linkEvents(
|
||||
&repository.Event{
|
||||
[]*repository.Event{
|
||||
{
|
||||
AggregateID: "1",
|
||||
AggregateType: "test.aggregate",
|
||||
Data: []byte(nil),
|
||||
@ -777,7 +772,7 @@ func TestEventstore_Push(t *testing.T) {
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
&repository.Event{
|
||||
{
|
||||
AggregateID: "1",
|
||||
AggregateType: "test.aggregate",
|
||||
Data: []byte(nil),
|
||||
@ -787,7 +782,7 @@ func TestEventstore_Push(t *testing.T) {
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
),
|
||||
},
|
||||
[]*repository.Event{
|
||||
{
|
||||
AggregateID: "2",
|
||||
@ -1305,7 +1300,7 @@ func combineEventLists(lists ...[]*repository.Event) []*repository.Event {
|
||||
|
||||
func linkEvents(events ...*repository.Event) []*repository.Event {
|
||||
for i := 1; i < len(events); i++ {
|
||||
events[i].PreviousEvent = events[i-1]
|
||||
// events[i].PreviousEvent = events[i-1]
|
||||
}
|
||||
return events
|
||||
}
|
||||
@ -1337,9 +1332,6 @@ func compareEvents(t *testing.T, want, got *repository.Event) {
|
||||
if want.Version != got.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 {
|
||||
t.Errorf("wrong previous sequence got %d want %d", got.PreviousSequence, want.PreviousSequence)
|
||||
}
|
||||
|
@ -16,17 +16,13 @@ type Event struct {
|
||||
// if it's 0 then it's the first event of this aggregate
|
||||
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
|
||||
// it's used for human readability.
|
||||
// Don't use it for event ordering,
|
||||
// time drifts in different services could cause integrity problems
|
||||
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
|
||||
Type EventType
|
||||
|
||||
|
@ -17,7 +17,10 @@ import (
|
||||
)
|
||||
|
||||
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, " +
|
||||
" aggregate_type, " +
|
||||
" aggregate_id, " +
|
||||
@ -27,47 +30,35 @@ const (
|
||||
" editor_user, " +
|
||||
" editor_service, " +
|
||||
" resource_owner, " +
|
||||
" previous_sequence, " +
|
||||
// variables below are calculated
|
||||
" max_event_seq " +
|
||||
") AS ( " +
|
||||
" ( " +
|
||||
//the following select will return no row if no previous event defined
|
||||
" SELECT " +
|
||||
" $1::VARCHAR, " +
|
||||
" $2::VARCHAR, " +
|
||||
" $3::VARCHAR, " +
|
||||
" $4::VARCHAR, " +
|
||||
" COALESCE($5::TIMESTAMPTZ, NOW()), " +
|
||||
" $6::JSONB, " +
|
||||
" $7::VARCHAR, " +
|
||||
" $8::VARCHAR, " +
|
||||
" resource_owner, " +
|
||||
" $10::BIGINT, " +
|
||||
" MAX(event_sequence) AS max_event_seq " +
|
||||
" FROM eventstore.events " +
|
||||
" WHERE " +
|
||||
" aggregate_type = $2::VARCHAR " +
|
||||
" AND aggregate_id = $3::VARCHAR " +
|
||||
" GROUP BY resource_owner " +
|
||||
" ) UNION (" +
|
||||
// if no previous event we use the given data
|
||||
" VALUES (" +
|
||||
" $1::VARCHAR, " +
|
||||
" $2::VARCHAR, " +
|
||||
" $3::VARCHAR, " +
|
||||
" $4::VARCHAR, " +
|
||||
" 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 " +
|
||||
" previous_sequence" +
|
||||
") AS (" +
|
||||
//previous_data selects the needed data of the latest event of the aggregate
|
||||
// and buffers it (crdb inmemory)
|
||||
" WITH previous_data AS (" +
|
||||
" SELECT MAX(event_sequence) AS seq, resource_owner " +
|
||||
" FROM eventstore.events " +
|
||||
//TODO: remove LIMIT 1 as soon as data cleaned up (only 1 resource_owner per aggregate)
|
||||
" WHERE aggregate_type = $2 AND aggregate_id = $3 GROUP BY resource_owner LIMIT 1" +
|
||||
" )" +
|
||||
// defines the data to be inserted
|
||||
" SELECT " +
|
||||
" $1::VARCHAR AS event_type, " +
|
||||
" $2::VARCHAR AS aggregate_type, " +
|
||||
" $3::VARCHAR AS aggregate_id, " +
|
||||
" $4::VARCHAR AS aggregate_version, " +
|
||||
" NOW() AS creation_date, " +
|
||||
" $5::JSONB AS event_data, " +
|
||||
" $6::VARCHAR AS editor_user, " +
|
||||
" $7::VARCHAR AS editor_service, " +
|
||||
" CASE WHEN EXISTS (SELECT * FROM previous_data) " +
|
||||
" THEN (SELECT resource_owner FROM previous_data) " +
|
||||
" ELSE $8::VARCHAR " +
|
||||
" end AS resource_owner, " +
|
||||
" CASE WHEN EXISTS (SELECT * FROM previous_data) " +
|
||||
" THEN (SELECT seq FROM previous_data) " +
|
||||
" ELSE NULL " +
|
||||
" end AS previous_sequence" +
|
||||
") " +
|
||||
"INSERT INTO eventstore.events " +
|
||||
" ( " +
|
||||
@ -94,9 +85,9 @@ const (
|
||||
" editor_service, " +
|
||||
" resource_owner, " +
|
||||
" 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 {
|
||||
@ -119,28 +110,17 @@ func (db *CRDB) Push(ctx context.Context, events ...*repository.Event) error {
|
||||
return caos_errs.ThrowInternal(err, "SQL-OdXRE", "prepare failed")
|
||||
}
|
||||
|
||||
var previousSequence Sequence
|
||||
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,
|
||||
event.Type,
|
||||
event.AggregateType,
|
||||
event.AggregateID,
|
||||
event.Version,
|
||||
&sql.NullTime{
|
||||
Time: event.CreationDate,
|
||||
Valid: !event.CreationDate.IsZero(),
|
||||
},
|
||||
Data(event.Data),
|
||||
event.EditorUser,
|
||||
event.EditorService,
|
||||
event.ResourceOwner,
|
||||
previousSequence,
|
||||
).Scan(&event.ID, &event.Sequence, &previousSequence, &event.CreationDate, &event.ResourceOwner)
|
||||
|
||||
event.PreviousSequence = uint64(previousSequence)
|
||||
|
@ -284,11 +284,11 @@ func TestCRDB_Push_OneAggregate(t *testing.T) {
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "push 1 event with check previous",
|
||||
name: "push 1 event",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
events: []*repository.Event{
|
||||
generateEvent(t, "1", true, 0),
|
||||
generateEvent(t, "1"),
|
||||
},
|
||||
},
|
||||
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{
|
||||
ctx: context.Background(),
|
||||
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{
|
||||
wantErr: false,
|
||||
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",
|
||||
args: args{
|
||||
ctx: canceledCtx(),
|
||||
events: []*repository.Event{
|
||||
generateEvent(t, "9", true, 0),
|
||||
generateEvent(t, "9"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@ -485,11 +377,11 @@ func TestCRDB_Push_MultipleAggregate(t *testing.T) {
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "push two aggregates both check previous",
|
||||
name: "push two aggregates",
|
||||
args: args{
|
||||
events: []*repository.Event{
|
||||
generateEvent(t, "100", true, 0),
|
||||
generateEvent(t, "101", true, 0),
|
||||
generateEvent(t, "100"),
|
||||
generateEvent(t, "101"),
|
||||
},
|
||||
},
|
||||
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{
|
||||
events: combineEventLists(
|
||||
linkEvents(
|
||||
generateEvent(t, "102", true, 0),
|
||||
generateEvent(t, "102", true, 0),
|
||||
),
|
||||
linkEvents(
|
||||
generateEvent(t, "103", true, 0),
|
||||
generateEvent(t, "103", true, 0),
|
||||
),
|
||||
),
|
||||
events: []*repository.Event{
|
||||
generateEvent(t, "102"),
|
||||
generateEvent(t, "102"),
|
||||
generateEvent(t, "103"),
|
||||
generateEvent(t, "103"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
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{
|
||||
events: linkEvents(
|
||||
generateEvent(t, "104", false, 0),
|
||||
generateEvent(t, "105", false, 0),
|
||||
),
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
eventsRes: eventsRes{
|
||||
pushedEventsCount: 0,
|
||||
aggID: []string{"104", "105"},
|
||||
aggType: []repository.AggregateType{repository.AggregateType(t.Name())},
|
||||
events: []*repository.Event{
|
||||
generateEvent(t, "106"),
|
||||
generateEvent(t, "106"),
|
||||
generateEvent(t, "106"),
|
||||
generateEvent(t, "106"),
|
||||
generateEvent(t, "107"),
|
||||
generateEvent(t, "107"),
|
||||
generateEvent(t, "107"),
|
||||
generateEvent(t, "107"),
|
||||
generateEvent(t, "108"),
|
||||
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{
|
||||
wantErr: true,
|
||||
wantErr: false,
|
||||
eventsRes: eventsRes{
|
||||
pushedEventsCount: 0,
|
||||
aggID: []string{"109"},
|
||||
pushedEventsCount: 12,
|
||||
aggID: []string{"106", "107", "108"},
|
||||
aggType: []repository.AggregateType{repository.AggregateType(t.Name())},
|
||||
},
|
||||
},
|
||||
@ -633,25 +485,19 @@ func TestCRDB_Push_Parallel(t *testing.T) {
|
||||
name: "clients push different aggregates",
|
||||
args: args{
|
||||
events: [][]*repository.Event{
|
||||
linkEvents(
|
||||
generateEvent(t, "200", false, 0),
|
||||
generateEvent(t, "200", true, 0),
|
||||
generateEvent(t, "200", false, 0),
|
||||
),
|
||||
linkEvents(
|
||||
generateEvent(t, "201", false, 0),
|
||||
generateEvent(t, "201", true, 0),
|
||||
generateEvent(t, "201", false, 0),
|
||||
),
|
||||
combineEventLists(
|
||||
linkEvents(
|
||||
generateEvent(t, "202", false, 0),
|
||||
),
|
||||
linkEvents(
|
||||
generateEvent(t, "203", true, 0),
|
||||
generateEvent(t, "203", false, 0),
|
||||
),
|
||||
),
|
||||
{
|
||||
generateEvent(t, "200"),
|
||||
generateEvent(t, "200"),
|
||||
generateEvent(t, "200"),
|
||||
generateEvent(t, "201"),
|
||||
generateEvent(t, "201"),
|
||||
generateEvent(t, "201"),
|
||||
},
|
||||
{
|
||||
generateEvent(t, "202"),
|
||||
generateEvent(t, "203"),
|
||||
generateEvent(t, "203"),
|
||||
},
|
||||
},
|
||||
},
|
||||
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{
|
||||
events: [][]*repository.Event{
|
||||
linkEvents(
|
||||
generateEvent(t, "204", false, 0),
|
||||
generateEvent(t, "204", false, 0),
|
||||
),
|
||||
linkEvents(
|
||||
generateEvent(t, "204", false, 0),
|
||||
generateEvent(t, "204", false, 0),
|
||||
),
|
||||
combineEventLists(
|
||||
linkEvents(
|
||||
generateEvent(t, "205", false, 0),
|
||||
generateEvent(t, "205", false, 0),
|
||||
generateEvent(t, "205", false, 0),
|
||||
),
|
||||
linkEvents(
|
||||
generateEvent(t, "206", false, 0),
|
||||
generateEvent(t, "206", false, 0),
|
||||
generateEvent(t, "206", false, 0),
|
||||
),
|
||||
),
|
||||
combineEventLists(
|
||||
linkEvents(
|
||||
generateEvent(t, "204", false, 0),
|
||||
),
|
||||
linkEvents(
|
||||
generateEvent(t, "205", false, 0),
|
||||
generateEvent(t, "205", false, 0),
|
||||
),
|
||||
linkEvents(
|
||||
generateEvent(t, "206", false, 0),
|
||||
),
|
||||
),
|
||||
{
|
||||
generateEvent(t, "204"),
|
||||
generateEvent(t, "204"),
|
||||
},
|
||||
{
|
||||
generateEvent(t, "204"),
|
||||
generateEvent(t, "204"),
|
||||
},
|
||||
{
|
||||
generateEvent(t, "205"),
|
||||
generateEvent(t, "205"),
|
||||
generateEvent(t, "205"),
|
||||
generateEvent(t, "206"),
|
||||
generateEvent(t, "206"),
|
||||
generateEvent(t, "206"),
|
||||
},
|
||||
{
|
||||
generateEvent(t, "204"),
|
||||
generateEvent(t, "205"),
|
||||
generateEvent(t, "205"),
|
||||
generateEvent(t, "206"),
|
||||
},
|
||||
},
|
||||
},
|
||||
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{
|
||||
events: [][]*repository.Event{
|
||||
linkEvents(
|
||||
generateEvent(t, "207", false, 0),
|
||||
generateEvent(t, "207", false, 0),
|
||||
generateEvent(t, "207", false, 0),
|
||||
generateEvent(t, "207", false, 0),
|
||||
generateEvent(t, "207", false, 0),
|
||||
generateEvent(t, "207", false, 0),
|
||||
),
|
||||
linkEvents(
|
||||
generateEvent(t, "208", true, 0),
|
||||
generateEvent(t, "208", true, 0),
|
||||
generateEvent(t, "208", true, 0),
|
||||
generateEvent(t, "208", true, 0),
|
||||
generateEvent(t, "208", true, 0),
|
||||
),
|
||||
{
|
||||
generateEvent(t, "207"),
|
||||
generateEvent(t, "207"),
|
||||
generateEvent(t, "207"),
|
||||
generateEvent(t, "207"),
|
||||
generateEvent(t, "207"),
|
||||
generateEvent(t, "207"),
|
||||
},
|
||||
{
|
||||
generateEvent(t, "208"),
|
||||
generateEvent(t, "208"),
|
||||
generateEvent(t, "208"),
|
||||
generateEvent(t, "208"),
|
||||
generateEvent(t, "208"),
|
||||
},
|
||||
},
|
||||
},
|
||||
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{
|
||||
events: [][]*repository.Event{
|
||||
linkEvents(
|
||||
generateEventWithData(t, "210", true, 0, []byte(`{ "transaction": 1 }`)),
|
||||
generateEventWithData(t, "210", false, 0, []byte(`{ "transaction": 1.1 }`)),
|
||||
),
|
||||
linkEvents(
|
||||
generateEventWithData(t, "210", true, 0, []byte(`{ "transaction": 2 }`)),
|
||||
generateEventWithData(t, "210", false, 0, []byte(`{ "transaction": 2.1 }`)),
|
||||
),
|
||||
linkEvents(
|
||||
generateEventWithData(t, "210", true, 0, []byte(`{ "transaction": 3 }`)),
|
||||
generateEventWithData(t, "210", false, 0, []byte(`{ "transaction": 30.1 }`)),
|
||||
),
|
||||
{
|
||||
generateEventWithData(t, "210", []byte(`{ "transaction": 1 }`)),
|
||||
generateEventWithData(t, "210", []byte(`{ "transaction": 1.1 }`)),
|
||||
},
|
||||
{
|
||||
generateEventWithData(t, "210", []byte(`{ "transaction": 2 }`)),
|
||||
generateEventWithData(t, "210", []byte(`{ "transaction": 2.1 }`)),
|
||||
},
|
||||
{
|
||||
generateEventWithData(t, "210", []byte(`{ "transaction": 3 }`)),
|
||||
generateEventWithData(t, "210", []byte(`{ "transaction": 30.1 }`)),
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@ -850,9 +686,9 @@ func TestCRDB_Filter(t *testing.T) {
|
||||
},
|
||||
fields: fields{
|
||||
existingEvents: []*repository.Event{
|
||||
generateEvent(t, "300", false, 0),
|
||||
generateEvent(t, "300", false, 0),
|
||||
generateEvent(t, "300", false, 0),
|
||||
generateEvent(t, "300"),
|
||||
generateEvent(t, "300"),
|
||||
generateEvent(t, "300"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@ -873,10 +709,10 @@ func TestCRDB_Filter(t *testing.T) {
|
||||
},
|
||||
fields: fields{
|
||||
existingEvents: []*repository.Event{
|
||||
generateEvent(t, "303", false, 0),
|
||||
generateEvent(t, "303", false, 0),
|
||||
generateEvent(t, "303", false, 0),
|
||||
generateEvent(t, "305", false, 0),
|
||||
generateEvent(t, "303"),
|
||||
generateEvent(t, "303"),
|
||||
generateEvent(t, "303"),
|
||||
generateEvent(t, "305"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@ -938,9 +774,9 @@ func TestCRDB_LatestSequence(t *testing.T) {
|
||||
},
|
||||
fields: fields{
|
||||
existingEvents: []*repository.Event{
|
||||
generateEvent(t, "400", false, 0),
|
||||
generateEvent(t, "400", false, 0),
|
||||
generateEvent(t, "400", false, 0),
|
||||
generateEvent(t, "400"),
|
||||
generateEvent(t, "400"),
|
||||
generateEvent(t, "400"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@ -960,9 +796,9 @@ func TestCRDB_LatestSequence(t *testing.T) {
|
||||
},
|
||||
fields: fields{
|
||||
existingEvents: []*repository.Event{
|
||||
generateEvent(t, "401", false, 0),
|
||||
generateEvent(t, "401", false, 0),
|
||||
generateEvent(t, "401", false, 0),
|
||||
generateEvent(t, "401"),
|
||||
generateEvent(t, "401"),
|
||||
generateEvent(t, "401"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@ -1016,8 +852,8 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) {
|
||||
name: "two events of same aggregate same resource owner",
|
||||
args: args{
|
||||
events: []*repository.Event{
|
||||
generateEvent(t, "500", false, 0, 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" }),
|
||||
generateEvent(t, "500", func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
@ -1032,8 +868,8 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) {
|
||||
name: "two events of different aggregate same resource owner",
|
||||
args: args{
|
||||
events: []*repository.Event{
|
||||
generateEvent(t, "501", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "502", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "501", func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "502", func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
@ -1048,8 +884,8 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) {
|
||||
name: "two events of different aggregate different resource owner",
|
||||
args: args{
|
||||
events: []*repository.Event{
|
||||
generateEvent(t, "503", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "504", false, 0, func(e *repository.Event) { e.ResourceOwner = "zitadel" }),
|
||||
generateEvent(t, "503", func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "504", func(e *repository.Event) { e.ResourceOwner = "zitadel" }),
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
@ -1063,16 +899,12 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) {
|
||||
{
|
||||
name: "events of different aggregate different resource owner",
|
||||
args: args{
|
||||
events: combineEventLists(
|
||||
linkEvents(
|
||||
generateEvent(t, "505", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "505", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
),
|
||||
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" }),
|
||||
),
|
||||
),
|
||||
events: []*repository.Event{
|
||||
generateEvent(t, "505", func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "505", 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" }),
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
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",
|
||||
args: args{
|
||||
events: combineEventLists(
|
||||
linkEvents(
|
||||
generateEvent(t, "507", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "507", false, 0, 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" }),
|
||||
),
|
||||
),
|
||||
events: []*repository.Event{
|
||||
generateEvent(t, "507", func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "507", 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" }),
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
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",
|
||||
args: args{
|
||||
events: combineEventLists(
|
||||
linkEvents(
|
||||
generateEvent(t, "509", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "509", false, 0, 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" }),
|
||||
),
|
||||
),
|
||||
events: []*repository.Event{
|
||||
generateEvent(t, "509", func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "509", 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" }),
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
aggregateIDs: []string{"509"},
|
||||
@ -1180,34 +1004,16 @@ func canceledCtx() context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
func combineEventLists(lists ...[]*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 {
|
||||
func generateEvent(t *testing.T, aggregateID string, opts ...func(*repository.Event)) *repository.Event {
|
||||
t.Helper()
|
||||
e := &repository.Event{
|
||||
AggregateID: aggregateID,
|
||||
AggregateType: repository.AggregateType(t.Name()),
|
||||
CheckPreviousSequence: checkPrevious,
|
||||
EditorService: "svc",
|
||||
EditorUser: "user",
|
||||
PreviousEvent: nil,
|
||||
PreviousSequence: previousSeq,
|
||||
ResourceOwner: "ro",
|
||||
Type: "test.created",
|
||||
Version: "v1",
|
||||
AggregateID: aggregateID,
|
||||
AggregateType: repository.AggregateType(t.Name()),
|
||||
EditorService: "svc",
|
||||
EditorUser: "user",
|
||||
ResourceOwner: "ro",
|
||||
Type: "test.created",
|
||||
Version: "v1",
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
@ -1217,19 +1023,16 @@ func generateEvent(t *testing.T, aggregateID string, checkPrevious bool, previou
|
||||
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()
|
||||
return &repository.Event{
|
||||
AggregateID: aggregateID,
|
||||
AggregateType: repository.AggregateType(t.Name()),
|
||||
CheckPreviousSequence: checkPrevious,
|
||||
EditorService: "svc",
|
||||
EditorUser: "user",
|
||||
PreviousEvent: nil,
|
||||
PreviousSequence: previousSeq,
|
||||
ResourceOwner: "ro",
|
||||
Type: "test.created",
|
||||
Version: "v1",
|
||||
Data: data,
|
||||
AggregateID: aggregateID,
|
||||
AggregateType: repository.AggregateType(t.Name()),
|
||||
EditorService: "svc",
|
||||
EditorUser: "user",
|
||||
ResourceOwner: "ro",
|
||||
Type: "test.created",
|
||||
Version: "v1",
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
@ -27,9 +27,13 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
testCRDBClient, err = sql.Open("postgres", ts.PGURL().String())
|
||||
|
||||
if err != nil {
|
||||
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() {
|
||||
testCRDBClient.Close()
|
||||
|
@ -112,12 +112,11 @@ func eventsScanner(scanner scan, dest interface{}) (err error) {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logging.Log("SQL-kn1Sw").WithError(err).Warn("unable to scan row")
|
||||
return z_errors.ThrowInternal(err, "SQL-J0hFS", "unable to scan row")
|
||||
logging.Log("SQL-3mofs").WithError(err).Warn("unable to scan row")
|
||||
return z_errors.ThrowInternal(err, "SQL-M0dsf", "unable to scan row")
|
||||
}
|
||||
|
||||
event.PreviousSequence = uint64(previousSequence)
|
||||
|
||||
event.Data = make([]byte, len(data))
|
||||
copy(event.Data, data)
|
||||
|
||||
|
@ -329,9 +329,9 @@ func Test_query_events_with_crdb(t *testing.T) {
|
||||
fields: fields{
|
||||
client: testCRDBClient,
|
||||
existingEvents: []*repository.Event{
|
||||
generateEvent(t, "300", false, 0),
|
||||
generateEvent(t, "300", false, 0),
|
||||
generateEvent(t, "300", false, 0),
|
||||
generateEvent(t, "300"),
|
||||
generateEvent(t, "300"),
|
||||
generateEvent(t, "300"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@ -352,10 +352,10 @@ func Test_query_events_with_crdb(t *testing.T) {
|
||||
fields: fields{
|
||||
client: testCRDBClient,
|
||||
existingEvents: []*repository.Event{
|
||||
generateEvent(t, "301", false, 0),
|
||||
generateEvent(t, "302", false, 0),
|
||||
generateEvent(t, "302", false, 0),
|
||||
generateEvent(t, "303", false, 0, func(e *repository.Event) { e.AggregateType = "not in list" }),
|
||||
generateEvent(t, "301"),
|
||||
generateEvent(t, "302"),
|
||||
generateEvent(t, "302"),
|
||||
generateEvent(t, "303", func(e *repository.Event) { e.AggregateType = "not in list" }),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@ -377,11 +377,11 @@ func Test_query_events_with_crdb(t *testing.T) {
|
||||
fields: fields{
|
||||
client: testCRDBClient,
|
||||
existingEvents: []*repository.Event{
|
||||
generateEvent(t, "303", false, 0),
|
||||
generateEvent(t, "303", false, 0),
|
||||
generateEvent(t, "303", false, 0),
|
||||
generateEvent(t, "304", false, 0, func(e *repository.Event) { e.AggregateType = "not in list" }),
|
||||
generateEvent(t, "305", false, 0),
|
||||
generateEvent(t, "303"),
|
||||
generateEvent(t, "303"),
|
||||
generateEvent(t, "303"),
|
||||
generateEvent(t, "304", func(e *repository.Event) { e.AggregateType = "not in list" }),
|
||||
generateEvent(t, "305"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@ -402,11 +402,11 @@ func Test_query_events_with_crdb(t *testing.T) {
|
||||
fields: fields{
|
||||
client: testCRDBClient,
|
||||
existingEvents: []*repository.Event{
|
||||
generateEvent(t, "306", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "307", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "308", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "309", false, 0, func(e *repository.Event) { e.ResourceOwner = "orgID" }),
|
||||
generateEvent(t, "309", false, 0, func(e *repository.Event) { e.ResourceOwner = "orgID" }),
|
||||
generateEvent(t, "306", func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "307", func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "308", func(e *repository.Event) { e.ResourceOwner = "caos" }),
|
||||
generateEvent(t, "309", func(e *repository.Event) { e.ResourceOwner = "orgID" }),
|
||||
generateEvent(t, "309", func(e *repository.Event) { e.ResourceOwner = "orgID" }),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@ -428,11 +428,11 @@ func Test_query_events_with_crdb(t *testing.T) {
|
||||
fields: fields{
|
||||
client: testCRDBClient,
|
||||
existingEvents: []*repository.Event{
|
||||
generateEvent(t, "307", false, 0, func(e *repository.Event) { e.EditorService = "MANAGEMENT-API" }),
|
||||
generateEvent(t, "307", false, 0, func(e *repository.Event) { e.EditorService = "MANAGEMENT-API" }),
|
||||
generateEvent(t, "308", false, 0, func(e *repository.Event) { e.EditorService = "ADMIN-API" }),
|
||||
generateEvent(t, "309", false, 0, func(e *repository.Event) { e.EditorService = "AUTHAPI" }),
|
||||
generateEvent(t, "309", false, 0, func(e *repository.Event) { e.EditorService = "AUTHAPI" }),
|
||||
generateEvent(t, "307", func(e *repository.Event) { e.EditorService = "MANAGEMENT-API" }),
|
||||
generateEvent(t, "307", func(e *repository.Event) { e.EditorService = "MANAGEMENT-API" }),
|
||||
generateEvent(t, "308", func(e *repository.Event) { e.EditorService = "ADMIN-API" }),
|
||||
generateEvent(t, "309", func(e *repository.Event) { e.EditorService = "AUTHAPI" }),
|
||||
generateEvent(t, "309", func(e *repository.Event) { e.EditorService = "AUTHAPI" }),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@ -455,13 +455,13 @@ func Test_query_events_with_crdb(t *testing.T) {
|
||||
fields: fields{
|
||||
client: testCRDBClient,
|
||||
existingEvents: []*repository.Event{
|
||||
generateEvent(t, "310", false, 0, func(e *repository.Event) { e.EditorUser = "adlerhurst" }),
|
||||
generateEvent(t, "310", false, 0, func(e *repository.Event) { e.EditorUser = "adlerhurst" }),
|
||||
generateEvent(t, "310", false, 0, func(e *repository.Event) { e.EditorUser = "nobody" }),
|
||||
generateEvent(t, "311", false, 0, func(e *repository.Event) { e.EditorUser = "" }),
|
||||
generateEvent(t, "311", false, 0, func(e *repository.Event) { e.EditorUser = "" }),
|
||||
generateEvent(t, "312", false, 0, func(e *repository.Event) { e.EditorUser = "fforootd" }),
|
||||
generateEvent(t, "312", false, 0, func(e *repository.Event) { e.EditorUser = "fforootd" }),
|
||||
generateEvent(t, "310", func(e *repository.Event) { e.EditorUser = "adlerhurst" }),
|
||||
generateEvent(t, "310", func(e *repository.Event) { e.EditorUser = "adlerhurst" }),
|
||||
generateEvent(t, "310", func(e *repository.Event) { e.EditorUser = "nobody" }),
|
||||
generateEvent(t, "311", func(e *repository.Event) { e.EditorUser = "" }),
|
||||
generateEvent(t, "311", func(e *repository.Event) { e.EditorUser = "" }),
|
||||
generateEvent(t, "312", func(e *repository.Event) { e.EditorUser = "fforootd" }),
|
||||
generateEvent(t, "312", func(e *repository.Event) { e.EditorUser = "fforootd" }),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@ -483,15 +483,15 @@ func Test_query_events_with_crdb(t *testing.T) {
|
||||
fields: fields{
|
||||
client: testCRDBClient,
|
||||
existingEvents: []*repository.Event{
|
||||
generateEvent(t, "311", false, 0, func(e *repository.Event) { e.Type = "user.created" }),
|
||||
generateEvent(t, "311", false, 0, func(e *repository.Event) { e.Type = "user.updated" }),
|
||||
generateEvent(t, "311", false, 0, func(e *repository.Event) { e.Type = "user.deactivated" }),
|
||||
generateEvent(t, "311", false, 0, func(e *repository.Event) { e.Type = "user.locked" }),
|
||||
generateEvent(t, "312", false, 0, func(e *repository.Event) { e.Type = "user.created" }),
|
||||
generateEvent(t, "312", false, 0, func(e *repository.Event) { e.Type = "user.updated" }),
|
||||
generateEvent(t, "312", false, 0, func(e *repository.Event) { e.Type = "user.deactivated" }),
|
||||
generateEvent(t, "312", false, 0, func(e *repository.Event) { e.Type = "user.reactivated" }),
|
||||
generateEvent(t, "313", false, 0, func(e *repository.Event) { e.Type = "user.locked" }),
|
||||
generateEvent(t, "311", func(e *repository.Event) { e.Type = "user.created" }),
|
||||
generateEvent(t, "311", func(e *repository.Event) { e.Type = "user.updated" }),
|
||||
generateEvent(t, "311", func(e *repository.Event) { e.Type = "user.deactivated" }),
|
||||
generateEvent(t, "311", func(e *repository.Event) { e.Type = "user.locked" }),
|
||||
generateEvent(t, "312", func(e *repository.Event) { e.Type = "user.created" }),
|
||||
generateEvent(t, "312", func(e *repository.Event) { e.Type = "user.updated" }),
|
||||
generateEvent(t, "312", func(e *repository.Event) { e.Type = "user.deactivated" }),
|
||||
generateEvent(t, "312", func(e *repository.Event) { e.Type = "user.reactivated" }),
|
||||
generateEvent(t, "313", func(e *repository.Event) { e.Type = "user.locked" }),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
|
@ -2,6 +2,8 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/v2/command"
|
||||
"github.com/caos/zitadel/internal/v2/query"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
@ -27,6 +29,8 @@ type Login struct {
|
||||
router http.Handler
|
||||
renderer *Renderer
|
||||
parser *form.Parser
|
||||
command *command.CommandSide
|
||||
query *query.QuerySide
|
||||
authRepo auth_repository.Repository
|
||||
baseURL string
|
||||
zitadelURL string
|
||||
@ -56,7 +60,7 @@ const (
|
||||
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)
|
||||
if err != nil {
|
||||
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,
|
||||
baseURL: config.BaseURL,
|
||||
zitadelURL: config.ZitadelURL,
|
||||
command: command,
|
||||
query: query,
|
||||
authRepo: authRepo,
|
||||
IDPConfigAesCrypto: aesCrypto,
|
||||
}
|
||||
|
@ -4,12 +4,14 @@ import (
|
||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing"
|
||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"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 {
|
||||
Handler handler.Config
|
||||
}
|
||||
|
||||
func Start(config Config, authRepo *eventsourcing.EsRepository, systemdefaults systemdefaults.SystemDefaults, localDevMode bool) (*handler.Login, string) {
|
||||
return handler.CreateLogin(config.Handler, authRepo, systemdefaults, localDevMode)
|
||||
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, command, query, authRepo, systemdefaults, localDevMode)
|
||||
}
|
||||
|
@ -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) {
|
||||
user, err := es.HumanByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webAuthN, err := es.webauthn.BeginRegistration(user, accountName, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementDiscouraged, isLoginUI, user.U2FTokens...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokenID, err := es.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webAuthN.WebAuthNTokenID = tokenID
|
||||
webAuthN.State = usr_model.MFAStateNotReady
|
||||
repoUser := model.UserFromModel(user)
|
||||
repoWebAuthN := model.WebAuthNFromModel(webAuthN)
|
||||
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FAddAggregate(es.AggregateCreator(), repoUser, repoWebAuthN))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return webAuthN, nil
|
||||
//user, err := es.HumanByID(ctx, userID)
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//webAuthN, err := es.webauthn.BeginRegistration(user, accountName, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementDiscouraged, isLoginUI, user.U2FTokens...)
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//tokenID, err := es.idGenerator.Next()
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//webAuthN.WebAuthNTokenID = tokenID
|
||||
//webAuthN.State = usr_model.MFAStateNotReady
|
||||
//repoUser := model.UserFromModel(user)
|
||||
//repoWebAuthN := model.WebAuthNFromModel(webAuthN)
|
||||
//
|
||||
//err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FAddAggregate(es.AggregateCreator(), repoUser, repoWebAuthN))
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//return webAuthN, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) VerifyU2FSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error {
|
||||
user, err := es.HumanByID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, token := user.Human.GetU2FToVerify()
|
||||
webAuthN, err := es.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repoUser := model.UserFromModel(user)
|
||||
repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN, userAgentID)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
es.userCache.cacheUser(repoUser)
|
||||
//user, err := es.HumanByID(ctx, userID)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//_, token := user.Human.GetU2FToVerify()
|
||||
//webAuthN, err := es.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "")
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//repoUser := model.UserFromModel(user)
|
||||
//repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN, userAgentID)
|
||||
//err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN))
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//es.userCache.cacheUser(repoUser)
|
||||
//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) {
|
||||
user, err := es.HumanByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user.U2FTokens == nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-5Mk8s", "Errors.User.MFA.U2F.NotExisting")
|
||||
}
|
||||
|
||||
webAuthNLogin, err := es.webauthn.BeginLogin(user, usr_model.UserVerificationRequirementDiscouraged, isLoginUI, user.U2FTokens...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webAuthNLogin.AuthRequest = authRequest
|
||||
repoUser := model.UserFromModel(user)
|
||||
repoWebAuthNLogin := model.WebAuthNLoginFromModel(webAuthNLogin)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FBeginLoginAggregate(es.AggregateCreator(), repoUser, repoWebAuthNLogin))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return webAuthNLogin, nil
|
||||
//user, err := es.HumanByID(ctx, userID)
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//if user.U2FTokens == nil {
|
||||
// return nil, errors.ThrowPreconditionFailed(nil, "EVENT-5Mk8s", "Errors.User.MFA.U2F.NotExisting")
|
||||
//}
|
||||
//
|
||||
//webAuthNLogin, err := es.webauthn.BeginLogin(user, usr_model.UserVerificationRequirementDiscouraged, isLoginUI, user.U2FTokens...)
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//webAuthNLogin.AuthRequest = authRequest
|
||||
//repoUser := model.UserFromModel(user)
|
||||
//repoWebAuthNLogin := model.WebAuthNLoginFromModel(webAuthNLogin)
|
||||
//err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FBeginLoginAggregate(es.AggregateCreator(), repoUser, repoWebAuthNLogin))
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//return webAuthNLogin, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) VerifyMFAU2F(ctx context.Context, userID string, credentialData []byte, authRequest *req_model.AuthRequest, isLoginUI bool) error {
|
||||
user, err := es.HumanByID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, u2f := user.GetU2FLogin(authRequest.ID)
|
||||
keyID, signCount, finishErr := es.webauthn.FinishLogin(user, u2f, credentialData, isLoginUI, user.U2FTokens...)
|
||||
if finishErr != nil && keyID == nil {
|
||||
return finishErr
|
||||
}
|
||||
|
||||
_, token := user.GetU2FByKeyID(keyID)
|
||||
repoUser := model.UserFromModel(user)
|
||||
repoAuthRequest := model.AuthRequestFromModel(authRequest)
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return finishErr
|
||||
//user, err := es.HumanByID(ctx, userID)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//_, u2f := user.GetU2FLogin(authRequest.ID)
|
||||
//keyID, signCount, finishErr := es.webauthn.FinishLogin(user, u2f, credentialData, isLoginUI, user.U2FTokens...)
|
||||
//if finishErr != nil && keyID == nil {
|
||||
// return finishErr
|
||||
//}
|
||||
//
|
||||
//_, token := user.GetU2FByKeyID(keyID)
|
||||
//repoUser := model.UserFromModel(user)
|
||||
//repoAuthRequest := model.AuthRequestFromModel(authRequest)
|
||||
//
|
||||
//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)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//return finishErr
|
||||
return nil
|
||||
}
|
||||
|
||||
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) {
|
||||
user, err := es.HumanByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webAuthN, err := es.webauthn.BeginRegistration(user, accountName, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementRequired, isLoginUI, user.PasswordlessTokens...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokenID, err := es.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webAuthN.WebAuthNTokenID = tokenID
|
||||
repoUser := model.UserFromModel(user)
|
||||
repoWebAuthN := model.WebAuthNFromModel(webAuthN)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessAddAggregate(es.AggregateCreator(), repoUser, repoWebAuthN))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return webAuthN, nil
|
||||
//user, err := es.HumanByID(ctx, userID)
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//webAuthN, err := es.webauthn.BeginRegistration(user, accountName, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementRequired, isLoginUI, user.PasswordlessTokens...)
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//tokenID, err := es.idGenerator.Next()
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//webAuthN.WebAuthNTokenID = tokenID
|
||||
//repoUser := model.UserFromModel(user)
|
||||
//repoWebAuthN := model.WebAuthNFromModel(webAuthN)
|
||||
//err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessAddAggregate(es.AggregateCreator(), repoUser, repoWebAuthN))
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//return webAuthN, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error {
|
||||
user, err := es.HumanByID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, token := user.Human.GetPasswordlessToVerify()
|
||||
webAuthN, err := es.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repoUser := model.UserFromModel(user)
|
||||
repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN, userAgentID)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
es.userCache.cacheUser(repoUser)
|
||||
//user, err := es.HumanByID(ctx, userID)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//_, token := user.Human.GetPasswordlessToVerify()
|
||||
//webAuthN, err := es.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "")
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//repoUser := model.UserFromModel(user)
|
||||
//repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN, userAgentID)
|
||||
//err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN))
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//es.userCache.cacheUser(repoUser)
|
||||
//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) {
|
||||
user, err := es.HumanByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user.PasswordlessTokens == nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-5M9sd", "Errors.User.MFA.Passwordless.NotExisting")
|
||||
}
|
||||
webAuthNLogin, err := es.webauthn.BeginLogin(user, usr_model.UserVerificationRequirementRequired, isLoginUI, user.PasswordlessTokens...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webAuthNLogin.AuthRequest = authRequest
|
||||
repoUser := model.UserFromModel(user)
|
||||
repoWebAuthNLogin := model.WebAuthNLoginFromModel(webAuthNLogin)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessBeginLoginAggregate(es.AggregateCreator(), repoUser, repoWebAuthNLogin))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return webAuthNLogin, nil
|
||||
//user, err := es.HumanByID(ctx, userID)
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//if user.PasswordlessTokens == nil {
|
||||
// return nil, errors.ThrowPreconditionFailed(nil, "EVENT-5M9sd", "Errors.User.MFA.Passwordless.NotExisting")
|
||||
//}
|
||||
//webAuthNLogin, err := es.webauthn.BeginLogin(user, usr_model.UserVerificationRequirementRequired, isLoginUI, user.PasswordlessTokens...)
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//webAuthNLogin.AuthRequest = authRequest
|
||||
//repoUser := model.UserFromModel(user)
|
||||
//repoWebAuthNLogin := model.WebAuthNLoginFromModel(webAuthNLogin)
|
||||
//err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessBeginLoginAggregate(es.AggregateCreator(), repoUser, repoWebAuthNLogin))
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//return webAuthNLogin, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) VerifyPasswordless(ctx context.Context, userID string, credentialData []byte, authRequest *req_model.AuthRequest, isLoginUI bool) error {
|
||||
user, err := es.HumanByID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, passwordless := user.GetPasswordlessLogin(authRequest.ID)
|
||||
keyID, signCount, finishErr := es.webauthn.FinishLogin(user, passwordless, credentialData, isLoginUI, user.PasswordlessTokens...)
|
||||
if finishErr != nil && keyID == nil {
|
||||
return finishErr
|
||||
}
|
||||
_, token := user.GetPasswordlessByKeyID(keyID)
|
||||
repoUser := model.UserFromModel(user)
|
||||
repoAuthRequest := model.AuthRequestFromModel(authRequest)
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return finishErr
|
||||
//user, err := es.HumanByID(ctx, userID)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//_, passwordless := user.GetPasswordlessLogin(authRequest.ID)
|
||||
//keyID, signCount, finishErr := es.webauthn.FinishLogin(user, passwordless, credentialData, isLoginUI, user.PasswordlessTokens...)
|
||||
//if finishErr != nil && keyID == nil {
|
||||
// return finishErr
|
||||
//}
|
||||
//_, token := user.GetPasswordlessByKeyID(keyID)
|
||||
//repoUser := model.UserFromModel(user)
|
||||
//repoAuthRequest := model.AuthRequestFromModel(authRequest)
|
||||
//
|
||||
//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)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//return finishErr
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) SignOut(ctx context.Context, agentID string, userIDs []string) error {
|
||||
|
@ -2,6 +2,8 @@ package command
|
||||
|
||||
import (
|
||||
"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"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
@ -9,6 +11,7 @@ import (
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
iam_repo "github.com/caos/zitadel/internal/v2/repository/iam"
|
||||
usr_repo "github.com/caos/zitadel/internal/v2/repository/user"
|
||||
)
|
||||
|
||||
type CommandSide struct {
|
||||
@ -18,14 +21,18 @@ type CommandSide struct {
|
||||
|
||||
idpConfigSecretCrypto crypto.Crypto
|
||||
|
||||
userPasswordAlg crypto.HashAlgorithm
|
||||
initializeUserCode crypto.Generator
|
||||
emailVerificationCode crypto.Generator
|
||||
phoneVerificationCode crypto.Generator
|
||||
passwordVerificationCode crypto.Generator
|
||||
machineKeyAlg crypto.EncryptionAlgorithm
|
||||
machineKeySize int
|
||||
userPasswordAlg crypto.HashAlgorithm
|
||||
initializeUserCode crypto.Generator
|
||||
emailVerificationCode crypto.Generator
|
||||
phoneVerificationCode crypto.Generator
|
||||
passwordVerificationCode crypto.Generator
|
||||
machineKeyAlg crypto.EncryptionAlgorithm
|
||||
machineKeySize int
|
||||
//TODO: remove global model, or move to domain
|
||||
multifactors global_model.Multifactors
|
||||
applicationSecretGenerator crypto.Generator
|
||||
|
||||
webauthn *webauthn_helper.WebAuthN
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@ -40,6 +47,7 @@ func StartCommandSide(config *Config) (repo *CommandSide, err error) {
|
||||
iamDomain: config.SystemDefaults.Domain,
|
||||
}
|
||||
iam_repo.RegisterEventMappers(repo.eventstore)
|
||||
usr_repo.RegisterEventMappers(repo.eventstore)
|
||||
|
||||
//TODO: simplify!!!!
|
||||
repo.idpConfigSecretCrypto, err = crypto.NewAESCrypto(config.SystemDefaults.IDPConfigVerificationKey)
|
||||
@ -58,8 +66,23 @@ func StartCommandSide(config *Config) (repo *CommandSide, err error) {
|
||||
repo.machineKeyAlg = userEncryptionAlgorithm
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,18 @@ import (
|
||||
"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)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -19,13 +30,13 @@ func (r *CommandSide) SetUpOrg(ctx context.Context, organisation *domain.Org, ad
|
||||
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)
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
@ -6,9 +6,10 @@ import (
|
||||
|
||||
func orgWriteModelToOrg(wm *OrgWriteModel) *domain.Org {
|
||||
return &domain.Org{
|
||||
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
||||
Name: wm.Name,
|
||||
State: wm.State,
|
||||
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
||||
Name: wm.Name,
|
||||
State: wm.State,
|
||||
PrimaryDomain: wm.PrimaryDomain,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,9 @@ import (
|
||||
type OrgWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
Name string
|
||||
State domain.OrgState
|
||||
Name string
|
||||
State domain.OrgState
|
||||
PrimaryDomain string
|
||||
}
|
||||
|
||||
func NewOrgWriteModel(orgID string) *OrgWriteModel {
|
||||
@ -30,6 +31,8 @@ func (wm *OrgWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
case *org.OrgAddedEvent,
|
||||
*iam.LabelPolicyChangedEvent:
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *org.DomainPrimarySetEvent:
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -42,6 +45,8 @@ func (wm *OrgWriteModel) Reduce() error {
|
||||
wm.State = domain.OrgStateActive
|
||||
case *org.OrgChangedEvent:
|
||||
wm.Name = e.Name
|
||||
case *org.DomainPrimarySetEvent:
|
||||
wm.PrimaryDomain = e.Domain
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -34,7 +34,7 @@ func (r *CommandSide) addOrgIAMPolicy(ctx context.Context, orgAgg *org.Aggregate
|
||||
return err
|
||||
}
|
||||
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))
|
||||
return nil
|
||||
|
@ -105,20 +105,18 @@ func (r *CommandSide) SetupStep1(ctx context.Context, step1 *Step1) error {
|
||||
Name: organisation.Name,
|
||||
Domains: []*domain.OrgDomain{{Domain: organisation.Domain}},
|
||||
},
|
||||
&domain.User{
|
||||
UserName: organisation.Owner.UserName,
|
||||
Human: &domain.Human{
|
||||
Profile: &domain.Profile{
|
||||
FirstName: organisation.Owner.FirstName,
|
||||
LastName: organisation.Owner.LastName,
|
||||
},
|
||||
Password: &domain.Password{
|
||||
SecretString: organisation.Owner.Password,
|
||||
},
|
||||
Email: &domain.Email{
|
||||
EmailAddress: organisation.Owner.Email,
|
||||
IsEmailVerified: true,
|
||||
},
|
||||
&domain.Human{
|
||||
Username: organisation.Owner.UserName,
|
||||
Profile: &domain.Profile{
|
||||
FirstName: organisation.Owner.FirstName,
|
||||
LastName: organisation.Owner.LastName,
|
||||
},
|
||||
Password: &domain.Password{
|
||||
SecretString: organisation.Owner.Password,
|
||||
},
|
||||
Email: &domain.Email{
|
||||
EmailAddress: organisation.Owner.Email,
|
||||
IsEmailVerified: true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -9,42 +9,6 @@ import (
|
||||
"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 {
|
||||
if orgID == "" || userID == "" || userName == "" {
|
||||
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)
|
||||
}
|
||||
|
||||
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 == "" {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
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.PushEvents(user.NewUserDeactivatedEvent(ctx))
|
||||
|
||||
err = r.eventstore.PushAggregate(ctx, existingUser, userAgg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToUser(existingUser), nil
|
||||
return r.eventstore.PushAggregate(ctx, existingUser, userAgg)
|
||||
}
|
||||
|
||||
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 == "" {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
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.PushEvents(user.NewUserReactivatedEvent(ctx))
|
||||
|
||||
err = r.eventstore.PushAggregate(ctx, existingUser, userAgg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToUser(existingUser), nil
|
||||
return r.eventstore.PushAggregate(ctx, existingUser, userAgg)
|
||||
}
|
||||
|
||||
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 == "" {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
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.PushEvents(user.NewUserLockedEvent(ctx))
|
||||
|
||||
err = r.eventstore.PushAggregate(ctx, existingUser, userAgg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToUser(existingUser), nil
|
||||
return r.eventstore.PushAggregate(ctx, existingUser, userAgg)
|
||||
}
|
||||
|
||||
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 == "" {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
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.PushEvents(user.NewUserUnlockedEvent(ctx))
|
||||
|
||||
err = r.eventstore.PushAggregate(ctx, existingUser, userAgg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToUser(existingUser), nil
|
||||
return r.eventstore.PushAggregate(ctx, existingUser, userAgg)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -4,17 +4,11 @@ import (
|
||||
"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 {
|
||||
return &domain.Human{
|
||||
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
||||
Username: wm.UserName,
|
||||
State: wm.UserState,
|
||||
Profile: &domain.Profile{
|
||||
FirstName: wm.FirstName,
|
||||
LastName: wm.LastName,
|
||||
@ -82,3 +76,34 @@ func writeModelToMachine(wm *MachineWriteModel) *domain.Machine {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,26 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/eventstore/v2"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/v2/domain"
|
||||
"github.com/caos/zitadel/internal/v2/repository/user"
|
||||
)
|
||||
|
||||
func (r *CommandSide) AddHuman(ctx context.Context, orgID, username string, human *domain.Human) (*domain.Human, error) {
|
||||
userAgg, addedHuman, err := r.addHuman(ctx, orgID, username, human)
|
||||
func (r *CommandSide) getHuman(ctx context.Context, userID, resourceowner string) (*domain.Human, error) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -21,10 +33,34 @@ func (r *CommandSide) AddHuman(ctx context.Context, orgID, username string, huma
|
||||
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() {
|
||||
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()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -40,8 +76,8 @@ func (r *CommandSide) addHuman(ctx context.Context, orgID, username string, huma
|
||||
}
|
||||
|
||||
addedHuman := NewHumanWriteModel(human.AggregateID, orgID)
|
||||
//TODO: Check Unique Username
|
||||
if err := human.CheckOrgIAMPolicy(username, orgIAMPolicy); err != nil {
|
||||
//TODO: Check Unique Username or unique external idp
|
||||
if err := human.CheckOrgIAMPolicy(human.Username, orgIAMPolicy); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
human.SetNamesAsDisplayname()
|
||||
@ -50,6 +86,73 @@ func (r *CommandSide) addHuman(ctx context.Context, orgID, username string, huma
|
||||
}
|
||||
|
||||
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(
|
||||
ctx,
|
||||
username,
|
||||
@ -75,60 +178,10 @@ func (r *CommandSide) addHuman(ctx context.Context, orgID, username string, huma
|
||||
if human.Password != nil {
|
||||
addEvent.AddPasswordData(human.SecretCrypto, human.ChangeRequired)
|
||||
}
|
||||
userAgg.PushEvents(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
|
||||
return addEvent
|
||||
}
|
||||
|
||||
func (r *CommandSide) RegisterHuman(ctx context.Context, orgID, username string, human *domain.Human, externalIDP *domain.ExternalIDP) (*domain.Human, error) {
|
||||
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)
|
||||
func createRegisterHumanEvent(ctx context.Context, username string, human *domain.Human) *user.HumanRegisteredEvent {
|
||||
addEvent := user.NewHumanRegisteredEvent(
|
||||
ctx,
|
||||
username,
|
||||
@ -154,61 +207,14 @@ func (r *CommandSide) RegisterHuman(ctx context.Context, orgID, username string,
|
||||
if human.Password != nil {
|
||||
addEvent.AddPasswordData(human.SecretCrypto, human.ChangeRequired)
|
||||
}
|
||||
userAgg.PushEvents(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))
|
||||
}
|
||||
return addEvent
|
||||
}
|
||||
|
||||
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, 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)
|
||||
func (r *CommandSide) getHumanWriteModelByID(ctx context.Context, userID, resourceowner string) (*HumanWriteModel, error) {
|
||||
humanWriteModel := NewHumanWriteModel(userID, resourceowner)
|
||||
err := r.eventstore.FilterToQueryReducer(ctx, humanWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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)
|
||||
return humanWriteModel, nil
|
||||
}
|
||||
|
@ -30,16 +30,7 @@ func NewHumanAddressWriteModel(userID, resourceOwner string) *HumanAddressWriteM
|
||||
}
|
||||
|
||||
func (wm *HumanAddressWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range 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)
|
||||
}
|
||||
}
|
||||
wm.WriteModel.AppendEvents(events...)
|
||||
}
|
||||
|
||||
func (wm *HumanAddressWriteModel) Reduce() error {
|
||||
|
@ -2,6 +2,8 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
"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
|
||||
}
|
||||
|
||||
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 {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
|
||||
|
@ -2,10 +2,11 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/eventstore/v2"
|
||||
"github.com/caos/zitadel/internal/v2/domain"
|
||||
"github.com/caos/zitadel/internal/v2/repository/user"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HumanEmailWriteModel struct {
|
||||
@ -14,6 +15,10 @@ type HumanEmailWriteModel struct {
|
||||
Email string
|
||||
IsEmailVerified bool
|
||||
|
||||
Code *crypto.CryptoValue
|
||||
CodeCreationDate time.Time
|
||||
CodeExpiry time.Duration
|
||||
|
||||
UserState domain.UserState
|
||||
}
|
||||
|
||||
@ -27,18 +32,7 @@ func NewHumanEmailWriteModel(userID, resourceOwner string) *HumanEmailWriteModel
|
||||
}
|
||||
|
||||
func (wm *HumanEmailWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range 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)
|
||||
}
|
||||
}
|
||||
wm.WriteModel.AppendEvents(events...)
|
||||
}
|
||||
|
||||
func (wm *HumanEmailWriteModel) Reduce() error {
|
||||
@ -53,8 +47,14 @@ func (wm *HumanEmailWriteModel) Reduce() error {
|
||||
case *user.HumanEmailChangedEvent:
|
||||
wm.Email = e.EmailAddress
|
||||
wm.IsEmailVerified = false
|
||||
wm.Code = nil
|
||||
case *user.HumanEmailCodeAddedEvent:
|
||||
wm.Code = e.Code
|
||||
wm.CodeCreationDate = e.CreationDate()
|
||||
wm.CodeExpiry = e.Expiry
|
||||
case *user.HumanEmailVerifiedEvent:
|
||||
wm.IsEmailVerified = true
|
||||
wm.Code = nil
|
||||
if wm.UserState == domain.UserStateInitial {
|
||||
wm.UserState = domain.UserStateActive
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ func (r *CommandSide) removeHumanExternalIDP(ctx context.Context, externalIDP *d
|
||||
return err
|
||||
}
|
||||
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)
|
||||
if !cascade {
|
||||
|
@ -28,24 +28,7 @@ func NewHumanExternalIDPWriteModel(userID, idpConfigID, externalUserID, resource
|
||||
}
|
||||
|
||||
func (wm *HumanExternalIDPWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range 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)
|
||||
}
|
||||
}
|
||||
wm.WriteModel.AppendEvents(events...)
|
||||
}
|
||||
|
||||
func (wm *HumanExternalIDPWriteModel) Reduce() error {
|
||||
|
@ -48,28 +48,10 @@ func NewHumanWriteModel(userID, resourceOwner string) *HumanWriteModel {
|
||||
}
|
||||
|
||||
func (wm *HumanWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range 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)
|
||||
}
|
||||
}
|
||||
wm.WriteModel.AppendEvents(events...)
|
||||
}
|
||||
|
||||
//TODO: Compute State? initial/active
|
||||
//TODO: Compute OTPState? initial/active
|
||||
func (wm *HumanWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
|
@ -3,11 +3,87 @@ package command
|
||||
import (
|
||||
"context"
|
||||
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/v2/domain"
|
||||
"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 {
|
||||
if userID == "" {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
if existingOTP.State == domain.OTPStateUnspecified || existingOTP.State == domain.OTPStateRemoved {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-5M0ds", "Errors.User.OTP.NotFound")
|
||||
if existingOTP.State == domain.MFAStateUnspecified || existingOTP.State == domain.MFAStateRemoved {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-Hd9sd", "Errors.User.MFA.OTP.NotExisting")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
|
||||
userAgg.PushEvents(
|
||||
|
@ -10,9 +10,8 @@ import (
|
||||
type HumanOTPWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
State domain.MFAState
|
||||
Secret *crypto.CryptoValue
|
||||
|
||||
State domain.OTPState
|
||||
}
|
||||
|
||||
func NewHumanOTPWriteModel(userID, resourceOwner string) *HumanOTPWriteModel {
|
||||
@ -25,16 +24,7 @@ func NewHumanOTPWriteModel(userID, resourceOwner string) *HumanOTPWriteModel {
|
||||
}
|
||||
|
||||
func (wm *HumanOTPWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *user.HumanOTPAddedEvent:
|
||||
wm.AppendEvents(e)
|
||||
case *user.HumanOTPRemovedEvent:
|
||||
wm.AppendEvents(e)
|
||||
case *user.UserRemovedEvent:
|
||||
wm.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
wm.WriteModel.AppendEvents(events...)
|
||||
}
|
||||
|
||||
func (wm *HumanOTPWriteModel) Reduce() error {
|
||||
@ -42,11 +32,13 @@ func (wm *HumanOTPWriteModel) Reduce() error {
|
||||
switch e := event.(type) {
|
||||
case *user.HumanOTPAddedEvent:
|
||||
wm.Secret = e.Secret
|
||||
wm.State = domain.OTPStateActive
|
||||
wm.State = domain.MFAStateNotReady
|
||||
case *user.HumanOTPVerifiedEvent:
|
||||
wm.State = domain.MFAStateReady
|
||||
case *user.HumanOTPRemovedEvent:
|
||||
wm.State = domain.OTPStateRemoved
|
||||
wm.State = domain.MFAStateRemoved
|
||||
case *user.UserRemovedEvent:
|
||||
wm.State = domain.OTPStateRemoved
|
||||
wm.State = domain.MFAStateRemoved
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
|
@ -24,11 +24,11 @@ func (r *CommandSide) SetOneTimePassword(ctx context.Context, orgID, userID, pas
|
||||
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)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
existingPassword, err := r.passwordWriteModel(ctx, userID, resourceOwner)
|
||||
existingPassword, err := r.passwordWriteModel(ctx, userID, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -26,18 +26,7 @@ func NewHumanPasswordWriteModel(userID, resourceOwner string) *HumanPasswordWrit
|
||||
}
|
||||
|
||||
func (wm *HumanPasswordWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range 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)
|
||||
}
|
||||
}
|
||||
wm.WriteModel.AppendEvents(events...)
|
||||
}
|
||||
|
||||
func (wm *HumanPasswordWriteModel) Reduce() error {
|
||||
|
@ -2,7 +2,8 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
"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")
|
||||
}
|
||||
|
||||
existingPhone, err := r.phoneWriteModel(ctx, phone.AggregateID, phone.ResourceOwner)
|
||||
existingPhone, err := r.phoneWriteModelByID(ctx, phone.AggregateID, phone.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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)
|
||||
if !hasChanged {
|
||||
@ -46,12 +47,48 @@ func (r *CommandSide) ChangeHumanPhone(ctx context.Context, phone *domain.Phone)
|
||||
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 == "" {
|
||||
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 {
|
||||
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")
|
||||
}
|
||||
|
||||
existingPhone, err := r.phoneWriteModel(ctx, userID, resourceOwner)
|
||||
existingPhone, err := r.phoneWriteModelByID(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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.PushEvents(
|
||||
@ -89,7 +126,7 @@ func (r *CommandSide) RemoveHumanPhone(ctx context.Context, userID, resourceOwne
|
||||
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)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
|
@ -2,10 +2,11 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/eventstore/v2"
|
||||
"github.com/caos/zitadel/internal/v2/domain"
|
||||
"github.com/caos/zitadel/internal/v2/repository/user"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HumanPhoneWriteModel struct {
|
||||
@ -14,6 +15,10 @@ type HumanPhoneWriteModel struct {
|
||||
Phone string
|
||||
IsPhoneVerified bool
|
||||
|
||||
Code *crypto.CryptoValue
|
||||
CodeCreationDate time.Time
|
||||
CodeExpiry time.Duration
|
||||
|
||||
State domain.PhoneState
|
||||
}
|
||||
|
||||
@ -27,20 +32,7 @@ func NewHumanPhoneWriteModel(userID, resourceOwner string) *HumanPhoneWriteModel
|
||||
}
|
||||
|
||||
func (wm *HumanPhoneWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range 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)
|
||||
}
|
||||
}
|
||||
wm.WriteModel.AppendEvents(events...)
|
||||
}
|
||||
|
||||
func (wm *HumanPhoneWriteModel) Reduce() error {
|
||||
@ -49,8 +41,8 @@ func (wm *HumanPhoneWriteModel) Reduce() error {
|
||||
case *user.HumanAddedEvent:
|
||||
if e.PhoneNumber != "" {
|
||||
wm.Phone = e.PhoneNumber
|
||||
wm.State = domain.PhoneStateActive
|
||||
}
|
||||
wm.State = domain.PhoneStateActive
|
||||
case *user.HumanRegisteredEvent:
|
||||
if e.PhoneNumber != "" {
|
||||
wm.Phone = e.PhoneNumber
|
||||
@ -60,8 +52,14 @@ func (wm *HumanPhoneWriteModel) Reduce() error {
|
||||
wm.Phone = e.PhoneNumber
|
||||
wm.IsPhoneVerified = false
|
||||
wm.State = domain.PhoneStateActive
|
||||
wm.Code = nil
|
||||
case *user.HumanPhoneVerifiedEvent:
|
||||
wm.IsPhoneVerified = true
|
||||
wm.Code = nil
|
||||
case *user.HumanPhoneCodeAddedEvent:
|
||||
wm.Code = e.Code
|
||||
wm.CodeCreationDate = e.CreationDate()
|
||||
wm.CodeExpiry = e.Expiry
|
||||
case *user.HumanPhoneRemovedEvent:
|
||||
wm.State = domain.PhoneStateRemoved
|
||||
case *user.UserRemovedEvent:
|
||||
|
@ -19,7 +19,7 @@ func (r *CommandSide) ChangeHumanProfile(ctx context.Context, profile *domain.Pr
|
||||
if existingProfile.UserState == domain.UserStateUnspecified || existingProfile.UserState == domain.UserStateDeleted {
|
||||
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 {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0fs", "Errors.User.Profile.NotChanged")
|
||||
}
|
||||
|
@ -33,16 +33,7 @@ func NewHumanProfileWriteModel(userID, resourceOwner string) *HumanProfileWriteM
|
||||
}
|
||||
|
||||
func (wm *HumanProfileWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range 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)
|
||||
}
|
||||
}
|
||||
wm.WriteModel.AppendEvents(events...)
|
||||
}
|
||||
|
||||
func (wm *HumanProfileWriteModel) Reduce() error {
|
||||
|
@ -6,16 +6,193 @@ import (
|
||||
"github.com/caos/zitadel/internal/eventstore/v2"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
"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 {
|
||||
event := user.NewHumanU2FRemovedEvent(ctx, webAuthNID)
|
||||
event := usr_repo.NewHumanU2FRemovedEvent(ctx, webAuthNID)
|
||||
return r.removeHumanWebAuthN(ctx, userID, webAuthNID, resourceOwner, event)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@ -28,8 +205,8 @@ func (r *CommandSide) removeHumanWebAuthN(ctx context.Context, userID, webAuthNI
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existingWebAuthN.State == domain.WebAuthNStateUnspecified || existingWebAuthN.State == domain.WebAuthNStateRemoved {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-5M0ds", "Errors.User.ExternalIDP.NotFound")
|
||||
if existingWebAuthN.State == domain.MFAStateUnspecified || existingWebAuthN.State == domain.MFAStateRemoved {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-2M9ds", "Errors.User.ExternalIDP.NotFound")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingWebAuthN.WriteModel)
|
||||
userAgg.PushEvents(event)
|
||||
|
@ -10,8 +10,16 @@ type HumanWebAuthNWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
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 {
|
||||
@ -29,14 +37,14 @@ func (wm *HumanWebAuthNWriteModel) AppendEvents(events ...eventstore.EventReader
|
||||
switch e := event.(type) {
|
||||
case *user.HumanWebAuthNAddedEvent:
|
||||
if wm.WebauthNTokenID == e.WebAuthNTokenID {
|
||||
wm.AppendEvents(e)
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
}
|
||||
case *user.HumanWebAuthNRemovedEvent:
|
||||
if wm.WebauthNTokenID == e.WebAuthNTokenID {
|
||||
wm.AppendEvents(e)
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
}
|
||||
case *user.UserRemovedEvent:
|
||||
wm.AppendEvents(e)
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -45,19 +53,185 @@ func (wm *HumanWebAuthNWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *user.HumanWebAuthNAddedEvent:
|
||||
wm.WebauthNTokenID = e.WebAuthNTokenID
|
||||
wm.State = domain.WebAuthNStateActive
|
||||
wm.appendAddedEvent(e)
|
||||
case *user.HumanWebAuthNVerifiedEvent:
|
||||
wm.appendVerifiedEvent(e)
|
||||
case *user.HumanWebAuthNRemovedEvent:
|
||||
wm.State = domain.WebAuthNStateRemoved
|
||||
wm.State = domain.MFAStateRemoved
|
||||
case *user.UserRemovedEvent:
|
||||
wm.State = domain.WebAuthNStateRemoved
|
||||
wm.State = domain.MFAStateRemoved
|
||||
}
|
||||
}
|
||||
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 {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
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
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ import (
|
||||
"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() {
|
||||
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()
|
||||
if err != nil {
|
||||
@ -31,11 +31,12 @@ func (r *CommandSide) AddMachine(ctx context.Context, orgID, username string, ma
|
||||
userAgg.PushEvents(
|
||||
user.NewMachineAddedEvent(
|
||||
ctx,
|
||||
username,
|
||||
machine.Username,
|
||||
machine.Name,
|
||||
machine.Description,
|
||||
),
|
||||
)
|
||||
err = r.eventstore.PushAggregate(ctx, addedMachine, userAgg)
|
||||
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) {
|
||||
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)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
@ -28,29 +28,10 @@ func NewMachineWriteModel(userID, resourceOwner string) *MachineWriteModel {
|
||||
}
|
||||
|
||||
func (wm *MachineWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range 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)
|
||||
}
|
||||
}
|
||||
wm.WriteModel.AppendEvents(events...)
|
||||
}
|
||||
|
||||
//TODO: Compute State? initial/active
|
||||
//TODO: Compute OTPState? initial/active
|
||||
func (wm *MachineWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
|
@ -26,29 +26,10 @@ func NewUserWriteModel(userID, resourceOwner string) *UserWriteModel {
|
||||
}
|
||||
|
||||
func (wm *UserWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range 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)
|
||||
}
|
||||
}
|
||||
wm.WriteModel.AppendEvents(events...)
|
||||
}
|
||||
|
||||
//TODO: Compute State? initial/active
|
||||
//TODO: Compute OTPState? initial/active
|
||||
func (wm *UserWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
|
@ -11,6 +11,8 @@ import (
|
||||
type Human struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
Username string
|
||||
State UserState
|
||||
*Password
|
||||
*Profile
|
||||
*Email
|
||||
@ -24,6 +26,14 @@ type Human struct {
|
||||
PasswordlessLogins []*WebAuthNLogin
|
||||
}
|
||||
|
||||
func (h Human) GetUsername() string {
|
||||
return h.Username
|
||||
}
|
||||
|
||||
func (h Human) GetState() UserState {
|
||||
return h.State
|
||||
}
|
||||
|
||||
type InitUserCode struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
@ -91,3 +101,10 @@ func NewInitUserCode(generator crypto.Generator) (*InitUserCode, error) {
|
||||
Expiry: generator.Expiry(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GenerateLoginName(username, domain string, appendDomain bool) string {
|
||||
if !appendDomain {
|
||||
return username
|
||||
}
|
||||
return username + "@" + domain
|
||||
}
|
||||
|
@ -2,7 +2,10 @@ package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
type OTP struct {
|
||||
@ -14,16 +17,27 @@ type OTP struct {
|
||||
State MFAState
|
||||
}
|
||||
|
||||
type OTPState int32
|
||||
|
||||
const (
|
||||
OTPStateUnspecified OTPState = iota
|
||||
OTPStateActive
|
||||
OTPStateRemoved
|
||||
|
||||
otpStateCount
|
||||
)
|
||||
|
||||
func (s OTPState) Valid() bool {
|
||||
return s >= 0 && s < otpStateCount
|
||||
func NewOTPKey(issuer, accountName string, cryptoAlg crypto.EncryptionAlgorithm) (*otp.Key, *crypto.CryptoValue, error) {
|
||||
key, err := totp.Generate(totp.GenerateOpts{Issuer: issuer, AccountName: accountName})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
encryptedSecret, err := crypto.Encrypt([]byte(key.Secret()), cryptoAlg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return key, encryptedSecret, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -39,16 +39,19 @@ const (
|
||||
UserVerificationRequirementDiscouraged
|
||||
)
|
||||
|
||||
type WebAuthNState int32
|
||||
type AuthenticatorAttachment int32
|
||||
|
||||
const (
|
||||
WebAuthNStateUnspecified WebAuthNState = iota
|
||||
WebAuthNStateActive
|
||||
WebAuthNStateRemoved
|
||||
|
||||
webAuthNStateCount
|
||||
AuthenticatorAttachmentUnspecified AuthenticatorAttachment = iota
|
||||
AuthenticatorAttachmentPlattform
|
||||
AuthenticatorAttachmentCrossPlattform
|
||||
)
|
||||
|
||||
func (s WebAuthNState) Valid() bool {
|
||||
return s >= 0 && s < webAuthNStateCount
|
||||
func GetTokenToVerify(tokens []*WebAuthNToken) (int, *WebAuthNToken) {
|
||||
for i, u2f := range tokens {
|
||||
if u2f.State == MFAStateNotReady {
|
||||
return i, u2f
|
||||
}
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
@ -5,10 +5,20 @@ import "github.com/caos/zitadel/internal/eventstore/models"
|
||||
type Machine struct {
|
||||
models.ObjectRoot
|
||||
|
||||
Username string
|
||||
State UserState
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
|
||||
func (m Machine) GetUsername() string {
|
||||
return m.Username
|
||||
}
|
||||
|
||||
func (m Machine) GetState() UserState {
|
||||
return m.State
|
||||
}
|
||||
|
||||
func (sa *Machine) IsValid() bool {
|
||||
return sa.Name != ""
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ const (
|
||||
MFAStateUnspecified MFAState = iota
|
||||
MFAStateNotReady
|
||||
MFAStateReady
|
||||
MFAStateRemoved
|
||||
|
||||
stateCount
|
||||
)
|
||||
|
@ -12,6 +12,7 @@ type Org struct {
|
||||
State OrgState
|
||||
Name string
|
||||
|
||||
PrimaryDomain string
|
||||
Domains []*OrgDomain
|
||||
Members []*Member
|
||||
OrgIamPolicy *OrgIAMPolicy
|
||||
@ -38,6 +39,7 @@ func (o *Org) nameForDomain(iamDomain string) string {
|
||||
type OrgState int32
|
||||
|
||||
const (
|
||||
OrgStateActive OrgState = iota
|
||||
OrgStateUnspecified OrgState = iota
|
||||
OrgStateActive
|
||||
OrgStateInactive
|
||||
)
|
||||
|
@ -1,14 +1,8 @@
|
||||
package domain
|
||||
|
||||
import es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
|
||||
type User struct {
|
||||
es_models.ObjectRoot
|
||||
State UserState
|
||||
UserName string
|
||||
|
||||
*Human
|
||||
*Machine
|
||||
type User interface {
|
||||
GetUsername() string
|
||||
GetState() UserState
|
||||
}
|
||||
|
||||
type UserState int32
|
||||
@ -28,13 +22,3 @@ const (
|
||||
func (f UserState) Valid() bool {
|
||||
return f >= 0 && f < userStateCount
|
||||
}
|
||||
|
||||
func (u *User) IsValid() bool {
|
||||
if u.Human == nil && u.Machine == nil || u.UserName == "" {
|
||||
return false
|
||||
}
|
||||
if u.Human != nil {
|
||||
return u.Human.IsValid()
|
||||
}
|
||||
return u.Machine.IsValid()
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ func readModelToLabelPolicy(readModel *IAMLabelPolicyReadModel) *model.LabelPoli
|
||||
PrimaryColor: readModel.PrimaryColor,
|
||||
SecondaryColor: readModel.SecondaryColor,
|
||||
Default: true,
|
||||
//TODO: State: int32,
|
||||
//TODO: OTPState: int32,
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ func readModelToLoginPolicy(readModel *IAMLoginPolicyReadModel) *model.LoginPoli
|
||||
AllowUsernamePassword: readModel.AllowUserNamePassword,
|
||||
Default: true,
|
||||
//TODO: IDPProviders: []*model.IDPProvider,
|
||||
//TODO: State: int32,
|
||||
//TODO: OTPState: int32,
|
||||
}
|
||||
}
|
||||
func readModelToOrgIAMPolicy(readModel *IAMOrgIAMPolicyReadModel) *model.OrgIAMPolicy {
|
||||
@ -97,7 +97,7 @@ func readModelToOrgIAMPolicy(readModel *IAMOrgIAMPolicyReadModel) *model.OrgIAMP
|
||||
ObjectRoot: readModelToObjectRoot(readModel.OrgIAMPolicyReadModel.ReadModel),
|
||||
UserLoginMustBeDomain: readModel.UserLoginMustBeDomain,
|
||||
Default: true,
|
||||
//TODO: State: int32,
|
||||
//TODO: OTPState: int32,
|
||||
}
|
||||
}
|
||||
func readModelToPasswordAgePolicy(readModel *IAMPasswordAgePolicyReadModel) *model.PasswordAgePolicy {
|
||||
@ -105,7 +105,7 @@ func readModelToPasswordAgePolicy(readModel *IAMPasswordAgePolicyReadModel) *mod
|
||||
ObjectRoot: readModelToObjectRoot(readModel.PasswordAgePolicyReadModel.ReadModel),
|
||||
ExpireWarnDays: uint64(readModel.ExpireWarnDays),
|
||||
MaxAgeDays: uint64(readModel.MaxAgeDays),
|
||||
//TODO: State: int32,
|
||||
//TODO: OTPState: int32,
|
||||
}
|
||||
}
|
||||
func readModelToPasswordComplexityPolicy(readModel *IAMPasswordComplexityPolicyReadModel) *model.PasswordComplexityPolicy {
|
||||
@ -116,7 +116,7 @@ func readModelToPasswordComplexityPolicy(readModel *IAMPasswordComplexityPolicyR
|
||||
HasSymbol: readModel.HasSymbol,
|
||||
HasUppercase: readModel.HasUpperCase,
|
||||
MinLength: uint64(readModel.MinLength),
|
||||
//TODO: State: int32,
|
||||
//TODO: OTPState: int32,
|
||||
}
|
||||
}
|
||||
func readModelToPasswordLockoutPolicy(readModel *IAMPasswordLockoutPolicyReadModel) *model.PasswordLockoutPolicy {
|
||||
@ -124,7 +124,7 @@ func readModelToPasswordLockoutPolicy(readModel *IAMPasswordLockoutPolicyReadMod
|
||||
ObjectRoot: readModelToObjectRoot(readModel.PasswordLockoutPolicyReadModel.ReadModel),
|
||||
MaxAttempts: uint64(readModel.MaxAttempts),
|
||||
ShowLockOutFailures: readModel.ShowLockOutFailures,
|
||||
//TODO: State: int32,
|
||||
//TODO: OTPState: int32,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,7 +138,6 @@ func IAMAggregateFromReadModel(rm *ReadModel) *iam.Aggregate {
|
||||
iam.AggregateType,
|
||||
rm.ResourceOwner,
|
||||
iam.AggregateVersion,
|
||||
rm.ProcessedSequence,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,6 @@ func UserAggregateFromReadModel(rm *UserReadModel) *user.Aggregate {
|
||||
user.AggregateType,
|
||||
rm.ResourceOwner,
|
||||
user.AggregateVersion,
|
||||
rm.ProcessedSequence,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -19,23 +19,6 @@ type Aggregate struct {
|
||||
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 {
|
||||
a.Aggregate = *a.PushEvents(NewSetupStepStartedEvent(ctx, step))
|
||||
return a
|
||||
|
@ -27,7 +27,7 @@ func NewIAMProjectSetEvent(ctx context.Context, projectID string) *ProjectSetEve
|
||||
return &ProjectSetEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
SetupDoneEventType,
|
||||
ProjectSetEventType,
|
||||
),
|
||||
ProjectID: projectID,
|
||||
}
|
||||
|
@ -16,20 +16,3 @@ const (
|
||||
type Aggregate struct {
|
||||
eventstore.Aggregate
|
||||
}
|
||||
|
||||
func NewAggregate(
|
||||
id,
|
||||
resourceOwner string,
|
||||
previousSequence uint64,
|
||||
) *Aggregate {
|
||||
|
||||
return &Aggregate{
|
||||
Aggregate: *eventstore.NewAggregate(
|
||||
id,
|
||||
AggregateType,
|
||||
resourceOwner,
|
||||
AggregateVersion,
|
||||
previousSequence,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -12,20 +12,3 @@ const (
|
||||
type Aggregate struct {
|
||||
eventstore.Aggregate
|
||||
}
|
||||
|
||||
func NewAggregate(
|
||||
id,
|
||||
resourceOwner string,
|
||||
previousSequence uint64,
|
||||
) *Aggregate {
|
||||
|
||||
return &Aggregate{
|
||||
Aggregate: *eventstore.NewAggregate(
|
||||
id,
|
||||
AggregateType,
|
||||
resourceOwner,
|
||||
AggregateVersion,
|
||||
previousSequence,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -12,20 +12,3 @@ const (
|
||||
type Aggregate struct {
|
||||
eventstore.Aggregate
|
||||
}
|
||||
|
||||
func NewAggregate(
|
||||
id,
|
||||
resourceOwner string,
|
||||
previousSequence uint64,
|
||||
) *Aggregate {
|
||||
|
||||
return &Aggregate{
|
||||
Aggregate: *eventstore.NewAggregate(
|
||||
id,
|
||||
AggregateType,
|
||||
resourceOwner,
|
||||
AggregateVersion,
|
||||
previousSequence,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
RegisterFilterEventMapper(UserV1MFAOTPCheckSucceededType, HumanOTPCheckSucceededEventMapper).
|
||||
RegisterFilterEventMapper(UserV1MFAOTPCheckFailedType, HumanOTPCheckFailedEventMapper).
|
||||
RegisterFilterEventMapper(UserLockedType, UserLockedEventMapper).
|
||||
RegisterFilterEventMapper(UserUnlockedType, UserLockedEventMapper).
|
||||
RegisterFilterEventMapper(UserUnlockedType, UserUnlockedEventMapper).
|
||||
RegisterFilterEventMapper(UserDeactivatedType, UserDeactivatedEventMapper).
|
||||
RegisterFilterEventMapper(UserReactivatedType, UserReactivatedEventMapper).
|
||||
RegisterFilterEventMapper(UserRemovedType, UserRemovedEventMapper).
|
||||
|
@ -52,18 +52,20 @@ func HumanOTPAddedEventMapper(event *repository.Event) (eventstore.EventReader,
|
||||
|
||||
type HumanOTPVerifiedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
UserAgentID string `json:"userAgentID,omitempty"`
|
||||
}
|
||||
|
||||
func (e *HumanOTPVerifiedEvent) Data() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewHumanOTPVerifiedEvent(ctx context.Context) *HumanOTPVerifiedEvent {
|
||||
func NewHumanOTPVerifiedEvent(ctx context.Context, userAgentID string) *HumanOTPVerifiedEvent {
|
||||
return &HumanOTPVerifiedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
HumanMFAOTPVerifiedType,
|
||||
),
|
||||
UserAgentID: userAgentID,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,9 +32,8 @@ const (
|
||||
type HumanWebAuthNAddedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
WebAuthNTokenID string `json:"webAuthNTokenId"`
|
||||
Challenge string `json:"challenge"`
|
||||
State domain.MFAState `json:"-"`
|
||||
WebAuthNTokenID string `json:"webAuthNTokenId"`
|
||||
Challenge string `json:"challenge"`
|
||||
}
|
||||
|
||||
func (e *HumanWebAuthNAddedEvent) Data() interface{} {
|
||||
@ -74,7 +73,6 @@ func NewHumanPasswordlessAddedEvent(
|
||||
func WebAuthNAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
webAuthNAdded := &HumanWebAuthNAddedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
State: domain.MFAStateNotReady,
|
||||
}
|
||||
err := json.Unmarshal(event.Data, webAuthNAdded)
|
||||
if err != nil {
|
||||
@ -86,14 +84,13 @@ func WebAuthNAddedEventMapper(event *repository.Event) (eventstore.EventReader,
|
||||
type HumanWebAuthNVerifiedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
WebAuthNTokenID string `json:"webAuthNTokenId"`
|
||||
KeyID []byte `json:"keyId"`
|
||||
PublicKey []byte `json:"publicKey"`
|
||||
AttestationType string `json:"attestationType"`
|
||||
AAGUID []byte `json:"aaguid"`
|
||||
SignCount uint32 `json:"signCount"`
|
||||
WebAuthNTokenName string `json:"webAuthNTokenName"`
|
||||
State domain.MFAState `json:"-"`
|
||||
WebAuthNTokenID string `json:"webAuthNTokenId"`
|
||||
KeyID []byte `json:"keyId"`
|
||||
PublicKey []byte `json:"publicKey"`
|
||||
AttestationType string `json:"attestationType"`
|
||||
AAGUID []byte `json:"aaguid"`
|
||||
SignCount uint32 `json:"signCount"`
|
||||
WebAuthNTokenName string `json:"webAuthNTokenName"`
|
||||
}
|
||||
|
||||
func (e *HumanWebAuthNVerifiedEvent) Data() interface{} {
|
||||
@ -153,7 +150,6 @@ func NewHumanPasswordlessVerifiedEvent(
|
||||
func HumanWebAuthNVerifiedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
webauthNVerified := &HumanWebAuthNVerifiedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
State: domain.MFAStateReady,
|
||||
}
|
||||
err := json.Unmarshal(event.Data, webauthNVerified)
|
||||
if err != nil {
|
||||
@ -165,9 +161,8 @@ func HumanWebAuthNVerifiedEventMapper(event *repository.Event) (eventstore.Event
|
||||
type HumanWebAuthNSignCountChangedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
WebAuthNTokenID string `json:"webAuthNTokenId"`
|
||||
SignCount uint32 `json:"signCount"`
|
||||
State domain.MFAState `json:"-"`
|
||||
WebAuthNTokenID string `json:"webAuthNTokenId"`
|
||||
SignCount uint32 `json:"signCount"`
|
||||
}
|
||||
|
||||
func (e *HumanWebAuthNSignCountChangedEvent) Data() interface{} {
|
||||
|
@ -1,15 +1,15 @@
|
||||
package webauthn
|
||||
|
||||
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/webauthn"
|
||||
)
|
||||
|
||||
func WebAuthNsToCredentials(webAuthNs []*model.WebAuthNToken) []webauthn.Credential {
|
||||
func WebAuthNsToCredentials(webAuthNs []*domain.WebAuthNToken) []webauthn.Credential {
|
||||
creds := make([]webauthn.Credential, 0)
|
||||
for _, webAuthN := range webAuthNs {
|
||||
if webAuthN.State == model.MFAStateReady {
|
||||
if webAuthN.State == domain.MFAStateReady {
|
||||
creds = append(creds, webauthn.Credential{
|
||||
ID: webAuthN.KeyID,
|
||||
PublicKey: webAuthN.PublicKey,
|
||||
@ -24,55 +24,55 @@ func WebAuthNsToCredentials(webAuthNs []*model.WebAuthNToken) []webauthn.Credent
|
||||
return creds
|
||||
}
|
||||
|
||||
func WebAuthNToSessionData(webAuthN *model.WebAuthNToken) webauthn.SessionData {
|
||||
func WebAuthNToSessionData(webAuthN *domain.WebAuthNToken) webauthn.SessionData {
|
||||
return webauthn.SessionData{
|
||||
Challenge: webAuthN.Challenge,
|
||||
UserID: []byte(webAuthN.AggregateID),
|
||||
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{
|
||||
Challenge: webAuthN.Challenge,
|
||||
UserID: []byte(webAuthN.AggregateID),
|
||||
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 {
|
||||
case protocol.VerificationRequired:
|
||||
return model.UserVerificationRequirementRequired
|
||||
return domain.UserVerificationRequirementRequired
|
||||
case protocol.VerificationPreferred:
|
||||
return model.UserVerificationRequirementPreferred
|
||||
return domain.UserVerificationRequirementPreferred
|
||||
case protocol.VerificationDiscouraged:
|
||||
return model.UserVerificationRequirementDiscouraged
|
||||
return domain.UserVerificationRequirementDiscouraged
|
||||
default:
|
||||
return model.UserVerificationRequirementUnspecified
|
||||
return domain.UserVerificationRequirementUnspecified
|
||||
}
|
||||
}
|
||||
|
||||
func UserVerificationFromModel(verification model.UserVerificationRequirement) protocol.UserVerificationRequirement {
|
||||
func UserVerificationFromDomain(verification domain.UserVerificationRequirement) protocol.UserVerificationRequirement {
|
||||
switch verification {
|
||||
case model.UserVerificationRequirementRequired:
|
||||
case domain.UserVerificationRequirementRequired:
|
||||
return protocol.VerificationRequired
|
||||
case model.UserVerificationRequirementPreferred:
|
||||
case domain.UserVerificationRequirementPreferred:
|
||||
return protocol.VerificationPreferred
|
||||
case model.UserVerificationRequirementDiscouraged:
|
||||
case domain.UserVerificationRequirementDiscouraged:
|
||||
return protocol.VerificationDiscouraged
|
||||
default:
|
||||
return protocol.VerificationDiscouraged
|
||||
}
|
||||
}
|
||||
|
||||
func AuthenticatorAttachmentFromModel(authType model.AuthenticatorAttachment) protocol.AuthenticatorAttachment {
|
||||
func AuthenticatorAttachmentFromDomain(authType domain.AuthenticatorAttachment) protocol.AuthenticatorAttachment {
|
||||
switch authType {
|
||||
case model.AuthenticatorAttachmentPlattform:
|
||||
case domain.AuthenticatorAttachmentPlattform:
|
||||
return protocol.Platform
|
||||
case model.AuthenticatorAttachmentCrossPlattform:
|
||||
case domain.AuthenticatorAttachmentCrossPlattform:
|
||||
return protocol.CrossPlatform
|
||||
default:
|
||||
return ""
|
||||
|
@ -3,13 +3,13 @@ package webauthn
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/caos/zitadel/internal/v2/domain"
|
||||
|
||||
"github.com/duo-labs/webauthn/protocol"
|
||||
"github.com/duo-labs/webauthn/webauthn"
|
||||
|
||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||
)
|
||||
|
||||
type WebAuthN struct {
|
||||
@ -41,7 +41,7 @@ func StartServer(sd systemdefaults.WebAuthN) (*WebAuthN, error) {
|
||||
}
|
||||
|
||||
type webUser struct {
|
||||
*usr_model.User
|
||||
*domain.Human
|
||||
accountName string
|
||||
credentials []webauthn.Credential
|
||||
}
|
||||
@ -54,7 +54,7 @@ func (u *webUser) WebAuthnName() string {
|
||||
if u.accountName != "" {
|
||||
return u.accountName
|
||||
}
|
||||
return u.UserName
|
||||
return u.GetUsername()
|
||||
}
|
||||
|
||||
func (u *webUser) WebAuthnDisplayName() string {
|
||||
@ -69,7 +69,7 @@ func (u *webUser) WebAuthnCredentials() []webauthn.Credential {
|
||||
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)
|
||||
existing := make([]protocol.CredentialDescriptor, len(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(
|
||||
&webUser{
|
||||
User: user,
|
||||
Human: user,
|
||||
accountName: accountName,
|
||||
credentials: creds,
|
||||
},
|
||||
webauthn.WithAuthenticatorSelection(protocol.AuthenticatorSelection{
|
||||
UserVerification: UserVerificationFromModel(userVerification),
|
||||
AuthenticatorAttachment: AuthenticatorAttachmentFromModel(authType),
|
||||
UserVerification: UserVerificationFromDomain(userVerification),
|
||||
AuthenticatorAttachment: AuthenticatorAttachmentFromDomain(authType),
|
||||
}),
|
||||
webauthn.WithConveyancePreference(protocol.PreferNoAttestation),
|
||||
webauthn.WithExclusions(existing),
|
||||
@ -98,15 +98,15 @@ func (w *WebAuthN) BeginRegistration(user *usr_model.User, accountName string, a
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowInternal(err, "WEBAU-D7cus", "Errors.User.WebAuthN.MarshalError")
|
||||
}
|
||||
return &usr_model.WebAuthNToken{
|
||||
return &domain.WebAuthNToken{
|
||||
Challenge: sessionData.Challenge,
|
||||
CredentialCreationData: cred,
|
||||
AllowedCredentialIDs: sessionData.AllowedCredentialIDs,
|
||||
UserVerification: UserVerificationToModel(sessionData.UserVerification),
|
||||
UserVerification: UserVerificationToDomain(sessionData.UserVerification),
|
||||
}, 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 {
|
||||
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)
|
||||
credential, err := w.web(isLoginUI).CreateCredential(
|
||||
&webUser{
|
||||
User: user,
|
||||
Human: user,
|
||||
},
|
||||
sessionData, credentialData)
|
||||
if err != nil {
|
||||
@ -133,11 +133,11 @@ func (w *WebAuthN) FinishRegistration(user *usr_model.User, webAuthN *usr_model.
|
||||
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{
|
||||
User: user,
|
||||
Human: user,
|
||||
credentials: WebAuthNsToCredentials(webAuthNs),
|
||||
}, webauthn.WithUserVerification(UserVerificationFromModel(userVerification)))
|
||||
}, webauthn.WithUserVerification(UserVerificationFromDomain(userVerification)))
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, caos_errs.ThrowInternal(err, "WEBAU-2M0s9", "Errors.User.WebAuthN.MarshalError")
|
||||
}
|
||||
return &usr_model.WebAuthNLogin{
|
||||
return &domain.WebAuthNLogin{
|
||||
Challenge: sessionData.Challenge,
|
||||
CredentialAssertionData: cred,
|
||||
AllowedCredentialIDs: sessionData.AllowedCredentialIDs,
|
||||
@ -153,13 +153,13 @@ func (w *WebAuthN) BeginLogin(user *usr_model.User, userVerification usr_model.U
|
||||
}, 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))
|
||||
if err != nil {
|
||||
return nil, 0, caos_errs.ThrowInternal(err, "WEBAU-ADgv4", "Errors.User.WebAuthN.ValidateLoginFailed")
|
||||
}
|
||||
webUser := &webUser{
|
||||
User: user,
|
||||
Human: user,
|
||||
credentials: WebAuthNsToCredentials(webAuthNs),
|
||||
}
|
||||
credential, err := w.web(isLoginUI).ValidateLogin(webUser, WebAuthNLoginToSessionData(webAuthN), assertionData)
|
||||
|
@ -114,7 +114,7 @@ service ManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
rpc DeactivateUser(UserID) returns (UserResponse) {
|
||||
rpc DeactivateUser(UserID) returns (google.protobuf.Empty) {
|
||||
option (google.api.http) = {
|
||||
put: "/users/{id}/_deactivate"
|
||||
body: "*"
|
||||
@ -125,7 +125,7 @@ service ManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
rpc ReactivateUser(UserID) returns (UserResponse) {
|
||||
rpc ReactivateUser(UserID) returns (google.protobuf.Empty) {
|
||||
option (google.api.http) = {
|
||||
put: "/users/{id}/_reactivate"
|
||||
body: "*"
|
||||
@ -136,7 +136,7 @@ service ManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
rpc LockUser(UserID) returns (UserResponse) {
|
||||
rpc LockUser(UserID) returns (google.protobuf.Empty) {
|
||||
option (google.api.http) = {
|
||||
put: "/users/{id}/_lock"
|
||||
body: "*"
|
||||
@ -147,7 +147,7 @@ service ManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
rpc UnlockUser(UserID) returns (UserResponse) {
|
||||
rpc UnlockUser(UserID) returns (google.protobuf.Empty) {
|
||||
option (google.api.http) = {
|
||||
put: "/users/{id}/_unlock"
|
||||
body: "*"
|
||||
@ -1741,16 +1741,15 @@ message CreateMachineRequest {
|
||||
message UserResponse {
|
||||
string id = 1;
|
||||
UserState state = 2;
|
||||
google.protobuf.Timestamp creation_date = 3;
|
||||
google.protobuf.Timestamp change_date = 4;
|
||||
uint64 sequence = 5;
|
||||
string user_name = 6;
|
||||
google.protobuf.Timestamp change_date = 3;
|
||||
uint64 sequence = 4;
|
||||
string user_name = 5;
|
||||
|
||||
oneof user {
|
||||
option (validate.required) = true;
|
||||
|
||||
HumanResponse human = 7;
|
||||
MachineResponse machine = 8;
|
||||
HumanResponse human = 6;
|
||||
MachineResponse machine = 7;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1954,8 +1953,7 @@ message UserProfile {
|
||||
string preferred_language = 6;
|
||||
Gender gender = 7;
|
||||
uint64 sequence = 8;
|
||||
google.protobuf.Timestamp creation_date = 9;
|
||||
google.protobuf.Timestamp change_date = 10;
|
||||
google.protobuf.Timestamp change_date = 9;
|
||||
}
|
||||
|
||||
message UserProfileView {
|
||||
@ -1992,8 +1990,7 @@ message UserEmail {
|
||||
string email = 2;
|
||||
bool is_email_verified = 3;
|
||||
uint64 sequence = 4;
|
||||
google.protobuf.Timestamp creation_date = 5;
|
||||
google.protobuf.Timestamp change_date = 6;
|
||||
google.protobuf.Timestamp change_date = 5;
|
||||
}
|
||||
|
||||
message UserEmailView {
|
||||
@ -2016,8 +2013,7 @@ message UserPhone {
|
||||
string phone = 2;
|
||||
bool is_phone_verified = 3;
|
||||
uint64 sequence = 5;
|
||||
google.protobuf.Timestamp creation_date = 6;
|
||||
google.protobuf.Timestamp change_date = 7;
|
||||
google.protobuf.Timestamp change_date = 6;
|
||||
}
|
||||
|
||||
message UserPhoneView {
|
||||
@ -2043,8 +2039,7 @@ message UserAddress {
|
||||
string region = 5;
|
||||
string street_address = 6;
|
||||
uint64 sequence = 7;
|
||||
google.protobuf.Timestamp creation_date = 8;
|
||||
google.protobuf.Timestamp change_date = 9;
|
||||
google.protobuf.Timestamp change_date = 8;
|
||||
}
|
||||
|
||||
message UserAddressView {
|
||||
@ -2528,7 +2523,6 @@ enum AppState {
|
||||
message Application {
|
||||
string id = 1;
|
||||
AppState state = 2;
|
||||
google.protobuf.Timestamp creation_date = 3;
|
||||
google.protobuf.Timestamp change_date = 4;
|
||||
string name = 5;
|
||||
oneof app_config {
|
||||
|
Loading…
Reference in New Issue
Block a user