diff --git a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs index 87d01e668..51d6be9db 100644 --- a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs @@ -15,13 +15,13 @@ namespace LearningHub.Nhs.WebUI.Controllers using LearningHub.Nhs.WebUI.Helpers; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models; - using Microsoft.ApplicationInsights.AspNetCore; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; 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; @@ -39,6 +39,7 @@ public class HomeController : BaseController private readonly IDashboardService dashboardService; private readonly IContentService contentService; private readonly IFeatureManager featureManager; + private readonly Microsoft.Extensions.Configuration.IConfiguration configuration; /// /// Initializes a new instance of the class. @@ -53,6 +54,7 @@ public class HomeController : BaseController /// Dashboard service. /// Content service. /// featureManager. + /// config. public HomeController( IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, @@ -63,7 +65,8 @@ public HomeController( LearningHubAuthServiceConfig authConfig, IDashboardService dashboardService, IContentService contentService, - IFeatureManager featureManager) + IFeatureManager featureManager, + Microsoft.Extensions.Configuration.IConfiguration configuration) : base(hostingEnvironment, httpClientFactory, logger, settings.Value) { this.authConfig = authConfig; @@ -72,6 +75,7 @@ public HomeController( this.dashboardService = dashboardService; this.contentService = contentService; this.featureManager = featureManager; + this.configuration = configuration; } /// @@ -133,11 +137,12 @@ public IActionResult CreateAccount() public IActionResult Error(int? httpStatusCode) { string originalPathUrlMessage = null; - + string originalPath = null; if (httpStatusCode.HasValue && httpStatusCode.Value == 404) { var exceptionHandlerPathFeature = this.HttpContext.Features.Get(); - originalPathUrlMessage = $"Page Not Found url: {exceptionHandlerPathFeature?.OriginalPath}. "; + originalPath = exceptionHandlerPathFeature?.OriginalPath; + originalPathUrlMessage = $"Page Not Found url: {originalPath}. "; } if (this.User.Identity.IsAuthenticated) @@ -165,16 +170,26 @@ public IActionResult Error(int? httpStatusCode) } else { - this.ViewBag.ErrorHeader = httpStatusCode.Value switch + if (originalPath == "/TooManyRequests") { - 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.Period = this.configuration["IpRateLimiting:GeneralRules:0:Period"]; + this.ViewBag.Limit = this.configuration["IpRateLimiting:GeneralRules:0:Limit"]; - this.ViewBag.HttpStatusCode = httpStatusCode.Value; - this.ViewBag.HomePageUrl = "/home"; - return this.View("CustomError"); + 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", + }; + + this.ViewBag.HttpStatusCode = httpStatusCode.Value; + this.ViewBag.HomePageUrl = "/home"; + return this.View("CustomError"); + } } } @@ -320,14 +335,19 @@ public IActionResult NhsSites() } /// - /// The Logout. - /// This is directly referenced in the LoginWizardFilter to allow - /// logouts to bypass LoginWizard redirects. - /// If the name is changed, the LoginWizardFilter MUST be updated. + /// The ChangePasswordAcknowledgement. /// /// The . - [AllowAnonymous] - public IActionResult Logout() + public IActionResult ChangePasswordAcknowledgement() + { + return this.View(); + } + + /// + /// StatusUpdate. + /// + /// Actionresult. + public IActionResult UserLogout() { if (!(this.User?.Identity.IsAuthenticated ?? false)) { @@ -337,6 +357,20 @@ public IActionResult Logout() return new SignOutResult(new[] { CookieAuthenticationDefaults.AuthenticationScheme, "oidc" }); } + /// + /// The Logout. + /// This is directly referenced in the LoginWizardFilter to allow + /// logouts to bypass LoginWizard redirects. + /// If the name is changed, the LoginWizardFilter MUST be updated. + /// + /// The . + [AllowAnonymous] + public IActionResult Logout() + { + var redirectUri = $"{this.configuration["LearningHubAuthServiceConfig:Authority"]}/Home/SetIsPasswordUpdate?isLogout=true"; + return this.Redirect(redirectUri); + } + /// /// The SessionTimeout. /// diff --git a/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs b/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs index 4494d9463..8711dd817 100644 --- a/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs @@ -21,10 +21,12 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; + using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NHSUKViewComponents.Web.ViewModels; using ChangePasswordViewModel = LearningHub.Nhs.WebUI.Models.UserProfile.ChangePasswordViewModel; + using IConfiguration = Microsoft.Extensions.Configuration.IConfiguration; /// /// The UserController. @@ -43,6 +45,7 @@ public partial class MyAccountController : BaseController private readonly ISpecialtyService specialtyService; private readonly ILocationService locationService; private readonly ICacheService cacheService; + private readonly IConfiguration configuration; /// /// Initializes a new instance of the class. @@ -61,6 +64,7 @@ public partial class MyAccountController : BaseController /// The locationService. /// The multiPageFormService. /// The cacheService. + /// The cacheService. public MyAccountController( IWebHostEnvironment hostingEnvironment, ILogger logger, @@ -75,7 +79,8 @@ public MyAccountController( ISpecialtyService specialtyService, ILocationService locationService, IMultiPageFormService multiPageFormService, - ICacheService cacheService) + ICacheService cacheService, + IConfiguration configuration) : base(hostingEnvironment, httpClientFactory, logger, settings.Value) { this.userService = userService; @@ -88,6 +93,7 @@ public MyAccountController( this.locationService = locationService; this.multiPageFormService = multiPageFormService; this.cacheService = cacheService; + this.configuration = configuration; } private string LoginWizardCacheKey => $"{this.CurrentUserId}:LoginWizard"; @@ -452,9 +458,8 @@ public async Task UpdatePassword(ChangePasswordViewModel model) if (this.ModelState.IsValid) { await this.userService.UpdatePassword(model.NewPassword); - - this.ViewBag.SuccessMessage = CommonValidationErrorMessages.PasswordSuccessMessage; - return this.View("SuccessMessage"); + var redirectUri = $"{this.configuration["LearningHubAuthServiceConfig:Authority"]}/Home/SetIsPasswordUpdate?isLogout=false"; + return this.Redirect(redirectUri); } else { diff --git a/LearningHub.Nhs.WebUI/Helpers/InMemoryTicketStore.cs b/LearningHub.Nhs.WebUI/Helpers/InMemoryTicketStore.cs new file mode 100644 index 000000000..4d17cc5b7 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Helpers/InMemoryTicketStore.cs @@ -0,0 +1,104 @@ +namespace LearningHub.Nhs.WebUI.Helpers +{ + using System; + using System.Collections.Concurrent; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Authentication; + using Microsoft.AspNetCore.Authentication.Cookies; + + /// + /// Defines the . + /// + public class InMemoryTicketStore : ITicketStore + { + private readonly ConcurrentDictionary cache; + + /// + /// Initializes a new instance of the class. + /// The InMemoryTicketStore. + /// + /// the cache. + public InMemoryTicketStore(ConcurrentDictionary cache) + { + this.cache = cache; + } + + /// + /// The StoreAsync. + /// + /// The ticket. + /// The key. + public async Task StoreAsync(AuthenticationTicket ticket) + { + var ticketUserId = ticket.Principal.Claims.Where(c => c.Type == "sub") + .FirstOrDefault() + .Value; + var matchingAuthTicket = this.cache.Values.FirstOrDefault( + t => t.Principal.Claims.FirstOrDefault( + c => c.Type == "sub" + && c.Value == ticketUserId) != null); + if (matchingAuthTicket != null) + { + var cacheKey = this.cache.Where( + entry => entry.Value == matchingAuthTicket) + .Select(entry => entry.Key) + .FirstOrDefault(); + this.cache.TryRemove( + cacheKey, + out _); + } + + var key = Guid + .NewGuid() + .ToString(); + await this.RenewAsync( + key, + ticket); + return key; + } + + /// + /// The RenewAsync. + /// + /// The key. + /// The ticket. + /// The Task. + public Task RenewAsync( + string key, + AuthenticationTicket ticket) + { + this.cache.AddOrUpdate( + key, + ticket, + (_, _) => ticket); + return Task.CompletedTask; + } + + /// + /// The RetrieveAsync. + /// + /// The Key. + /// The Task. + public Task RetrieveAsync(string key) + { + this.cache.TryGetValue( + key, + out var ticket); + return Task.FromResult(ticket); + } + + /// + /// The RemoveAsync. + /// + /// The key. + /// The Task. + public Task RemoveAsync(string key) + { + this.cache.TryRemove( + key, + out _); + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index 1461f089a..66ab9d294 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -104,6 +104,7 @@ + diff --git a/LearningHub.Nhs.WebUI/Middleware/LHIPRateLimitMiddleware.cs b/LearningHub.Nhs.WebUI/Middleware/LHIPRateLimitMiddleware.cs new file mode 100644 index 000000000..31f2f62f5 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Middleware/LHIPRateLimitMiddleware.cs @@ -0,0 +1,57 @@ +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 8aee50089..2de0aa053 100644 --- a/LearningHub.Nhs.WebUI/Program.cs +++ b/LearningHub.Nhs.WebUI/Program.cs @@ -84,7 +84,7 @@ 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 aa90f135e..1e538331e 100644 --- a/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs +++ b/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs @@ -3,6 +3,7 @@ using System; using System.IdentityModel.Tokens.Jwt; using System.Net; + using AspNetCoreRateLimit; using LearningHub.Nhs.Caching; using LearningHub.Nhs.Models.Binders; using LearningHub.Nhs.Models.Enums; @@ -110,6 +111,8 @@ 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); @@ -136,5 +139,17 @@ 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/Startup/AuthenticationConfiguration.cs b/LearningHub.Nhs.WebUI/Startup/AuthenticationConfiguration.cs index 40caab241..bbd39d9e2 100644 --- a/LearningHub.Nhs.WebUI/Startup/AuthenticationConfiguration.cs +++ b/LearningHub.Nhs.WebUI/Startup/AuthenticationConfiguration.cs @@ -1,12 +1,14 @@ namespace LearningHub.Nhs.WebUI.Startup { using System; + using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; using IdentityModel; using LearningHub.Nhs.Caching; using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Handlers; + using LearningHub.Nhs.WebUI.Helpers; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; @@ -41,6 +43,7 @@ public static void ConfigureAuthentication(this IServiceCollection services, Lea options.SlidingExpiration = true; options.EventsType = typeof(CookieEventHandler); options.AccessDeniedPath = "/Home/AccessDenied"; + options.SessionStore = new InMemoryTicketStore(new ConcurrentDictionary()); }) .AddOpenIdConnect(AuthenticationScheme, options => { diff --git a/LearningHub.Nhs.WebUI/Views/Home/ChangePasswordAcknowledgement.cshtml b/LearningHub.Nhs.WebUI/Views/Home/ChangePasswordAcknowledgement.cshtml new file mode 100644 index 000000000..28bf1b66a --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/Home/ChangePasswordAcknowledgement.cshtml @@ -0,0 +1,24 @@ +@{ + ViewData["Title"] = "Change password Acknowledgement"; +} + +
+
+
+
+
+

