diff --git a/server/Controllers/FetchController.cs b/server/Controllers/FetchController.cs index bb381a6..1790d31 100644 --- a/server/Controllers/FetchController.cs +++ b/server/Controllers/FetchController.cs @@ -41,6 +41,7 @@ namespace Sockeye.Api.Controllers ////////////////////////////////////////////////////////////////////////// // GET: fetch/somecode/bob@bob.com [HttpGet("{code}/{email}")] + [Microsoft.AspNetCore.RateLimiting.EnableRateLimiting(Startup.RATE_LIMIT_LICENSE_FETCH)]//case 4681 public async Task Get([FromRoute] string code, [FromRoute] string email) { diff --git a/server/Controllers/RvfController.cs b/server/Controllers/RvfController.cs index e59923c..b9e84bd 100644 --- a/server/Controllers/RvfController.cs +++ b/server/Controllers/RvfController.cs @@ -46,6 +46,7 @@ namespace Sockeye.Api.Controllers //### RAVEN FETCH LICENSE REQUEST HERE #### //This route is used by Raven to check if there is an unfetched license key and return it [HttpPost] + [Microsoft.AspNetCore.RateLimiting.EnableRateLimiting(Startup.RATE_LIMIT_LICENSE_FETCH)]//case 4681 public async Task PostLicenseFetchRequest([FromBody] dtoFetchRequest fetchRequest) { if (!serverState.IsOpen) diff --git a/server/Controllers/RvrController.cs b/server/Controllers/RvrController.cs index 92cb747..b4570b9 100644 --- a/server/Controllers/RvrController.cs +++ b/server/Controllers/RvrController.cs @@ -60,6 +60,7 @@ namespace Sockeye.Api.Controllers //REQUEST TRIAL ROUTE CALLED FROM RAVEN //######################################################### [HttpPost] + [Microsoft.AspNetCore.RateLimiting.EnableRateLimiting(Startup.RATE_LIMIT_TRIAL_REQUESTS)]//case 4681 public async Task Post([FromBody] dtoRequest r) { if (!serverState.IsOpen) @@ -70,6 +71,12 @@ namespace Sockeye.Api.Controllers return BadRequest(ModelState); } + //case 4681: a verification email is sent to this address, so it must at least be a valid address + if (string.IsNullOrWhiteSpace(r.Email) || !System.Net.Mail.MailAddress.TryCreate(r.Email.Trim(), out var parsedEmail) || parsedEmail.Address != r.Email.Trim()) + { + return BadRequest("E1000 - Email address invalid"); + } + // if (r.DbId == Guid.Empty) if (string.IsNullOrWhiteSpace(r.DbId)) { @@ -114,6 +121,7 @@ namespace Sockeye.Api.Controllers //### CUSTOMER ROUTE CALLED FROM CUSTOMER BROWSER / EMAIL #### [HttpGet("verify/{code}")] + [Microsoft.AspNetCore.RateLimiting.EnableRateLimiting(Startup.RATE_LIMIT_LICENSE_FETCH)]//case 4681 public async Task GetVerify([FromRoute] string code) { if (!serverState.IsOpen) diff --git a/server/Controllers/SubscriptionServerTrialRequestController.cs b/server/Controllers/SubscriptionServerTrialRequestController.cs index 713f1c3..8fca368 100644 --- a/server/Controllers/SubscriptionServerTrialRequestController.cs +++ b/server/Controllers/SubscriptionServerTrialRequestController.cs @@ -57,6 +57,7 @@ namespace Sockeye.Api.Controllers //REQUEST ROUTE CALLED FROM CONTACT FORMS APP //######################################################### [HttpPost] + [Microsoft.AspNetCore.RateLimiting.EnableRateLimiting(Startup.RATE_LIMIT_TRIAL_REQUESTS)]//case 4681 public async Task Post([FromBody] dtoSubscriptionServerTrialRequest r) { if (!serverState.IsOpen) diff --git a/server/Startup.cs b/server/Startup.cs index 4f46628..7ee14fb 100644 --- a/server/Startup.cs +++ b/server/Startup.cs @@ -28,6 +28,10 @@ namespace Sockeye { public class Startup { + //case 4681: rate limit policy names used on the anonymous trial and licence routes + internal const string RATE_LIMIT_TRIAL_REQUESTS = "trial-requests"; + internal const string RATE_LIMIT_LICENSE_FETCH = "license-fetch"; + ///////////////////////////////////////////////////////////// // public Startup(Microsoft.AspNetCore.Hosting.IWebHostEnvironment hostingEnvironment) @@ -174,6 +178,19 @@ namespace Sockeye ); }); + //case 4681: per client address limits on the anonymous trial request and licence fetch routes + _newLog.LogDebug("Rate limiting"); + services.AddRateLimiter(options => + { + options.RejectionStatusCode = StatusCodes.Status429TooManyRequests; + options.AddPolicy(RATE_LIMIT_TRIAL_REQUESTS, context => System.Threading.RateLimiting.RateLimitPartition.GetFixedWindowLimiter( + context.Connection.RemoteIpAddress?.ToString() ?? "unknown", + _ => new System.Threading.RateLimiting.FixedWindowRateLimiterOptions { PermitLimit = 5, Window = TimeSpan.FromHours(1), QueueLimit = 0 })); + options.AddPolicy(RATE_LIMIT_LICENSE_FETCH, context => System.Threading.RateLimiting.RateLimitPartition.GetFixedWindowLimiter( + context.Connection.RemoteIpAddress?.ToString() ?? "unknown", + _ => new System.Threading.RateLimiting.FixedWindowRateLimiterOptions { PermitLimit = 30, Window = TimeSpan.FromMinutes(10), QueueLimit = 0 })); + }); + @@ -375,7 +392,10 @@ namespace Sockeye #endregion _newLog.LogDebug("Routing pipeline"); - app.UseRouting();//this wasn't here for 2.2 but added for 3.0, needs to come before the stuff after + app.UseRouting();//this wasn't here for 2.2 but added for 3.0, needs to come before the stuff after + + //case 4681: after routing so the per-route policies apply (and after forwarded headers so the client address is the real one) + app.UseRateLimiter(); _newLog.LogDebug("CORS pipeline"); app.UseCors("CorsPolicy"); diff --git a/server/util/StringUtil.cs b/server/util/StringUtil.cs index 0d66ae9..47c1895 100644 --- a/server/util/StringUtil.cs +++ b/server/util/StringUtil.cs @@ -178,11 +178,11 @@ namespace Sockeye.Util var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; var stringChars = new char[10]; - var random = new Random(); + //case 4681: cryptographic random, same format as before for (int i = 0; i < stringChars.Length; i++) { - stringChars[i] = chars[random.Next(chars.Length)]; + stringChars[i] = chars[System.Security.Cryptography.RandomNumberGenerator.GetInt32(chars.Length)]; } var finalString = new String(stringChars);