diff --git a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor index 0fe5c328..77878a1b 100644 --- a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor +++ b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor @@ -27,9 +27,27 @@
- +
- + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
@@ -38,6 +56,18 @@
+
+ +
+ +
+
+
+ +
+ +
+
@@ -119,8 +149,13 @@ private string _version = string.Empty; private string _clrversion = string.Empty; private string _osversion = string.Empty; - private string _serverpath = string.Empty; + private string _machinename = string.Empty; + private string _ipaddress = string.Empty; + private string _contentrootpath = string.Empty; + private string _webrootpath = string.Empty; private string _servertime = string.Empty; + private string _tickcount = string.Empty; + private string _workingset = string.Empty; private string _installationid = string.Empty; private string _detailederrors = string.Empty; @@ -133,33 +168,42 @@ { _version = Constants.Version; - Dictionary systeminfo = await SystemService.GetSystemInfoAsync(); + Dictionary systeminfo = await SystemService.GetSystemInfoAsync("environment"); if (systeminfo != null) { - _clrversion = systeminfo["clrversion"]; - _osversion = systeminfo["osversion"]; - _serverpath = systeminfo["serverpath"]; - _servertime = systeminfo["servertime"] + " UTC"; - _installationid = systeminfo["installationid"]; + _clrversion = systeminfo["CLRVersion"].ToString(); + _osversion = systeminfo["OSVersion"].ToString(); + _machinename = systeminfo["MachineName"].ToString(); + _ipaddress = systeminfo["IPAddress"].ToString(); + _contentrootpath = systeminfo["ContentRootPath"].ToString(); + _webrootpath = systeminfo["WebRootPath"].ToString(); + _servertime = systeminfo["ServerTime"].ToString() + " UTC"; + _tickcount = TimeSpan.FromMilliseconds(Convert.ToInt64(systeminfo["TickCount"].ToString())).ToString(); + _workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB"; + } - _detailederrors = systeminfo["detailederrors"]; - _logginglevel = systeminfo["logginglevel"]; - _notificationlevel = systeminfo["notificationlevel"]; - _swagger = systeminfo["swagger"]; - _packageservice = systeminfo["packageservice"]; - } - } + systeminfo = await SystemService.GetSystemInfoAsync(); + if (systeminfo != null) + { + _installationid = systeminfo["InstallationId"].ToString(); + _detailederrors = systeminfo["DetailedErrors"].ToString(); + _logginglevel = systeminfo["Logging:LogLevel:Default"].ToString(); + _notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString(); + _swagger = systeminfo["UseSwagger"].ToString(); + _packageservice = systeminfo["PackageService"].ToString(); + } + } private async Task SaveConfig() { try { - var settings = new Dictionary(); - settings.Add("detailederrors", _detailederrors); - settings.Add("logginglevel", _logginglevel); - settings.Add("notificationlevel", _notificationlevel); - settings.Add("swagger", _swagger); - settings.Add("packageservice", _packageservice); + var settings = new Dictionary(); + settings.Add("DetailedErrors", _detailederrors); + settings.Add("Logging:LogLevel:Default", _logginglevel); + settings.Add("Logging:LogLevel:Notify", _notificationlevel); + settings.Add("UseSwagger", _swagger); + settings.Add("PackageService", _packageservice); await SystemService.UpdateSystemInfoAsync(settings); AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success); } diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index 4f017b85..a0b1a54d 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -4,6 +4,7 @@ @inject IUserService UserService @inject ISettingService SettingService @inject ISiteService SiteService +@inject ISystemService SystemService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer @@ -64,6 +65,74 @@ else
+ @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) + { +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ }
@@ -72,79 +141,104 @@ else } @code { - private List allroles; - private List userroles; - private string _search; + private List allroles; + private List userroles; + private string _search; + private string _allowregistration; + private string _minimumlength = "6"; + private string _uniquecharacters = "1"; + private string _requiredigit = "true"; + private string _requireupper = "true"; + private string _requirelower = "true"; + private string _requirepunctuation = "true"; + private string _maximumfailures = "5"; + private string _lockoutduration = "5"; - public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; + + protected override async Task OnInitializedAsync() + { + allroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId); + await LoadSettingsAsync(); + userroles = Search(_search); - protected override async Task OnInitializedAsync() - { - allroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId); - await LoadSettingsAsync(); - userroles = Search(_search); _allowregistration = PageState.Site.AllowRegistration.ToString(); - } + if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) + { + Dictionary systeminfo = await SystemService.GetSystemInfoAsync(); + if (systeminfo != null) + { + _minimumlength = systeminfo["Password:RequiredLength"].ToString(); + _uniquecharacters = systeminfo["Password:RequiredUniqueChars"].ToString(); + _requiredigit = systeminfo["Password:RequireDigit"].ToString(); + _requireupper = systeminfo["Password:RequireUppercase"].ToString(); + _requirelower = systeminfo["Password:RequireLowercase"].ToString(); + _requirepunctuation = systeminfo["Password:RequireNonAlphanumeric"].ToString(); + _maximumfailures = systeminfo["Lockout:MaxFailedAccessAttempts"].ToString(); + _lockoutduration = TimeSpan.Parse(systeminfo["Lockout:DefaultLockoutTimeSpan"].ToString()).TotalMinutes.ToString(); + } + } + } - private List Search(string search) - { - var results = allroles.Where(item => item.Role.Name == RoleNames.Registered || (item.Role.Name == RoleNames.Host && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))); + private List Search(string search) + { + var results = allroles.Where(item => item.Role.Name == RoleNames.Registered || (item.Role.Name == RoleNames.Host && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))); - if (!string.IsNullOrEmpty(_search)) - { - results = results.Where(item => - ( - item.User.Username.Contains(search, StringComparison.OrdinalIgnoreCase) || - item.User.Email.Contains(search, StringComparison.OrdinalIgnoreCase) || - item.User.DisplayName.Contains(search, StringComparison.OrdinalIgnoreCase) - ) - ); - } - return results.ToList(); - } + if (!string.IsNullOrEmpty(_search)) + { + results = results.Where(item => + ( + item.User.Username.Contains(search, StringComparison.OrdinalIgnoreCase) || + item.User.Email.Contains(search, StringComparison.OrdinalIgnoreCase) || + item.User.DisplayName.Contains(search, StringComparison.OrdinalIgnoreCase) + ) + ); + } + return results.ToList(); + } - private async Task OnSearch() - { - userroles = Search(_search); - await UpdateSettingsAsync(); - } + private async Task OnSearch() + { + userroles = Search(_search); + await UpdateSettingsAsync(); + } - private async Task DeleteUser(UserRole UserRole) - { - try - { - var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId); - if (user != null) - { - await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId); - await logger.LogInformation("User Deleted {User}", UserRole.User); - allroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId); - userroles = Search(_search); - StateHasChanged(); - } - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message); - AddModuleMessage(ex.Message, MessageType.Error); - } - } + private async Task DeleteUser(UserRole UserRole) + { + try + { + var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId); + if (user != null) + { + await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId); + await logger.LogInformation("User Deleted {User}", UserRole.User); + allroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId); + userroles = Search(_search); + StateHasChanged(); + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message); + AddModuleMessage(ex.Message, MessageType.Error); + } + } - private string settingSearch = "AU-search"; + private string settingSearch = "AU-search"; - private async Task LoadSettingsAsync() - { - Dictionary settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); - _search = SettingService.GetSetting(settings, settingSearch, ""); - } + private async Task LoadSettingsAsync() + { + Dictionary settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); + _search = SettingService.GetSetting(settings, settingSearch, ""); + } - private async Task UpdateSettingsAsync() - { - Dictionary settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); - SettingService.SetSetting(settings, settingSearch, _search); - await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); - } + private async Task UpdateSettingsAsync() + { + Dictionary settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); + SettingService.SetSetting(settings, settingSearch, _search); + await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); + } private async Task SaveSiteSettings() { @@ -153,7 +247,25 @@ else var site = PageState.Site; site.AllowRegistration = bool.Parse(_allowregistration); await SiteService.UpdateSiteAsync(site); - AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success); + + if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) + { + var settings = new Dictionary(); + settings.Add("Password:RequiredLength", _minimumlength); + settings.Add("Password:RequiredUniqueChars", _uniquecharacters); + settings.Add("Password:RequireDigit", _requiredigit); + settings.Add("Password:RequireUppercase", _requireupper); + settings.Add("Password:RequireLowercase", _requirelower); + settings.Add("Password:RequireNonAlphanumeric", _requirepunctuation); + settings.Add("Lockout:MaxFailedAccessAttempts", _maximumfailures); + settings.Add("Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString()); + await SystemService.UpdateSystemInfoAsync(settings); + AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success); + } + else + { + AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success); + } } catch (Exception ex) { diff --git a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx index 7e1ad433..6582bb79 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx @@ -157,10 +157,10 @@ Verification Failed. Please Ensure You Entered The Code Exactly In The Form Provided In Your Email. If You Wish To Request A New Verification Code Please Select The Cancel Option And Sign In Again. - A Secure Verification Code Has Been Sent To Your Email Address. Please Enter The Code That You Received. If You Do Not Receive The Code Within 10 Minutes Or You Have Lost Access To Your Email, Please Contact Your Administrator. + A Secure Verification Code Has Been Sent To Your Email Address. Please Enter The Code That You Received. If You Do Not Receive The Code Or You Have Lost Access To Your Email, Please Contact Your Administrator. - Please Enter The Password Related To Your Account. Remember That Passwords Are Case Sensitive. If Your Sign In Attempts Fail 3 Times In Succession, Your Account Will Be Locked Out For 10 Minutes. + Please Enter The Password Related To Your Account. Remember That Passwords Are Case Sensitive. If You Attempt Unsuccessfully To Log In To Your Account Multiple Times, You Will Be Locked Out For A Period Of Time. Password diff --git a/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx b/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx index 95a0fcc9..ed9f15b6 100644 --- a/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx @@ -129,8 +129,8 @@ Operating System Version - - Server Path + + Server Root Path Server Date/Time (in UTC) @@ -144,8 +144,8 @@ OS Version: - - Server Path: + + Root Path: Server Date/Time: @@ -240,4 +240,34 @@ Notification Level: + + Server IP Address + + + IP Address: + + + Server Machine Name + + + Machine Name: + + + Amount Of Time The Service Has Been Available And Operational + + + Service Uptime: + + + Server Web Root Path + + + Web Path: + + + Memory Allocation Of Service (in MB) + + + Memory Allocation: + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx index 70155229..a2b74514 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx @@ -153,4 +153,55 @@ Roles + + The Number Of Minutes A User Should Be Locked Out + + + Lockout Duration: + + + The Maximum Number Of Sign In Attempts Before A User Is Locked Out + + + Maximum Failures: + + + Indicate If Passwords Must Contain A Digit + + + Require Digit? + + + The Minimum Length For A Password + + + Minimum Length: + + + Indicate If Passwords Must Contain A Lower Case Character + + + Require Lowercase? + + + Indicate if Passwords Must Contain A Non-alphanumeric Character (ie. Punctuation) + + + Require Punctuation? + + + Indicate If Passwords Must Contain An Upper Case Character + + + Require Uppercase? + + + Configuration Updated. Please Select Restart Application For These Changes To Be Activated. + + + The Minimum Number Of Unique Characters Which A Password Must Contain + + + Unique Characters: + \ No newline at end of file diff --git a/Oqtane.Client/Services/Interfaces/ISystemService.cs b/Oqtane.Client/Services/Interfaces/ISystemService.cs index cc7bf00a..8648cc4a 100644 --- a/Oqtane.Client/Services/Interfaces/ISystemService.cs +++ b/Oqtane.Client/Services/Interfaces/ISystemService.cs @@ -9,16 +9,34 @@ namespace Oqtane.Services public interface ISystemService { /// - /// returns a key-value directory with the current system information (os-version, clr-version, etc.) + /// returns a key-value directory with the current system configuration information /// /// - Task> GetSystemInfoAsync(); + Task> GetSystemInfoAsync(); + + /// + /// returns a key-value directory with the current system information - "environment" or "configuration" + /// + /// + Task> GetSystemInfoAsync(string type); + + /// + /// returns a config value + /// + /// + Task GetSystemInfoAsync(string settingKey, object defaultValue); /// /// Updates system information /// /// /// - Task UpdateSystemInfoAsync(Dictionary settings); + Task UpdateSystemInfoAsync(Dictionary settings); + + /// + /// updates a config value + /// + /// + Task UpdateSystemInfoAsync(string settingKey, object settingValue); } } diff --git a/Oqtane.Client/Services/SystemService.cs b/Oqtane.Client/Services/SystemService.cs index ba0f54d0..6bc5a880 100644 --- a/Oqtane.Client/Services/SystemService.cs +++ b/Oqtane.Client/Services/SystemService.cs @@ -18,14 +18,28 @@ namespace Oqtane.Services private string Apiurl => CreateApiUrl("System", _siteState.Alias); - public async Task> GetSystemInfoAsync() + public async Task> GetSystemInfoAsync() { - return await GetJsonAsync>(Apiurl); + return await GetSystemInfoAsync("configuration"); } - public async Task UpdateSystemInfoAsync(Dictionary settings) + public async Task> GetSystemInfoAsync(string type) + { + return await GetJsonAsync>($"{Apiurl}?type={type}"); + } + + public async Task GetSystemInfoAsync(string settingKey, object defaultValue) + { + return await GetJsonAsync($"{Apiurl}/{settingKey}/{defaultValue}"); + } + + public async Task UpdateSystemInfoAsync(Dictionary settings) { await PostJsonAsync(Apiurl, settings); } + public async Task UpdateSystemInfoAsync(string settingKey, object settingValue) + { + await PutJsonAsync($"{Apiurl}/{settingKey}/{settingValue}", ""); + } } } diff --git a/Oqtane.Server/Controllers/SystemController.cs b/Oqtane.Server/Controllers/SystemController.cs index 0991c60f..31035d37 100644 --- a/Oqtane.Server/Controllers/SystemController.cs +++ b/Oqtane.Server/Controllers/SystemController.cs @@ -5,6 +5,7 @@ using Oqtane.Shared; using System; using Microsoft.AspNetCore.Hosting; using Oqtane.Infrastructure; +using Microsoft.AspNetCore.Http.Features; namespace Oqtane.Controllers { @@ -20,62 +21,76 @@ namespace Oqtane.Controllers _configManager = configManager; } - // GET: api/ + // GET: api/?type=x [HttpGet] [Authorize(Roles = RoleNames.Host)] - public Dictionary Get() + public Dictionary Get(string type) { - Dictionary systeminfo = new Dictionary(); + Dictionary systeminfo = new Dictionary(); - systeminfo.Add("clrversion", Environment.Version.ToString()); - systeminfo.Add("osversion", Environment.OSVersion.ToString()); - systeminfo.Add("machinename", Environment.MachineName); - systeminfo.Add("serverpath", _environment.ContentRootPath); - systeminfo.Add("servertime", DateTime.UtcNow.ToString()); - systeminfo.Add("installationid", _configManager.GetInstallationId()); - - systeminfo.Add("runtime", _configManager.GetSetting("Runtime", "Server")); - systeminfo.Add("rendermode", _configManager.GetSetting("RenderMode", "ServerPrerendered")); - systeminfo.Add("detailederrors", _configManager.GetSetting("DetailedErrors", "false")); - systeminfo.Add("logginglevel", _configManager.GetSetting("Logging:LogLevel:Default", "Information")); - systeminfo.Add("notificationlevel", _configManager.GetSetting("Logging:LogLevel:Notify", "Error")); - systeminfo.Add("swagger", _configManager.GetSetting("UseSwagger", "true")); - systeminfo.Add("packageservice", _configManager.GetSetting("PackageService", "true")); + switch (type.ToLower()) + { + case "environment": + systeminfo.Add("CLRVersion", Environment.Version.ToString()); + systeminfo.Add("OSVersion", Environment.OSVersion.ToString()); + systeminfo.Add("MachineName", Environment.MachineName); + systeminfo.Add("WorkingSet", Environment.WorkingSet.ToString()); + systeminfo.Add("TickCount", Environment.TickCount64.ToString()); + systeminfo.Add("ContentRootPath", _environment.ContentRootPath); + systeminfo.Add("WebRootPath", _environment.WebRootPath); + systeminfo.Add("ServerTime", DateTime.UtcNow.ToString()); + var feature = HttpContext.Features.Get(); + systeminfo.Add("IPAddress", feature?.LocalIpAddress?.ToString()); + break; + case "configuration": + systeminfo.Add("InstallationId", _configManager.GetInstallationId()); + systeminfo.Add("Runtime", _configManager.GetSetting("Runtime", "Server")); + systeminfo.Add("RenderMode", _configManager.GetSetting("RenderMode", "ServerPrerendered")); + systeminfo.Add("DetailedErrors", _configManager.GetSetting("DetailedErrors", "false")); + systeminfo.Add("Logging:LogLevel:Default", _configManager.GetSetting("Logging:LogLevel:Default", "Information")); + systeminfo.Add("Logging:LogLevel:Notify", _configManager.GetSetting("Logging:LogLevel:Notify", "Error")); + systeminfo.Add("UseSwagger", _configManager.GetSetting("UseSwagger", "true")); + systeminfo.Add("PackageService", _configManager.GetSetting("PackageService", "true")); + systeminfo.Add("Password:RequiredLength", _configManager.GetSetting("Password:RequiredLength", "6")); + systeminfo.Add("Password:RequiredUniqueChars", _configManager.GetSetting("Password:RequiredUniqueChars", "1")); + systeminfo.Add("Password:RequireDigit", _configManager.GetSetting("Password:RequireDigit", "true")); + systeminfo.Add("Password:RequireUppercase", _configManager.GetSetting("Password:RequireUppercase", "true")); + systeminfo.Add("Password:RequireLowercase", _configManager.GetSetting("Password:RequireLowercase", "true")); + systeminfo.Add("Password:RequireNonAlphanumeric", _configManager.GetSetting("Password:RequireNonAlphanumeric", "true")); + systeminfo.Add("Lockout:MaxFailedAccessAttempts", _configManager.GetSetting("Lockout:MaxFailedAccessAttempts", "5")); + systeminfo.Add("Lockout:DefaultLockoutTimeSpan", _configManager.GetSetting("Lockout:DefaultLockoutTimeSpan", "00:05:00")); + break; + } return systeminfo; } + + // GET: api/ + [HttpGet("{key}/{value}")] + [Authorize(Roles = RoleNames.Host)] + public object Get(string key, object value) + { + return _configManager.GetSetting(key, value); + } + + // POST: api/ [HttpPost] [Authorize(Roles = RoleNames.Host)] - public void Post([FromBody] Dictionary settings) + public void Post([FromBody] Dictionary settings) { - foreach(KeyValuePair kvp in settings) + foreach(KeyValuePair kvp in settings) { - switch (kvp.Key) - { - case "runtime": - _configManager.AddOrUpdateSetting("Runtime", kvp.Value, false); - break; - case "rendermode": - _configManager.AddOrUpdateSetting("RenderMode", kvp.Value, false); - break; - case "detailederrors": - _configManager.AddOrUpdateSetting("DetailedErrors", kvp.Value, false); - break; - case "logginglevel": - _configManager.AddOrUpdateSetting("Logging:LogLevel:Default", kvp.Value, false); - break; - case "notificationlevel": - _configManager.AddOrUpdateSetting("Logging:LogLevel:Notify", kvp.Value, false); - break; - case "swagger": - _configManager.AddOrUpdateSetting("UseSwagger", kvp.Value, false); - break; - case "packageservice": - _configManager.AddOrUpdateSetting("PackageService", kvp.Value, false); - break; - } + _configManager.AddOrUpdateSetting(kvp.Key, kvp.Value, false); } } + + // PUT: api/ + [HttpPut("{key}/{value}")] + [Authorize(Roles = RoleNames.Host)] + public void Put(string key, object value) + { + _configManager.AddOrUpdateSetting(key, value, false); + } } } diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index d9920953..9128c8aa 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -397,7 +397,7 @@ namespace Oqtane.Controllers user = _users.GetUser(user.Username); string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser); string url = HttpContext.Request.Scheme + "://" + _alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); - string body = "Dear " + user.DisplayName + ",\n\nYou attempted 3 times unsuccessfully to log in to your account and it is now locked out. Please wait 10 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\nThank You!"; var notification = new Notification(loginUser.SiteId, user, "User Lockout", body); diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 2d1c36a8..15f70c2e 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; @@ -129,26 +130,36 @@ namespace Microsoft.Extensions.DependencyInjection return services; } - public static IServiceCollection ConfigureOqtaneIdentityOptions(this IServiceCollection services) + public static IServiceCollection ConfigureOqtaneIdentityOptions(this IServiceCollection services, IConfigurationRoot Configuration) { + // default settings services.Configure(options => { // Password settings - options.Password.RequireDigit = false; + options.Password.RequireDigit = true; options.Password.RequiredLength = 6; - options.Password.RequireNonAlphanumeric = false; - options.Password.RequireUppercase = false; - options.Password.RequireLowercase = false; + options.Password.RequireNonAlphanumeric = true; + options.Password.RequireUppercase = true; + options.Password.RequireLowercase = true; + options.Password.RequiredUniqueChars = 1; // Lockout settings - options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10); - options.Lockout.MaxFailedAccessAttempts = 3; + options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); + options.Lockout.MaxFailedAccessAttempts = 5; options.Lockout.AllowedForNewUsers = false; + // SignIn settings + options.SignIn.RequireConfirmedEmail = true; + options.SignIn.RequireConfirmedPhoneNumber = false; + // User settings options.User.RequireUniqueEmail = false; + options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+"; }); + // overrides defined in appsettings + services.Configure(Configuration); + return services; } diff --git a/Oqtane.Server/Infrastructure/ConfigManager.cs b/Oqtane.Server/Infrastructure/ConfigManager.cs index 6b1d2349..a9390912 100644 --- a/Oqtane.Server/Infrastructure/ConfigManager.cs +++ b/Oqtane.Server/Infrastructure/ConfigManager.cs @@ -100,7 +100,7 @@ namespace Oqtane.Infrastructure switch (action) { case "set": - jsonObj[currentSection] = value; + jsonObj[currentSection] = JToken.FromObject(value); break; case "remove": if (jsonObj.Property(currentSection) != null) diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 83cb5a6a..34ca42d1 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -86,7 +86,7 @@ namespace Oqtane .AddDefaultTokenProviders() .AddClaimsPrincipalFactory>(); // role claims - services.ConfigureOqtaneIdentityOptions(); + services.ConfigureOqtaneIdentityOptions(Configuration); services.AddAuthentication(Constants.AuthenticationScheme) .AddCookie(Constants.AuthenticationScheme);