2024-08-28 19:46:45 +00:00
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2024-09-25 13:31:31 +00:00
|
|
|
"time"
|
2024-08-28 19:46:45 +00:00
|
|
|
|
2024-09-25 13:31:31 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
|
|
|
"github.com/zitadel/zitadel/internal/crypto"
|
2024-08-28 19:46:45 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
2024-09-25 13:31:31 +00:00
|
|
|
domain_schema "github.com/zitadel/zitadel/internal/domain/schema"
|
2024-08-28 19:46:45 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
|
|
"github.com/zitadel/zitadel/internal/repository/user/schemauser"
|
2024-09-25 13:31:31 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
2024-08-28 19:46:45 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type UserV3WriteModel struct {
|
|
|
|
eventstore.WriteModel
|
|
|
|
|
|
|
|
PhoneWM bool
|
|
|
|
EmailWM bool
|
|
|
|
DataWM bool
|
|
|
|
|
|
|
|
SchemaID string
|
|
|
|
SchemaRevision uint64
|
|
|
|
|
|
|
|
Email string
|
|
|
|
IsEmailVerified bool
|
|
|
|
EmailVerifiedFailedCount int
|
2024-09-25 13:31:31 +00:00
|
|
|
EmailCode *VerifyCode
|
|
|
|
|
2024-08-28 19:46:45 +00:00
|
|
|
Phone string
|
|
|
|
IsPhoneVerified bool
|
|
|
|
PhoneVerifiedFailedCount int
|
2024-09-25 13:31:31 +00:00
|
|
|
PhoneCode *VerifyCode
|
2024-08-28 19:46:45 +00:00
|
|
|
|
|
|
|
Data json.RawMessage
|
|
|
|
|
2024-09-17 08:27:48 +00:00
|
|
|
Locked bool
|
|
|
|
State domain.UserState
|
2024-09-25 13:31:31 +00:00
|
|
|
|
|
|
|
checkPermission domain.PermissionCheck
|
|
|
|
writePermissionCheck bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) GetWriteModel() *eventstore.WriteModel {
|
|
|
|
return &wm.WriteModel
|
|
|
|
}
|
|
|
|
|
|
|
|
type VerifyCode struct {
|
|
|
|
Code *crypto.CryptoValue
|
|
|
|
CreationDate time.Time
|
|
|
|
Expiry time.Duration
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewExistsUserV3WriteModel(resourceOwner, userID string, checkPermission domain.PermissionCheck) *UserV3WriteModel {
|
|
|
|
return &UserV3WriteModel{
|
|
|
|
WriteModel: eventstore.WriteModel{
|
|
|
|
AggregateID: userID,
|
|
|
|
ResourceOwner: resourceOwner,
|
|
|
|
},
|
|
|
|
PhoneWM: false,
|
|
|
|
EmailWM: false,
|
|
|
|
DataWM: false,
|
|
|
|
checkPermission: checkPermission,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewUserV3WriteModel(resourceOwner, userID string, checkPermission domain.PermissionCheck) *UserV3WriteModel {
|
|
|
|
return &UserV3WriteModel{
|
|
|
|
WriteModel: eventstore.WriteModel{
|
|
|
|
AggregateID: userID,
|
|
|
|
ResourceOwner: resourceOwner,
|
|
|
|
},
|
|
|
|
PhoneWM: true,
|
|
|
|
EmailWM: true,
|
|
|
|
DataWM: true,
|
|
|
|
checkPermission: checkPermission,
|
|
|
|
}
|
2024-08-28 19:46:45 +00:00
|
|
|
}
|
|
|
|
|
2024-09-25 13:31:31 +00:00
|
|
|
func NewUserV3EmailWriteModel(resourceOwner, userID string, checkPermission domain.PermissionCheck) *UserV3WriteModel {
|
2024-08-28 19:46:45 +00:00
|
|
|
return &UserV3WriteModel{
|
|
|
|
WriteModel: eventstore.WriteModel{
|
|
|
|
AggregateID: userID,
|
|
|
|
ResourceOwner: resourceOwner,
|
|
|
|
},
|
2024-09-25 13:31:31 +00:00
|
|
|
EmailWM: true,
|
|
|
|
checkPermission: checkPermission,
|
2024-08-28 19:46:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-25 13:31:31 +00:00
|
|
|
func NewUserV3PhoneWriteModel(resourceOwner, userID string, checkPermission domain.PermissionCheck) *UserV3WriteModel {
|
2024-08-28 19:46:45 +00:00
|
|
|
return &UserV3WriteModel{
|
|
|
|
WriteModel: eventstore.WriteModel{
|
|
|
|
AggregateID: userID,
|
|
|
|
ResourceOwner: resourceOwner,
|
|
|
|
},
|
2024-09-25 13:31:31 +00:00
|
|
|
PhoneWM: true,
|
|
|
|
checkPermission: checkPermission,
|
2024-08-28 19:46:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) Reduce() error {
|
|
|
|
for _, event := range wm.Events {
|
|
|
|
switch e := event.(type) {
|
|
|
|
case *schemauser.CreatedEvent:
|
|
|
|
wm.SchemaID = e.SchemaID
|
2024-09-17 08:27:48 +00:00
|
|
|
wm.SchemaRevision = e.SchemaRevision
|
2024-08-28 19:46:45 +00:00
|
|
|
wm.Data = e.Data
|
2024-09-17 08:27:48 +00:00
|
|
|
wm.Locked = false
|
2024-08-28 19:46:45 +00:00
|
|
|
|
|
|
|
wm.State = domain.UserStateActive
|
|
|
|
case *schemauser.UpdatedEvent:
|
|
|
|
if e.SchemaID != nil {
|
|
|
|
wm.SchemaID = *e.SchemaID
|
|
|
|
}
|
|
|
|
if e.SchemaRevision != nil {
|
|
|
|
wm.SchemaRevision = *e.SchemaRevision
|
|
|
|
}
|
|
|
|
if len(e.Data) > 0 {
|
|
|
|
wm.Data = e.Data
|
|
|
|
}
|
|
|
|
case *schemauser.DeletedEvent:
|
|
|
|
wm.State = domain.UserStateDeleted
|
|
|
|
case *schemauser.EmailUpdatedEvent:
|
|
|
|
wm.Email = string(e.EmailAddress)
|
2024-09-17 08:27:48 +00:00
|
|
|
wm.IsEmailVerified = false
|
|
|
|
wm.EmailVerifiedFailedCount = 0
|
2024-09-25 13:31:31 +00:00
|
|
|
wm.EmailCode = nil
|
2024-08-28 19:46:45 +00:00
|
|
|
case *schemauser.EmailCodeAddedEvent:
|
|
|
|
wm.IsEmailVerified = false
|
|
|
|
wm.EmailVerifiedFailedCount = 0
|
2024-09-25 13:31:31 +00:00
|
|
|
wm.EmailCode = &VerifyCode{
|
|
|
|
Code: e.Code,
|
|
|
|
CreationDate: e.CreationDate(),
|
|
|
|
Expiry: e.Expiry,
|
|
|
|
}
|
2024-08-28 19:46:45 +00:00
|
|
|
case *schemauser.EmailVerifiedEvent:
|
|
|
|
wm.IsEmailVerified = true
|
|
|
|
wm.EmailVerifiedFailedCount = 0
|
2024-09-25 13:31:31 +00:00
|
|
|
wm.EmailCode = nil
|
2024-08-28 19:46:45 +00:00
|
|
|
case *schemauser.EmailVerificationFailedEvent:
|
|
|
|
wm.EmailVerifiedFailedCount += 1
|
2024-09-17 08:27:48 +00:00
|
|
|
case *schemauser.PhoneUpdatedEvent:
|
2024-08-28 19:46:45 +00:00
|
|
|
wm.Phone = string(e.PhoneNumber)
|
2024-09-17 08:27:48 +00:00
|
|
|
wm.IsPhoneVerified = false
|
|
|
|
wm.PhoneVerifiedFailedCount = 0
|
2024-09-25 13:31:31 +00:00
|
|
|
wm.EmailCode = nil
|
2024-08-28 19:46:45 +00:00
|
|
|
case *schemauser.PhoneCodeAddedEvent:
|
|
|
|
wm.IsPhoneVerified = false
|
|
|
|
wm.PhoneVerifiedFailedCount = 0
|
2024-09-25 13:31:31 +00:00
|
|
|
wm.PhoneCode = &VerifyCode{
|
|
|
|
Code: e.Code,
|
|
|
|
CreationDate: e.CreationDate(),
|
|
|
|
Expiry: e.Expiry,
|
|
|
|
}
|
2024-08-28 19:46:45 +00:00
|
|
|
case *schemauser.PhoneVerifiedEvent:
|
|
|
|
wm.PhoneVerifiedFailedCount = 0
|
|
|
|
wm.IsPhoneVerified = true
|
2024-09-25 13:31:31 +00:00
|
|
|
wm.PhoneCode = nil
|
2024-08-28 19:46:45 +00:00
|
|
|
case *schemauser.PhoneVerificationFailedEvent:
|
|
|
|
wm.PhoneVerifiedFailedCount += 1
|
2024-09-17 08:27:48 +00:00
|
|
|
case *schemauser.LockedEvent:
|
|
|
|
wm.Locked = true
|
|
|
|
case *schemauser.UnlockedEvent:
|
|
|
|
wm.Locked = false
|
|
|
|
case *schemauser.DeactivatedEvent:
|
|
|
|
wm.State = domain.UserStateInactive
|
|
|
|
case *schemauser.ActivatedEvent:
|
|
|
|
wm.State = domain.UserStateActive
|
2024-08-28 19:46:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return wm.WriteModel.Reduce()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) Query() *eventstore.SearchQueryBuilder {
|
2024-09-17 08:27:48 +00:00
|
|
|
builder := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent)
|
|
|
|
if wm.ResourceOwner != "" {
|
|
|
|
builder = builder.ResourceOwner(wm.ResourceOwner)
|
|
|
|
}
|
|
|
|
eventtypes := []eventstore.EventType{
|
|
|
|
schemauser.CreatedType,
|
|
|
|
schemauser.DeletedType,
|
|
|
|
schemauser.ActivatedType,
|
|
|
|
schemauser.DeactivatedType,
|
|
|
|
schemauser.LockedType,
|
|
|
|
schemauser.UnlockedType,
|
|
|
|
}
|
2024-08-28 19:46:45 +00:00
|
|
|
if wm.DataWM {
|
2024-09-17 08:27:48 +00:00
|
|
|
eventtypes = append(eventtypes,
|
2024-08-28 19:46:45 +00:00
|
|
|
schemauser.UpdatedType,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if wm.EmailWM {
|
2024-09-17 08:27:48 +00:00
|
|
|
eventtypes = append(eventtypes,
|
2024-08-28 19:46:45 +00:00
|
|
|
schemauser.EmailUpdatedType,
|
|
|
|
schemauser.EmailVerifiedType,
|
|
|
|
schemauser.EmailCodeAddedType,
|
|
|
|
schemauser.EmailVerificationFailedType,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if wm.PhoneWM {
|
2024-09-17 08:27:48 +00:00
|
|
|
eventtypes = append(eventtypes,
|
2024-08-28 19:46:45 +00:00
|
|
|
schemauser.PhoneUpdatedType,
|
|
|
|
schemauser.PhoneVerifiedType,
|
|
|
|
schemauser.PhoneCodeAddedType,
|
|
|
|
schemauser.PhoneVerificationFailedType,
|
|
|
|
)
|
|
|
|
}
|
2024-09-17 08:27:48 +00:00
|
|
|
return builder.AddQuery().
|
|
|
|
AggregateTypes(schemauser.AggregateType).
|
|
|
|
AggregateIDs(wm.AggregateID).
|
|
|
|
EventTypes(eventtypes...).Builder()
|
2024-08-28 19:46:45 +00:00
|
|
|
}
|
|
|
|
|
2024-09-25 13:31:31 +00:00
|
|
|
func (wm *UserV3WriteModel) NewCreated(
|
2024-08-28 19:46:45 +00:00
|
|
|
ctx context.Context,
|
2024-09-17 08:27:48 +00:00
|
|
|
schemaID string,
|
|
|
|
schemaRevision uint64,
|
2024-08-28 19:46:45 +00:00
|
|
|
data json.RawMessage,
|
2024-09-25 13:31:31 +00:00
|
|
|
email *Email,
|
|
|
|
phone *Phone,
|
2024-09-26 07:14:33 +00:00
|
|
|
emailCode func(context.Context) (*EncryptedCode, error),
|
|
|
|
phoneCode func(context.Context) (*EncryptedCode, string, error),
|
2024-09-25 13:31:31 +00:00
|
|
|
) (_ []eventstore.Command, codeEmail string, codePhone string, err error) {
|
|
|
|
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
|
|
|
return nil, "", "", err
|
|
|
|
}
|
|
|
|
if wm.Exists() {
|
|
|
|
return nil, "", "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-Nn8CRVlkeZ", "Errors.User.AlreadyExists")
|
|
|
|
}
|
|
|
|
events := []eventstore.Command{
|
|
|
|
schemauser.NewCreatedEvent(ctx,
|
|
|
|
UserV3AggregateFromWriteModel(&wm.WriteModel),
|
|
|
|
schemaID, schemaRevision, data,
|
|
|
|
),
|
|
|
|
}
|
|
|
|
if email != nil {
|
|
|
|
emailEvents, plainCodeEmail, err := wm.NewEmailCreate(ctx,
|
|
|
|
email,
|
2024-09-26 07:14:33 +00:00
|
|
|
emailCode,
|
2024-09-25 13:31:31 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", "", err
|
|
|
|
}
|
|
|
|
if plainCodeEmail != "" {
|
|
|
|
codeEmail = plainCodeEmail
|
|
|
|
}
|
|
|
|
events = append(events, emailEvents...)
|
|
|
|
}
|
|
|
|
|
|
|
|
if phone != nil {
|
|
|
|
phoneEvents, plainCodePhone, err := wm.NewPhoneCreate(ctx,
|
|
|
|
phone,
|
2024-09-26 07:14:33 +00:00
|
|
|
phoneCode,
|
2024-09-25 13:31:31 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", "", err
|
|
|
|
}
|
|
|
|
if plainCodePhone != "" {
|
|
|
|
codePhone = plainCodePhone
|
|
|
|
}
|
|
|
|
events = append(events, phoneEvents...)
|
|
|
|
}
|
|
|
|
|
|
|
|
return events, codeEmail, codePhone, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) getSchemaRoleForWrite(ctx context.Context, resourceOwner, userID string) (domain_schema.Role, error) {
|
|
|
|
if userID == authz.GetCtxData(ctx).UserID {
|
|
|
|
return domain_schema.RoleSelf, nil
|
|
|
|
}
|
|
|
|
if err := wm.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, userID); err != nil {
|
|
|
|
return domain_schema.RoleUnspecified, err
|
|
|
|
}
|
|
|
|
return domain_schema.RoleOwner, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) validateData(ctx context.Context, data []byte, schemaWM *UserSchemaWriteModel) (string, uint64, error) {
|
|
|
|
// get role for permission check in schema through extension
|
|
|
|
role, err := wm.getSchemaRoleForWrite(ctx, wm.ResourceOwner, wm.AggregateID)
|
|
|
|
if err != nil {
|
|
|
|
return "", 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
schema, err := domain_schema.NewSchema(role, bytes.NewReader(schemaWM.Schema))
|
|
|
|
if err != nil {
|
|
|
|
return "", 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// if data not changed but a new schema or revision should be used
|
|
|
|
if data == nil {
|
|
|
|
data = wm.Data
|
|
|
|
}
|
|
|
|
var v interface{}
|
|
|
|
if err := json.Unmarshal(data, &v); err != nil {
|
|
|
|
return "", 0, zerrors.ThrowInvalidArgument(nil, "COMMAND-7o3ZGxtXUz", "Errors.User.Invalid")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := schema.Validate(v); err != nil {
|
|
|
|
return "", 0, zerrors.ThrowPreconditionFailed(nil, "COMMAND-SlKXqLSeL6", "Errors.UserSchema.Data.Invalid")
|
|
|
|
}
|
|
|
|
return schemaWM.AggregateID, schemaWM.SchemaRevision, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) NewUpdate(
|
|
|
|
ctx context.Context,
|
|
|
|
schemaWM *UserSchemaWriteModel,
|
|
|
|
user *SchemaUser,
|
|
|
|
email *Email,
|
|
|
|
phone *Phone,
|
2024-09-26 07:14:33 +00:00
|
|
|
emailCode func(context.Context) (*EncryptedCode, error),
|
|
|
|
phoneCode func(context.Context) (*EncryptedCode, string, error),
|
2024-09-25 13:31:31 +00:00
|
|
|
) (_ []eventstore.Command, codeEmail string, codePhone string, err error) {
|
|
|
|
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
|
|
|
return nil, "", "", err
|
|
|
|
}
|
|
|
|
if !wm.Exists() {
|
|
|
|
return nil, "", "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-Nn8CRVlkeZ", "Errors.User.NotFound")
|
|
|
|
}
|
|
|
|
events := make([]eventstore.Command, 0)
|
|
|
|
if user != nil {
|
|
|
|
schemaID, schemaRevision, err := wm.validateData(ctx, user.Data, schemaWM)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", "", err
|
|
|
|
}
|
|
|
|
userEvents := wm.newUpdatedEvents(ctx,
|
|
|
|
schemaID,
|
|
|
|
schemaRevision,
|
|
|
|
user.Data,
|
|
|
|
)
|
|
|
|
events = append(events, userEvents...)
|
|
|
|
}
|
|
|
|
if email != nil {
|
|
|
|
emailEvents, plainCodeEmail, err := wm.NewEmailUpdate(ctx,
|
|
|
|
email,
|
2024-09-26 07:14:33 +00:00
|
|
|
emailCode,
|
2024-09-25 13:31:31 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", "", err
|
|
|
|
}
|
|
|
|
if plainCodeEmail != "" {
|
|
|
|
codeEmail = plainCodeEmail
|
|
|
|
}
|
|
|
|
events = append(events, emailEvents...)
|
|
|
|
}
|
|
|
|
|
|
|
|
if phone != nil {
|
|
|
|
phoneEvents, plainCodePhone, err := wm.NewPhoneCreate(ctx,
|
|
|
|
phone,
|
2024-09-26 07:14:33 +00:00
|
|
|
phoneCode,
|
2024-09-25 13:31:31 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", "", err
|
|
|
|
}
|
|
|
|
if plainCodePhone != "" {
|
|
|
|
codePhone = plainCodePhone
|
|
|
|
}
|
|
|
|
events = append(events, phoneEvents...)
|
|
|
|
}
|
|
|
|
|
|
|
|
return events, codeEmail, codePhone, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) newUpdatedEvents(
|
|
|
|
ctx context.Context,
|
|
|
|
schemaID string,
|
|
|
|
schemaRevision uint64,
|
|
|
|
data json.RawMessage,
|
|
|
|
) []eventstore.Command {
|
2024-08-28 19:46:45 +00:00
|
|
|
changes := make([]schemauser.Changes, 0)
|
2024-09-17 08:27:48 +00:00
|
|
|
if wm.SchemaID != schemaID {
|
|
|
|
changes = append(changes, schemauser.ChangeSchemaID(schemaID))
|
2024-08-28 19:46:45 +00:00
|
|
|
}
|
2024-09-17 08:27:48 +00:00
|
|
|
if wm.SchemaRevision != schemaRevision {
|
|
|
|
changes = append(changes, schemauser.ChangeSchemaRevision(schemaRevision))
|
2024-08-28 19:46:45 +00:00
|
|
|
}
|
2024-09-17 08:27:48 +00:00
|
|
|
if data != nil && !bytes.Equal(wm.Data, data) {
|
2024-08-28 19:46:45 +00:00
|
|
|
changes = append(changes, schemauser.ChangeData(data))
|
|
|
|
}
|
|
|
|
if len(changes) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
2024-09-25 13:31:31 +00:00
|
|
|
return []eventstore.Command{schemauser.NewUpdatedEvent(ctx, UserV3AggregateFromWriteModel(&wm.WriteModel), changes)}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) NewDelete(
|
|
|
|
ctx context.Context,
|
|
|
|
) (_ []eventstore.Command, err error) {
|
|
|
|
if !wm.Exists() {
|
|
|
|
return nil, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound")
|
|
|
|
}
|
|
|
|
if err := wm.checkPermissionDelete(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return []eventstore.Command{schemauser.NewDeletedEvent(ctx, UserV3AggregateFromWriteModel(&wm.WriteModel))}, nil
|
|
|
|
|
2024-08-28 19:46:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func UserV3AggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
|
|
|
|
return &eventstore.Aggregate{
|
|
|
|
ID: wm.AggregateID,
|
|
|
|
Type: schemauser.AggregateType,
|
|
|
|
ResourceOwner: wm.ResourceOwner,
|
|
|
|
InstanceID: wm.InstanceID,
|
|
|
|
Version: schemauser.AggregateVersion,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) Exists() bool {
|
|
|
|
return wm.State != domain.UserStateDeleted && wm.State != domain.UserStateUnspecified
|
|
|
|
}
|
2024-09-25 13:31:31 +00:00
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) checkPermissionWrite(
|
|
|
|
ctx context.Context,
|
|
|
|
resourceOwner string,
|
|
|
|
userID string,
|
|
|
|
) error {
|
|
|
|
if wm.writePermissionCheck {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if userID != "" && userID == authz.GetCtxData(ctx).UserID {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if err := wm.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, userID); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
wm.writePermissionCheck = true
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) checkPermissionDelete(
|
|
|
|
ctx context.Context,
|
|
|
|
resourceOwner string,
|
|
|
|
userID string,
|
|
|
|
) error {
|
|
|
|
if userID != "" && userID == authz.GetCtxData(ctx).UserID {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return wm.checkPermission(ctx, domain.PermissionUserDelete, resourceOwner, userID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) NewEmailCreate(
|
|
|
|
ctx context.Context,
|
|
|
|
email *Email,
|
|
|
|
code func(context.Context) (*EncryptedCode, error),
|
|
|
|
) (_ []eventstore.Command, plainCode string, err error) {
|
|
|
|
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
if email == nil || wm.Email == string(email.Address) {
|
|
|
|
return nil, "", nil
|
|
|
|
}
|
|
|
|
events := []eventstore.Command{
|
|
|
|
schemauser.NewEmailUpdatedEvent(ctx,
|
|
|
|
UserV3AggregateFromWriteModel(&wm.WriteModel),
|
|
|
|
email.Address,
|
|
|
|
),
|
|
|
|
}
|
|
|
|
if email.Verified {
|
|
|
|
events = append(events, wm.newEmailVerifiedEvent(ctx))
|
|
|
|
} else {
|
|
|
|
codeEvent, code, err := wm.newEmailCodeAddedEvent(ctx, code, email.URLTemplate, email.ReturnCode)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
events = append(events, codeEvent)
|
|
|
|
if code != "" {
|
|
|
|
plainCode = code
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return events, plainCode, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) NewEmailUpdate(
|
|
|
|
ctx context.Context,
|
|
|
|
email *Email,
|
|
|
|
code func(context.Context) (*EncryptedCode, error),
|
|
|
|
) (_ []eventstore.Command, plainCode string, err error) {
|
|
|
|
if !wm.EmailWM {
|
|
|
|
return nil, "", nil
|
|
|
|
}
|
|
|
|
if !wm.Exists() {
|
|
|
|
return nil, "", zerrors.ThrowNotFound(nil, "COMMAND-nJ0TQFuRmP", "Errors.User.NotFound")
|
|
|
|
}
|
|
|
|
return wm.NewEmailCreate(ctx, email, code)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) NewEmailVerify(
|
|
|
|
ctx context.Context,
|
|
|
|
verify func(creationDate time.Time, expiry time.Duration, cryptoCode *crypto.CryptoValue) error,
|
|
|
|
) ([]eventstore.Command, error) {
|
|
|
|
if !wm.EmailWM {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
if !wm.Exists() {
|
|
|
|
return nil, zerrors.ThrowNotFound(nil, "COMMAND-qbGyMPvjvj", "Errors.User.NotFound")
|
|
|
|
}
|
|
|
|
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if wm.EmailCode == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
if err := verify(wm.EmailCode.CreationDate, wm.EmailCode.Expiry, wm.EmailCode.Code); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return []eventstore.Command{wm.newEmailVerifiedEvent(ctx)}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) newEmailVerifiedEvent(
|
|
|
|
ctx context.Context,
|
|
|
|
) *schemauser.EmailVerifiedEvent {
|
|
|
|
return schemauser.NewEmailVerifiedEvent(ctx, UserV3AggregateFromWriteModel(&wm.WriteModel))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) NewResendEmailCode(
|
|
|
|
ctx context.Context,
|
|
|
|
code func(context.Context) (*EncryptedCode, error),
|
|
|
|
urlTemplate string,
|
|
|
|
isReturnCode bool,
|
|
|
|
) (_ []eventstore.Command, plainCode string, err error) {
|
|
|
|
if !wm.EmailWM {
|
|
|
|
return nil, "", nil
|
|
|
|
}
|
|
|
|
if !wm.Exists() {
|
|
|
|
return nil, "", zerrors.ThrowNotFound(nil, "COMMAND-EajeF6ypOV", "Errors.User.NotFound")
|
|
|
|
}
|
|
|
|
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
if wm.EmailCode == nil {
|
|
|
|
return nil, "", zerrors.ThrowPreconditionFailed(err, "COMMAND-QRkNTBwF8q", "Errors.User.Code.Empty")
|
|
|
|
}
|
|
|
|
event, plainCode, err := wm.newEmailCodeAddedEvent(ctx, code, urlTemplate, isReturnCode)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
return []eventstore.Command{event}, plainCode, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) newEmailCodeAddedEvent(
|
|
|
|
ctx context.Context,
|
|
|
|
code func(context.Context) (*EncryptedCode, error),
|
|
|
|
urlTemplate string,
|
|
|
|
isReturnCode bool,
|
|
|
|
) (_ *schemauser.EmailCodeAddedEvent, plainCode string, err error) {
|
|
|
|
cryptoCode, err := code(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
if isReturnCode {
|
|
|
|
plainCode = cryptoCode.Plain
|
|
|
|
}
|
|
|
|
return schemauser.NewEmailCodeAddedEvent(ctx,
|
|
|
|
UserV3AggregateFromWriteModel(&wm.WriteModel),
|
|
|
|
cryptoCode.Crypted,
|
|
|
|
cryptoCode.Expiry,
|
|
|
|
urlTemplate,
|
|
|
|
isReturnCode,
|
|
|
|
), plainCode, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) NewPhoneCreate(
|
|
|
|
ctx context.Context,
|
|
|
|
phone *Phone,
|
2024-09-26 07:14:33 +00:00
|
|
|
code func(context.Context) (*EncryptedCode, string, error),
|
2024-09-25 13:31:31 +00:00
|
|
|
) (_ []eventstore.Command, plainCode string, err error) {
|
|
|
|
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
if phone == nil || wm.Phone == string(phone.Number) {
|
|
|
|
return nil, "", nil
|
|
|
|
}
|
|
|
|
events := []eventstore.Command{
|
|
|
|
schemauser.NewPhoneUpdatedEvent(ctx,
|
|
|
|
UserV3AggregateFromWriteModel(&wm.WriteModel),
|
|
|
|
phone.Number,
|
|
|
|
),
|
|
|
|
}
|
|
|
|
if phone.Verified {
|
|
|
|
events = append(events, wm.newPhoneVerifiedEvent(ctx))
|
|
|
|
} else {
|
|
|
|
codeEvent, code, err := wm.newPhoneCodeAddedEvent(ctx, code, phone.ReturnCode)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
events = append(events, codeEvent)
|
|
|
|
if code != "" {
|
|
|
|
plainCode = code
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return events, plainCode, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) NewPhoneUpdate(
|
|
|
|
ctx context.Context,
|
|
|
|
phone *Phone,
|
2024-09-26 07:14:33 +00:00
|
|
|
code func(context.Context) (*EncryptedCode, string, error),
|
2024-09-25 13:31:31 +00:00
|
|
|
) (_ []eventstore.Command, plainCode string, err error) {
|
|
|
|
if !wm.PhoneWM {
|
|
|
|
return nil, "", nil
|
|
|
|
}
|
|
|
|
if !wm.Exists() {
|
|
|
|
return nil, "", zerrors.ThrowNotFound(nil, "COMMAND-b33QAVgel6", "Errors.User.NotFound")
|
|
|
|
}
|
|
|
|
return wm.NewPhoneCreate(ctx, phone, code)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) NewPhoneVerify(
|
|
|
|
ctx context.Context,
|
|
|
|
verify func(creationDate time.Time, expiry time.Duration, cryptoCode *crypto.CryptoValue) error,
|
|
|
|
) ([]eventstore.Command, error) {
|
|
|
|
if !wm.PhoneWM {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
if !wm.Exists() {
|
|
|
|
return nil, zerrors.ThrowNotFound(nil, "COMMAND-bx2OLtgGNS", "Errors.User.NotFound")
|
|
|
|
}
|
|
|
|
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if wm.PhoneCode == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
if err := verify(wm.PhoneCode.CreationDate, wm.PhoneCode.Expiry, wm.PhoneCode.Code); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return []eventstore.Command{wm.newPhoneVerifiedEvent(ctx)}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) newPhoneVerifiedEvent(
|
|
|
|
ctx context.Context,
|
|
|
|
) *schemauser.PhoneVerifiedEvent {
|
|
|
|
return schemauser.NewPhoneVerifiedEvent(ctx, UserV3AggregateFromWriteModel(&wm.WriteModel))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) NewResendPhoneCode(
|
|
|
|
ctx context.Context,
|
2024-09-26 07:14:33 +00:00
|
|
|
code func(context.Context) (*EncryptedCode, string, error),
|
2024-09-25 13:31:31 +00:00
|
|
|
isReturnCode bool,
|
|
|
|
) (_ []eventstore.Command, plainCode string, err error) {
|
|
|
|
if !wm.PhoneWM {
|
|
|
|
return nil, "", nil
|
|
|
|
}
|
|
|
|
if !wm.Exists() {
|
|
|
|
return nil, "", zerrors.ThrowNotFound(nil, "COMMAND-z8Bu9vuL9s", "Errors.User.NotFound")
|
|
|
|
}
|
|
|
|
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
if wm.PhoneCode == nil {
|
|
|
|
return nil, "", zerrors.ThrowPreconditionFailed(err, "COMMAND-fEsHdqECzb", "Errors.User.Code.Empty")
|
|
|
|
}
|
|
|
|
event, plainCode, err := wm.newPhoneCodeAddedEvent(ctx, code, isReturnCode)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
return []eventstore.Command{event}, plainCode, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wm *UserV3WriteModel) newPhoneCodeAddedEvent(
|
|
|
|
ctx context.Context,
|
2024-09-26 07:14:33 +00:00
|
|
|
code func(context.Context) (*EncryptedCode, string, error),
|
2024-09-25 13:31:31 +00:00
|
|
|
isReturnCode bool,
|
|
|
|
) (_ *schemauser.PhoneCodeAddedEvent, plainCode string, err error) {
|
2024-09-26 07:14:33 +00:00
|
|
|
cryptoCode, generatorID, err := code(ctx)
|
2024-09-25 13:31:31 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
if isReturnCode {
|
|
|
|
plainCode = cryptoCode.Plain
|
|
|
|
}
|
|
|
|
return schemauser.NewPhoneCodeAddedEvent(ctx,
|
|
|
|
UserV3AggregateFromWriteModel(&wm.WriteModel),
|
2024-09-26 07:14:33 +00:00
|
|
|
cryptoCode.CryptedCode(),
|
|
|
|
cryptoCode.CodeExpiry(),
|
2024-09-25 13:31:31 +00:00
|
|
|
isReturnCode,
|
2024-09-26 07:14:33 +00:00
|
|
|
generatorID,
|
2024-09-25 13:31:31 +00:00
|
|
|
), plainCode, nil
|
|
|
|
}
|