diff --git a/Oqtane.Client/Installer/Installer.razor b/Oqtane.Client/Installer/Installer.razor index 574b3730..c0187971 100644 --- a/Oqtane.Client/Installer/Installer.razor +++ b/Oqtane.Client/Installer/Installer.razor @@ -156,129 +156,130 @@ private List _templates; private string _template = Constants.DefaultSiteTemplate; private bool _register = true; - private string _message = string.Empty; - private string _loadingDisplay = "display: none;"; + private string _message = string.Empty; + private string _loadingDisplay = "display: none;"; - protected override async Task OnInitializedAsync() - { + protected override async Task OnInitializedAsync() + { // include CSS var content = $""; SiteState.AppendHeadContent(content); _togglePassword = SharedLocalizer["ShowPassword"]; - _toggleConfirmPassword = SharedLocalizer["ShowPassword"]; + _toggleConfirmPassword = SharedLocalizer["ShowPassword"]; - _databases = await DatabaseService.GetDatabasesAsync(); - if (_databases.Exists(item => item.IsDefault)) - { - _databaseName = _databases.Find(item => item.IsDefault).Name; - } - else - { - _databaseName = "LocalDB"; - } - LoadDatabaseConfigComponent(); + _databases = await DatabaseService.GetDatabasesAsync(); + if (_databases.Exists(item => item.IsDefault)) + { + _databaseName = _databases.Find(item => item.IsDefault).Name; + } + else + { + _databaseName = "LocalDB"; + } + LoadDatabaseConfigComponent(); _templates = await SiteTemplateService.GetSiteTemplatesAsync(); } - private void DatabaseChanged(ChangeEventArgs eventArgs) - { - try - { - _databaseName = (string)eventArgs.Value; - _showConnectionString = false; - LoadDatabaseConfigComponent(); - } - catch - { - _message = Localizer["Error.DbConfig.Load"]; - } - } + private void DatabaseChanged(ChangeEventArgs eventArgs) + { + try + { + _databaseName = (string)eventArgs.Value; + _showConnectionString = false; + LoadDatabaseConfigComponent(); + } + catch + { + _message = Localizer["Error.DbConfig.Load"]; + } + } - private void LoadDatabaseConfigComponent() - { - var database = _databases.SingleOrDefault(d => d.Name == _databaseName); - if (database != null) - { - _databaseConfigType = Type.GetType(database.ControlType); - DatabaseConfigComponent = builder => - { - builder.OpenComponent(0, _databaseConfigType); - builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); }); - builder.CloseComponent(); - }; - } - } + private void LoadDatabaseConfigComponent() + { + var database = _databases.SingleOrDefault(d => d.Name == _databaseName); + if (database != null) + { + _databaseConfigType = Type.GetType(database.ControlType); + DatabaseConfigComponent = builder => + { + builder.OpenComponent(0, _databaseConfigType); + builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); }); + builder.CloseComponent(); + }; + } + } - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { // include JavaScript - var interop = new Interop(JSRuntime); + var interop = new Interop(JSRuntime); await interop.IncludeScript("", Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous", "", "head"); - } - } + } + } - private async Task Install() - { - var connectionString = String.Empty; - if (_showConnectionString) - { - connectionString = _connectionString; - } - else - { - if (_databaseConfig is IDatabaseConfigControl databaseConfigControl) - { - connectionString = databaseConfigControl.GetConnectionString(); - } - } + private async Task Install() + { + var connectionString = String.Empty; + if (_showConnectionString) + { + connectionString = _connectionString; + } + else + { + if (_databaseConfig is IDatabaseConfigControl databaseConfigControl) + { + connectionString = databaseConfigControl.GetConnectionString(); + } + } - if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@")) - { - if (await UserService.ValidatePasswordAsync(_hostPassword)) - { - _loadingDisplay = ""; - StateHasChanged(); + if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@")) + { + var result = await UserService.ValidateUserAsync(_hostUsername, _hostEmail, _hostPassword); + if (result.Succeeded) + { + _loadingDisplay = ""; + StateHasChanged(); - Uri uri = new Uri(NavigationManager.Uri); + Uri uri = new Uri(NavigationManager.Uri); - var database = _databases.SingleOrDefault(d => d.Name == _databaseName); + var database = _databases.SingleOrDefault(d => d.Name == _databaseName); - var config = new InstallConfig - { - DatabaseType = database.DBType, - ConnectionString = connectionString, - Aliases = uri.Authority, - HostUsername = _hostUsername, - HostPassword = _hostPassword, - HostEmail = _hostEmail, - HostName = _hostUsername, - TenantName = TenantNames.Master, - IsNewTenant = true, - SiteName = Constants.DefaultSite, - Register = _register, - SiteTemplate = _template, - RenderMode = RenderModes.Static, - Runtime = Runtimes.Server - }; + var config = new InstallConfig + { + DatabaseType = database.DBType, + ConnectionString = connectionString, + Aliases = uri.Authority, + HostUsername = _hostUsername, + HostPassword = _hostPassword, + HostEmail = _hostEmail, + HostName = _hostUsername, + TenantName = TenantNames.Master, + IsNewTenant = true, + SiteName = Constants.DefaultSite, + Register = _register, + SiteTemplate = _template, + RenderMode = RenderModes.Static, + Runtime = Runtimes.Server + }; - var installation = await InstallationService.Install(config); - if (installation.Success) - { - NavigationManager.NavigateTo(uri.Scheme + "://" + uri.Authority, true); - } - else - { - _message = installation.Message; - _loadingDisplay = "display: none;"; - } - } - else - { - _message = Localizer["Message.Password.Invalid"]; + var installation = await InstallationService.Install(config); + if (installation.Success) + { + NavigationManager.NavigateTo(uri.Scheme + "://" + uri.Authority, true); + } + else + { + _message = installation.Message; + _loadingDisplay = "display: none;"; + } + } + else + { + _message = string.Join("
", result.Errors.Select(i => !string.IsNullOrEmpty(i.Value) ? i.Value : Localizer[i.Key])); } } else diff --git a/Oqtane.Client/Resources/Installer/Installer.resx b/Oqtane.Client/Resources/Installer/Installer.resx index 23e0f7d7..a06752d9 100644 --- a/Oqtane.Client/Resources/Installer/Installer.resx +++ b/Oqtane.Client/Resources/Installer/Installer.resx @@ -183,4 +183,7 @@ Select a site template + + The Username Provided Does Not Meet The System Requirement, It Can Only Contains Letters Or Digits. + \ No newline at end of file diff --git a/Oqtane.Client/Services/Interfaces/IUserService.cs b/Oqtane.Client/Services/Interfaces/IUserService.cs index 534466a5..b32a66f0 100644 --- a/Oqtane.Client/Services/Interfaces/IUserService.cs +++ b/Oqtane.Client/Services/Interfaces/IUserService.cs @@ -113,6 +113,15 @@ namespace Oqtane.Services /// Task VerifyTwoFactorAsync(User user, string token); + /// + /// Validate identity user info. + /// + /// + /// + /// + /// + Task ValidateUserAsync(string username, string email, string password); + /// /// Validate a users password against the password policy /// diff --git a/Oqtane.Client/Services/UserService.cs b/Oqtane.Client/Services/UserService.cs index 2133d2de..d69aa10d 100644 --- a/Oqtane.Client/Services/UserService.cs +++ b/Oqtane.Client/Services/UserService.cs @@ -89,6 +89,11 @@ namespace Oqtane.Services return await PostJsonAsync($"{Apiurl}/twofactor?token={token}", user); } + public async Task ValidateUserAsync(string username, string email, string password) + { + return await GetJsonAsync($"{Apiurl}/validateuser?username={WebUtility.UrlEncode(username)}&email={WebUtility.UrlEncode(email)}&password={WebUtility.UrlEncode(password)}"); + } + public async Task ValidatePasswordAsync(string password) { return await GetJsonAsync($"{Apiurl}/validate/{WebUtility.UrlEncode(password)}"); diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 9e80be8d..acbcb1a1 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -347,6 +347,13 @@ namespace Oqtane.Controllers return user; } + // GET api//validate/x + [HttpGet("validateuser")] + public async Task ValidateUser(string username, string email, string password) + { + return await _userManager.ValidateUser(username, email, password); + } + // GET api//validate/x [HttpGet("validate/{password}")] public async Task Validate(string password) diff --git a/Oqtane.Server/Managers/Interfaces/IUserManager.cs b/Oqtane.Server/Managers/Interfaces/IUserManager.cs index 5ada9827..4fde062c 100644 --- a/Oqtane.Server/Managers/Interfaces/IUserManager.cs +++ b/Oqtane.Server/Managers/Interfaces/IUserManager.cs @@ -19,6 +19,7 @@ namespace Oqtane.Managers Task ResetPassword(User user, string token); User VerifyTwoFactor(User user, string token); Task LinkExternalAccount(User user, string token, string type, string key, string name); + Task ValidateUser(string username, string email, string password); Task ValidatePassword(string password); Task> ImportUsers(int siteId, string filePath, bool notify); } diff --git a/Oqtane.Server/Managers/UserManager.cs b/Oqtane.Server/Managers/UserManager.cs index e0e92e97..03bff1bb 100644 --- a/Oqtane.Server/Managers/UserManager.cs +++ b/Oqtane.Server/Managers/UserManager.cs @@ -540,6 +540,30 @@ namespace Oqtane.Managers return user; } + public async Task ValidateUser(string username, string email, string password) + { + var validateResult = new UserValidateResult { Succeeded = true }; + + //validate username + var allowedChars = _identityUserManager.Options.User.AllowedUserNameCharacters; + if (string.IsNullOrWhiteSpace(username) || (!string.IsNullOrEmpty(allowedChars) && username.Any(c => !allowedChars.Contains(c)))) + { + validateResult.Succeeded = false; + validateResult.Errors.Add("Message.Username.Invalid", string.Empty); + } + + //validate password + var passwordValidator = new PasswordValidator(); + var passwordResult = await passwordValidator.ValidateAsync(_identityUserManager, null, password); + if (!passwordResult.Succeeded) + { + validateResult.Succeeded = false; + validateResult.Errors.Add("Message.Password.Invalid", string.Empty); + } + + return validateResult; + } + public async Task ValidatePassword(string password) { var validator = new PasswordValidator(); diff --git a/Oqtane.Shared/Models/UserValidateResult.cs b/Oqtane.Shared/Models/UserValidateResult.cs new file mode 100644 index 00000000..d531ab82 --- /dev/null +++ b/Oqtane.Shared/Models/UserValidateResult.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Oqtane.Models +{ + public class UserValidateResult + { + public bool Succeeded { get; set; } + + public IDictionary Errors { get; set; } = new Dictionary(); + } +}