some refactoring

This commit is contained in:
Stanley Dimant
2024-05-06 14:05:24 +02:00
parent 880676de09
commit dc33858626
48 changed files with 448 additions and 112 deletions

View File

@@ -1,3 +0,0 @@
namespace MareSynchronosServer.Authentication;
public record SecretKeyAuthReply(bool Success, string Uid, string PrimaryUid, string Alias, bool TempBan, bool Permaban);

View File

@@ -1,98 +0,0 @@
using System.Collections.Concurrent;
using MareSynchronosShared.Data;
using MareSynchronosShared.Metrics;
using MareSynchronosShared.Services;
using MareSynchronosShared.Utils;
using Microsoft.EntityFrameworkCore;
namespace MareSynchronosServer.Authentication;
public class SecretKeyAuthenticatorService
{
private readonly MareMetrics _metrics;
private readonly IDbContextFactory<MareDbContext> _dbContextFactory;
private readonly IConfigurationService<MareConfigurationAuthBase> _configurationService;
private readonly ILogger<SecretKeyAuthenticatorService> _logger;
private readonly ConcurrentDictionary<string, SecretKeyFailedAuthorization> _failedAuthorizations = new(StringComparer.Ordinal);
public SecretKeyAuthenticatorService(MareMetrics metrics, IDbContextFactory<MareDbContext> dbContextFactory,
IConfigurationService<MareConfigurationAuthBase> configuration, ILogger<SecretKeyAuthenticatorService> logger)
{
_logger = logger;
_configurationService = configuration;
_metrics = metrics;
_dbContextFactory = dbContextFactory;
}
public async Task<SecretKeyAuthReply> AuthorizeAsync(string ip, string hashedSecretKey)
{
_metrics.IncCounter(MetricsAPI.CounterAuthenticationRequests);
if (_failedAuthorizations.TryGetValue(ip, out var existingFailedAuthorization)
&& existingFailedAuthorization.FailedAttempts > _configurationService.GetValueOrDefault(nameof(MareConfigurationAuthBase.FailedAuthForTempBan), 5))
{
if (existingFailedAuthorization.ResetTask == null)
{
_logger.LogWarning("TempBan {ip} for authorization spam", ip);
existingFailedAuthorization.ResetTask = Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromMinutes(_configurationService.GetValueOrDefault(nameof(MareConfigurationAuthBase.TempBanDurationInMinutes), 5))).ConfigureAwait(false);
}).ContinueWith((t) =>
{
_failedAuthorizations.Remove(ip, out _);
});
}
return new(Success: false, Uid: null, PrimaryUid: null, Alias: null, TempBan: true, Permaban: false);
}
using var context = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false);
var authReply = await context.Auth.Include(a => a.User).AsNoTracking()
.SingleOrDefaultAsync(u => u.HashedKey == hashedSecretKey).ConfigureAwait(false);
var isBanned = authReply?.IsBanned ?? false;
var primaryUid = authReply?.PrimaryUserUID ?? authReply?.UserUID;
if (authReply?.PrimaryUserUID != null)
{
var primaryUser = await context.Auth.AsNoTracking().SingleOrDefaultAsync(u => u.UserUID == authReply.PrimaryUserUID).ConfigureAwait(false);
isBanned = isBanned || primaryUser.IsBanned;
}
SecretKeyAuthReply reply = new(authReply != null, authReply?.UserUID,
authReply?.PrimaryUserUID ?? authReply?.UserUID, authReply?.User?.Alias ?? string.Empty, TempBan: false, isBanned);
if (reply.Success)
{
_metrics.IncCounter(MetricsAPI.CounterAuthenticationSuccesses);
_metrics.IncGauge(MetricsAPI.GaugeAuthenticationCacheEntries);
}
else
{
return AuthenticationFailure(ip);
}
return reply;
}
private SecretKeyAuthReply AuthenticationFailure(string ip)
{
_metrics.IncCounter(MetricsAPI.CounterAuthenticationFailures);
_logger.LogWarning("Failed authorization from {ip}", ip);
var whitelisted = _configurationService.GetValueOrDefault(nameof(MareConfigurationAuthBase.WhitelistedIps), new List<string>());
if (!whitelisted.Exists(w => ip.Contains(w, StringComparison.OrdinalIgnoreCase)))
{
if (_failedAuthorizations.TryGetValue(ip, out var auth))
{
auth.IncreaseFailedAttempts();
}
else
{
_failedAuthorizations[ip] = new SecretKeyFailedAuthorization();
}
}
return new(Success: false, Uid: null, PrimaryUid: null, Alias: null, TempBan: false, Permaban: false);
}
}

