mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-03 12:52:16 +00:00
feat: User login commands (#1228)
* feat: change login to command side * feat: change login to command side * fix: fix push on user * feat: user command side * feat: sign out * feat: command side login * feat: command side login * feat: fix register user * feat: fix register user * feat: fix web auth n events * feat: add machine keys * feat: send codes * feat: move authrequest to domain * feat: move authrequest to domain * feat: webauthn working * feat: external users * feat: external users login * feat: notify users * fix: tests * feat: cascade remove user grants on project remove * fix: webauthn * fix: pr requests * fix: register human with member * fix: fix bugs * fix: fix bugs
This commit is contained in:
@@ -38,7 +38,7 @@ func writeModelToLoginPolicy(wm *LoginPolicyWriteModel) *domain.LoginPolicy {
|
||||
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
||||
AllowUsernamePassword: wm.AllowUserNamePassword,
|
||||
AllowRegister: wm.AllowRegister,
|
||||
AllowExternalIdp: wm.AllowExternalIDP,
|
||||
AllowExternalIDP: wm.AllowExternalIDP,
|
||||
ForceMFA: wm.ForceMFA,
|
||||
PasswordlessType: wm.PasswordlessType,
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ func (wm *IAMIDPOIDCConfigWriteModel) NewChangedEvent(
|
||||
if userNameMapping.Valid() && wm.UserNameMapping != userNameMapping {
|
||||
changes = append(changes, idpconfig.ChangeUserNameMapping(userNameMapping))
|
||||
}
|
||||
if reflect.DeepEqual(wm.Scopes, scopes) {
|
||||
if !reflect.DeepEqual(wm.Scopes, scopes) {
|
||||
changes = append(changes, idpconfig.ChangeScopes(scopes))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
|
||||
@@ -44,7 +44,7 @@ func (r *CommandSide) addDefaultLoginPolicy(ctx context.Context, iamAgg *iam_rep
|
||||
return caos_errs.ThrowAlreadyExists(nil, "IAM-2B0ps", "Errors.IAM.LoginPolicy.AlreadyExists")
|
||||
}
|
||||
|
||||
iamAgg.PushEvents(iam_repo.NewLoginPolicyAddedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIdp, policy.ForceMFA, policy.PasswordlessType))
|
||||
iamAgg.PushEvents(iam_repo.NewLoginPolicyAddedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.PasswordlessType))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -72,7 +72,7 @@ func (r *CommandSide) changeDefaultLoginPolicy(ctx context.Context, iamAgg *iam_
|
||||
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
|
||||
return caos_errs.ThrowNotFound(nil, "IAM-M0sif", "Errors.IAM.LoginPolicy.NotFound")
|
||||
}
|
||||
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIdp, policy.ForceMFA, policy.PasswordlessType)
|
||||
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.PasswordlessType)
|
||||
if !hasChanged {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "IAM-5M9vdd", "Errors.IAM.LoginPolicy.NotChanged")
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func (r *CommandSide) AddLoginPolicy(ctx context.Context, policy *domain.LoginPo
|
||||
}
|
||||
|
||||
orgAgg := OrgAggregateFromWriteModel(&addedPolicy.WriteModel)
|
||||
orgAgg.PushEvents(org.NewLoginPolicyAddedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIdp, policy.ForceMFA, policy.PasswordlessType))
|
||||
orgAgg.PushEvents(org.NewLoginPolicyAddedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.PasswordlessType))
|
||||
|
||||
err = r.eventstore.PushAggregate(ctx, addedPolicy, orgAgg)
|
||||
if err != nil {
|
||||
@@ -38,7 +38,7 @@ func (r *CommandSide) ChangeLoginPolicy(ctx context.Context, policy *domain.Logi
|
||||
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "Org-M0sif", "Errors.Org.LoginPolicy.NotFound")
|
||||
}
|
||||
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIdp, policy.ForceMFA, domain.PasswordlessType(policy.PasswordlessType))
|
||||
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, domain.PasswordlessType(policy.PasswordlessType))
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-5M9vdd", "Errors.Org.LoginPolicy.NotChanged")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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"
|
||||
@@ -144,7 +145,7 @@ func (r *CommandSide) ReactivateProject(ctx context.Context, projectID string, r
|
||||
return r.eventstore.PushAggregate(ctx, existingProject, projectAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) RemoveProject(ctx context.Context, projectID, resourceOwner string) error {
|
||||
func (r *CommandSide) RemoveProject(ctx context.Context, projectID, resourceOwner string, cascadingGrantIDs ...string) error {
|
||||
if projectID == "" || resourceOwner == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-66hM9", "Errors.Project.ProjectIDMissing")
|
||||
}
|
||||
@@ -157,11 +158,21 @@ func (r *CommandSide) RemoveProject(ctx context.Context, projectID, resourceOwne
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.Project.NotFound")
|
||||
}
|
||||
|
||||
aggregates := make([]eventstore.Aggregater, 0)
|
||||
projectAgg := ProjectAggregateFromWriteModel(&existingProject.WriteModel)
|
||||
projectAgg.PushEvents(project.NewProjectRemovedEvent(ctx, existingProject.Name, existingProject.ResourceOwner))
|
||||
//TODO: Remove User Grants by ProjectID
|
||||
aggregates = append(aggregates, projectAgg)
|
||||
|
||||
return r.eventstore.PushAggregate(ctx, existingProject, projectAgg)
|
||||
for _, grantID := range cascadingGrantIDs {
|
||||
grantAgg, _, err := r.removeUserGrant(ctx, grantID, "", true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
aggregates = append(aggregates, grantAgg)
|
||||
}
|
||||
|
||||
_, err = r.eventstore.PushAggregates(ctx, aggregates...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *CommandSide) getProjectWriteModelByID(ctx context.Context, projectID, resourceOwner string) (*ProjectWriteModel, error) {
|
||||
|
||||
@@ -91,7 +91,7 @@ func (r *CommandSide) SetupStep1(ctx context.Context, step1 *Step1) error {
|
||||
&domain.LoginPolicy{
|
||||
AllowUsernamePassword: step1.DefaultLoginPolicy.AllowUsernamePassword,
|
||||
AllowRegister: step1.DefaultLoginPolicy.AllowRegister,
|
||||
AllowExternalIdp: step1.DefaultLoginPolicy.AllowExternalIdp,
|
||||
AllowExternalIDP: step1.DefaultLoginPolicy.AllowExternalIdp,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -2,6 +2,10 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
auth_req_model "github.com/caos/zitadel/internal/auth_request/model"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
@@ -21,7 +25,7 @@ func (r *CommandSide) ChangeUsername(ctx context.Context, orgID, userID, userNam
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-5N9ds", "Errors.User.NotFound")
|
||||
}
|
||||
if existingUser.UserName == userName {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.UsernameNotChanged")
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6m9gs", "Errors.User.UsernameNotChanged")
|
||||
}
|
||||
|
||||
orgIAMPolicy, err := r.getOrgIAMPolicy(ctx, orgID)
|
||||
@@ -89,7 +93,7 @@ func (r *CommandSide) LockUser(ctx context.Context, userID, resourceOwner string
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-5M9fs", "Errors.User.NotFound")
|
||||
}
|
||||
if existingUser.UserState != domain.UserStateActive && existingUser.UserState != domain.UserStateInitial {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.ShouldBeActiveOrInitial")
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3NN8v", "Errors.User.ShouldBeActiveOrInitial")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
|
||||
userAgg.PushEvents(user.NewUserLockedEvent(ctx))
|
||||
@@ -139,6 +143,69 @@ func (r *CommandSide) RemoveUser(ctx context.Context, userID, resourceOwner stri
|
||||
return r.eventstore.PushAggregate(ctx, existingUser, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) CreateUserToken(ctx context.Context, orgID, agentID, clientID, userID string, audience, scopes []string, lifetime time.Duration) (*domain.Token, error) {
|
||||
if orgID == "" || userID == "" {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-55n8M", "Errors.IDMissing")
|
||||
}
|
||||
existingUser, err := r.userWriteModelByID(ctx, userID, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-1d6Gg", "Errors.User.NotFound")
|
||||
}
|
||||
|
||||
for _, scope := range scopes {
|
||||
if strings.HasPrefix(scope, auth_req_model.ProjectIDScope) && strings.HasSuffix(scope, auth_req_model.AudSuffix) {
|
||||
audience = append(audience, strings.TrimSuffix(strings.TrimPrefix(scope, auth_req_model.ProjectIDScope), auth_req_model.AudSuffix))
|
||||
}
|
||||
}
|
||||
|
||||
preferredLanguage := ""
|
||||
existingHuman, err := r.getHumanWriteModelByID(ctx, userID, orgID)
|
||||
if existingHuman != nil {
|
||||
preferredLanguage = existingHuman.PreferredLanguage.String()
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
tokenID, err := r.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
|
||||
userAgg.PushEvents(user.NewUserTokenAddedEvent(ctx, tokenID, clientID, agentID, preferredLanguage, audience, scopes, now.Add(lifetime)))
|
||||
|
||||
err = r.eventstore.PushAggregate(ctx, existingUser, userAgg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &domain.Token{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: userID,
|
||||
},
|
||||
TokenID: tokenID,
|
||||
UserAgentID: agentID,
|
||||
ApplicationID: clientID,
|
||||
Audience: audience,
|
||||
Scopes: scopes,
|
||||
Expiration: now.Add(lifetime),
|
||||
PreferredLanguage: preferredLanguage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) UserDomainClaimedSent(ctx context.Context, orgID, userID string) (err error) {
|
||||
existingUser, err := r.userWriteModelByID(ctx, userID, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-5m9gK", "Errors.User.NotFound")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
|
||||
userAgg.PushEvents(user.NewDomainClaimedSentEvent(ctx))
|
||||
return r.eventstore.PushAggregate(ctx, existingUser, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) checkUserExists(ctx context.Context, userID, resourceOwner string) error {
|
||||
userWriteModel, err := r.userWriteModelByID(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package command
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/v2/domain"
|
||||
"github.com/caos/zitadel/internal/v2/repository/user"
|
||||
)
|
||||
|
||||
func writeModelToHuman(wm *HumanWriteModel) *domain.Human {
|
||||
@@ -77,6 +78,15 @@ func writeModelToMachine(wm *MachineWriteModel) *domain.Machine {
|
||||
}
|
||||
}
|
||||
|
||||
func keyWriteModelToMachineKey(wm *MachineKeyWriteModel) *domain.MachineKey {
|
||||
return &domain.MachineKey{
|
||||
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
||||
KeyID: wm.KeyID,
|
||||
Type: wm.KeyType,
|
||||
ExpirationDate: wm.ExpirationDate,
|
||||
}
|
||||
}
|
||||
|
||||
func readModelToU2FTokens(wm *HumanU2FTokensReadModel) []*domain.WebAuthNToken {
|
||||
tokens := make([]*domain.WebAuthNToken, len(wm.WebAuthNTokens))
|
||||
for i, token := range wm.WebAuthNTokens {
|
||||
@@ -107,3 +117,19 @@ func writeModelToWebAuthN(wm *HumanWebAuthNWriteModel) *domain.WebAuthNToken {
|
||||
State: wm.State,
|
||||
}
|
||||
}
|
||||
|
||||
func authRequestDomainToAuthRequestInfo(authRequest *domain.AuthRequest) *user.AuthRequestInfo {
|
||||
info := &user.AuthRequestInfo{
|
||||
ID: authRequest.ID,
|
||||
UserAgentID: authRequest.AgentID,
|
||||
SelectedIDPConfigID: authRequest.SelectedIDPConfigID,
|
||||
}
|
||||
if authRequest.BrowserInfo != nil {
|
||||
info.BrowserInfo = &user.BrowserInfo{
|
||||
UserAgent: authRequest.BrowserInfo.UserAgent,
|
||||
AcceptLanguage: authRequest.BrowserInfo.AcceptLanguage,
|
||||
RemoteIP: authRequest.BrowserInfo.RemoteIP,
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ func (r *CommandSide) BulkRemoveUserGrant(ctx context.Context, grantIDs []string
|
||||
}
|
||||
|
||||
func (r *CommandSide) removeUserGrant(ctx context.Context, grantID, resourceOwner string, cascade bool) (_ *usergrant.Aggregate, _ *UserGrantWriteModel, err error) {
|
||||
if grantID == "" || resourceOwner == "" {
|
||||
if grantID == "" {
|
||||
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-J9sc5", "Errors.UserGrant.IDMissing")
|
||||
}
|
||||
|
||||
@@ -205,7 +205,6 @@ func (r *CommandSide) removeUserGrant(ctx context.Context, grantID, resourceOwne
|
||||
return nil, nil, caos_errs.ThrowNotFound(nil, "COMMAND-1My0t", "Errors.UserGrant.NotFound")
|
||||
}
|
||||
|
||||
//TODO: Remove Uniqueness
|
||||
removeUserGrant := NewUserGrantWriteModel(grantID, resourceOwner)
|
||||
userGrantAgg := UserGrantAggregateFromWriteModel(&removeUserGrant.WriteModel)
|
||||
if !cascade {
|
||||
|
||||
@@ -62,9 +62,12 @@ func (wm *UserGrantWriteModel) Reduce() error {
|
||||
}
|
||||
|
||||
func (wm *UserGrantWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, usergrant.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
ResourceOwner(wm.ResourceOwner)
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, usergrant.AggregateType).
|
||||
AggregateIDs(wm.AggregateID)
|
||||
if wm.ResourceOwner != "" {
|
||||
query.ResourceOwner(wm.ResourceOwner)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
func UserGrantAggregateFromWriteModel(wm *eventstore.WriteModel) *usergrant.Aggregate {
|
||||
|
||||
@@ -2,6 +2,7 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/v2"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
@@ -27,9 +28,6 @@ func (r *CommandSide) AddHuman(ctx context.Context, orgID string, human *domain.
|
||||
}
|
||||
err = r.eventstore.PushAggregate(ctx, addedHuman, userAgg)
|
||||
if err != nil {
|
||||
if caos_errs.IsErrorAlreadyExists(err) {
|
||||
return nil, caos_errs.ThrowAlreadyExists(err, "COMMAND-4kSff", "Errors.User.AlreadyExists")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -43,19 +41,36 @@ func (r *CommandSide) addHuman(ctx context.Context, orgID string, human *domain.
|
||||
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) {
|
||||
func (r *CommandSide) RegisterHuman(ctx context.Context, orgID string, human *domain.Human, externalIDP *domain.ExternalIDP, orgMemberRoles []string) (*domain.Human, error) {
|
||||
aggregates := make([]eventstore.Aggregater, 2)
|
||||
|
||||
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 {
|
||||
if caos_errs.IsErrorAlreadyExists(err) {
|
||||
return nil, caos_errs.ThrowAlreadyExists(err, "COMMAND-4kSff", "Errors.User.AlreadyExists")
|
||||
aggregates[0] = userAgg
|
||||
|
||||
orgMemberWriteModel := NewOrgMemberWriteModel(orgID, addedHuman.AggregateID)
|
||||
orgAgg := OrgAggregateFromWriteModel(&orgMemberWriteModel.WriteModel)
|
||||
if orgMemberRoles != nil {
|
||||
orgMember := &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: orgID,
|
||||
},
|
||||
UserID: userAgg.ID(),
|
||||
Roles: orgMemberRoles,
|
||||
}
|
||||
return nil, err
|
||||
r.addOrgMember(ctx, orgAgg, orgMemberWriteModel, orgMember)
|
||||
}
|
||||
|
||||
aggregates[1] = orgAgg
|
||||
|
||||
eventReader, err := r.eventstore.PushAggregates(ctx, aggregates...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addedHuman.AppendEvents(eventReader...)
|
||||
addedHuman.Reduce()
|
||||
return writeModelToHuman(addedHuman), nil
|
||||
}
|
||||
|
||||
@@ -82,7 +97,7 @@ func (r *CommandSide) createHuman(ctx context.Context, orgID string, human *doma
|
||||
}
|
||||
|
||||
addedHuman := NewHumanWriteModel(human.AggregateID, orgID)
|
||||
if err := human.CheckOrgIAMPolicy(human.Username, orgIAMPolicy); err != nil {
|
||||
if err := human.CheckOrgIAMPolicy(orgIAMPolicy); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
human.SetNamesAsDisplayname()
|
||||
@@ -100,11 +115,10 @@ func (r *CommandSide) createHuman(ctx context.Context, orgID string, human *doma
|
||||
userAgg.PushEvents(createEvent)
|
||||
|
||||
if externalIDP != nil {
|
||||
if !externalIDP.IsValid() {
|
||||
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4Dj9s", "Errors.User.ExternalIDP.Invalid")
|
||||
err = r.addHumanExternalIDP(ctx, userAgg, externalIDP)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
//TODO: check if idpconfig exists
|
||||
userAgg.PushEvents(user.NewHumanExternalIDPAddedEvent(ctx, externalIDP.IDPConfigID, externalIDP.DisplayName))
|
||||
}
|
||||
if human.IsInitialState() {
|
||||
initCode, err := domain.NewInitUserCode(r.initializeUserCode)
|
||||
@@ -121,7 +135,7 @@ func (r *CommandSide) createHuman(ctx context.Context, orgID string, human *doma
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
user.NewHumanPhoneCodeAddedEvent(ctx, phoneCode.Code, phoneCode.Expiry)
|
||||
userAgg.PushEvents(user.NewHumanPhoneCodeAddedEvent(ctx, phoneCode.Code, phoneCode.Expiry))
|
||||
} else if human.Phone != nil && human.PhoneNumber != "" && human.IsPhoneVerified {
|
||||
userAgg.PushEvents(user.NewHumanPhoneVerifiedEvent(ctx))
|
||||
}
|
||||
@@ -129,32 +143,21 @@ func (r *CommandSide) createHuman(ctx context.Context, orgID string, human *doma
|
||||
return userAgg, addedHuman, nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) ResendInitialMail(ctx context.Context, userID, email, resourceowner string) (err error) {
|
||||
func (r *CommandSide) HumanSkipMFAInit(ctx context.Context, userID, resourceowner string) (err error) {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.UserIDMissing")
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2xpX9", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingEmail, err := r.emailWriteModel(ctx, userID, resourceowner)
|
||||
existingHuman, err := r.getHumanWriteModelByID(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 existingHuman.UserState == domain.UserStateUnspecified || existingHuman.UserState == domain.UserStateDeleted {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-m9cV8", "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)
|
||||
userAgg := UserAggregateFromWriteModel(&existingHuman.WriteModel)
|
||||
userAgg.PushEvents(user.NewHumanMFAInitSkippedEvent(ctx))
|
||||
return r.eventstore.PushAggregate(ctx, existingHuman, userAgg)
|
||||
}
|
||||
|
||||
func createAddHumanEvent(ctx context.Context, orgID string, human *domain.Human, userLoginMustBeDomain bool) *user.HumanAddedEvent {
|
||||
@@ -219,6 +222,28 @@ func createRegisterHumanEvent(ctx context.Context, orgID string, human *domain.H
|
||||
return addEvent
|
||||
}
|
||||
|
||||
func (r *CommandSide) HumansSignOut(ctx context.Context, agentID string, userIDs []string) error {
|
||||
if agentID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing")
|
||||
}
|
||||
aggregates := make([]eventstore.Aggregater, len(userIDs))
|
||||
for i, userID := range userIDs {
|
||||
existingUser, err := r.getHumanWriteModelByID(ctx, userID, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted {
|
||||
continue
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
|
||||
userAgg.PushEvents(user.NewHumanSignedOutEvent(ctx, agentID))
|
||||
aggregates[i] = userAgg
|
||||
}
|
||||
|
||||
_, err := r.eventstore.PushAggregates(ctx, aggregates...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *CommandSide) getHumanWriteModelByID(ctx context.Context, userID, resourceowner string) (*HumanWriteModel, error) {
|
||||
humanWriteModel := NewHumanWriteModel(userID, resourceowner)
|
||||
err := r.eventstore.FilterToQueryReducer(ctx, humanWriteModel)
|
||||
|
||||
@@ -24,7 +24,7 @@ func (r *CommandSide) ChangeHumanEmail(ctx context.Context, email *domain.Email)
|
||||
}
|
||||
changedEvent, hasChanged := existingEmail.NewChangedEvent(ctx, email.EmailAddress)
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.Email.NotChanged")
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2b7fM", "Errors.User.Email.NotChanged")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingEmail.WriteModel)
|
||||
userAgg.PushEvents(changedEvent)
|
||||
@@ -60,7 +60,7 @@ func (r *CommandSide) VerifyHumanEmail(ctx context.Context, userID, code, resour
|
||||
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")
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-3n8ud", "Errors.User.Code.NotFound")
|
||||
}
|
||||
|
||||
userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel)
|
||||
@@ -103,6 +103,19 @@ func (r *CommandSide) CreateHumanEmailVerificationCode(ctx context.Context, user
|
||||
return r.eventstore.PushAggregate(ctx, existingEmail, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) HumanEmailVerificationCodeSent(ctx context.Context, orgID, userID string) (err error) {
|
||||
existingEmail, err := r.emailWriteModel(ctx, userID, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existingEmail.UserState == domain.UserStateUnspecified || existingEmail.UserState == domain.UserStateDeleted {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-6n8uH", "Errors.User.Email.NotFound")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingEmail.WriteModel)
|
||||
userAgg.PushEvents(user.NewHumanEmailCodeSentEvent(ctx))
|
||||
return r.eventstore.PushAggregate(ctx, existingEmail, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) emailWriteModel(ctx context.Context, userID, resourceOwner string) (writeModel *HumanEmailWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -66,9 +66,12 @@ func (wm *HumanEmailWriteModel) Reduce() error {
|
||||
}
|
||||
|
||||
func (wm *HumanEmailWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
ResourceOwner(wm.ResourceOwner)
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType).
|
||||
AggregateIDs(wm.AggregateID)
|
||||
if wm.ResourceOwner != "" {
|
||||
query.ResourceOwner(wm.ResourceOwner)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
func (wm *HumanEmailWriteModel) NewChangedEvent(
|
||||
|
||||
@@ -3,11 +3,40 @@ package command
|
||||
import (
|
||||
"context"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"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"
|
||||
)
|
||||
|
||||
func (r *CommandSide) BulkAddedHumanExternalIDP(ctx context.Context, userID, resourceOwner string, externalIDPs []*domain.ExternalIDP) error {
|
||||
if len(externalIDPs) == 0 {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Ek9s", "Errors.User.ExternalIDP.MinimumExternalIDPNeeded")
|
||||
}
|
||||
aggregates := make([]eventstore.Aggregater, len(externalIDPs))
|
||||
|
||||
for i, externalIDP := range externalIDPs {
|
||||
externalIDPWriteModel := NewHumanExternalIDPWriteModel(userID, externalIDP.IDPConfigID, externalIDP.ExternalUserID, resourceOwner)
|
||||
userAgg := UserAggregateFromWriteModel(&externalIDPWriteModel.WriteModel)
|
||||
err := r.addHumanExternalIDP(ctx, userAgg, externalIDP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
aggregates[i] = userAgg
|
||||
}
|
||||
_, err := r.eventstore.PushAggregates(ctx, aggregates...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *CommandSide) addHumanExternalIDP(ctx context.Context, userAgg *user.Aggregate, externalIDP *domain.ExternalIDP) error {
|
||||
if !externalIDP.IsValid() {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6m9Kd", "Errors.User.ExternalIDP.Invalid")
|
||||
}
|
||||
//TODO: check if idpconfig exists
|
||||
userAgg.PushEvents(user.NewHumanExternalIDPAddedEvent(ctx, externalIDP.IDPConfigID, externalIDP.DisplayName, externalIDP.ExternalUserID))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) RemoveHumanExternalIDP(ctx context.Context, externalIDP *domain.ExternalIDP) error {
|
||||
return r.removeHumanExternalIDP(ctx, externalIDP, false)
|
||||
}
|
||||
@@ -37,6 +66,24 @@ func (r *CommandSide) removeHumanExternalIDP(ctx context.Context, externalIDP *d
|
||||
return r.eventstore.PushAggregate(ctx, existingExternalIDP, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) HumanExternalLoginChecked(ctx context.Context, orgID, userID string, authRequest *domain.AuthRequest) (err error) {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-5n8sM", "Errors.IDMissing")
|
||||
}
|
||||
|
||||
existingHuman, err := r.getHumanWriteModelByID(ctx, userID, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existingHuman.UserState == domain.UserStateUnspecified || existingHuman.UserState == domain.UserStateDeleted {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-dn88J", "Errors.User.NotFound")
|
||||
}
|
||||
|
||||
userAgg := UserAggregateFromWriteModel(&existingHuman.WriteModel)
|
||||
userAgg.PushEvents(user.NewHumanExternalIDPCheckSucceededEvent(ctx, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||
return r.eventstore.PushAggregate(ctx, existingHuman, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) externalIDPWriteModelByID(ctx context.Context, userID, idpConfigID, externalUserID, resourceOwner string) (writeModel *HumanExternalIDPWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
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/v2/domain"
|
||||
"github.com/caos/zitadel/internal/v2/repository/user"
|
||||
)
|
||||
|
||||
//ResendInitialMail resend inital mail and changes email if provided
|
||||
func (r *CommandSide) ResendInitialMail(ctx context.Context, userID, email, resourceOwner string) (err error) {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2n8vs", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingCode, err := r.getHumanInitWriteModelByID(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existingCode.UserState == domain.UserStateUnspecified || existingCode.UserState == domain.UserStateDeleted {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-2M9df", "Errors.User.NotFound")
|
||||
}
|
||||
if existingCode.UserState != domain.UserStateInitial {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9sd", "Errors.User.AlreadyInitialised")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel)
|
||||
if email != "" && existingCode.Email != email {
|
||||
changedEvent, _ := existingCode.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, existingCode, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) HumanVerifyInitCode(ctx context.Context, userID, resourceOwner, code, passwordString string) error {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-mkM9f", "Errors.User.UserIDMissing")
|
||||
}
|
||||
if code == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-44G8s", "Errors.User.Code.Empty")
|
||||
}
|
||||
|
||||
existingCode, err := r.getHumanInitWriteModelByID(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-mmn5f", "Errors.User.Code.NotFound")
|
||||
}
|
||||
|
||||
userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel)
|
||||
err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, r.initializeUserCode)
|
||||
if err != nil {
|
||||
userAgg.PushEvents(user.NewHumanInitializedCheckFailedEvent(ctx))
|
||||
err = r.eventstore.PushAggregate(ctx, existingCode, userAgg)
|
||||
logging.LogWithFields("COMMAND-Dg2z5", "userID", userAgg.ID()).OnError(err).Error("NewHumanInitializedCheckFailedEvent push failed")
|
||||
return caos_errs.ThrowInvalidArgument(err, "COMMAND-11v6G", "Errors.User.Code.Invalid")
|
||||
}
|
||||
|
||||
userAgg.PushEvents(user.NewHumanInitializedCheckSucceededEvent(ctx))
|
||||
if !existingCode.IsEmailVerified {
|
||||
userAgg.PushEvents(user.NewHumanEmailVerifiedEvent(ctx))
|
||||
}
|
||||
if passwordString != "" {
|
||||
passwordWriteModel := NewHumanPasswordWriteModel(userID, existingCode.ResourceOwner)
|
||||
password := &domain.Password{
|
||||
SecretString: passwordString,
|
||||
ChangeRequired: false,
|
||||
}
|
||||
err = r.changePassword(ctx, existingCode.ResourceOwner, userID, "", password, userAgg, passwordWriteModel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return r.eventstore.PushAggregate(ctx, existingCode, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) HumanInitCodeSent(ctx context.Context, orgID, userID string) (err error) {
|
||||
existingInitCode, err := r.getHumanInitWriteModelByID(ctx, userID, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existingInitCode.UserState == domain.UserStateUnspecified || existingInitCode.UserState == domain.UserStateDeleted {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-556zg", "Errors.User.Code.NotFound")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingInitCode.WriteModel)
|
||||
userAgg.PushEvents(user.NewHumanInitialCodeSentEvent(ctx))
|
||||
return r.eventstore.PushAggregate(ctx, existingInitCode, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) getHumanInitWriteModelByID(ctx context.Context, userID, resourceowner string) (*HumanInitCodeWriteModel, error) {
|
||||
initWriteModel := NewHumanInitCodeWriteModel(userID, resourceowner)
|
||||
err := r.eventstore.FilterToQueryReducer(ctx, initWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return initWriteModel, nil
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
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 HumanInitCodeWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
Email string
|
||||
IsEmailVerified bool
|
||||
|
||||
Code *crypto.CryptoValue
|
||||
CodeCreationDate time.Time
|
||||
CodeExpiry time.Duration
|
||||
|
||||
UserState domain.UserState
|
||||
}
|
||||
|
||||
func NewHumanInitCodeWriteModel(userID, resourceOwner string) *HumanInitCodeWriteModel {
|
||||
return &HumanInitCodeWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *HumanInitCodeWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
wm.WriteModel.AppendEvents(events...)
|
||||
}
|
||||
|
||||
func (wm *HumanInitCodeWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *user.HumanAddedEvent:
|
||||
wm.Email = e.EmailAddress
|
||||
wm.UserState = domain.UserStateInitial
|
||||
case *user.HumanRegisteredEvent:
|
||||
wm.Email = e.EmailAddress
|
||||
wm.UserState = domain.UserStateInitial
|
||||
case *user.HumanEmailChangedEvent:
|
||||
wm.Email = e.EmailAddress
|
||||
wm.IsEmailVerified = false
|
||||
case *user.HumanEmailVerifiedEvent:
|
||||
wm.IsEmailVerified = true
|
||||
wm.Code = nil
|
||||
if wm.UserState == domain.UserStateInitial {
|
||||
wm.UserState = domain.UserStateActive
|
||||
}
|
||||
case *user.HumanInitialCodeAddedEvent:
|
||||
wm.Code = e.Code
|
||||
wm.CodeCreationDate = e.CreationDate()
|
||||
wm.CodeExpiry = e.Expiry
|
||||
case *user.HumanInitializedCheckSucceededEvent:
|
||||
wm.Code = nil
|
||||
case *user.UserRemovedEvent:
|
||||
wm.UserState = domain.UserStateDeleted
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *HumanInitCodeWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType).
|
||||
AggregateIDs(wm.AggregateID)
|
||||
if wm.ResourceOwner != "" {
|
||||
query.ResourceOwner(wm.ResourceOwner)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
func (wm *HumanInitCodeWriteModel) NewChangedEvent(
|
||||
ctx context.Context,
|
||||
email string,
|
||||
) (*user.HumanEmailChangedEvent, bool) {
|
||||
hasChanged := false
|
||||
changedEvent := user.NewHumanEmailChangedEvent(ctx)
|
||||
if wm.Email != email {
|
||||
hasChanged = true
|
||||
changedEvent.EmailAddress = email
|
||||
}
|
||||
return changedEvent, hasChanged
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/logging"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
@@ -15,14 +16,17 @@ func (r *CommandSide) AddHumanOTP(ctx context.Context, userID, resourceowner str
|
||||
}
|
||||
human, err := r.getHuman(ctx, userID, resourceowner)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND-DAqe1").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get human for loginname")
|
||||
return nil, err
|
||||
}
|
||||
org, err := r.getOrg(ctx, human.ResourceOwner)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND-Cm0ds").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org for loginname")
|
||||
return nil, err
|
||||
}
|
||||
orgPolicy, err := r.getOrgIAMPolicy(ctx, org.AggregateID)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND-y5zv9").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org policy for loginname")
|
||||
return nil, err
|
||||
}
|
||||
otpWriteModel, err := r.otpWriteModelByID(ctx, userID, resourceowner)
|
||||
@@ -58,7 +62,7 @@ func (r *CommandSide) AddHumanOTP(ctx context.Context, userID, resourceowner str
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) CheckMFAOTPSetup(ctx context.Context, userID, code, userAgentID, resourceowner string) error {
|
||||
func (r *CommandSide) HumanCheckMFAOTPSetup(ctx context.Context, userID, code, userAgentID, resourceowner string) error {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing")
|
||||
}
|
||||
@@ -84,7 +88,32 @@ func (r *CommandSide) CheckMFAOTPSetup(ctx context.Context, userID, code, userAg
|
||||
return r.eventstore.PushAggregate(ctx, existingOTP, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) RemoveHumanOTP(ctx context.Context, userID, resourceOwner string) error {
|
||||
func (r *CommandSide) HumanCheckMFAOTP(ctx context.Context, userID, code, resourceowner string, authRequest *domain.AuthRequest) 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.MFAStateReady {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotReady")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
|
||||
err = domain.VerifyMFAOTP(code, existingOTP.Secret, r.multifactors.OTP.CryptoMFA)
|
||||
if err == nil {
|
||||
userAgg.PushEvents(
|
||||
user.NewHumanOTPCheckSucceededEvent(ctx, authRequestDomainToAuthRequestInfo(authRequest)),
|
||||
)
|
||||
return r.eventstore.PushAggregate(ctx, existingOTP, userAgg)
|
||||
}
|
||||
userAgg.PushEvents(user.NewHumanOTPCheckFailedEvent(ctx, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||
pushErr := r.eventstore.PushAggregate(ctx, existingOTP, userAgg)
|
||||
logging.Log("COMMAND-9fj7s").OnError(pushErr).Error("error create password check failed event")
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *CommandSide) HumanRemoveOTP(ctx context.Context, userID, resourceOwner string) error {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
@@ -45,7 +45,10 @@ func (wm *HumanOTPWriteModel) Reduce() error {
|
||||
}
|
||||
|
||||
func (wm *HumanOTPWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
ResourceOwner(wm.ResourceOwner)
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType).
|
||||
AggregateIDs(wm.AggregateID)
|
||||
if wm.ResourceOwner != "" {
|
||||
query.ResourceOwner(wm.ResourceOwner)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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"
|
||||
@@ -21,7 +22,34 @@ func (r *CommandSide) SetOneTimePassword(ctx context.Context, orgID, userID, pas
|
||||
SecretString: passwordString,
|
||||
ChangeRequired: true,
|
||||
}
|
||||
return r.changePassword(ctx, orgID, userID, "", password, existingPassword)
|
||||
userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel)
|
||||
return r.changePassword(ctx, orgID, userID, "", password, userAgg, existingPassword)
|
||||
}
|
||||
|
||||
func (r *CommandSide) SetPassword(ctx context.Context, orgID, userID, code, passwordString, userAgentID string) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
existingCode, err := r.passwordWriteModel(ctx, userID, orgID)
|
||||
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")
|
||||
}
|
||||
|
||||
err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, r.emailVerificationCode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
password := &domain.Password{
|
||||
SecretString: passwordString,
|
||||
ChangeRequired: false,
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel)
|
||||
return r.changePassword(ctx, existingCode.ResourceOwner, userID, userAgentID, password, userAgg, existingCode)
|
||||
}
|
||||
|
||||
func (r *CommandSide) ChangePassword(ctx context.Context, orgID, userID, oldPassword, newPassword, userAgentID string) (err error) {
|
||||
@@ -32,7 +60,7 @@ func (r *CommandSide) ChangePassword(ctx context.Context, orgID, userID, oldPass
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existingPassword.Secret != nil {
|
||||
if existingPassword.Secret == nil {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Fds3s", "Errors.User.Password.Empty")
|
||||
}
|
||||
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash")
|
||||
@@ -44,12 +72,14 @@ func (r *CommandSide) ChangePassword(ctx context.Context, orgID, userID, oldPass
|
||||
}
|
||||
password := &domain.Password{
|
||||
SecretString: newPassword,
|
||||
ChangeRequired: true,
|
||||
ChangeRequired: false,
|
||||
}
|
||||
return r.changePassword(ctx, orgID, userID, userAgentID, password, existingPassword)
|
||||
|
||||
userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel)
|
||||
return r.changePassword(ctx, orgID, userID, userAgentID, password, userAgg, existingPassword)
|
||||
}
|
||||
|
||||
func (r *CommandSide) changePassword(ctx context.Context, orgID, userID, userAgentID string, password *domain.Password, existingPassword *HumanPasswordWriteModel) (err error) {
|
||||
func (r *CommandSide) changePassword(ctx context.Context, orgID, userID, userAgentID string, password *domain.Password, userAgg *user.Aggregate, existingPassword *HumanPasswordWriteModel) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -69,7 +99,6 @@ func (r *CommandSide) changePassword(ctx context.Context, orgID, userID, userAge
|
||||
if err := password.HashPasswordIfExisting(pwPolicy, r.userPasswordAlg); err != nil {
|
||||
return err
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel)
|
||||
userAgg.PushEvents(user.NewHumanPasswordChangedEvent(ctx, password.SecretCrypto, password.ChangeRequired, userAgentID))
|
||||
return r.eventstore.PushAggregate(ctx, existingPassword, userAgg)
|
||||
}
|
||||
@@ -94,6 +123,54 @@ func (r *CommandSide) RequestSetPassword(ctx context.Context, userID, resourceOw
|
||||
return r.eventstore.PushAggregate(ctx, existingHuman, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) PasswordCodeSent(ctx context.Context, orgID, userID string) (err error) {
|
||||
existingPassword, err := r.passwordWriteModel(ctx, userID, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existingPassword.UserState == domain.UserStateUnspecified || existingPassword.UserState == domain.UserStateDeleted {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-3n77z", "Errors.User.NotFound")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel)
|
||||
userAgg.PushEvents(user.NewHumanPasswordCodeSentEvent(ctx))
|
||||
return r.eventstore.PushAggregate(ctx, existingPassword, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) HumanCheckPassword(ctx context.Context, orgID, userID, password string, authRequest *domain.AuthRequest) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if password == "" {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-3n8fs", "Errors.User.Password.Empty")
|
||||
}
|
||||
|
||||
existingPassword, err := r.passwordWriteModel(ctx, userID, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existingPassword.UserState == domain.UserStateUnspecified || existingPassword.UserState == domain.UserStateDeleted {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-3n77z", "Errors.User.NotFound")
|
||||
}
|
||||
|
||||
if existingPassword.Secret == nil {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-3n77z", "Errors.User.Password.NotSet")
|
||||
}
|
||||
|
||||
userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel)
|
||||
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash")
|
||||
err = crypto.CompareHash(existingPassword.Secret, []byte(password), r.userPasswordAlg)
|
||||
spanPasswordComparison.EndWithError(err)
|
||||
if err == nil {
|
||||
userAgg.PushEvents(user.NewHumanPasswordCheckSucceededEvent(ctx, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||
return r.eventstore.PushAggregate(ctx, existingPassword, userAgg)
|
||||
}
|
||||
|
||||
userAgg.PushEvents(user.NewHumanPasswordCheckFailedEvent(ctx, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||
err = r.eventstore.PushAggregate(ctx, existingPassword, userAgg)
|
||||
logging.Log("COMMAND-9fj7s").OnError(err).Error("error create password check failed event")
|
||||
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-452ad", "Errors.User.Password.Invalid")
|
||||
}
|
||||
|
||||
func (r *CommandSide) passwordWriteModel(ctx context.Context, userID, resourceOwner string) (writeModel *HumanPasswordWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/caos/zitadel/internal/eventstore/v2"
|
||||
"github.com/caos/zitadel/internal/v2/domain"
|
||||
"github.com/caos/zitadel/internal/v2/repository/user"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HumanPasswordWriteModel struct {
|
||||
@@ -13,6 +14,10 @@ type HumanPasswordWriteModel struct {
|
||||
Secret *crypto.CryptoValue
|
||||
SecretChangeRequired bool
|
||||
|
||||
Code *crypto.CryptoValue
|
||||
CodeCreationDate time.Time
|
||||
CodeExpiry time.Duration
|
||||
|
||||
UserState domain.UserState
|
||||
}
|
||||
|
||||
@@ -43,6 +48,11 @@ func (wm *HumanPasswordWriteModel) Reduce() error {
|
||||
case *user.HumanPasswordChangedEvent:
|
||||
wm.Secret = e.Secret
|
||||
wm.SecretChangeRequired = e.ChangeRequired
|
||||
wm.Code = nil
|
||||
case *user.HumanPasswordCodeAddedEvent:
|
||||
wm.Code = e.Code
|
||||
wm.CodeCreationDate = e.CreationDate()
|
||||
wm.CodeExpiry = e.Expiry
|
||||
case *user.HumanEmailVerifiedEvent:
|
||||
if wm.UserState == domain.UserStateInitial {
|
||||
wm.UserState = domain.UserStateActive
|
||||
@@ -55,7 +65,10 @@ func (wm *HumanPasswordWriteModel) Reduce() error {
|
||||
}
|
||||
|
||||
func (wm *HumanPasswordWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
ResourceOwner(wm.ResourceOwner)
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType).
|
||||
AggregateIDs(wm.AggregateID)
|
||||
if wm.ResourceOwner != "" {
|
||||
query.ResourceOwner(wm.ResourceOwner)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ func (r *CommandSide) CreateHumanPhoneVerificationCode(ctx context.Context, user
|
||||
return err
|
||||
}
|
||||
if existingPhone.State == domain.PhoneStateUnspecified || existingPhone.State == domain.PhoneStateRemoved {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-2M9fs", "Errors.User.Phone.NotFound")
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-2b7Hf", "Errors.User.Phone.NotFound")
|
||||
}
|
||||
if existingPhone.IsPhoneVerified {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9sf", "Errors.User.Phone.AlreadyVerified")
|
||||
@@ -107,6 +107,19 @@ func (r *CommandSide) CreateHumanPhoneVerificationCode(ctx context.Context, user
|
||||
return r.eventstore.PushAggregate(ctx, existingPhone, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) HumanPhoneVerificationCodeSent(ctx context.Context, orgID, userID string) (err error) {
|
||||
existingPhone, err := r.phoneWriteModelByID(ctx, userID, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existingPhone.State == domain.PhoneStateUnspecified || existingPhone.State == domain.PhoneStateRemoved {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-66n8J", "Errors.User.Phone.NotFound")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingPhone.WriteModel)
|
||||
userAgg.PushEvents(user.NewHumanPhoneCodeSentEvent(ctx))
|
||||
return r.eventstore.PushAggregate(ctx, existingPhone, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) RemoveHumanPhone(ctx context.Context, userID, resourceOwner string) error {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0ds", "Errors.User.UserIDMissing")
|
||||
|
||||
@@ -33,7 +33,35 @@ func (r *CommandSide) getHumanPasswordlessTokens(ctx context.Context, userID, re
|
||||
return readModelToPasswordlessTokens(tokenReadModel), nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) AddHumanU2F(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) {
|
||||
func (r *CommandSide) getHumanU2FLogin(ctx context.Context, userID, authReqID, resourceowner string) (*domain.WebAuthNLogin, error) {
|
||||
tokenReadModel := NewHumanU2FLoginReadModel(userID, authReqID, resourceowner)
|
||||
err := r.eventstore.FilterToQueryReducer(ctx, tokenReadModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tokenReadModel.State == domain.UserStateDeleted {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-5m88U", "Errors.User.NotFound")
|
||||
}
|
||||
return &domain.WebAuthNLogin{
|
||||
Challenge: tokenReadModel.Challenge,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) getHumanPasswordlessLogin(ctx context.Context, userID, authReqID, resourceowner string) (*domain.WebAuthNLogin, error) {
|
||||
tokenReadModel := NewHumanPasswordlessLoginReadModel(userID, authReqID, resourceowner)
|
||||
err := r.eventstore.FilterToQueryReducer(ctx, tokenReadModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tokenReadModel.State == domain.UserStateDeleted {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-fm84R", "Errors.User.NotFound")
|
||||
}
|
||||
return &domain.WebAuthNLogin{
|
||||
Challenge: tokenReadModel.Challenge,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) HumanAddU2FSetup(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) {
|
||||
u2fTokens, err := r.getHumanU2FTokens(ctx, userID, resourceowner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -55,7 +83,7 @@ func (r *CommandSide) AddHumanU2F(ctx context.Context, userID, resourceowner str
|
||||
return createdWebAuthN, nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) AddHumanPasswordless(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) {
|
||||
func (r *CommandSide) HumanAddPasswordlessSetup(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) {
|
||||
passwordlessTokens, err := r.getHumanPasswordlessTokens(ctx, userID, resourceowner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -64,7 +92,7 @@ func (r *CommandSide) AddHumanPasswordless(ctx context.Context, userID, resource
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userAgg.PushEvents(usr_repo.NewHumanU2FAddedEvent(ctx, addWebAuthN.WebauthNTokenID, webAuthN.Challenge))
|
||||
userAgg.PushEvents(usr_repo.NewHumanPasswordlessAddedEvent(ctx, addWebAuthN.WebauthNTokenID, webAuthN.Challenge))
|
||||
|
||||
err = r.eventstore.PushAggregate(ctx, addWebAuthN, userAgg)
|
||||
if err != nil {
|
||||
@@ -114,7 +142,7 @@ func (r *CommandSide) addHumanWebAuthN(ctx context.Context, userID, resourceowne
|
||||
return addWebAuthN, userAgg, webAuthN, nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) VerifyHumanU2F(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) error {
|
||||
func (r *CommandSide) HumanVerifyU2FSetup(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) error {
|
||||
u2fTokens, err := r.getHumanU2FTokens(ctx, userID, resourceowner)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -139,7 +167,7 @@ func (r *CommandSide) VerifyHumanU2F(ctx context.Context, userID, resourceowner,
|
||||
return r.eventstore.PushAggregate(ctx, verifyWebAuthN, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) VerifyHumanPasswordless(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) error {
|
||||
func (r *CommandSide) HumanHumanPasswordlessSetup(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) error {
|
||||
u2fTokens, err := r.getHumanPasswordlessTokens(ctx, userID, resourceowner)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -149,7 +177,7 @@ func (r *CommandSide) VerifyHumanPasswordless(ctx context.Context, userID, resou
|
||||
return err
|
||||
}
|
||||
userAgg.PushEvents(
|
||||
usr_repo.NewHumanU2FVerifiedEvent(
|
||||
usr_repo.NewHumanPasswordlessVerifiedEvent(
|
||||
ctx,
|
||||
verifyWebAuthN.WebauthNTokenID,
|
||||
webAuthN.WebAuthNTokenName,
|
||||
@@ -186,12 +214,156 @@ func (r *CommandSide) verifyHumanWebAuthN(ctx context.Context, userID, resourceo
|
||||
return verifyWebAuthN, userAgg, webAuthN, nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) RemoveHumanU2F(ctx context.Context, userID, webAuthNID, resourceOwner string) error {
|
||||
func (r *CommandSide) HumanBeginU2FLogin(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest, isLoginUI bool) (*domain.WebAuthNLogin, error) {
|
||||
u2fTokens, err := r.getHumanU2FTokens(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
writeModel, userAgg, webAuthNLogin, err := r.beginWebAuthNLogin(ctx, userID, resourceOwner, u2fTokens, isLoginUI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userAgg.PushEvents(
|
||||
usr_repo.NewHumanU2FBeginLoginEvent(
|
||||
ctx,
|
||||
webAuthNLogin.Challenge,
|
||||
authRequestDomainToAuthRequestInfo(authRequest),
|
||||
),
|
||||
)
|
||||
|
||||
err = r.eventstore.PushAggregate(ctx, writeModel, userAgg)
|
||||
return webAuthNLogin, err
|
||||
}
|
||||
|
||||
func (r *CommandSide) HumanBeginPasswordlessLogin(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest, isLoginUI bool) (*domain.WebAuthNLogin, error) {
|
||||
u2fTokens, err := r.getHumanPasswordlessTokens(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
writeModel, userAgg, webAuthNLogin, err := r.beginWebAuthNLogin(ctx, userID, resourceOwner, u2fTokens, isLoginUI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userAgg.PushEvents(
|
||||
usr_repo.NewHumanPasswordlessBeginLoginEvent(
|
||||
ctx,
|
||||
webAuthNLogin.Challenge,
|
||||
authRequestDomainToAuthRequestInfo(authRequest),
|
||||
),
|
||||
)
|
||||
|
||||
err = r.eventstore.PushAggregate(ctx, writeModel, userAgg)
|
||||
return webAuthNLogin, err
|
||||
}
|
||||
|
||||
func (r *CommandSide) beginWebAuthNLogin(ctx context.Context, userID, resourceOwner string, tokens []*domain.WebAuthNToken, isLoginUI bool) (*HumanWebAuthNWriteModel, *usr_repo.Aggregate, *domain.WebAuthNLogin, error) {
|
||||
if userID == "" {
|
||||
return nil, nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-hh8K9", "Errors.IDMissing")
|
||||
}
|
||||
|
||||
human, err := r.getHuman(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
webAuthNLogin, err := r.webauthn.BeginLogin(human, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
writeModel, err := r.webauthNWriteModelByID(ctx, userID, "", resourceOwner)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
userAgg := UserAggregateFromWriteModel(&writeModel.WriteModel)
|
||||
return writeModel, userAgg, webAuthNLogin, nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) HumanFinishU2FLogin(ctx context.Context, userID, resourceOwner string, credentialData []byte, authRequest *domain.AuthRequest, isLoginUI bool) error {
|
||||
webAuthNLogin, err := r.getHumanU2FLogin(ctx, userID, authRequest.ID, resourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u2fTokens, err := r.getHumanU2FTokens(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
writeModel, userAgg, token, signCount, err := r.finishWebAuthNLogin(ctx, userID, resourceOwner, credentialData, webAuthNLogin, u2fTokens, isLoginUI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userAgg.PushEvents(
|
||||
usr_repo.NewHumanU2FSignCountChangedEvent(
|
||||
ctx,
|
||||
token.WebAuthNTokenID,
|
||||
signCount,
|
||||
),
|
||||
)
|
||||
|
||||
return r.eventstore.PushAggregate(ctx, writeModel, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) HumanFinishPasswordlessLogin(ctx context.Context, userID, resourceOwner string, credentialData []byte, authRequest *domain.AuthRequest, isLoginUI bool) error {
|
||||
webAuthNLogin, err := r.getHumanPasswordlessLogin(ctx, userID, authRequest.ID, resourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
passwordlessTokens, err := r.getHumanPasswordlessTokens(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
writeModel, userAgg, token, signCount, err := r.finishWebAuthNLogin(ctx, userID, resourceOwner, credentialData, webAuthNLogin, passwordlessTokens, isLoginUI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userAgg.PushEvents(
|
||||
usr_repo.NewHumanPasswordlessSignCountChangedEvent(
|
||||
ctx,
|
||||
token.WebAuthNTokenID,
|
||||
signCount,
|
||||
),
|
||||
)
|
||||
|
||||
return r.eventstore.PushAggregate(ctx, writeModel, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) finishWebAuthNLogin(ctx context.Context, userID, resourceOwner string, credentialData []byte, webAuthN *domain.WebAuthNLogin, tokens []*domain.WebAuthNToken, isLoginUI bool) (*HumanWebAuthNWriteModel, *usr_repo.Aggregate, *domain.WebAuthNToken, uint32, error) {
|
||||
if userID == "" {
|
||||
return nil, nil, nil, 0, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-hh8K9", "Errors.IDMissing")
|
||||
}
|
||||
|
||||
human, err := r.getHuman(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, nil, nil, 0, err
|
||||
}
|
||||
keyID, signCount, err := r.webauthn.FinishLogin(human, webAuthN, credentialData, isLoginUI, tokens...)
|
||||
if err != nil && keyID == nil {
|
||||
return nil, nil, nil, 0, err
|
||||
}
|
||||
|
||||
_, token := domain.GetTokenByKeyID(tokens, keyID)
|
||||
if token == nil {
|
||||
return nil, nil, nil, 0, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3b7zs", "Errors.User.WebAuthN.NotFound")
|
||||
}
|
||||
writeModel, err := r.webauthNWriteModelByID(ctx, userID, "", resourceOwner)
|
||||
if err != nil {
|
||||
return nil, nil, nil, 0, err
|
||||
}
|
||||
|
||||
userAgg := UserAggregateFromWriteModel(&writeModel.WriteModel)
|
||||
return writeModel, userAgg, token, signCount, nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) HumanRemoveU2F(ctx context.Context, userID, webAuthNID, resourceOwner string) error {
|
||||
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 {
|
||||
func (r *CommandSide) HumanRemovePasswordless(ctx context.Context, userID, webAuthNID, resourceOwner string) error {
|
||||
event := usr_repo.NewHumanPasswordlessRemovedEvent(ctx, webAuthNID)
|
||||
return r.removeHumanWebAuthN(ctx, userID, webAuthNID, resourceOwner, event)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,14 @@ func (wm *HumanWebAuthNWriteModel) AppendEvents(events ...eventstore.EventReader
|
||||
if wm.WebauthNTokenID == e.WebAuthNTokenID {
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
}
|
||||
case *user.HumanWebAuthNVerifiedEvent:
|
||||
if wm.WebauthNTokenID == e.WebAuthNTokenID {
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
}
|
||||
case *user.HumanWebAuthNSignCountChangedEvent:
|
||||
if wm.WebauthNTokenID == e.WebAuthNTokenID {
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
}
|
||||
case *user.HumanWebAuthNRemovedEvent:
|
||||
if wm.WebauthNTokenID == e.WebAuthNTokenID {
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
@@ -56,6 +64,8 @@ func (wm *HumanWebAuthNWriteModel) Reduce() error {
|
||||
wm.appendAddedEvent(e)
|
||||
case *user.HumanWebAuthNVerifiedEvent:
|
||||
wm.appendVerifiedEvent(e)
|
||||
case *user.HumanWebAuthNSignCountChangedEvent:
|
||||
wm.SignCount = e.SignCount
|
||||
case *user.HumanWebAuthNRemovedEvent:
|
||||
wm.State = domain.MFAStateRemoved
|
||||
case *user.UserRemovedEvent:
|
||||
@@ -104,34 +114,35 @@ func NewHumanU2FTokensReadModel(userID, resourceOwner string) *HumanU2FTokensRea
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
wm.WriteModel.AppendEvents(events...)
|
||||
}
|
||||
|
||||
func (wm *HumanU2FTokensReadModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *user.HumanWebAuthNAddedEvent:
|
||||
case *user.HumanU2FAddedEvent:
|
||||
token := &HumanWebAuthNWriteModel{}
|
||||
token.appendAddedEvent(e)
|
||||
wm.WebAuthNTokens = append(wm.WebAuthNTokens, token)
|
||||
case *user.HumanWebAuthNVerifiedEvent:
|
||||
token.appendAddedEvent(&e.HumanWebAuthNAddedEvent)
|
||||
token.WriteModel = eventstore.WriteModel{
|
||||
AggregateID: e.AggregateID(),
|
||||
}
|
||||
replaced := false
|
||||
for i, existingTokens := range wm.WebAuthNTokens {
|
||||
if existingTokens.State == domain.MFAStateNotReady {
|
||||
wm.WebAuthNTokens[i] = token
|
||||
replaced = true
|
||||
}
|
||||
}
|
||||
if !replaced {
|
||||
wm.WebAuthNTokens = append(wm.WebAuthNTokens, token)
|
||||
}
|
||||
case *user.HumanU2FVerifiedEvent:
|
||||
idx, token := wm.WebAuthNTokenByID(e.WebAuthNTokenID)
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
token.appendVerifiedEvent(e)
|
||||
case *user.HumanWebAuthNRemovedEvent:
|
||||
token.appendVerifiedEvent(&e.HumanWebAuthNVerifiedEvent)
|
||||
case *user.HumanU2FRemovedEvent:
|
||||
idx, _ := wm.WebAuthNTokenByID(e.WebAuthNTokenID)
|
||||
if idx < 0 {
|
||||
continue
|
||||
@@ -153,8 +164,7 @@ func (rm *HumanU2FTokensReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
EventTypes(
|
||||
user.HumanU2FTokenAddedType,
|
||||
user.HumanU2FTokenVerifiedType,
|
||||
user.HumanU2FTokenRemovedType,
|
||||
user.UserV1MFAOTPRemovedType)
|
||||
user.HumanU2FTokenRemovedType)
|
||||
|
||||
}
|
||||
|
||||
@@ -190,17 +200,29 @@ func (wm *HumanPasswordlessTokensReadModel) AppendEvents(events ...eventstore.Ev
|
||||
func (wm *HumanPasswordlessTokensReadModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *user.HumanWebAuthNAddedEvent:
|
||||
case *user.HumanPasswordlessAddedEvent:
|
||||
token := &HumanWebAuthNWriteModel{}
|
||||
token.appendAddedEvent(e)
|
||||
wm.WebAuthNTokens = append(wm.WebAuthNTokens, token)
|
||||
case *user.HumanWebAuthNVerifiedEvent:
|
||||
token.appendAddedEvent(&e.HumanWebAuthNAddedEvent)
|
||||
token.WriteModel = eventstore.WriteModel{
|
||||
AggregateID: e.AggregateID(),
|
||||
}
|
||||
replaced := false
|
||||
for i, existingTokens := range wm.WebAuthNTokens {
|
||||
if existingTokens.State == domain.MFAStateNotReady {
|
||||
wm.WebAuthNTokens[i] = token
|
||||
replaced = true
|
||||
}
|
||||
}
|
||||
if !replaced {
|
||||
wm.WebAuthNTokens = append(wm.WebAuthNTokens, token)
|
||||
}
|
||||
case *user.HumanPasswordlessVerifiedEvent:
|
||||
idx, token := wm.WebAuthNTokenByID(e.WebAuthNTokenID)
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
token.appendVerifiedEvent(e)
|
||||
case *user.HumanWebAuthNRemovedEvent:
|
||||
token.appendVerifiedEvent(&e.HumanWebAuthNVerifiedEvent)
|
||||
case *user.HumanPasswordlessRemovedEvent:
|
||||
idx, _ := wm.WebAuthNTokenByID(e.WebAuthNTokenID)
|
||||
if idx < 0 {
|
||||
continue
|
||||
@@ -222,8 +244,7 @@ func (rm *HumanPasswordlessTokensReadModel) Query() *eventstore.SearchQueryBuild
|
||||
EventTypes(
|
||||
user.HumanPasswordlessTokenAddedType,
|
||||
user.HumanPasswordlessTokenVerifiedType,
|
||||
user.HumanPasswordlessTokenRemovedType,
|
||||
user.UserV1MFAOTPRemovedType)
|
||||
user.HumanPasswordlessTokenRemovedType)
|
||||
|
||||
}
|
||||
|
||||
@@ -235,3 +256,114 @@ func (wm *HumanPasswordlessTokensReadModel) WebAuthNTokenByID(id string) (idx in
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
type HumanU2FLoginReadModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
AuthReqID string
|
||||
Challenge string
|
||||
State domain.UserState
|
||||
}
|
||||
|
||||
func NewHumanU2FLoginReadModel(userID, authReqID, resourceOwner string) *HumanU2FLoginReadModel {
|
||||
return &HumanU2FLoginReadModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
AuthReqID: authReqID,
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *HumanU2FLoginReadModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *user.HumanU2FBeginLoginEvent:
|
||||
if e.AuthRequestInfo.ID != wm.AuthReqID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *user.UserRemovedEvent:
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *HumanU2FLoginReadModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *user.HumanU2FBeginLoginEvent:
|
||||
wm.Challenge = e.Challenge
|
||||
wm.State = domain.UserStateActive
|
||||
case *user.UserRemovedEvent:
|
||||
wm.State = domain.UserStateDeleted
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (rm *HumanU2FLoginReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType).
|
||||
AggregateIDs(rm.AggregateID).
|
||||
ResourceOwner(rm.ResourceOwner).
|
||||
EventTypes(
|
||||
user.HumanU2FTokenBeginLoginType,
|
||||
user.UserRemovedType,
|
||||
)
|
||||
}
|
||||
|
||||
type HumanPasswordlessLoginReadModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
AuthReqID string
|
||||
Challenge string
|
||||
State domain.UserState
|
||||
}
|
||||
|
||||
func NewHumanPasswordlessLoginReadModel(userID, authReqID, resourceOwner string) *HumanPasswordlessLoginReadModel {
|
||||
return &HumanPasswordlessLoginReadModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
AuthReqID: authReqID,
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *HumanPasswordlessLoginReadModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *user.HumanPasswordlessBeginLoginEvent:
|
||||
if e.AuthRequestInfo.ID != wm.AuthReqID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *user.UserRemovedEvent:
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *HumanPasswordlessLoginReadModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *user.HumanPasswordlessBeginLoginEvent:
|
||||
wm.Challenge = e.Challenge
|
||||
wm.State = domain.UserStateActive
|
||||
case *user.UserRemovedEvent:
|
||||
wm.State = domain.UserStateDeleted
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (rm *HumanPasswordlessLoginReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType).
|
||||
AggregateIDs(rm.AggregateID).
|
||||
ResourceOwner(rm.ResourceOwner).
|
||||
EventTypes(
|
||||
user.HumanPasswordlessTokenBeginLoginType,
|
||||
user.UserRemovedType,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func (r *CommandSide) ChangeMachine(ctx context.Context, machine *domain.Machine
|
||||
|
||||
changedEvent, hasChanged := existingUser.NewChangedEvent(ctx, machine.Name, machine.Description)
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.Email.NotChanged")
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2n8vs", "Errors.User.Email.NotChanged")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
|
||||
userAgg.PushEvents(changedEvent)
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
"github.com/caos/zitadel/internal/v2/domain"
|
||||
"github.com/caos/zitadel/internal/v2/repository/user"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
yearLayout = "2006-01-02"
|
||||
defaultExpirationDate = "9999-01-01"
|
||||
)
|
||||
|
||||
func (r *CommandSide) AddUserMachineKey(ctx context.Context, machineKey *domain.MachineKey, resourceOwner string) (*domain.MachineKey, error) {
|
||||
err := r.checkUserExists(ctx, machineKey.AggregateID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyID, err := r.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyWriteModel := NewMachineKeyWriteModel(machineKey.AggregateID, keyID, resourceOwner)
|
||||
userAgg := UserAggregateFromWriteModel(&keyWriteModel.WriteModel)
|
||||
err = r.eventstore.FilterToQueryReducer(ctx, keyWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if machineKey.ExpirationDate.IsZero() {
|
||||
machineKey.ExpirationDate, err = time.Parse(yearLayout, defaultExpirationDate)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND9-v8jMi").WithError(err).Warn("unable to set default date")
|
||||
return nil, errors.ThrowInternal(err, "COMMAND-38jfus", "Errors.Internal")
|
||||
}
|
||||
}
|
||||
if machineKey.ExpirationDate.Before(time.Now()) {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-38vns", "Errors.MachineKey.ExpireBeforeNow")
|
||||
}
|
||||
|
||||
machineKey.GenerateNewMachineKeyPair(r.machineKeySize)
|
||||
|
||||
userAgg.PushEvents(user.NewMachineKeyAddedEvent(ctx, keyID, machineKey.Type, machineKey.ExpirationDate, machineKey.PublicKey))
|
||||
err = r.eventstore.PushAggregate(ctx, keyWriteModel, userAgg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := keyWriteModelToMachineKey(keyWriteModel)
|
||||
key.PrivateKey = machineKey.PrivateKey
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) RemoveUserMachineKey(ctx context.Context, userID, keyID, resourceOwner string) error {
|
||||
keyWriteModel, err := r.machineKeyWriteModelByID(ctx, userID, keyID, resourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if keyWriteModel.State == domain.MachineKeyStateUnspecified || keyWriteModel.State == domain.MachineKeyStateRemoved {
|
||||
return errors.ThrowNotFound(nil, "COMMAND-4m77G", "Errors.User.Machine.Key.NotFound")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&keyWriteModel.WriteModel)
|
||||
userAgg.PushEvents(user.NewMachineKeyRemovedEvent(ctx, keyID))
|
||||
return r.eventstore.PushAggregate(ctx, keyWriteModel, userAgg)
|
||||
}
|
||||
|
||||
func (r *CommandSide) machineKeyWriteModelByID(ctx context.Context, userID, keyID, resourceOwner string) (writeModel *MachineKeyWriteModel, err error) {
|
||||
if userID == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-4n8vs", "Errors.User.UserIDMissing")
|
||||
}
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel = NewMachineKeyWriteModel(userID, keyID, resourceOwner)
|
||||
err = r.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/eventstore/v2"
|
||||
"github.com/caos/zitadel/internal/v2/domain"
|
||||
"github.com/caos/zitadel/internal/v2/repository/user"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MachineKeyWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
KeyID string
|
||||
KeyType domain.MachineKeyType
|
||||
ExpirationDate time.Time
|
||||
|
||||
State domain.MachineKeyState
|
||||
}
|
||||
|
||||
func NewMachineKeyWriteModel(userID, keyID, resourceOwner string) *MachineKeyWriteModel {
|
||||
return &MachineKeyWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
KeyID: keyID,
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *MachineKeyWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *user.MachineKeyAddedEvent:
|
||||
if wm.KeyID != e.KeyID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *user.MachineKeyRemovedEvent:
|
||||
if wm.KeyID != e.KeyID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *user.UserRemovedEvent:
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *MachineKeyWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *user.MachineKeyAddedEvent:
|
||||
wm.KeyID = e.KeyID
|
||||
wm.KeyType = e.KeyType
|
||||
wm.ExpirationDate = e.ExpirationDate
|
||||
wm.State = domain.MachineKeyStateActive
|
||||
case *user.MachineKeyRemovedEvent:
|
||||
wm.State = domain.MachineKeyStateRemoved
|
||||
case *user.UserRemovedEvent:
|
||||
wm.State = domain.MachineKeyStateRemoved
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *MachineKeyWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
EventTypes(
|
||||
user.MachineKeyAddedEventType,
|
||||
user.MachineKeyRemovedEventType,
|
||||
user.UserRemovedType)
|
||||
}
|
||||
@@ -39,6 +39,8 @@ func (wm *UserWriteModel) Reduce() error {
|
||||
case *user.HumanRegisteredEvent:
|
||||
wm.UserName = e.UserName
|
||||
wm.UserState = domain.UserStateInitial
|
||||
case *user.HumanInitializedCheckSucceededEvent:
|
||||
wm.UserState = domain.UserStateActive
|
||||
case *user.MachineAddedEvent:
|
||||
wm.UserName = e.UserName
|
||||
wm.UserState = domain.UserStateActive
|
||||
|
||||
Reference in New Issue
Block a user