diff --git a/SS14.Auth.Shared/Config/AccountConfiguration.cs b/SS14.Auth.Shared/Config/AccountConfiguration.cs
new file mode 100644
index 0000000..b326aac
--- /dev/null
+++ b/SS14.Auth.Shared/Config/AccountConfiguration.cs
@@ -0,0 +1,9 @@
+namespace SS14.Auth.Shared.Config;
+
+public sealed class AccountConfiguration
+{
+ ///
+ /// Delete unconfirmed accounts after this many days.
+ ///
+ public int DeleteUnconfirmedAfter { get; set; } = 3;
+}
\ No newline at end of file
diff --git a/SS14.Auth.Shared/StartupHelpers.cs b/SS14.Auth.Shared/StartupHelpers.cs
index 75f08e6..b8594bc 100644
--- a/SS14.Auth.Shared/StartupHelpers.cs
+++ b/SS14.Auth.Shared/StartupHelpers.cs
@@ -28,6 +28,7 @@ public static void AddShared(IServiceCollection services, IConfiguration config)
services.Configure(config.GetSection("Mutex"));
services.Configure(config.GetSection("Patreon"));
services.Configure(config.GetSection("AccountLogRetention"));
+ services.Configure(config.GetSection("Account"));
services.Configure(options =>
{
// The fact that this isn't default absolutely baffles me.
diff --git a/SS14.Auth/Jobs/DeleteUnconfirmedAccountsJob.cs b/SS14.Auth/Jobs/DeleteUnconfirmedAccountsJob.cs
new file mode 100644
index 0000000..f13a191
--- /dev/null
+++ b/SS14.Auth/Jobs/DeleteUnconfirmedAccountsJob.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Quartz;
+using SS14.Auth.Shared.Config;
+using SS14.Auth.Shared.Data;
+
+namespace SS14.Auth.Jobs;
+
+public sealed class DeleteUnconfirmedAccounts(ApplicationDbContext dbContext,
+ ILogger logger,
+ IOptions configuration) : IJob
+{
+ public async Task Execute(IJobExecutionContext context)
+ {
+ var optionsValue = configuration.Value;
+
+ var retainMinimum = DateTime.UtcNow - TimeSpan.FromDays(optionsValue.DeleteUnconfirmedAfter);
+
+ // TODO: Use ExecuteDelete() on newer EF Core version.
+ var accounts = await dbContext.Users
+ .Where(user => !user.EmailConfirmed && user.CreatedTime < retainMinimum)
+ .ToListAsync(context.CancellationToken);
+
+ dbContext.RemoveRange(accounts);
+ await dbContext.SaveChangesAsync();
+ logger.LogInformation("Deleted {Count} unconfirmed accounts from database", accounts.Count);
+ }
+}
\ No newline at end of file
diff --git a/SS14.Auth/Startup.cs b/SS14.Auth/Startup.cs
index 342203b..817196e 100644
--- a/SS14.Auth/Startup.cs
+++ b/SS14.Auth/Startup.cs
@@ -61,6 +61,11 @@ public void ConfigureServices(IServiceCollection services)
{
schedule.RepeatForever().WithIntervalInHours(24);
}));
+
+ q.ScheduleJob(trigger => trigger.WithSimpleSchedule(schedule =>
+ {
+ schedule.RepeatForever().WithIntervalInHours(24);
+ }));
}
});
services.AddQuartzHostedService();