View File

@@ -1,12 +0,0 @@
namespace MareSynchronosServer.Authentication;
internal record SecretKeyFailedAuthorization
{
private int failedAttempts = 1;
public int FailedAttempts => failedAttempts;
public Task ResetTask { get; set; }
public void IncreaseFailedAttempts()
{
Interlocked.Increment(ref failedAttempts);
}
}

View File

@@ -1,223 +0,0 @@
using MareSynchronos.API.Routes;
using MareSynchronosServer.Authentication;
using MareSynchronosServer.Services;
using MareSynchronosShared;
using MareSynchronosShared.Data;
using MareSynchronosShared.Models;
using MareSynchronosShared.Services;
using MareSynchronosShared.Utils;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using StackExchange.Redis.Extensions.Core.Abstractions;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace MareSynchronosServer.Controllers;
[AllowAnonymous]
[Route(MareAuth.Auth)]
public class JwtController : Controller
{
private readonly ILogger<JwtController> _logger;
private readonly IHttpContextAccessor _accessor;
private readonly IConfigurationService<MareConfigurationAuthBase> _configuration;
private readonly MareDbContext _mareDbContext;
private readonly IRedisDatabase _redis;
private readonly GeoIPService _geoIPProvider;
private readonly SecretKeyAuthenticatorService _secretKeyAuthenticatorService;
public JwtController(ILogger<JwtController> logger,
IHttpContextAccessor accessor, MareDbContext mareDbContext,
SecretKeyAuthenticatorService secretKeyAuthenticatorService,
IConfigurationService<MareConfigurationAuthBase> configuration,
IRedisDatabase redisDb, GeoIPService geoIPProvider)
{
_logger = logger;
_accessor = accessor;
_redis = redisDb;
_geoIPProvider = geoIPProvider;
_mareDbContext = mareDbContext;
_secretKeyAuthenticatorService = secretKeyAuthenticatorService;
_configuration = configuration;
}
[AllowAnonymous]
[HttpPost(MareAuth.Auth_CreateIdent)]
public async Task<IActionResult> CreateToken(string auth, string charaIdent)
{
return await AuthenticateInternal(auth, charaIdent).ConfigureAwait(false);
}
[Authorize(Policy = "Authenticated")]
[HttpGet("renewToken")]
public async Task<IActionResult> RenewToken()
{
try
{
var uid = HttpContext.User.Claims.Single(p => string.Equals(p.Type, MareClaimTypes.Uid, StringComparison.Ordinal))!.Value;
var ident = HttpContext.User.Claims.Single(p => string.Equals(p.Type, MareClaimTypes.CharaIdent, StringComparison.Ordinal))!.Value;
var alias = HttpContext.User.Claims.SingleOrDefault(p => string.Equals(p.Type, MareClaimTypes.Alias))?.Value ?? string.Empty;
if (await _mareDbContext.Auth.Where(u => u.UserUID == uid || u.PrimaryUserUID == uid).AnyAsync(a => a.IsBanned))
{
await EnsureBan(uid, ident);
return Unauthorized("You are permanently banned.");
}
if (await IsIdentBanned(uid, ident))
{
return Unauthorized("Your character is banned from using the service.");
}
_logger.LogInformation("RenewToken:SUCCESS:{id}:{ident}", uid, ident);
return await CreateJwtFromId(uid, ident, alias);
}
catch (Exception ex)
{
_logger.LogError(ex, "RenewToken:FAILURE");
return Unauthorized("Unknown error while renewing authentication token");
}
}
private async Task<IActionResult> AuthenticateInternal(string auth, string charaIdent)
{
try
{
if (string.IsNullOrEmpty(auth)) return BadRequest("No Authkey");
if (string.IsNullOrEmpty(charaIdent)) return BadRequest("No CharaIdent");
var ip = _accessor.GetIpAddress();
var authResult = await _secretKeyAuthenticatorService.AuthorizeAsync(ip, auth);
if (await IsIdentBanned(authResult.Uid, charaIdent))
{
_logger.LogWarning("Authenticate:IDENTBAN:{id}:{ident}", authResult.Uid, charaIdent);
return Unauthorized("Your character is banned from using the service.");
}
if (!authResult.Success && !authResult.TempBan)
{
_logger.LogWarning("Authenticate:INVALID:{id}:{ident}", authResult?.Uid ?? "NOUID", charaIdent);
return Unauthorized("The provided secret key is invalid. Verify your accounts existence and/or recover the secret key.");
}
if (!authResult.Success && authResult.TempBan)
{
_logger.LogWarning("Authenticate:TEMPBAN:{id}:{ident}", authResult.Uid ?? "NOUID", charaIdent);
return Unauthorized("Due to an excessive amount of failed authentication attempts you are temporarily banned. Check your Secret Key configuration and try connecting again in 5 minutes.");
}
if (authResult.Permaban)
{
await EnsureBan(authResult.Uid, charaIdent);
_logger.LogWarning("Authenticate:UIDBAN:{id}:{ident}", authResult.Uid, charaIdent);
return Unauthorized("You are permanently banned.");
}
var existingIdent = await _redis.GetAsync<string>("UID:" + authResult.Uid);
if (!string.IsNullOrEmpty(existingIdent))
{
_logger.LogWarning("Authenticate:DUPLICATE:{id}:{ident}", authResult.Uid, charaIdent);
return Unauthorized("Already logged in to this account. Reconnect in 60 seconds. If you keep seeing this issue, restart your game.");
}
_logger.LogInformation("Authenticate:SUCCESS:{id}:{ident}", authResult.Uid, charaIdent);
return await CreateJwtFromId(authResult.Uid, charaIdent, authResult.Alias ?? string.Empty);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Authenticate:UNKNOWN");
return Unauthorized("Unknown internal server error during authentication");
}
}
private JwtSecurityToken CreateJwt(IEnumerable<Claim> authClaims)
{
var authSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_configuration.GetValue<string>(nameof(MareConfigurationAuthBase.Jwt))));
var token = new SecurityTokenDescriptor()
{
Subject = new ClaimsIdentity(authClaims),
SigningCredentials = new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256Signature),
Expires = new(long.Parse(authClaims.First(f => string.Equals(f.Type, MareClaimTypes.Expires, StringComparison.Ordinal)).Value!, CultureInfo.InvariantCulture), DateTimeKind.Utc),
};
var handler = new JwtSecurityTokenHandler();
return handler.CreateJwtSecurityToken(token);
}
private async Task<IActionResult> CreateJwtFromId(string uid, string charaIdent, string alias)
{
var token = CreateJwt(new List<Claim>()
{
new Claim(MareClaimTypes.Uid, uid),
new Claim(MareClaimTypes.CharaIdent, charaIdent),
new Claim(MareClaimTypes.Alias, alias),
new Claim(MareClaimTypes.Expires, DateTime.UtcNow.AddHours(6).Ticks.ToString(CultureInfo.InvariantCulture)),
new Claim(MareClaimTypes.Continent, await _geoIPProvider.GetCountryFromIP(_accessor))
});
return Content(token.RawData);
}
private async Task EnsureBan(string uid, string charaIdent)
{
if (!_mareDbContext.BannedUsers.Any(c => c.CharacterIdentification == charaIdent))
{
_mareDbContext.BannedUsers.Add(new Banned()
{
CharacterIdentification = charaIdent,
Reason = "Autobanned CharacterIdent (" + uid + ")",
});
await _mareDbContext.SaveChangesAsync();
}
var primaryUser = await _mareDbContext.Auth.Include(a => a.User).FirstOrDefaultAsync(f => f.PrimaryUserUID == uid);
var toBanUid = primaryUser == null ? uid : primaryUser.UserUID;
var lodestone = await _mareDbContext.LodeStoneAuth.Include(a => a.User).FirstOrDefaultAsync(c => c.User.UID == toBanUid);
if (lodestone != null)
{
if (!_mareDbContext.BannedRegistrations.Any(c => c.DiscordIdOrLodestoneAuth == lodestone.HashedLodestoneId))
{
_mareDbContext.BannedRegistrations.Add(new BannedRegistrations()
{
DiscordIdOrLodestoneAuth = lodestone.HashedLodestoneId,
});
}
if (!_mareDbContext.BannedRegistrations.Any(c => c.DiscordIdOrLodestoneAuth == lodestone.DiscordId.ToString()))
{
_mareDbContext.BannedRegistrations.Add(new BannedRegistrations()
{
DiscordIdOrLodestoneAuth = lodestone.DiscordId.ToString(),
});
}
await _mareDbContext.SaveChangesAsync();
}
}
private async Task<bool> IsIdentBanned(string uid, string charaIdent)
{
var isBanned = await _mareDbContext.BannedUsers.AsNoTracking().AnyAsync(u => u.CharacterIdentification == charaIdent).ConfigureAwait(false);
if (isBanned)
{
var authToBan = _mareDbContext.Auth.SingleOrDefault(a => a.UserUID == uid);
if (authToBan != null)
{
authToBan.IsBanned = true;
await _mareDbContext.SaveChangesAsync().ConfigureAwait(false);
}
}
return isBanned;
}
}

