Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/Application/Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,20 @@
<ItemGroup>
<ProjectReference Include="..\Domain\Domain.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\Features\Identity\DTOs\ApplicationUserDtoValidator.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ApplicationUserDtoValidator.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Features\Identity\DTOs\ApplicationUserDtoValidator.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ApplicationUserDtoValidator.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\Common\" />
</ItemGroup>
</Project>
35 changes: 18 additions & 17 deletions src/Application/Common/Security/ChangePasswordModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CleanArchitecture.Blazor.Application.Common.Interfaces.Identity;
using static CleanArchitecture.Blazor.Application.Features.Identity.DTOs.ApplicationUserDto;

namespace CleanArchitecture.Blazor.Application.Common.Security;

Expand All @@ -12,29 +13,29 @@ public class ChangePasswordModel
public class ChangePasswordModelValidator : AbstractValidator<ChangePasswordModel>
{
private readonly IIdentitySettings _identitySettings;
private readonly IStringLocalizer<ChangePasswordModelValidator> _localizer;
private readonly IStringLocalizer<ApplicationUserDtoValidator> _localizer;

public ChangePasswordModelValidator(IIdentitySettings identitySettings,
IStringLocalizer<ChangePasswordModelValidator> localizer)
IStringLocalizer<ApplicationUserDtoValidator> localizer)
{
_identitySettings = identitySettings;
_localizer = localizer;
RuleFor(p => p.NewPassword).NotEmpty().WithMessage(_localizer["CannotBeEmpty"])
.MinimumLength(_identitySettings.RequiredLength)
.WithMessage(string.Format(_localizer["MinLength"], _identitySettings.RequiredLength))
.MaximumLength(_identitySettings.MaxLength)
.WithMessage(string.Format(_localizer["MaxLength"], _identitySettings.MaxLength))
.Matches(_identitySettings.RequireUpperCase ? @"[A-Z]+" : string.Empty)
.WithMessage(_localizer["MustContainUpperCase"])
.Matches(_identitySettings.RequireLowerCase ? @"[a-z]+" : string.Empty)
.WithMessage(_localizer["MustContainLowerCase"])
.Matches(_identitySettings.RequireDigit ? @"[0-9]+" : string.Empty)
.WithMessage(_localizer["MustContainDigit"])
.Matches(_identitySettings.RequireNonAlphanumeric ? @"[\@\!\?\*\.]+" : string.Empty)
.WithMessage(_localizer["MustContainNonAlphanumericCharacter"]);
RuleFor(p => p.NewPassword).NotEmpty().WithMessage(_localizer["New password cannot be empty"])
.MinimumLength(_identitySettings.RequiredLength)
.WithMessage(_localizer["Password must be at least {0} characters long", _identitySettings.RequiredLength])
.MaximumLength(_identitySettings.MaxLength)
.WithMessage(_localizer["Password cannot be longer than {0} characters", _identitySettings.MaxLength])
.Matches(_identitySettings.RequireUpperCase ? @"[A-Z]+" : string.Empty)
.WithMessage(_localizer["Password must contain at least one uppercase letter"])
.Matches(_identitySettings.RequireLowerCase ? @"[a-z]+" : string.Empty)
.WithMessage(_localizer["Password must contain at least one lowercase letter"])
.Matches(_identitySettings.RequireDigit ? @"[0-9]+" : string.Empty)
.WithMessage(_localizer["Password must contain at least one digit"])
.Matches(_identitySettings.RequireNonAlphanumeric ? @"[\@\!\?\*\.]+" : string.Empty)
.WithMessage(_localizer["Password must contain at least one non-alphanumeric character (e.g., @, !, ?, *, .)"]);

RuleFor(x => x.ConfirmPassword)
.Equal(x => x.NewPassword);
.Equal(x => x.NewPassword).WithMessage(_localizer["Confirm password must match the new password"]);

}
}
32 changes: 24 additions & 8 deletions src/Application/Common/Security/LoginFormModel.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
using CleanArchitecture.Blazor.Application.Common.Interfaces.Identity;
using static CleanArchitecture.Blazor.Application.Features.Identity.DTOs.ApplicationUserDto;

namespace CleanArchitecture.Blazor.Application.Common.Security;

public class LoginFormModel
Expand All @@ -9,17 +12,30 @@ public class LoginFormModel

