From cbf5665f15fc10ade4f0ba37eb9b28320d49719b Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 15 Sep 2025 19:14:39 +0200 Subject: [PATCH] V16: Fix member validation endpoints (#20116) * Call the validation of member data * Fix return status * Refactor to remove duplicate code * Update src/Umbraco.Infrastructure/Services/MemberEditingService.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Removed `disableNotifications` from members validation --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: leekelleher --- .../Services/MemberEditingService.cs | 84 +++++++++++++++++-- .../member-validation.server.data-source.ts | 1 - 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Infrastructure/Services/MemberEditingService.cs b/src/Umbraco.Infrastructure/Services/MemberEditingService.cs index 974a70254f..6f062f1d43 100644 --- a/src/Umbraco.Infrastructure/Services/MemberEditingService.cs +++ b/src/Umbraco.Infrastructure/Services/MemberEditingService.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Models.ContentEditing.Validation; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services.OperationStatus; @@ -48,15 +49,21 @@ internal sealed class MemberEditingService : IMemberEditingService public Task GetAsync(Guid key) => Task.FromResult(_memberService.GetById(key)); - public async Task> ValidateCreateAsync(MemberCreateModel createModel) - => await _memberContentEditingService.ValidateAsync(createModel, createModel.ContentTypeKey); + public async Task> ValidateCreateAsync( + MemberCreateModel createModel) => + await ValidateMember(createModel, null, createModel.Password, createModel.ContentTypeKey); + + public async Task> ValidateUpdateAsync(Guid key, MemberUpdateModel updateModel) { IMember? member = _memberService.GetById(key); - return member is not null - ? await _memberContentEditingService.ValidateAsync(updateModel, member.ContentType.Key) - : Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, new ContentValidationResult()); + if (member is null) + { + return Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, new ContentValidationResult()); + } + + return await ValidateMember(updateModel, key, updateModel.NewPassword, member.ContentType.Key); } public async Task> CreateAsync(MemberCreateModel createModel, IUser user) @@ -221,6 +228,73 @@ internal sealed class MemberEditingService : IMemberEditingService contentDeleteResult.Result); } + private async Task> ValidateMember(MemberEditingModelBase model, Guid? memberKey, string? password, Guid memberTypeKey) + { + var validationErrors = new List(); + MemberEditingOperationStatus validationStatus = await ValidateMemberDataAsync(model, memberKey, password); + if (validationStatus is not MemberEditingOperationStatus.Success) + { + validationErrors.Add(MapStatusToPropertyValidationError(validationStatus)); + } + Attempt propertyValidation = await _memberContentEditingService.ValidateAsync(model, memberTypeKey); + + if (propertyValidation.Success is false) + { + if (propertyValidation.Status is ContentEditingOperationStatus.ContentTypeNotFound) + { + return Attempt.FailWithStatus(ContentEditingOperationStatus.ContentTypeNotFound, new ContentValidationResult()); + } + else + { + validationErrors.AddRange(propertyValidation.Result.ValidationErrors); + } + } + + var result = new ContentValidationResult { ValidationErrors = validationErrors }; + return result.ValidationErrors.Any() is false + ? Attempt.SucceedWithStatus(ContentEditingOperationStatus.Success, result) + : Attempt.FailWithStatus(ContentEditingOperationStatus.PropertyValidationError, result); + } + + private PropertyValidationError MapStatusToPropertyValidationError(MemberEditingOperationStatus memberEditingOperationStatus) + { + string alias; + string[] errorMessages; + switch (memberEditingOperationStatus) + { + case MemberEditingOperationStatus.InvalidName: + alias = "name"; + errorMessages = ["Invalid or empty name"]; + break; + case MemberEditingOperationStatus.InvalidPassword: + alias = "password"; + errorMessages = ["Invalid password"]; + break; + case MemberEditingOperationStatus.InvalidUsername: + alias = "username"; + errorMessages = ["Invalid username"]; + break; + case MemberEditingOperationStatus.InvalidEmail: + alias = "email"; + errorMessages = ["Invalid email"]; + break; + case MemberEditingOperationStatus.DuplicateUsername: + alias = "username"; + errorMessages = ["Duplicate username"]; + break; + case MemberEditingOperationStatus.DuplicateEmail: + alias = "email"; + errorMessages = ["Duplicate email"]; + break; + default: + alias = string.Empty; + errorMessages = []; + break; + } + + return new PropertyValidationError { Alias = alias, Culture = null, Segment = null, ErrorMessages = errorMessages, JsonPath = string.Empty }; + } + private async Task ValidateMemberDataAsync(MemberEditingModelBase model, Guid? memberKey, string? password) { if (model.Variants.FirstOrDefault(v => v.Culture is null && v.Segment is null)?.Name.IsNullOrWhiteSpace() is not false) diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/validation/member-validation.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/validation/member-validation.server.data-source.ts index 544069add2..b6889b741f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/validation/member-validation.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/validation/member-validation.server.data-source.ts @@ -94,7 +94,6 @@ export class UmbMemberValidationServerDataSource { path: { id: model.unique }, body, }), - { disableNotifications: true }, ); if (data && typeof data === 'string') {