From ad972d888d081aa8ef4e68546ae50d57f2134f66 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Fri, 20 Sep 2024 09:14:51 +0200 Subject: [PATCH] feat: authenticator username for user v3 API --- internal/command/user_v3_username.go | 97 +++++++++++++++++++ internal/command/user_v3_username_model.go | 48 +++++++++ .../repository/user/authenticator/username.go | 3 + 3 files changed, 148 insertions(+) create mode 100644 internal/command/user_v3_username.go create mode 100644 internal/command/user_v3_username_model.go diff --git a/internal/command/user_v3_username.go b/internal/command/user_v3_username.go new file mode 100644 index 0000000000..06d023b9d3 --- /dev/null +++ b/internal/command/user_v3_username.go @@ -0,0 +1,97 @@ +package command + +import ( + "context" + + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/repository/user/authenticator" + "github.com/zitadel/zitadel/internal/zerrors" +) + +type AddUsername struct { + Details *domain.ObjectDetails + + ResourceOwner string + UserID string + + Username string + IsOrgSpecific bool +} + +func (c *Commands) AddUsername(ctx context.Context, username *AddUsername) (err error) { + existing, err := existingSchemaUserWithPermission(ctx, c, username.ResourceOwner, username.UserID) + if err != nil { + return err + } + id, err := c.idGenerator.Next() + if err != nil { + return err + } + events, err := c.eventstore.Push(ctx, + authenticator.NewUsernameCreatedEvent(ctx, + &authenticator.NewAggregate(id, existing.ResourceOwner).Aggregate, + existing.AggregateID, + username.IsOrgSpecific, + username.Username, + ), + ) + if err != nil { + return err + } + username.Details = pushedEventsToObjectDetails(events) + return nil +} + +func (c *Commands) DeleteUsername(ctx context.Context, resourceOwner, id string) (_ *domain.ObjectDetails, err error) { + existing, err := c.getSchemaUsernameExists(ctx, resourceOwner, id) + if err != nil { + return nil, err + } + if existing.Username == "" { + return nil, zerrors.ThrowNotFound(nil, "TODO", "TODO") + } + events, err := c.eventstore.Push(ctx, + authenticator.NewUsernameDeletedEvent(ctx, + &authenticator.NewAggregate(id, existing.ResourceOwner).Aggregate, + existing.IsOrgSpecific, + existing.Username, + ), + ) + if err != nil { + return nil, err + } + return pushedEventsToObjectDetails(events), nil +} + +func (c *Commands) getSchemaUsernameExists(ctx context.Context, resourceOwner, id string) (*UsernameV3WriteModel, error) { + writeModel := NewUsernameV3WriteModel(resourceOwner, id) + if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil { + return nil, err + } + return writeModel, nil +} + +func existingSchemaUserWithPermission(ctx context.Context, c *Commands, resourceOwner, userID string) (*UserV3WriteModel, error) { + if userID == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aS3Vz5t6BS", "Errors.IDMissing") + } + existingUser, err := c.getSchemaUserExists(ctx, resourceOwner, userID) + if err != nil { + return nil, err + } + if !existingUser.Exists() { + return nil, zerrors.ThrowNotFound(nil, "COMMAND-6T2xrOHxTx", "Errors.User.NotFound") + } + + _, err = c.getSchemaWriteModelByID(ctx, "", existingUser.SchemaID) + if err != nil { + return nil, err + } + if !existingUser.Exists() { + return nil, zerrors.ThrowNotFound(nil, "COMMAND-6T2xrOHxTx", "Errors.User.NotFound") + } + if err := c.checkPermissionUpdateUser(ctx, existingUser.ResourceOwner, existingUser.AggregateID); err != nil { + return nil, err + } + return existingUser, nil +} diff --git a/internal/command/user_v3_username_model.go b/internal/command/user_v3_username_model.go new file mode 100644 index 0000000000..765326faa4 --- /dev/null +++ b/internal/command/user_v3_username_model.go @@ -0,0 +1,48 @@ +package command + +import ( + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/repository/user/authenticator" + "github.com/zitadel/zitadel/internal/repository/user/schemauser" +) + +type UsernameV3WriteModel struct { + eventstore.WriteModel + Username string + IsOrgSpecific bool +} + +func NewUsernameV3WriteModel(resourceOwner, userID string) *UsernameV3WriteModel { + return &UsernameV3WriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: userID, + ResourceOwner: resourceOwner, + }, + } +} + +func (wm *UsernameV3WriteModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *authenticator.UsernameCreatedEvent: + wm.Username = e.Username + wm.IsOrgSpecific = e.IsOrgSpecific + case *authenticator.UsernameDeletedEvent: + wm.Username = "" + wm.IsOrgSpecific = false + } + } + return wm.WriteModel.Reduce() +} + +func (wm *UsernameV3WriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + ResourceOwner(wm.ResourceOwner). + AddQuery(). + AggregateTypes(schemauser.AggregateType). + AggregateIDs(wm.AggregateID). + EventTypes( + authenticator.UsernameCreatedType, + authenticator.UsernameDeletedType, + ).Builder() +} diff --git a/internal/repository/user/authenticator/username.go b/internal/repository/user/authenticator/username.go index 4171878ea1..84162b41e1 100644 --- a/internal/repository/user/authenticator/username.go +++ b/internal/repository/user/authenticator/username.go @@ -75,6 +75,9 @@ func NewUsernameCreatedEvent( aggregate, UsernameCreatedType, ), + UserID: userID, + IsOrgSpecific: isOrgSpecific, + Username: username, } }