feat: rate limit emails to two per address per hour
This commit is contained in:
		
							parent
							
								
									5cb3faa92b
								
							
						
					
					
						commit
						ff8d53814d
					
				
					 6 changed files with 189 additions and 49 deletions
				
			
		|  | @ -33,13 +33,24 @@ public class DataCleanupService( | |||
| 
 | ||||
|     public async Task InvokeAsync(CancellationToken ct = default) | ||||
|     { | ||||
|         _logger.Information("Cleaning up expired users"); | ||||
|         _logger.Debug("Cleaning up sent email cache"); | ||||
|         await CleanEmailsAsync(ct); | ||||
| 
 | ||||
|         _logger.Debug("Cleaning up expired users"); | ||||
|         await CleanUsersAsync(ct); | ||||
| 
 | ||||
|         _logger.Information("Cleaning up expired data exports"); | ||||
|         _logger.Debug("Cleaning up expired data exports"); | ||||
|         await CleanExportsAsync(ct); | ||||
|     } | ||||
| 
 | ||||
|     private async Task CleanEmailsAsync(CancellationToken ct = default) | ||||
|     { | ||||
|         Instant expiry = clock.GetCurrentInstant() - Duration.FromHours(2); | ||||
|         int count = await db.SentEmails.Where(e => e.SentAt < expiry).ExecuteDeleteAsync(ct); | ||||
|         if (count != 0) | ||||
|             _logger.Information("Deleted {Count} entries from the sent email cache", expiry); | ||||
|     } | ||||
| 
 | ||||
|     private async Task CleanUsersAsync(CancellationToken ct = default) | ||||
|     { | ||||
|         Instant selfDeleteExpires = clock.GetCurrentInstant() - User.DeleteAfter; | ||||
|  |  | |||
|  | @ -12,13 +12,25 @@ | |||
| // | ||||
| // You should have received a copy of the GNU Affero General Public License | ||||
| // along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
| using Coravel.Mailer.Mail; | ||||
| using Coravel.Mailer.Mail.Interfaces; | ||||
| using Coravel.Queuing.Interfaces; | ||||
| using Foxnouns.Backend.Database; | ||||
| using Foxnouns.Backend.Database.Models; | ||||
| using Foxnouns.Backend.Mailables; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using NodaTime; | ||||
| 
 | ||||
| namespace Foxnouns.Backend.Services; | ||||
| 
 | ||||
| public class MailService(ILogger logger, IMailer mailer, IQueue queue, Config config) | ||||
| public class MailService( | ||||
|     ILogger logger, | ||||
|     IMailer mailer, | ||||
|     IQueue queue, | ||||
|     IClock clock, | ||||
|     Config config, | ||||
|     IServiceProvider serviceProvider | ||||
| ) | ||||
| { | ||||
|     private readonly ILogger _logger = logger.ForContext<MailService>(); | ||||
| 
 | ||||
|  | @ -26,25 +38,18 @@ public class MailService(ILogger logger, IMailer mailer, IQueue queue, Config co | |||
|     { | ||||
|         queue.QueueAsyncTask(async () => | ||||
|         { | ||||
|             _logger.Debug("Sending account creation email to {ToEmail}", to); | ||||
|             try | ||||
|             { | ||||
|                 await mailer.SendAsync( | ||||
|                     new AccountCreationMailable( | ||||
|                         config, | ||||
|                         new AccountCreationMailableView | ||||
|                         { | ||||
|                             BaseUrl = config.BaseUrl, | ||||
|                             To = to, | ||||
|                             Code = code, | ||||
|                         } | ||||
|                     ) | ||||
|                 ); | ||||
|             } | ||||
|             catch (Exception exc) | ||||
|             { | ||||
|                 _logger.Error(exc, "Sending account creation email"); | ||||
|             } | ||||
|             await SendEmailAsync( | ||||
|                 to, | ||||
|                 new AccountCreationMailable( | ||||
|                     config, | ||||
|                     new AccountCreationMailableView | ||||
|                     { | ||||
|                         BaseUrl = config.BaseUrl, | ||||
|                         To = to, | ||||
|                         Code = code, | ||||
|                     } | ||||
|                 ) | ||||
|             ); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | @ -53,25 +58,53 @@ public class MailService(ILogger logger, IMailer mailer, IQueue queue, Config co | |||
|         _logger.Debug("Sending add email address email to {ToEmail}", to); | ||||
|         queue.QueueAsyncTask(async () => | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await mailer.SendAsync( | ||||
|                     new AddEmailMailable( | ||||
|                         config, | ||||
|                         new AddEmailMailableView | ||||
|                         { | ||||
|                             BaseUrl = config.BaseUrl, | ||||
|                             To = to, | ||||
|                             Code = code, | ||||
|                             Username = username, | ||||
|                         } | ||||
|                     ) | ||||
|                 ); | ||||
|             } | ||||
|             catch (Exception exc) | ||||
|             { | ||||
|                 _logger.Error(exc, "Sending add email address email"); | ||||
|             } | ||||
|             await SendEmailAsync( | ||||
|                 to, | ||||
|                 new AddEmailMailable( | ||||
|                     config, | ||||
|                     new AddEmailMailableView | ||||
|                     { | ||||
|                         BaseUrl = config.BaseUrl, | ||||
|                         To = to, | ||||
|                         Code = code, | ||||
|                         Username = username, | ||||
|                     } | ||||
|                 ) | ||||
|             ); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private async Task SendEmailAsync<T>(string to, Mailable<T> mailable) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             // ReSharper disable SuggestVarOrType_SimpleTypes | ||||
|             await using var scope = serviceProvider.CreateAsyncScope(); | ||||
|             await using var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>(); | ||||
|             // ReSharper restore SuggestVarOrType_SimpleTypes | ||||
| 
 | ||||
|             Instant now = clock.GetCurrentInstant(); | ||||
| 
 | ||||
|             int count = await db.SentEmails.CountAsync(e => | ||||
|                 e.Email == to && e.SentAt > (now - Duration.FromHours(1)) | ||||
|             ); | ||||
|             if (count >= 2) | ||||
|             { | ||||
|                 _logger.Information( | ||||
|                     "Have already sent 2 or more emails to {ToAddress} in the past hour, not sending new email", | ||||
|                     to | ||||
|                 ); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             await mailer.SendAsync(mailable); | ||||
| 
 | ||||
|             db.SentEmails.Add(new SentEmail { Email = to, SentAt = now }); | ||||
|             await db.SaveChangesAsync(); | ||||
|         } | ||||
|         catch (Exception exc) | ||||
|         { | ||||
|             _logger.Error(exc, "Sending email"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue