feat: new user auth api (#1168)

* fix: correct selectors for extended writemodel

* fix: no previous checks in eventstore

* start check previous

* feat: auth user commands

* feat: auth user commands

* feat: auth user commands

* feat: otp

* feat: corrections from pr merge

* feat: webauthn

* feat: comment old webauthn

* feat: refactor user, human, machine

* feat: webauth command side

* feat: command and query side in login

* feat: fix user writemodel append events

* fix: remove creation dates on command side

* fix: remove previous sequence

* previous sequence

* fix: external idps

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

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

* Update internal/v2/command/user_human_email.go

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

* fix: pr changes

* fix: phone verification

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

View File

@ -119,7 +119,7 @@ func startZitadel(configPaths []string) {
}
startAPI(ctx, conf, authZRepo, authRepo, command, query)
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 {

View File

@ -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
}

View File

@ -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 {

View File

@ -42,7 +42,8 @@ func (s *Server) GetMyUserPhone(ctx context.Context, _ *empty.Empty) (*auth.User
}
func (s *Server) RemoveMyUserPhone(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) {
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
}

View File

@ -194,7 +194,7 @@ func updatePhoneToDomain(ctx context.Context, e *auth.UpdateUserPhoneRequest) *d
}
}
func addressFromModel(address *usr_model.Address) *auth.UserAddress {
func addressFromDomain(address *domain.Address) *auth.UserAddress {
creationDate, err := ptypes.TimestampProto(address.CreationDate)
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),
}
}

View File

@ -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),
}
}

View File

@ -52,43 +52,39 @@ func (s *Server) IsUserUnique(ctx context.Context, request *management.UniqueUse
}
func (s *Server) CreateUser(ctx context.Context, in *management.CreateUserRequest) (*management.UserResponse, error) {
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) {

View File

@ -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,

View File

@ -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

View File

@ -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)
}

View File

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

View File

@ -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
}

View File

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

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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)

View File

@ -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,
}
}

View File

@ -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()

View File

@ -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)

View File

@ -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{

View File

@ -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,
}

View File

@ -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)
}

View File

@ -1303,47 +1303,49 @@ func (es *UserEventstore) verifyMFAOTP(otp *usr_model.OTP, code string) error {
}
func (es *UserEventstore) AddU2F(ctx context.Context, userID string, accountName string, isLoginUI bool) (*usr_model.WebAuthNToken, error) {
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 {

View File

@ -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
}

View File

@ -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
}

View File

@ -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,
}
}

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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")

View File

@ -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
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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) {

View File

@ -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(

View File

@ -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()

View File

@ -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
}

View File

@ -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 {

View File

@ -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) }()

View File

@ -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:

View File

@ -19,7 +19,7 @@ func (r *CommandSide) ChangeHumanProfile(ctx context.Context, profile *domain.Pr
if existingProfile.UserState == domain.UserStateUnspecified || existingProfile.UserState == domain.UserStateDeleted {
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")
}

View File

@ -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 {

View File

@ -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)

View File

@ -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
}

View File

@ -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) }()

View File

@ -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) {

View File

@ -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) {

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 != ""
}

View File

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

View File

@ -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
)

View File

@ -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()
}

View File

@ -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,
}
}

View File

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

View File

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

View File

@ -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

View File

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

View File

@ -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,
),
}
}

View File

@ -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,
),
}
}

View File

@ -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,
),
}
}

View File

@ -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).

View File

@ -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,
}
}

View File

@ -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{} {

View File

@ -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 ""

View File

@ -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)

View File

@ -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 {