View File

@@ -9,7 +9,7 @@ using MareSynchronosShared.Data;
using MareSynchronosShared.Metrics;
using MareSynchronosShared.Models;
using MareSynchronosShared.Services;
using MareSynchronosShared.Utils;
using MareSynchronosShared.Utils.Configuration;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;

View File

@@ -26,7 +26,6 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="lz4net" Version="1.0.15.93" />
<PackageReference Include="MaxMind.GeoIP2" Version="5.2.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.149">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore;
using MareSynchronosShared.Data;
using MareSynchronosShared.Metrics;
using MareSynchronosShared.Services;
using MareSynchronosShared.Utils;
using MareSynchronosShared.Utils.Configuration;
namespace MareSynchronosServer;

View File

@@ -1,137 +0,0 @@
using MareSynchronosShared;
using MareSynchronosShared.Services;
using MareSynchronosShared.Utils;
using MaxMind.GeoIP2;
namespace MareSynchronosServer.Services;
public class GeoIPService : IHostedService
{
private readonly ILogger<GeoIPService> _logger;
private readonly IConfigurationService<ServerConfiguration> _mareConfiguration;
private bool _useGeoIP = false;
private string _cityFile = string.Empty;
private DatabaseReader? _dbReader;
private DateTime _dbLastWriteTime = DateTime.Now;
private CancellationTokenSource _fileWriteTimeCheckCts = new();
private bool _processingReload = false;
public GeoIPService(ILogger<GeoIPService> logger,
IConfigurationService<ServerConfiguration> mareConfiguration)
{
_logger = logger;
_mareConfiguration = mareConfiguration;
}
public async Task<string> GetCountryFromIP(IHttpContextAccessor httpContextAccessor)
{
if (!_useGeoIP)
{
return "*";
}
try
{
var ip = httpContextAccessor.GetIpAddress();
using CancellationTokenSource waitCts = new();
waitCts.CancelAfter(TimeSpan.FromSeconds(5));
while (_processingReload) await Task.Delay(100, waitCts.Token).ConfigureAwait(false);
if (_dbReader.TryCity(ip, out var response))
{
var continent = response.Continent.Code;
if (string.Equals(continent, "NA", StringComparison.Ordinal)
&& response.Location.Longitude != null)
{
if (response.Location.Longitude < -102)
{
continent = "NA-W";
}
else
{
continent = "NA-E";
}
}
return continent ?? "*";
}
return "*";
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Error handling Geo IP country in request");
return "*";
}
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("GeoIP module starting update task");
var token = _fileWriteTimeCheckCts.Token;
_ = PeriodicReloadTask(token);
return Task.CompletedTask;
}
private async Task PeriodicReloadTask(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
try
{
_processingReload = true;
var useGeoIP = _mareConfiguration.GetValueOrDefault(nameof(ServerConfiguration.UseGeoIP), false);
var cityFile = _mareConfiguration.GetValueOrDefault(nameof(ServerConfiguration.GeoIPDbCityFile), string.Empty);
var lastWriteTime = new FileInfo(cityFile).LastWriteTimeUtc;
if (useGeoIP && (!string.Equals(cityFile, _cityFile, StringComparison.OrdinalIgnoreCase) || lastWriteTime != _dbLastWriteTime))
{
_cityFile = cityFile;
if (!File.Exists(_cityFile)) throw new FileNotFoundException($"Could not open GeoIP City Database, path does not exist: {_cityFile}");
_dbReader?.Dispose();
_dbReader = null;
_dbReader = new DatabaseReader(_cityFile);
_dbLastWriteTime = lastWriteTime;
_ = _dbReader.City("8.8.8.8").Continent;
_logger.LogInformation($"Loaded GeoIP city file from {_cityFile}");
if (_useGeoIP != useGeoIP)
{
_logger.LogInformation("GeoIP module is now enabled");
_useGeoIP = useGeoIP;
}
}
if (_useGeoIP != useGeoIP && !useGeoIP)
{
_logger.LogInformation("GeoIP module is now disabled");
_useGeoIP = useGeoIP;
}
}
catch (Exception e)
{
_logger.LogWarning(e, "Error during periodic GeoIP module reload task, disabling GeoIP");
_useGeoIP = false;
}
finally
{
_processingReload = false;
}
await Task.Delay(TimeSpan.FromMinutes(1)).ConfigureAwait(false);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_fileWriteTimeCheckCts.Cancel();
_fileWriteTimeCheckCts.Dispose();
_dbReader.Dispose();
return Task.CompletedTask;
}
}

