Allow user identity password and lockout configuration to be customized. Included additional environment information in System Info.
This commit is contained in:
		| @ -27,9 +27,27 @@ | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="row mb-1 align-items-center"> | ||||
|                 <Label Class="col-sm-3" For="serverpath" HelpText="Server Path" ResourceKey="ServerPath">Server Path: </Label> | ||||
|                 <Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label> | ||||
|                 <div class="col-sm-9"> | ||||
|                     <input id="serverpath" class="form-control" @bind="@_serverpath" readonly /> | ||||
|                     <input id="machinename" class="form-control" @bind="@_machinename" readonly /> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="row mb-1 align-items-center"> | ||||
|                 <Label Class="col-sm-3" For="ipaddress" HelpText="Server IP Address" ResourceKey="IPAddress">IP Address: </Label> | ||||
|                 <div class="col-sm-9"> | ||||
|                     <input id="ipaddress" class="form-control" @bind="@_ipaddress" readonly /> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="row mb-1 align-items-center"> | ||||
|                 <Label Class="col-sm-3" For="contentrootpath" HelpText="Root Path" ResourceKey="ContentRootPath">Root Path: </Label> | ||||
|                 <div class="col-sm-9"> | ||||
|                     <input id="contentrootpath" class="form-control" @bind="@_contentrootpath" readonly /> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="row mb-1 align-items-center"> | ||||
|                 <Label Class="col-sm-3" For="webrootpath" HelpText="Web Path" ResourceKey="WebRootPath">Web Path: </Label> | ||||
|                 <div class="col-sm-9"> | ||||
|                     <input id="webrootpath" class="form-control" @bind="@_webrootpath" readonly /> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="row mb-1 align-items-center"> | ||||
| @ -38,6 +56,18 @@ | ||||
|                     <input id="servertime" class="form-control" @bind="@_servertime" readonly /> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="row mb-1 align-items-center"> | ||||
|                 <Label Class="col-sm-3" For="tickcount" HelpText="Amount Of Time The Service Has Been Available And Operational" ResourceKey="TickCount">Service Uptime: </Label> | ||||
|                 <div class="col-sm-9"> | ||||
|                     <input id="tickcount" class="form-control" @bind="@_tickcount" readonly /> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="row mb-1 align-items-center"> | ||||
|                 <Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label> | ||||
|                 <div class="col-sm-9"> | ||||
|                     <input id="workingset" class="form-control" @bind="@_workingset" readonly /> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="row mb-1 align-items-center"> | ||||
|                 <Label Class="col-sm-3" For="installationid" HelpText="The Unique Identifier For Your Installation" ResourceKey="InstallationId">Installation ID: </Label> | ||||
|                 <div class="col-sm-9"> | ||||
| @ -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<string, string> systeminfo = await SystemService.GetSystemInfoAsync(); | ||||
| 		Dictionary<string, object> 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<string, string>(); | ||||
|             settings.Add("detailederrors", _detailederrors); | ||||
|             settings.Add("logginglevel", _logginglevel); | ||||
|             settings.Add("notificationlevel", _notificationlevel); | ||||
|             settings.Add("swagger", _swagger); | ||||
|             settings.Add("packageservice", _packageservice); | ||||
|             var settings = new Dictionary<string, object>(); | ||||
|             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); | ||||
|         } | ||||
|  | ||||
| @ -4,6 +4,7 @@ | ||||
| @inject IUserService UserService | ||||
| @inject ISettingService SettingService | ||||
| @inject ISiteService SiteService | ||||
| @inject ISystemService SystemService | ||||
| @inject IStringLocalizer<Index> Localizer | ||||
| @inject IStringLocalizer<SharedResources> SharedLocalizer | ||||
|  | ||||
| @ -64,6 +65,74 @@ else | ||||
| 						</select> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) | ||||
| 				{ | ||||
| 					<br /> | ||||
| 					<Section Name="Password" Heading="Password Settings" ResourceKey="PasswordSettings"> | ||||
| 						<div class="row mb-1 align-items-center"> | ||||
| 							<Label Class="col-sm-3" For="minimumlength" HelpText="The Minimum Length For A Password" ResourceKey="RequiredLength">Minimum Length:</Label> | ||||
| 							<div class="col-sm-9"> | ||||
| 								<input id="minimumlength" class="form-control" @bind="@_minimumlength" required /> | ||||
| 							</div> | ||||
| 						</div>					 | ||||
| 						<div class="row mb-1 align-items-center"> | ||||
| 							<Label Class="col-sm-3" For="uniquecharacters" HelpText="The Minimum Number Of Unique Characters Which A Password Must Contain" ResourceKey="UniqueCharacters">Unique Characters:</Label> | ||||
| 							<div class="col-sm-9"> | ||||
| 								<input id="uniquecharacters" class="form-control" @bind="@_uniquecharacters" required /> | ||||
| 							</div> | ||||
| 						</div>					 | ||||
| 						<div class="row mb-1 align-items-center"> | ||||
| 							<Label Class="col-sm-3" For="requiredigit" HelpText="Indicate If Passwords Must Contain A Digit" ResourceKey="RequireDigit">Require Digit?</Label> | ||||
| 							<div class="col-sm-9"> | ||||
| 								<select id="requiredigit" class="form-select" @bind="@_requiredigit" required> | ||||
| 									<option value="true">@SharedLocalizer["Yes"]</option> | ||||
| 									<option value="false">@SharedLocalizer["No"]</option> | ||||
| 								</select> | ||||
| 							</div> | ||||
| 						</div>					 | ||||
| 						<div class="row mb-1 align-items-center"> | ||||
| 							<Label Class="col-sm-3" For="requireupper" HelpText="Indicate If Passwords Must Contain An Upper Case Character" ResourceKey="RequireUpper">Require Uppercase?</Label> | ||||
| 							<div class="col-sm-9"> | ||||
| 								<select id="requireupper" class="form-select" @bind="@_requireupper" required> | ||||
| 									<option value="true">@SharedLocalizer["Yes"]</option> | ||||
| 									<option value="false">@SharedLocalizer["No"]</option> | ||||
| 								</select> | ||||
| 							</div> | ||||
| 						</div>					 | ||||
| 						<div class="row mb-1 align-items-center"> | ||||
| 							<Label Class="col-sm-3" For="requirelower" HelpText="Indicate If Passwords Must Contain A Lower Case Character" ResourceKey="RequireLower">Require Lowercase?</Label> | ||||
| 							<div class="col-sm-9"> | ||||
| 								<select id="requirelower" class="form-select" @bind="@_requirelower" required> | ||||
| 									<option value="true">@SharedLocalizer["Yes"]</option> | ||||
| 									<option value="false">@SharedLocalizer["No"]</option> | ||||
| 								</select> | ||||
| 							</div> | ||||
| 						</div>					 | ||||
| 						<div class="row mb-1 align-items-center"> | ||||
| 							<Label Class="col-sm-3" For="requirepunctuation" HelpText="Indicate if Passwords Must Contain A Non-alphanumeric Character (ie. Punctuation)" ResourceKey="RequirePunctuation">Require Punctuation?</Label> | ||||
| 							<div class="col-sm-9"> | ||||
| 								<select id="requirepunctuation" class="form-select" @bind="@_requirepunctuation" required> | ||||
| 									<option value="true">@SharedLocalizer["Yes"]</option> | ||||
| 									<option value="false">@SharedLocalizer["No"]</option> | ||||
| 								</select> | ||||
| 							</div> | ||||
| 						</div>					 | ||||
| 					</Section> | ||||
| 					<Section Name="Lockout" Heading="Lockout Settings" ResourceKey="LockoutSettings"> | ||||
| 						<div class="row mb-1 align-items-center"> | ||||
| 							<Label Class="col-sm-3" For="maximum" HelpText="The Maximum Number Of Sign In Attempts Before A User Is Locked Out" ResourceKey="MaximumFailures">Maximum Failures:</Label> | ||||
| 							<div class="col-sm-9"> | ||||
| 								<input id="maximum" class="form-control" @bind="@_maximumfailures" required /> | ||||
| 							</div> | ||||
| 						</div>					 | ||||
| 						<div class="row mb-1 align-items-center"> | ||||
| 							<Label Class="col-sm-3" For="lockoutduration" HelpText="The Number Of Minutes A User Should Be Locked Out" ResourceKey="LockoutDuration">Lockout Duration:</Label> | ||||
| 							<div class="col-sm-9"> | ||||
| 								<input id="lockoutduration" class="form-control" @bind="@_lockoutduration" required /> | ||||
| 							</div> | ||||
| 						</div>					 | ||||
| 					</Section> | ||||
| 				} | ||||
| 			</div> | ||||
| 			<br /> | ||||
| 			<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button> | ||||
| @ -72,79 +141,104 @@ else | ||||
| } | ||||
|  | ||||
| @code { | ||||
|     private List<UserRole> allroles; | ||||
|     private List<UserRole> userroles; | ||||
|     private string _search; | ||||
| 	private List<UserRole> allroles; | ||||
| 	private List<UserRole> 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<string, object> 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<UserRole> 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<UserRole> 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<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); | ||||
|         _search = SettingService.GetSetting(settings, settingSearch, ""); | ||||
|     } | ||||
| 	private async Task LoadSettingsAsync() | ||||
| 	{ | ||||
| 		Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); | ||||
| 		_search = SettingService.GetSetting(settings, settingSearch, ""); | ||||
| 	} | ||||
|  | ||||
|     private async Task UpdateSettingsAsync() | ||||
|     { | ||||
|         Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); | ||||
|         SettingService.SetSetting(settings, settingSearch, _search); | ||||
|         await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); | ||||
|     } | ||||
| 	private async Task UpdateSettingsAsync() | ||||
| 	{ | ||||
| 		Dictionary<string, string> 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<string, object>(); | ||||
| 				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) | ||||
| 		{ | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Shaun Walker
					Shaun Walker