feat: rate limit emails to two per address per hour

This commit is contained in:
sam 2024-12-11 20:42:48 +01:00
parent 5cb3faa92b
commit ff8d53814d
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
6 changed files with 189 additions and 49 deletions

View file

@ -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");
}
}
}