View File

@@ -4,7 +4,7 @@ using MareSynchronosServer.Hubs;
using MareSynchronosShared.Data;
using MareSynchronosShared.Metrics;
using MareSynchronosShared.Services;
using MareSynchronosShared.Utils;
using MareSynchronosShared.Utils.Configuration;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using StackExchange.Redis.Extensions.Core.Abstractions;

View File

@@ -3,6 +3,7 @@ using MareSynchronosShared.Metrics;
using MareSynchronosShared.Models;
using MareSynchronosShared.Services;
using MareSynchronosShared.Utils;
using MareSynchronosShared.Utils.Configuration;
using Microsoft.EntityFrameworkCore;
namespace MareSynchronosServer.Services;

View File

@@ -13,7 +13,6 @@ using Prometheus;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using MareSynchronosServer.Authentication;
using StackExchange.Redis;
using StackExchange.Redis.Extensions.Core.Configuration;
using System.Net;
@@ -24,6 +23,7 @@ using MessagePack.Resolvers;
using Microsoft.AspNetCore.Mvc.Controllers;
using MareSynchronosServer.Controllers;
using MareSynchronosShared.RequirementHandlers;
using MareSynchronosShared.Utils.Configuration;
namespace MareSynchronosServer;
@@ -71,7 +71,7 @@ public class Startup
a.FeatureProviders.Remove(a.FeatureProviders.OfType<ControllerFeatureProvider>().First());
if (mareConfig.GetValue<Uri>(nameof(ServerConfiguration.MainServerAddress), defaultValue: null) == null)
{
a.FeatureProviders.Add(new AllowedControllersFeatureProvider(typeof(MareServerConfigurationController), typeof(MareAuthBaseConfigurationController), typeof(JwtController), typeof(ClientMessageController)));
a.FeatureProviders.Add(new AllowedControllersFeatureProvider(typeof(MareServerConfigurationController), typeof(MareAuthBaseConfigurationController), typeof(ClientMessageController)));
}
else
{
@@ -86,7 +86,6 @@ public class Startup
services.Configure<ServerConfiguration>(Configuration.GetRequiredSection("MareSynchronos"));
services.Configure<MareConfigurationBase>(Configuration.GetRequiredSection("MareSynchronos"));
services.Configure<MareConfigurationAuthBase>(Configuration.GetRequiredSection("MareSynchronos"));
services.AddSingleton<ServerTokenGenerator>();
services.AddSingleton<SystemInfoService>();
@@ -100,10 +99,8 @@ public class Startup
if (isMainServer)
{
services.AddSingleton<GeoIPService>();
services.AddSingleton<UserCleanupService>();
services.AddHostedService(provider => provider.GetService<UserCleanupService>());
services.AddHostedService(provider => provider.GetService<GeoIPService>());
}
}
@@ -188,13 +185,12 @@ public class Startup
private static void ConfigureAuthorization(IServiceCollection services)
{
services.AddSingleton<SecretKeyAuthenticatorService>();
services.AddTransient<IAuthorizationHandler, UserRequirementHandler>();
services.AddTransient<IAuthorizationHandler, ValidTokenRequirementHandler>();
services.AddTransient<IAuthorizationHandler, ValidTokenHubRequirementHandler>();
services.AddOptions<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme)
.Configure<IConfigurationService<MareConfigurationAuthBase>>((options, config) =>
.Configure<IConfigurationService<MareConfigurationBase>>((options, config) =>
{
options.TokenValidationParameters = new()
{
@@ -202,7 +198,7 @@ public class Startup
ValidateLifetime = true,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.GetValue<string>(nameof(MareConfigurationAuthBase.Jwt)))),
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.GetValue<string>(nameof(MareConfigurationBase.Jwt)))),
};
});
@@ -305,15 +301,15 @@ public class Startup
if (!isMainServer)
{
services.AddSingleton<IConfigurationService<ServerConfiguration>, MareConfigurationServiceClient<ServerConfiguration>>();
services.AddSingleton<IConfigurationService<MareConfigurationAuthBase>, MareConfigurationServiceClient<MareConfigurationAuthBase>>();
services.AddSingleton<IConfigurationService<MareConfigurationBase>, MareConfigurationServiceClient<MareConfigurationBase>>();
services.AddHostedService(p => (MareConfigurationServiceClient<ServerConfiguration>)p.GetService<IConfigurationService<ServerConfiguration>>());
services.AddHostedService(p => (MareConfigurationServiceClient<MareConfigurationAuthBase>)p.GetService<IConfigurationService<MareConfigurationAuthBase>>());
services.AddHostedService(p => (MareConfigurationServiceClient<MareConfigurationBase>)p.GetService<IConfigurationService<MareConfigurationBase>>());
}
else
{
services.AddSingleton<IConfigurationService<ServerConfiguration>, MareConfigurationServiceServer<ServerConfiguration>>();
services.AddSingleton<IConfigurationService<MareConfigurationAuthBase>, MareConfigurationServiceServer<MareConfigurationAuthBase>>();
services.AddSingleton<IConfigurationService<MareConfigurationBase>, MareConfigurationServiceServer<MareConfigurationBase>>();
}
}
@@ -321,7 +317,7 @@ public class Startup
{
logger.LogInformation("Running Configure");
var config = app.ApplicationServices.GetRequiredService<IConfigurationService<MareConfigurationAuthBase>>();
var config = app.ApplicationServices.GetRequiredService<IConfigurationService<MareConfigurationBase>>();
app.UseIpRateLimiting();