diff --git a/.gitignore b/.gitignore index 5e8ca2030..fcd06e014 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ obj /OpenAPI/LearningHub.Nhs.OpenApi/web.config /AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj.user /WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj.user +/ReportAPI/LearningHub.Nhs.ReportApi/web.config diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index b7d26718c..eba6b6ffa 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj @@ -89,7 +89,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/Configuration/Settings.cs b/LearningHub.Nhs.WebUI/Configuration/Settings.cs index 9764d293e..ccb06c921 100644 --- a/LearningHub.Nhs.WebUI/Configuration/Settings.cs +++ b/LearningHub.Nhs.WebUI/Configuration/Settings.cs @@ -186,6 +186,16 @@ public Settings() /// public string GoogleAnalyticsId { get; set; } + /// + /// Gets or sets the PasswordRequestLimitingPeriod. + /// + public int PasswordRequestLimitingPeriod { get; set; } + + /// + /// Gets or sets the PasswordRequestLimit. + /// + public int PasswordRequestLimit { get; set; } + /// /// Gets or sets the SupportUrls. /// diff --git a/LearningHub.Nhs.WebUI/Controllers/AccountController.cs b/LearningHub.Nhs.WebUI/Controllers/AccountController.cs index bdd0cf317..b082eaee4 100644 --- a/LearningHub.Nhs.WebUI/Controllers/AccountController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/AccountController.cs @@ -1198,8 +1198,20 @@ public async Task ForgotPassword(Models.Account.ForgotPasswordVie return this.Ok(new { duplicate = true }); } - await this.userService.ForgotPasswordAsync(model.EmailAddress); - return this.View("ForgotPasswordAcknowledgement"); + var passwordRequestLimitingPeriod = this.Settings.PasswordRequestLimitingPeriod; + var passwordRequestLimit = this.Settings.PasswordRequestLimit; + var status = await this.userService.CanRequestPasswordResetAsync(model.EmailAddress, passwordRequestLimitingPeriod, passwordRequestLimit); + if (status) + { + await this.userService.ForgotPasswordAsync(model.EmailAddress); + return this.View("ForgotPasswordAcknowledgement"); + } + else + { + this.ViewBag.Period = passwordRequestLimitingPeriod; + this.ViewBag.Limit = passwordRequestLimit; + return this.View("TooManyRequests"); + } } /// diff --git a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs index 51d6be9db..92d550393 100644 --- a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs @@ -6,7 +6,6 @@ namespace LearningHub.Nhs.WebUI.Controllers using System.Linq; using System.Net.Http; using System.Threading.Tasks; - using elfhHub.Nhs.Models.Common; using LearningHub.Nhs.Models.Content; using LearningHub.Nhs.Models.Enums.Content; using LearningHub.Nhs.Models.Extensions; @@ -21,7 +20,6 @@ namespace LearningHub.Nhs.WebUI.Controllers using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; - using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.FeatureManagement; @@ -54,7 +52,6 @@ public class HomeController : BaseController /// Dashboard service. /// Content service. /// featureManager. - /// config. public HomeController( IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, @@ -65,8 +62,7 @@ public HomeController( LearningHubAuthServiceConfig authConfig, IDashboardService dashboardService, IContentService contentService, - IFeatureManager featureManager, - Microsoft.Extensions.Configuration.IConfiguration configuration) + IFeatureManager featureManager) : base(hostingEnvironment, httpClientFactory, logger, settings.Value) { this.authConfig = authConfig; @@ -75,7 +71,6 @@ public HomeController( this.dashboardService = dashboardService; this.contentService = contentService; this.featureManager = featureManager; - this.configuration = configuration; } /// @@ -170,26 +165,16 @@ public IActionResult Error(int? httpStatusCode) } else { - if (originalPath == "/TooManyRequests") + this.ViewBag.ErrorHeader = httpStatusCode.Value switch { - this.ViewBag.Period = this.configuration["IpRateLimiting:GeneralRules:0:Period"]; - this.ViewBag.Limit = this.configuration["IpRateLimiting:GeneralRules:0:Limit"]; - - return this.View("TooManyRequests"); - } - else - { - this.ViewBag.ErrorHeader = httpStatusCode.Value switch - { - 401 => "You do not have permission to access this page", - 404 => "We cannot find the page you are looking for", - _ => "We cannot find the page you are looking for", - }; + 401 => "You do not have permission to access this page", + 404 => "We cannot find the page you are looking for", + _ => "We cannot find the page you are looking for", + }; - this.ViewBag.HttpStatusCode = httpStatusCode.Value; - this.ViewBag.HomePageUrl = "/home"; - return this.View("CustomError"); - } + this.ViewBag.HttpStatusCode = httpStatusCode.Value; + this.ViewBag.HomePageUrl = "/home"; + return this.View("CustomError"); } } diff --git a/LearningHub.Nhs.WebUI/Interfaces/IUserService.cs b/LearningHub.Nhs.WebUI/Interfaces/IUserService.cs index 0b9dd4b77..1efd29ad5 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IUserService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IUserService.cs @@ -418,6 +418,15 @@ public interface IUserService /// A representing the result of the asynchronous operation. Task RegenerateEmailChangeValidationTokenAsync(string newPrimaryEmail, bool isUserRoleUpgrade); + /// + /// User Can request for password reset. + /// + /// The email Address. + /// The passwordRequestLimitingPeriod. + /// ThepasswordRequestLimit. + /// A representing the result of the asynchronous operation. + Task CanRequestPasswordResetAsync(string emailAddress, int passwordRequestLimitingPeriod, int passwordRequestLimit); + /// /// GenerateEmailChangeValidationTokenAndSendEmail. /// diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index 227e516ff..1b4ac83d1 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -113,7 +113,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/Middleware/LHIPRateLimitMiddleware.cs b/LearningHub.Nhs.WebUI/Middleware/LHIPRateLimitMiddleware.cs deleted file mode 100644 index 31f2f62f5..000000000 --- a/LearningHub.Nhs.WebUI/Middleware/LHIPRateLimitMiddleware.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace LearningHub.Nhs.WebUI.Middleware -{ - using System.Threading.Tasks; - using AspNetCoreRateLimit; - using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Options; - - /// - /// Defines the . - /// - public class LHIPRateLimitMiddleware : IpRateLimitMiddleware - { - /// - /// Initializes a new instance of the class. - /// - /// The next. - /// The processingStrategy. - /// The options. - /// The policyStore. - /// The config. - /// The logger. - public LHIPRateLimitMiddleware( - RequestDelegate next, - IProcessingStrategy processingStrategy, - IOptions options, - IIpPolicyStore policyStore, - IRateLimitConfiguration config, - ILogger logger) - : base( - next, - processingStrategy, - options, - policyStore, - config, - logger) - { - } - - /// - /// The ReturnQuotaExceededResponse method. - /// - /// The httpContext. - /// The rule. - /// The retryAfter. - /// A representing the asynchronous operation. - public override Task ReturnQuotaExceededResponse( - HttpContext httpContext, - RateLimitRule rule, - string retryAfter) - { - httpContext.Response.Headers["Location"] = "/TooManyRequests"; - httpContext.Response.StatusCode = 302; - return httpContext.Response.WriteAsync(string.Empty); - } - } -} diff --git a/LearningHub.Nhs.WebUI/Program.cs b/LearningHub.Nhs.WebUI/Program.cs index 2de0aa053..c24d9057c 100644 --- a/LearningHub.Nhs.WebUI/Program.cs +++ b/LearningHub.Nhs.WebUI/Program.cs @@ -84,7 +84,6 @@ app.UseAuthorization(); app.UseMiddleware(); - app.UseMiddleware(); app.UseStaticFiles(); app.Map(TimezoneInfoMiddleware.TimezoneInfoUrl, b => b.UseMiddleware()); diff --git a/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs b/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs index 9adb40e44..5ed5cc926 100644 --- a/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs +++ b/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs @@ -111,8 +111,6 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur } }); - ConfigureIpRateLimiting(services, configuration); - // this method setup so httpcontext is available from controllers services.AddHttpContextAccessor(); services.AddSingleton(learningHubAuthSvcConf); @@ -139,17 +137,5 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur services.AddFeatureManagement(); } - - /// - /// ConfigureIpRateLimiting. - /// - /// The services. - /// The configuration. - private static void ConfigureIpRateLimiting(IServiceCollection services, IConfiguration configuration) - { - services.Configure(configuration.GetSection("IpRateLimiting")); - services.AddInMemoryRateLimiting(); - services.AddSingleton(); - } } } \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Services/UserService.cs b/LearningHub.Nhs.WebUI/Services/UserService.cs index 83b8f64d8..942360eae 100644 --- a/LearningHub.Nhs.WebUI/Services/UserService.cs +++ b/LearningHub.Nhs.WebUI/Services/UserService.cs @@ -1640,6 +1640,31 @@ public async Task RegenerateEmailChangeVali return viewmodel; } + /// + public async Task CanRequestPasswordResetAsync(string emailAddress, int passwordRequestLimitingPeriod, int passwordRequestLimit) + { + bool status = false; + + var client = await this.LearningHubHttpClient.GetClientAsync(); + + var request = $"User/CanRequestPasswordReset/{emailAddress}/{passwordRequestLimitingPeriod}/{passwordRequestLimit}"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = await response.Content.ReadAsStringAsync(); + status = JsonConvert.DeserializeObject(result); + } + else if (response.StatusCode == HttpStatusCode.Unauthorized + || + response.StatusCode == HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return status; + } + /// public async Task GenerateEmailChangeValidationTokenAndSendEmailAsync(string emailAddress, bool isUserRoleUpgrade) { diff --git a/LearningHub.Nhs.WebUI/Views/Shared/TooManyRequests.cshtml b/LearningHub.Nhs.WebUI/Views/Account/TooManyRequests.cshtml similarity index 79% rename from LearningHub.Nhs.WebUI/Views/Shared/TooManyRequests.cshtml rename to LearningHub.Nhs.WebUI/Views/Account/TooManyRequests.cshtml index b0b967408..fbc8a468d 100644 --- a/LearningHub.Nhs.WebUI/Views/Shared/TooManyRequests.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Account/TooManyRequests.cshtml @@ -2,12 +2,6 @@ ViewData["Title"] = "Reset limit reached"; // Get the value from ViewBag var period = ViewBag.Period.ToString(); - - // Remove the last character (if the string is not empty) - if (!string.IsNullOrEmpty(period) && period.Length > 0) - { - period = period.Substring(0, period.Length - 1); - } }
diff --git a/LearningHub.Nhs.WebUI/appsettings.json b/LearningHub.Nhs.WebUI/appsettings.json index e50f1ad55..e67ceaa8b 100644 --- a/LearningHub.Nhs.WebUI/appsettings.json +++ b/LearningHub.Nhs.WebUI/appsettings.json @@ -37,6 +37,8 @@ "KeepUserSessionAliveIntervalMins": 15, "SecurityQuestionsToAsk": 2, "Restricted": false, + "PasswordRequestLimitingPeriod": 1, // minutes + "PasswordRequestLimit": 2, "AzureBlobSettings": { "ConnectionString": "", "UploadContainer": "" @@ -158,18 +160,5 @@ "FeatureManagement": { "ContributeAudioVideoResource": true, "DisplayAudioVideoResource": true - }, - "IpRateLimiting": { - "EnableEndpointRateLimiting": true, - "StackBlockedRequests": false, - "RealIpHeader": "X-Real-IP", - "HttpStatusCode": 429, - "GeneralRules": [ - { - "Endpoint": "post:/Account/ForgotPassword", - "Period": "1m", - "Limit": 5 - } - ] } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj index 440fcb5e7..f34790d51 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj @@ -16,7 +16,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj index 01cfe7b6f..20b3a1c06 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj @@ -17,7 +17,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj index c843ac88c..f84d5121c 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj @@ -30,7 +30,7 @@ - + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj index f03e5f430..0a697a51f 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj @@ -16,7 +16,7 @@ - + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj index 4a17dde71..50e3d7536 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj @@ -18,7 +18,7 @@ - + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj index cf0c17583..55c8a1be4 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj @@ -19,7 +19,7 @@ - + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj index f05db707a..2f5518784 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj @@ -17,7 +17,7 @@ - + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj b/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj index b0c9a1151..583caa9f2 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj @@ -20,7 +20,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.API/Controllers/UserController.cs b/WebAPI/LearningHub.Nhs.API/Controllers/UserController.cs index 838e2e0a2..ec0676a46 100644 --- a/WebAPI/LearningHub.Nhs.API/Controllers/UserController.cs +++ b/WebAPI/LearningHub.Nhs.API/Controllers/UserController.cs @@ -28,6 +28,11 @@ public class UserController : ApiControllerBase ///
private readonly IUserProfileService userProfileService; + /// + /// The user password reset requets service. + /// + private readonly IUserPasswordResetRequestsService userPasswordResetRequestsService; + /// /// Initializes a new instance of the class. /// @@ -41,18 +46,21 @@ public class UserController : ApiControllerBase /// The user notification service. /// /// The security service. + /// The userPasswordResetRequests service. /// The logger. public UserController( IUserService userService, IUserProfileService userProfileService, IUserNotificationService userNotificationService, ISecurityService securityService, + IUserPasswordResetRequestsService userPasswordResetRequestsService, ILogger logger) : base(userService, logger) { this.userProfileService = userProfileService; this.userNotificationService = userNotificationService; this.securityService = securityService; + this.userPasswordResetRequestsService = userPasswordResetRequestsService; } /// @@ -270,6 +278,29 @@ public async Task ReGenerateEmailChangeValidationToken(string new return this.Ok(result); } + /// + /// Check user can request password reset. + /// + /// emailAddress. + /// The passwordRequestLimitingPeriod. + /// ThepasswordRequestLimit. + /// + /// The . + /// + [HttpGet] + [AllowAnonymous] + [Route("CanRequestPasswordReset/{emailAddress}/{passwordRequestLimitingPeriod}/{passwordRequestLimit}")] + public async Task CanRequestPasswordReset(string emailAddress, int passwordRequestLimitingPeriod, int passwordRequestLimit) + { + var result = await this.userPasswordResetRequestsService.CanRequestPasswordReset(emailAddress, passwordRequestLimitingPeriod, passwordRequestLimit); + if (result) + { + await this.userPasswordResetRequestsService.CreateUserPasswordRequest(emailAddress); + } + + return result; + } + /// /// Regenerate email change token. /// diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj index 9f081a054..bb0c6c6cc 100644 --- a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj +++ b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj @@ -29,7 +29,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.API/Startup/ServiceMappings.cs b/WebAPI/LearningHub.Nhs.API/Startup/ServiceMappings.cs index 03bf68127..a9f0988ce 100644 --- a/WebAPI/LearningHub.Nhs.API/Startup/ServiceMappings.cs +++ b/WebAPI/LearningHub.Nhs.API/Startup/ServiceMappings.cs @@ -147,6 +147,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -228,6 +229,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -284,6 +286,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddTransient(); diff --git a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj index c512629ba..74d2ccc77 100644 --- a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj +++ b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj index d33315f1b..9663e4ee7 100644 --- a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj @@ -11,7 +11,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index 42a432ae2..06e1a1598 100644 --- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj +++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj @@ -536,6 +536,7 @@ + diff --git a/WebAPI/LearningHub.Nhs.Database/Tables/Hub/PasswordResetRequests.sql b/WebAPI/LearningHub.Nhs.Database/Tables/Hub/PasswordResetRequests.sql new file mode 100644 index 000000000..e1a2bd06a --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Tables/Hub/PasswordResetRequests.sql @@ -0,0 +1,17 @@ + +CREATE TABLE [hub].[PasswordResetRequests]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [EmailAddress] [nvarchar](100) NOT NULL, + [RequestTime] [datetimeoffset](7) NOT NULL, + [Deleted] [bit] NOT NULL, + [CreateUserId] [int] NOT NULL, + [CreateDate] [datetimeoffset](7) NOT NULL, + [AmendUserId] [int] NOT NULL, + [AmendDate] [datetimeoffset](7) NOT NULL, + CONSTRAINT [PK_Hub_PasswordResetRequests] PRIMARY KEY CLUSTERED +( + [Id] ASC +) +) +GO + diff --git a/WebAPI/LearningHub.Nhs.Repository.Interface/IUserPasswordResetRequestsRepository.cs b/WebAPI/LearningHub.Nhs.Repository.Interface/IUserPasswordResetRequestsRepository.cs new file mode 100644 index 000000000..dbe4610a8 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Repository.Interface/IUserPasswordResetRequestsRepository.cs @@ -0,0 +1,32 @@ +namespace LearningHub.Nhs.Repository.Interface +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + using Microsoft.AspNetCore.Mvc; + + /// + /// The UserPasswordResetRequestsRepository interface. + /// + public interface IUserPasswordResetRequestsRepository + { + /// + /// To check user can request a password reset. + /// + /// + /// The lookup. + /// + /// The passwordRequestLimitingPeriod. + /// ThepasswordRequestLimit. + /// + /// The . + /// + Task CanRequestPasswordResetAsync(string emailAddress, int passwordRequestLimitingPeriod, int passwordRequestLimit); + + /// + /// CreatePasswordRequests. + /// + /// The emailAddress. + /// The . + Task CreatePasswordRequests(string emailAddress); + } +} \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj index e6e0db066..d41ba9e68 100644 --- a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj @@ -10,7 +10,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj index 86ba264f1..904bda4c8 100644 --- a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj +++ b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj @@ -9,7 +9,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository/LearningHubDbContext.cs b/WebAPI/LearningHub.Nhs.Repository/LearningHubDbContext.cs index 77f14343c..80c45ed58 100644 --- a/WebAPI/LearningHub.Nhs.Repository/LearningHubDbContext.cs +++ b/WebAPI/LearningHub.Nhs.Repository/LearningHubDbContext.cs @@ -115,6 +115,11 @@ public LearningHubDbContextOptions Options /// public virtual DbSet EmailChangeValidationToken { get; set; } + /// + /// Gets or sets the password requests. + /// + public virtual DbSet PasswordResetRequests { get; set; } + /// /// Gets or sets the Notification. /// diff --git a/WebAPI/LearningHub.Nhs.Repository/Map/PasswordResetRequestsMap.cs b/WebAPI/LearningHub.Nhs.Repository/Map/PasswordResetRequestsMap.cs new file mode 100644 index 000000000..f3d60981c --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Repository/Map/PasswordResetRequestsMap.cs @@ -0,0 +1,24 @@ +namespace LearningHub.Nhs.Repository +{ + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Repository.Map; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// The password reset requests map. + /// + public class PasswordResetRequestsMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// + /// The model builder. + /// + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("PasswordResetRequests", "hub"); + } + } +} \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Repository/UserPasswordResetRequestsRepository.cs b/WebAPI/LearningHub.Nhs.Repository/UserPasswordResetRequestsRepository.cs new file mode 100644 index 000000000..5130bf9df --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Repository/UserPasswordResetRequestsRepository.cs @@ -0,0 +1,86 @@ +namespace LearningHub.Nhs.Repository +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using elfhHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Repository.Interface; + using Microsoft.AspNetCore.Mvc; + using Microsoft.EntityFrameworkCore; + + /// + /// The user password reset requests repository. + /// + public class UserPasswordResetRequestsRepository : GenericRepository, IUserPasswordResetRequestsRepository + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The db context. + /// + /// + /// The Timezone offset manager. + /// + public UserPasswordResetRequestsRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// To check user can request a password reset. + /// + /// + /// The lookup. + /// + /// The passwordRequestLimitingPeriod. + /// ThepasswordRequestLimit. + /// + /// The . + /// + public async Task CanRequestPasswordResetAsync(string emailAddress, int passwordRequestLimitingPeriod, int passwordRequestLimit) + { + // passwordRequestLimitingPeriod is in minutes. + var oneMinuteAgo = DateTime.UtcNow.AddMinutes(-passwordRequestLimitingPeriod); + + // Get the count of password reset requests for the user in the last 1 minute + var recentRequests = await this.DbContext.PasswordResetRequests + .Where(r => r.EmailAddress == emailAddress && r.RequestTime >= oneMinuteAgo) + .CountAsync(); + + return recentRequests < passwordRequestLimit; + } + + /// + /// CreatePasswordRequests. + /// + /// The emailAddress. + /// The . + public async Task CreatePasswordRequests(string emailAddress) + { + try + { + var passwordResetRequests = new PasswordResetRequests + { + EmailAddress = emailAddress, + RequestTime = DateTime.UtcNow, + Deleted = false, + CreateUserId = 4, + CreateDate = DateTime.UtcNow, + AmendUserId = 4, + AmendDate = DateTime.UtcNow, + }; + + await this.DbContext.PasswordResetRequests.AddAsync(passwordResetRequests); + await this.DbContext.SaveChangesAsync(); + return true; + } + catch (Exception ex) + { + return false; + } + } + } +} \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Services.Interface/IUserPasswordResetRequestsService.cs b/WebAPI/LearningHub.Nhs.Services.Interface/IUserPasswordResetRequestsService.cs new file mode 100644 index 000000000..610561f87 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Services.Interface/IUserPasswordResetRequestsService.cs @@ -0,0 +1,33 @@ +namespace LearningHub.Nhs.Services.Interface +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Resource; + using LearningHub.Nhs.Models.User; + using LearningHub.Nhs.Models.Validation; + using Microsoft.AspNetCore.Mvc; + + /// + /// Theuser password resets interface. + /// + public interface IUserPasswordResetRequestsService + { + /// + /// The check user can rtequest password reset async. + /// + /// The user name. + /// The passwordRequestLimitingPeriod. + /// ThepasswordRequestLimit. + /// The . + Task CanRequestPasswordReset(string emailAddress, int passwordRequestLimitingPeriod, int passwordRequestLimit); + + /// + /// CreatePasswordRequests. + /// + /// The emailAddress. + /// The . + Task CreateUserPasswordRequest(string emailAddress); + } +} diff --git a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj index 5599e343a..b259b3a7e 100644 --- a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj @@ -16,7 +16,7 @@ - + all diff --git a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj index 97abe26a9..f78df0084 100644 --- a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj @@ -13,7 +13,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj index 090d5cb80..e99e5300a 100644 --- a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj +++ b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj @@ -13,7 +13,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services/UserPasswordResetRequestsService.cs b/WebAPI/LearningHub.Nhs.Services/UserPasswordResetRequestsService.cs new file mode 100644 index 000000000..78bee3308 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Services/UserPasswordResetRequestsService.cs @@ -0,0 +1,96 @@ +namespace LearningHub.Nhs.Services +{ + using System; + using System.Collections.Generic; + using System.IO.Enumeration; + using System.Linq; + using System.Reflection.Metadata.Ecma335; + using System.Threading.Tasks; + using AutoMapper; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Constants; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Resource; + using LearningHub.Nhs.Models.User; + using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.Repository.Interface; + using LearningHub.Nhs.Services.Extensions; + using LearningHub.Nhs.Services.Interface; + using Microsoft.AspNetCore.Mvc; + using Microsoft.EntityFrameworkCore; + using Microsoft.Extensions.Logging; + using Newtonsoft.Json; + + /// + /// The user service. + /// + public class UserPasswordResetRequestsService : IUserPasswordResetRequestsService + { + /// + /// The user password reset requests repository. + /// + private readonly IUserPasswordResetRequestsRepository userPasswordResetRequestsRepository; + + /// + /// The mapper. + /// + private readonly IMapper mapper; + + /// + /// The cache. + /// + private readonly ICachingService cachingService; + + /// + /// The logger. + /// + private readonly ILogger logger; + + /// + /// Initializes a new instance of the class. + /// + /// The user password reset requests repository. + /// The mapper. + /// The caching service. + /// The logger. + /// The userDetailsRepository. + public UserPasswordResetRequestsService( + IUserPasswordResetRequestsRepository userPasswordResetRequestsRepository, + IMapper mapper, + ICachingService cachingService, + ILogger logger, + IUserProfileRepository userDetailsRepository) + { + this.userPasswordResetRequestsRepository = userPasswordResetRequestsRepository; + this.mapper = mapper; + this.cachingService = cachingService; + this.logger = logger; + } + + /// + /// The get by username async. + /// + /// The user name. + /// The passwordRequestLimitingPeriod. + /// ThepasswordRequestLimit. + /// The . + public async Task CanRequestPasswordReset(string emailAddress, int passwordRequestLimitingPeriod, int passwordRequestLimit) + { + var result = await this.userPasswordResetRequestsRepository.CanRequestPasswordResetAsync(emailAddress, passwordRequestLimitingPeriod, passwordRequestLimit); + + return result; + } + + /// + /// CreateUserPasswordRequest. + /// + /// the emailAddress. + /// The . + public async Task CreateUserPasswordRequest(string emailAddress) + { + var result = await this.userPasswordResetRequestsRepository.CreatePasswordRequests(emailAddress); + return result; + } + } +} diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj index 8b75e5a4b..697d1db2f 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj @@ -25,7 +25,7 @@ - + all diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj index ac957415c..89ca1344e 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj index 05b6c681a..4ab608bac 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj @@ -10,7 +10,7 @@ - + all diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj index c0c4fbfe5..7dd7febb7 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj @@ -9,7 +9,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj index b2ecc0d2c..5b83ec893 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj @@ -10,7 +10,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj index 98c4954b5..bf0235325 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive