Merge pull request #2048 from sbwalker/dev
Added password policy validation in install wizard
This commit is contained in:
commit
a47ecbdea9
|
@ -121,90 +121,97 @@
|
||||||
{
|
{
|
||||||
_databaseName = "LocalDB";
|
_databaseName = "LocalDB";
|
||||||
}
|
}
|
||||||
LoadDatabaseConfigComponent();
|
LoadDatabaseConfigComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DatabaseChanged(ChangeEventArgs eventArgs)
|
private void DatabaseChanged(ChangeEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_databaseName = (string)eventArgs.Value;
|
_databaseName = (string)eventArgs.Value;
|
||||||
|
|
||||||
LoadDatabaseConfigComponent();
|
LoadDatabaseConfigComponent();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
_message = Localizer["Error.DbConfig.Load"];
|
_message = Localizer["Error.DbConfig.Load"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadDatabaseConfigComponent()
|
private void LoadDatabaseConfigComponent()
|
||||||
{
|
{
|
||||||
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
||||||
if (database != null)
|
if (database != null)
|
||||||
{
|
{
|
||||||
_databaseConfigType = Type.GetType(database.ControlType);
|
_databaseConfigType = Type.GetType(database.ControlType);
|
||||||
DatabaseConfigComponent = builder =>
|
DatabaseConfigComponent = builder =>
|
||||||
{
|
{
|
||||||
builder.OpenComponent(0, _databaseConfigType);
|
builder.OpenComponent(0, _databaseConfigType);
|
||||||
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
|
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
await interop.IncludeLink("", "stylesheet", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css", "text/css", "sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==", "anonymous", "");
|
await interop.IncludeLink("", "stylesheet", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css", "text/css", "sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==", "anonymous", "");
|
||||||
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js", "sha512-pax4MlgXjHEPfCwcJLQhigY7+N8rt6bVvWLFyUMuxShv170X53TRzGPmPkZmGBhk+jikR8WBM4yl7A9WMHHqvg==", "anonymous", "", "head", "");
|
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js", "sha512-pax4MlgXjHEPfCwcJLQhigY7+N8rt6bVvWLFyUMuxShv170X53TRzGPmPkZmGBhk+jikR8WBM4yl7A9WMHHqvg==", "anonymous", "", "head", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Install()
|
private async Task Install()
|
||||||
{
|
{
|
||||||
var connectionString = String.Empty;
|
var connectionString = String.Empty;
|
||||||
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||||
{
|
{
|
||||||
connectionString = databaseConfigControl.GetConnectionString();
|
connectionString = databaseConfigControl.GetConnectionString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && _hostPassword.Length >= 6 && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
|
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
|
||||||
{
|
{
|
||||||
_loadingDisplay = "";
|
if (await UserService.ValidatePasswordAsync(_hostPassword))
|
||||||
StateHasChanged();
|
{
|
||||||
|
_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
|
var config = new InstallConfig
|
||||||
{
|
{
|
||||||
DatabaseType = database.DBType,
|
DatabaseType = database.DBType,
|
||||||
ConnectionString = connectionString,
|
ConnectionString = connectionString,
|
||||||
Aliases = uri.Authority,
|
Aliases = uri.Authority,
|
||||||
HostUsername = _hostUsername,
|
HostUsername = _hostUsername,
|
||||||
HostPassword = _hostPassword,
|
HostPassword = _hostPassword,
|
||||||
HostEmail = _hostEmail,
|
HostEmail = _hostEmail,
|
||||||
HostName = _hostUsername,
|
HostName = _hostUsername,
|
||||||
TenantName = TenantNames.Master,
|
TenantName = TenantNames.Master,
|
||||||
IsNewTenant = true,
|
IsNewTenant = true,
|
||||||
SiteName = Constants.DefaultSite,
|
SiteName = Constants.DefaultSite,
|
||||||
Register = _register
|
Register = _register
|
||||||
};
|
};
|
||||||
|
|
||||||
var installation = await InstallationService.Install(config);
|
var installation = await InstallationService.Install(config);
|
||||||
if (installation.Success)
|
if (installation.Success)
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(uri.Scheme + "://" + uri.Authority, true);
|
NavigationManager.NavigateTo(uri.Scheme + "://" + uri.Authority, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_message = installation.Message;
|
_message = installation.Message;
|
||||||
_loadingDisplay = "display: none;";
|
_loadingDisplay = "display: none;";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_message = Localizer["Message.Password.Invalid"];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -130,12 +130,15 @@
|
||||||
<value>Install Now</value>
|
<value>Install Now</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Error.DbConfig.Load" xml:space="preserve">
|
<data name="Error.DbConfig.Load" xml:space="preserve">
|
||||||
<value>Error loading Database Configuration Control</value>
|
<value>Error Loading Database Configuration Control</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Message.Require.DbInfo" xml:space="preserve">
|
<data name="Message.Require.DbInfo" xml:space="preserve">
|
||||||
<value>Please Enter All Required Fields. Ensure Passwords Match And Are Greater Than 5 Characters In Length. Ensure Email Address Provided Is Valid.</value>
|
<value>Please Enter All Required Fields. Ensure Passwords Match And Email Address Provided Is Valid.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Register" xml:space="preserve">
|
<data name="Message.Password.Invalid" xml:space="preserve">
|
||||||
|
<value>The Password Provided Does Not Meet The Password Policy. Please Verify The Minimum Password Length And Complexity Requirements.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Register" xml:space="preserve">
|
||||||
<value>Please Register Me For Major Product Updates And Security Bulletins</value>
|
<value>Please Register Me For Major Product Updates And Security Bulletins</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Confirm.HelpText" xml:space="preserve">
|
<data name="Confirm.HelpText" xml:space="preserve">
|
||||||
|
|
|
@ -96,5 +96,13 @@ namespace Oqtane.Services
|
||||||
/// <param name="token"></param>
|
/// <param name="token"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<User> VerifyTwoFactorAsync(User user, string token);
|
Task<User> VerifyTwoFactorAsync(User user, string token);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validate a users password against the password policy
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="password"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<bool> ValidatePasswordAsync(string password);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using Oqtane.Models;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Oqtane.Documentation;
|
using Oqtane.Documentation;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
namespace Oqtane.Services
|
namespace Oqtane.Services
|
||||||
{
|
{
|
||||||
|
@ -73,5 +74,10 @@ namespace Oqtane.Services
|
||||||
{
|
{
|
||||||
return await PostJsonAsync<User>($"{Apiurl}/twofactor?token={token}", user);
|
return await PostJsonAsync<User>($"{Apiurl}/twofactor?token={token}", user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ValidatePasswordAsync(string password)
|
||||||
|
{
|
||||||
|
return await GetJsonAsync<bool>($"{Apiurl}/validate/{WebUtility.UrlEncode(password)}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,12 +26,12 @@ namespace Oqtane.Controllers
|
||||||
private readonly IUserRoleRepository _userRoles;
|
private readonly IUserRoleRepository _userRoles;
|
||||||
private readonly UserManager<IdentityUser> _identityUserManager;
|
private readonly UserManager<IdentityUser> _identityUserManager;
|
||||||
private readonly SignInManager<IdentityUser> _identitySignInManager;
|
private readonly SignInManager<IdentityUser> _identitySignInManager;
|
||||||
|
private readonly ITenantManager _tenantManager;
|
||||||
private readonly INotificationRepository _notifications;
|
private readonly INotificationRepository _notifications;
|
||||||
private readonly IFolderRepository _folders;
|
private readonly IFolderRepository _folders;
|
||||||
private readonly ISyncManager _syncManager;
|
private readonly ISyncManager _syncManager;
|
||||||
private readonly ISiteRepository _sites;
|
private readonly ISiteRepository _sites;
|
||||||
private readonly ILogManager _logger;
|
private readonly ILogManager _logger;
|
||||||
private readonly Alias _alias;
|
|
||||||
|
|
||||||
public UserController(IUserRepository users, IRoleRepository roles, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ISiteRepository sites, ILogManager logger)
|
public UserController(IUserRepository users, IRoleRepository roles, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ISiteRepository sites, ILogManager logger)
|
||||||
{
|
{
|
||||||
|
@ -40,12 +40,12 @@ namespace Oqtane.Controllers
|
||||||
_userRoles = userRoles;
|
_userRoles = userRoles;
|
||||||
_identityUserManager = identityUserManager;
|
_identityUserManager = identityUserManager;
|
||||||
_identitySignInManager = identitySignInManager;
|
_identitySignInManager = identitySignInManager;
|
||||||
|
_tenantManager = tenantManager;
|
||||||
_folders = folders;
|
_folders = folders;
|
||||||
_notifications = notifications;
|
_notifications = notifications;
|
||||||
_syncManager = syncManager;
|
_syncManager = syncManager;
|
||||||
_sites = sites;
|
_sites = sites;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_alias = tenantManager.GetAlias();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET api/<controller>/5?siteid=x
|
// GET api/<controller>/5?siteid=x
|
||||||
|
@ -54,7 +54,7 @@ namespace Oqtane.Controllers
|
||||||
public User Get(int id, string siteid)
|
public User Get(int id, string siteid)
|
||||||
{
|
{
|
||||||
int SiteId;
|
int SiteId;
|
||||||
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
|
if (int.TryParse(siteid, out SiteId) && SiteId == _tenantManager.GetAlias().SiteId)
|
||||||
{
|
{
|
||||||
User user = _users.GetUser(id);
|
User user = _users.GetUser(id);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
|
@ -77,7 +77,7 @@ namespace Oqtane.Controllers
|
||||||
public User Get(string name, string siteid)
|
public User Get(string name, string siteid)
|
||||||
{
|
{
|
||||||
int SiteId;
|
int SiteId;
|
||||||
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
|
if (int.TryParse(siteid, out SiteId) && SiteId == _tenantManager.GetAlias().SiteId)
|
||||||
{
|
{
|
||||||
User user = _users.GetUser(name);
|
User user = _users.GetUser(name);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
|
@ -129,7 +129,7 @@ namespace Oqtane.Controllers
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<User> Post([FromBody] User user)
|
public async Task<User> Post([FromBody] User user)
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid && user.SiteId == _alias.SiteId)
|
if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId)
|
||||||
{
|
{
|
||||||
var User = await CreateUser(user);
|
var User = await CreateUser(user);
|
||||||
return User;
|
return User;
|
||||||
|
@ -178,7 +178,7 @@ namespace Oqtane.Controllers
|
||||||
if (!verified)
|
if (!verified)
|
||||||
{
|
{
|
||||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||||
string url = HttpContext.Request.Scheme + "://" + _alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
string url = HttpContext.Request.Scheme + "://" + _tenantManager.GetAlias().Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||||
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
|
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
|
||||||
var notification = new Notification(user.SiteId, newUser, "User Account Verification", body);
|
var notification = new Notification(user.SiteId, newUser, "User Account Verification", body);
|
||||||
_notifications.AddNotification(notification);
|
_notifications.AddNotification(notification);
|
||||||
|
@ -252,7 +252,7 @@ namespace Oqtane.Controllers
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<User> Put(int id, [FromBody] User user)
|
public async Task<User> Put(int id, [FromBody] User user)
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid && user.SiteId == _alias.SiteId && _users.GetUser(user.UserId, false) != null && (User.IsInRole(RoleNames.Admin) || User.Identity.Name == user.Username))
|
if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && _users.GetUser(user.UserId, false) != null && (User.IsInRole(RoleNames.Admin) || User.Identity.Name == user.Username))
|
||||||
{
|
{
|
||||||
if (user.Password != "")
|
if (user.Password != "")
|
||||||
{
|
{
|
||||||
|
@ -264,7 +264,7 @@ namespace Oqtane.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
user = _users.UpdateUser(user);
|
user = _users.UpdateUser(user);
|
||||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, user.UserId);
|
_syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId);
|
||||||
user.Password = ""; // remove sensitive information
|
user.Password = ""; // remove sensitive information
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user);
|
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user);
|
||||||
}
|
}
|
||||||
|
@ -285,7 +285,7 @@ namespace Oqtane.Controllers
|
||||||
{
|
{
|
||||||
int SiteId;
|
int SiteId;
|
||||||
User user = _users.GetUser(id);
|
User user = _users.GetUser(id);
|
||||||
if (user != null && int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
|
if (user != null && int.TryParse(siteid, out SiteId) && SiteId == _tenantManager.GetAlias().SiteId)
|
||||||
{
|
{
|
||||||
// remove user roles for site
|
// remove user roles for site
|
||||||
foreach (UserRole userrole in _userRoles.GetUserRoles(user.UserId, SiteId).ToList())
|
foreach (UserRole userrole in _userRoles.GetUserRoles(user.UserId, SiteId).ToList())
|
||||||
|
@ -396,7 +396,7 @@ namespace Oqtane.Controllers
|
||||||
{
|
{
|
||||||
user = _users.GetUser(user.Username);
|
user = _users.GetUser(user.Username);
|
||||||
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
|
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
|
||||||
string url = HttpContext.Request.Scheme + "://" + _alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
string url = HttpContext.Request.Scheme + "://" + _tenantManager.GetAlias().Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||||
string body = "Dear " + user.DisplayName + ",\n\nYou attempted multiple times unsuccessfully to log in to your account and it is now locked out. Please wait a few minutes and then try again... or use the link below to reset your password:\n\n" + url +
|
string body = "Dear " + user.DisplayName + ",\n\nYou attempted multiple times unsuccessfully to log in to your account and it is now locked out. Please wait a few minutes and then try again... or use the link below to reset your password:\n\n" + url +
|
||||||
"\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." +
|
"\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." +
|
||||||
"\n\nThank You!";
|
"\n\nThank You!";
|
||||||
|
@ -464,7 +464,7 @@ namespace Oqtane.Controllers
|
||||||
{
|
{
|
||||||
user = _users.GetUser(user.Username);
|
user = _users.GetUser(user.Username);
|
||||||
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
|
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
|
||||||
string url = HttpContext.Request.Scheme + "://" + _alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
string url = HttpContext.Request.Scheme + "://" + _tenantManager.GetAlias().Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||||
string body = "Dear " + user.DisplayName + ",\n\nYou recently requested to reset your password. Please use the link below to complete the process:\n\n" + url +
|
string body = "Dear " + user.DisplayName + ",\n\nYou recently requested to reset your password. Please use the link below to complete the process:\n\n" + url +
|
||||||
"\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." +
|
"\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." +
|
||||||
"\n\nIf you did not request to reset your password you can safely ignore this message." +
|
"\n\nIf you did not request to reset your password you can safely ignore this message." +
|
||||||
|
@ -532,6 +532,15 @@ namespace Oqtane.Controllers
|
||||||
return loginUser;
|
return loginUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET api/<controller>/validate/x
|
||||||
|
[HttpGet("validate/{password}")]
|
||||||
|
public async Task<bool> Validate(string password)
|
||||||
|
{
|
||||||
|
var validator = new PasswordValidator<IdentityUser>();
|
||||||
|
var result = await validator.ValidateAsync(_identityUserManager, null, password);
|
||||||
|
return result.Succeeded;
|
||||||
|
}
|
||||||
|
|
||||||
// GET api/<controller>/authenticate
|
// GET api/<controller>/authenticate
|
||||||
[HttpGet("authenticate")]
|
[HttpGet("authenticate")]
|
||||||
public User Authenticate()
|
public User Authenticate()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user