Your password has been changed successfully.

+ +

+ You have been logged out. Please log in using your username and new password to explore the Learning Hub. +

+
+
+ +
+
+
+
+
+
+
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/Shared/TooManyRequests.cshtml b/LearningHub.Nhs.WebUI/Views/Shared/TooManyRequests.cshtml new file mode 100644 index 000000000..b0b967408 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/Shared/TooManyRequests.cshtml @@ -0,0 +1,22 @@ +@{ + 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); + } +} +
+
+
+
+

@ViewData["Title"]

+

You've requested the maximum number of password resets (@ViewBag.Limit). Please wait @period minutes before trying again or contact the support team.

+

@DateTimeOffset.Now.ToString("d MMMM yyyy HH:mm:ss")

+
+
+
+
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/appsettings.json b/LearningHub.Nhs.WebUI/appsettings.json index b459b9b6b..e50f1ad55 100644 --- a/LearningHub.Nhs.WebUI/appsettings.json +++ b/LearningHub.Nhs.WebUI/appsettings.json @@ -113,7 +113,7 @@ }, "EnableTempDebugging": "false", "LimitScormToAdmin": "false" - + }, "LearningHubAuthServiceConfig": { "Authority": "", @@ -158,5 +158,18 @@ "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/LearningHub.Nhs.WebUI/wwwroot/js/pagingcontrol.js b/LearningHub.Nhs.WebUI/wwwroot/js/pagingcontrol.js deleted file mode 100644 index 31cd8f56e..000000000 --- a/LearningHub.Nhs.WebUI/wwwroot/js/pagingcontrol.js +++ /dev/null @@ -1,106 +0,0 @@ -var WebUI = WebUI || {}; -WebUI.PagingRequest = { Page: 1, SortColumn: '', SortDirection: '', Filter: [] }; - -WebUI.pagingSetup = function (sortColumn, sortDirection, filter) { - - WebUI.PagingRequest.SortColumn = sortColumn; - WebUI.PagingRequest.SortDirection = sortDirection; - if (!filter) { filter = []; } - WebUI.PagingRequest.Filter = filter; - - // Add Click events to column headers - $('table.lh-datatable th.orderable').attr('onClick', 'WebUI.changeTableOrder(this);'); - - // Add asc & desc class - $('table.lh-datatable th[data-column="' + sortColumn + '"].orderable').addClass(sortDirection === 'D' ? 'desc' : 'asc'); - - // Add filter row - var columns = $('table.lh-datatable.filtered thead tr th'); - if (columns.length > 0) { - row = document.createElement('tr'); - $(row).addClass('filter-row'); - - for (var i = 0; i < columns.length; i++) { - var columnName = $(columns[i]).attr('data-column'); - //var filterColumn = filter.find(x => x.Column === columnName); fails in IE11 - var filterColumn = filter.filter(function (x) { return x.Column === columnName; })[0]; - var filterValue = ''; - if (filterColumn) { filterValue = filterColumn.Value; } - var inputType = 'text'; - if ($(columns[i]).attr('data-type')) { inputType = $(columns[i]).attr('data-type'); }; - - if (columnName) { - $(row).append('
'); - } else { - $(row).append(''); - } - //console.log('Add cell :' + i); - } - $('table.lh-datatable tbody').prepend(row); - - // Add click event to filter buttons - $('table.lh-datatable tr.filter-row td button').attr('onClick', 'WebUI.filterTable();'); - $('table.lh-datatable tr.filter-row td input').keypress(function (e) { - if (e.which === 13) { - WebUI.filterTable(); - return false; - } - }); - $('table.lh-datatable tr.filter-row td input')[0].focus(); - } -}; - -WebUI.requestPage = function (page) { - WebUI.PagingRequest.Page = page; - WebUI.retrievePage(); -}; - -WebUI.changeTableOrder = function (headerLink) { - var sortDirection = 'A'; - var sortColumn = $(headerLink).attr('data-column'); - var currentSortDirection = $(headerLink).hasClass('asc'); - - if (currentSortDirection) { sortDirection = 'D'; } - - WebUI.PagingRequest.SortDirection = sortDirection; - WebUI.PagingRequest.SortColumn = sortColumn; - - WebUI.retrievePage(); -}; - -WebUI.filterTable = function () { - WebUI.PagingRequest.Filter = []; - $('table.lh-datatable.filtered tbody tr.filter-row td input').each(function (index) { - var filterColumn = $(this).attr('data-column'); - var filterValue = $(this).val(); - if ($(this).val() !== '') { - WebUI.PagingRequest.Filter.push({ Column: filterColumn, Value: filterValue }); - } - }); - WebUI.retrievePage(); -}; - -WebUI.clearFilters = function () { - WebUI.PagingRequest.Page = 1; - WebUI.PagingRequest.Filter = []; - WebUI.retrievePage(); - return false; -}; - -WebUI.retrievePage = function () { - var url = window.location.pathname; - - var form = $(document.createElement('form')); - $(form).attr("action", url); - $(form).attr("method", "POST"); - - var pagingRequestString = JSON.stringify(WebUI.PagingRequest); - var input = $("") - .attr("type", "hidden") - .attr("name", "pagingRequestModel") - .val(pagingRequestString); - - $(form).append($(input)); - form.appendTo(document.body); - $(form).submit(); -}; \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs index d9a3a757b..b9c34c9b3 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs @@ -190,6 +190,17 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) c.OAuthUsePkce(); }); + app.Use(async (context, next) => + { + context.Response.Headers.Add("content-security-policy", "object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts allow-popups; base-uri 'self';"); + context.Response.Headers.Add("Referrer-Policy", "no-referrer"); + context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); + context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); + context.Response.Headers.Add("X-XSS-protection", "0"); + await next(); + }); + app.UseHttpsRedirection(); app.UseRouting(); diff --git a/ReportAPI/LearningHub.Nhs.ReportApi/Program.cs b/ReportAPI/LearningHub.Nhs.ReportApi/Program.cs index d3de44e97..ac23e07ab 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi/Program.cs +++ b/ReportAPI/LearningHub.Nhs.ReportApi/Program.cs @@ -20,6 +20,17 @@ var app = builder.Build(); + app.Use(async (context, next) => + { + context.Response.Headers.Add("content-security-policy", "object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts allow-popups; base-uri 'self';"); + context.Response.Headers.Add("Referrer-Policy", "no-referrer"); + context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); + context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); + context.Response.Headers.Add("X-XSS-protection", "0"); + await next(); + }); + app.UseRouting(); app.UseAuthorization(); diff --git a/WebAPI/LearningHub.Nhs.API/Program.cs b/WebAPI/LearningHub.Nhs.API/Program.cs index 1bf33805f..0303c6397 100644 --- a/WebAPI/LearningHub.Nhs.API/Program.cs +++ b/WebAPI/LearningHub.Nhs.API/Program.cs @@ -38,6 +38,17 @@ app.UseMiddleware(); + app.Use(async (context, next) => + { + context.Response.Headers.Add("content-security-policy", "object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts allow-popups; base-uri 'self';"); + context.Response.Headers.Add("Referrer-Policy", "no-referrer"); + context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); + context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); + context.Response.Headers.Add("X-XSS-protection", "0"); + await next(); + }); + app.UseEndpoints(endpoints => endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}")); app.Run();