public class LoginFormModelFluentValidator : AbstractValidator<LoginFormModel>
{
private readonly IStringLocalizer<LoginFormModelFluentValidator> _localizer;
private readonly IIdentitySettings _identitySettings;
private readonly IStringLocalizer<ApplicationUserDtoValidator> _localizer;

public LoginFormModelFluentValidator(IStringLocalizer<LoginFormModelFluentValidator> localizer)
public LoginFormModelFluentValidator(
IIdentitySettings identitySettings,
IStringLocalizer<ApplicationUserDtoValidator> localizer)
{
_identitySettings = identitySettings;
_localizer = localizer;
RuleFor(x => x.UserName)
.NotEmpty().WithMessage(_localizer["Your username cannot be empty."])
.Length(2, 100).WithMessage(_localizer["Username must be between 2 and 100 characters."]);
RuleFor(p => p.Password)
.NotEmpty().WithMessage(_localizer["Your password cannot be empty"])
.MinimumLength(6).WithMessage(_localizer["Your password length must be at least 6 characters."])
.MaximumLength(16).WithMessage(_localizer["Your password length must not exceed 16 characters."]);
.NotEmpty().WithMessage(_localizer["User name cannot be empty"])
.Length(2, 100).WithMessage(_localizer["User name must be between 2 and 100 characters"]);
RuleFor(p => p.Password).NotEmpty().WithMessage(_localizer["Password cannot be empty"])
.MinimumLength(_identitySettings.RequiredLength)
.WithMessage(_localizer["Password must be at least {0} characters long", _identitySettings.RequiredLength])
.MaximumLength(_identitySettings.MaxLength)
.WithMessage(_localizer["Password cannot be longer than {0} characters", _identitySettings.MaxLength])
.Matches(_identitySettings.RequireUpperCase ? @"[A-Z]+" : string.Empty)
.WithMessage(_localizer["Password must contain at least one uppercase letter"])
.Matches(_identitySettings.RequireLowerCase ? @"[a-z]+" : string.Empty)
.WithMessage(_localizer["Password must contain at least one lowercase letter"])
.Matches(_identitySettings.RequireDigit ? @"[0-9]+" : string.Empty)
.WithMessage(_localizer["Password must contain at least one digit"])
.Matches(_identitySettings.RequireNonAlphanumeric ? @"[\@\!\?\*\.]+" : string.Empty)
.WithMessage(_localizer["Password must contain at least one non-alphanumeric character (e.g., @, !, ?, *, .)"]);
}
}
36 changes: 19 additions & 17 deletions src/Application/Common/Security/RegisterFormModelFluentValidator.cs
Original file line number Diff line number Diff line change
@@ -1,42 +1,44 @@
using CleanArchitecture.Blazor.Application.Common.Interfaces.Identity;
using static CleanArchitecture.Blazor.Application.Features.Identity.DTOs.ApplicationUserDto;

namespace CleanArchitecture.Blazor.Application.Common.Security;

public class RegisterFormModelFluentValidator : AbstractValidator<RegisterFormModel>
{
private readonly IIdentitySettings _identitySettings;
private readonly IStringLocalizer<RegisterFormModelFluentValidator> _localizer;
private readonly IStringLocalizer<ApplicationUserDtoValidator> _localizer;

public RegisterFormModelFluentValidator(
IStringLocalizer<RegisterFormModelFluentValidator> localizer,
IStringLocalizer<ApplicationUserDtoValidator> localizer,
IIdentitySettings identitySettings)
{
_localizer = localizer;
_identitySettings = identitySettings;

RuleFor(x => x.UserName)
.NotEmpty()
.Length(2, 100);
.NotEmpty().WithMessage(_localizer["User name cannot be empty"])
.Length(2, 100).WithMessage(_localizer["User name must be between 2 and 100 characters"]);
RuleFor(x => x.Email)
.NotEmpty()
.MaximumLength(255)
.EmailAddress();
RuleFor(p => p.Password).NotEmpty().WithMessage(_localizer["CannotBeEmpty"])
.MinimumLength(_identitySettings!.RequiredLength)
.WithMessage(string.Format(_localizer["MinLength"], _identitySettings.RequiredLength))
.NotEmpty().WithMessage(_localizer["E-mail cannot be empty"])
.MaximumLength(100).WithMessage(_localizer["E-mail must be less than 100 characters"])
.EmailAddress().WithMessage(_localizer["E-mail must be a valid email address"]);
RuleFor(p => p.Password).NotEmpty().WithMessage(_localizer["Password cannot be empty"])
.MinimumLength(_identitySettings.RequiredLength)
.WithMessage(_localizer["Password must be at least {0} characters long", _identitySettings.RequiredLength])
.MaximumLength(_identitySettings.MaxLength)
.WithMessage(string.Format(_localizer["MaxLength"], _identitySettings.MaxLength))
.WithMessage(_localizer["Password cannot be longer than {0} characters", _identitySettings.MaxLength])
.Matches(_identitySettings.RequireUpperCase ? @"[A-Z]+" : string.Empty)
.WithMessage(_localizer["MustContainUpperCase"])
.WithMessage(_localizer["Password must contain at least one uppercase letter"])
.Matches(_identitySettings.RequireLowerCase ? @"[a-z]+" : string.Empty)
.WithMessage(_localizer["MustContainLowerCase"])
.WithMessage(_localizer["Password must contain at least one lowercase letter"])
.Matches(_identitySettings.RequireDigit ? @"[0-9]+" : string.Empty)
.WithMessage(_localizer["MustContainDigit"])
.WithMessage(_localizer["Password must contain at least one digit"])
.Matches(_identitySettings.RequireNonAlphanumeric ? @"[\@\!\?\*\.]+" : string.Empty)
.WithMessage(_localizer["MustContainNonAlphanumericCharacter"]);
.WithMessage(_localizer["Password must contain at least one non-alphanumeric character (e.g., @, !, ?, *, .)"]);
RuleFor(x => x.ConfirmPassword)
.Equal(x => x.Password);
.Equal(x => x.Password).WithMessage(_localizer["Confirm password must match the password"]);

RuleFor(x => x.AgreeToTerms)
.Equal(true);
.Equal(true).WithMessage(_localizer["You must agree to the terms and conditions"]);
}
}
39 changes: 22 additions & 17 deletions src/Application/Common/Security/ResetPasswordFormModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CleanArchitecture.Blazor.Application.Common.Interfaces.Identity;
using static CleanArchitecture.Blazor.Application.Features.Identity.DTOs.ApplicationUserDto;

namespace CleanArchitecture.Blazor.Application.Common.Security;

Expand All @@ -14,30 +15,34 @@ public class ResetPasswordFormModel

public class ResetPasswordFormModelValidator : AbstractValidator<ResetPasswordFormModel>
{
private readonly IStringLocalizer<ResetPasswordFormModelValidator> _localizer;
private readonly IStringLocalizer<ApplicationUserDtoValidator> _localizer;
private readonly IIdentitySettings _identitySettings;

public ResetPasswordFormModelValidator(IStringLocalizer<ResetPasswordFormModelValidator> localizer,
public ResetPasswordFormModelValidator(IStringLocalizer<ApplicationUserDtoValidator> localizer,
IIdentitySettings identitySettings)
{
_localizer = localizer;
_identitySettings = identitySettings;
RuleFor(p => p.Password).NotEmpty().WithMessage(_localizer["CannotBeEmpty"])
.MinimumLength(_identitySettings.RequiredLength)
.WithMessage(string.Format(_localizer["MinLength"], _identitySettings.RequiredLength))
.MaximumLength(_identitySettings.MaxLength)
.WithMessage(string.Format(_localizer["MaxLength"], _identitySettings.MaxLength))
.Matches(_identitySettings.RequireUpperCase ? @"[A-Z]+" : string.Empty)
.WithMessage(_localizer["MustContainUpperCase"])
.Matches(_identitySettings.RequireLowerCase ? @"[a-z]+" : string.Empty)
.WithMessage(_localizer["MustContainLowerCase"])
.Matches(_identitySettings.RequireDigit ? @"[0-9]+" : string.Empty)
.WithMessage(_localizer["MustContainDigit"])
.Matches(_identitySettings.RequireNonAlphanumeric ? @"[\@\!\?\*\.]+" : string.Empty)
.WithMessage(_localizer["MustContainNonAlphanumericCharacter"]);
RuleFor(x => x.UserName)
.NotEmpty().WithMessage(_localizer["User name cannot be empty."])
.Length(2, 100).WithMessage(_localizer["User name must be between 2 and 100 characters."]);
RuleFor(p => p.Password).NotEmpty().WithMessage(_localizer["Password cannot be empty"])
.MinimumLength(_identitySettings.RequiredLength)
.WithMessage(_localizer["Password must be at least {0} characters long", _identitySettings.RequiredLength])
.MaximumLength(_identitySettings.MaxLength)
.WithMessage(_localizer["Password cannot be longer than {0} characters", _identitySettings.MaxLength])
.Matches(_identitySettings.RequireUpperCase ? @"[A-Z]+" : string.Empty)
.WithMessage(_localizer["Password must contain at least one uppercase letter"])
.Matches(_identitySettings.RequireLowerCase ? @"[a-z]+" : string.Empty)
.WithMessage(_localizer["Password must contain at least one lowercase letter"])
.Matches(_identitySettings.RequireDigit ? @"[0-9]+" : string.Empty)
.WithMessage(_localizer["Password must contain at least one digit"])
.Matches(_identitySettings.RequireNonAlphanumeric ? @"[\@\!\?\*\.]+" : string.Empty)
.WithMessage(_localizer["Password must contain at least one non-alphanumeric character (e.g., @, !, ?, *, .)"]);
RuleFor(x => x.ConfirmPassword)
.Equal(x => x.Password);

.Equal(x => x.Password).WithMessage(_localizer["Confirm password must match the password"]);


}

}
25 changes: 20 additions & 5 deletions src/Application/Common/Security/UserProfile.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace CleanArchitecture.Blazor.Application.Common.Security;
using FluentValidation;
using static CleanArchitecture.Blazor.Application.Features.Identity.DTOs.ApplicationUserDto;

namespace CleanArchitecture.Blazor.Application.Common.Security;

public class UserProfile
{
Expand All @@ -20,12 +23,24 @@ public class UserProfile

public class UserProfileEditValidator : AbstractValidator<UserProfile>
{
public UserProfileEditValidator()
private readonly IStringLocalizer<ApplicationUserDtoValidator> _localizer;

public UserProfileEditValidator(IStringLocalizer<ApplicationUserDtoValidator> localizer)
{
_localizer = localizer;
RuleFor(x => x.UserName)
.NotEmpty().WithMessage(_localizer["User name cannot be empty"])
.Length(2, 100).WithMessage(_localizer["User name must be between 2 and 100 characters"]);
RuleFor(x => x.Email)
.NotEmpty().WithMessage(_localizer["E-mail cannot be empty"])
.MaximumLength(100).WithMessage(_localizer["E-mail must be less than 100 characters"])
.EmailAddress().WithMessage(_localizer["E-mail must be a valid email address"]);

RuleFor(x => x.DisplayName)
.MaximumLength(128)
.NotEmpty();
.MaximumLength(128).WithMessage(_localizer["Display name must be less than 128 characters"]);

RuleFor(x => x.PhoneNumber)
.MaximumLength(2);
.MaximumLength(20).WithMessage(_localizer["Phone number must be less than 20 digits"]);

}
}
38 changes: 25 additions & 13 deletions src/Application/Features/Identity/DTOs/ApplicationUserDto.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using CleanArchitecture.Blazor.Application.Common.Security;
using CleanArchitecture.Blazor.Application.Common.Interfaces.Identity;
using CleanArchitecture.Blazor.Application.Common.Security;
using CleanArchitecture.Blazor.Domain.Identity;
using FluentValidation;

namespace CleanArchitecture.Blazor.Application.Features.Identity.DTOs;

Expand Down Expand Up @@ -80,21 +82,31 @@ public Mapping()

public class ApplicationUserDtoValidator : AbstractValidator<ApplicationUserDto>
{
public ApplicationUserDtoValidator()
private readonly IStringLocalizer<ApplicationUserDtoValidator> _localizer;

public ApplicationUserDtoValidator(IStringLocalizer<ApplicationUserDtoValidator> localizer)
{
_localizer = localizer;
RuleFor(v => v.TenantId)
.MaximumLength(256)
.NotEmpty();
.MaximumLength(128).WithMessage(_localizer["Tenant id must be less than 128 characters"])
.NotEmpty().WithMessage(_localizer["Tenant name cannot be empty"]);
RuleFor(v => v.Provider)
.MaximumLength(256)
.NotEmpty();
RuleFor(v => v.UserName)
.MaximumLength(256)
.NotEmpty();
RuleFor(v => v.Email)
.MaximumLength(256)
.NotEmpty()
.EmailAddress();
.MaximumLength(128).WithMessage(_localizer["Provider must be less than 100 characters"])
.NotEmpty().WithMessage(_localizer["Provider cannot be empty"]);
RuleFor(x => x.UserName)
.NotEmpty().WithMessage(_localizer["User name cannot be empty"])
.Length(2, 100).WithMessage(_localizer["User name must be between 2 and 100 characters"]);
RuleFor(x => x.Email)
.NotEmpty().WithMessage(_localizer["E-mail cannot be empty"])
.MaximumLength(100).WithMessage(_localizer["E-mail must be less than 100 characters"])
.EmailAddress().WithMessage(_localizer["E-mail must be a valid email address"]);

RuleFor(x => x.DisplayName)
.MaximumLength(128).WithMessage(_localizer["Display name must be less than 128 characters"]);

RuleFor(x => x.PhoneNumber)
.MaximumLength(20).WithMessage(_localizer["Phone number must be less than 20 digits"]);
_localizer = localizer;
}
}
}
Loading