mirror of
https://github.com/JustArchiNET/ArchiSteamFarm
synced 2024-11-10 23:24:36 +00:00
* Closes #2371 * Change the default to no known networks * Address @Vital7 note * Handle both IPv4 and IPv6 when mapped This follows ASP.NET Core logic * Refactor forwarded headers usage
This commit is contained in:
parent
485caab0f4
commit
13e9f1ac2a
2 changed files with 38 additions and 12 deletions
|
@ -30,7 +30,9 @@ using ArchiSteamFarm.Core;
|
|||
using ArchiSteamFarm.Helpers;
|
||||
using ArchiSteamFarm.Storage;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace ArchiSteamFarm.IPC.Integration {
|
||||
|
@ -46,11 +48,18 @@ namespace ArchiSteamFarm.IPC.Integration {
|
|||
|
||||
private static Timer? ClearFailedAuthorizationsTimer;
|
||||
|
||||
private readonly ForwardedHeadersOptions ForwardedHeadersOptions;
|
||||
private readonly RequestDelegate Next;
|
||||
|
||||
public ApiAuthenticationMiddleware(RequestDelegate next) {
|
||||
public ApiAuthenticationMiddleware(RequestDelegate next, IOptions<ForwardedHeadersOptions> forwardedHeadersOptions) {
|
||||
Next = next ?? throw new ArgumentNullException(nameof(next));
|
||||
|
||||
if (forwardedHeadersOptions == null) {
|
||||
throw new ArgumentNullException(nameof(forwardedHeadersOptions));
|
||||
}
|
||||
|
||||
ForwardedHeadersOptions = forwardedHeadersOptions.Value ?? throw new InvalidOperationException(nameof(forwardedHeadersOptions));
|
||||
|
||||
lock (FailedAuthorizations) {
|
||||
ClearFailedAuthorizationsTimer ??= new Timer(
|
||||
_ => FailedAuthorizations.Clear(),
|
||||
|
@ -78,7 +87,7 @@ namespace ArchiSteamFarm.IPC.Integration {
|
|||
await Next(context).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task<HttpStatusCode> GetAuthenticationStatus(HttpContext context) {
|
||||
private async Task<HttpStatusCode> GetAuthenticationStatus(HttpContext context) {
|
||||
if (context == null) {
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
@ -87,18 +96,34 @@ namespace ArchiSteamFarm.IPC.Integration {
|
|||
throw new InvalidOperationException(nameof(ClearFailedAuthorizationsTimer));
|
||||
}
|
||||
|
||||
string? ipcPassword = ASF.GlobalConfig != null ? ASF.GlobalConfig.IPCPassword : GlobalConfig.DefaultIPCPassword;
|
||||
|
||||
if (string.IsNullOrEmpty(ipcPassword)) {
|
||||
return HttpStatusCode.OK;
|
||||
}
|
||||
|
||||
IPAddress? clientIP = context.Connection.RemoteIpAddress;
|
||||
|
||||
if (clientIP == null) {
|
||||
throw new InvalidOperationException(nameof(clientIP));
|
||||
}
|
||||
|
||||
string? ipcPassword = ASF.GlobalConfig != null ? ASF.GlobalConfig.IPCPassword : GlobalConfig.DefaultIPCPassword;
|
||||
|
||||
if (string.IsNullOrEmpty(ipcPassword)) {
|
||||
if (IPAddress.IsLoopback(clientIP)) {
|
||||
return HttpStatusCode.OK;
|
||||
}
|
||||
|
||||
if (ForwardedHeadersOptions.KnownNetworks.Count == 0) {
|
||||
return HttpStatusCode.Forbidden;
|
||||
}
|
||||
|
||||
if (clientIP.IsIPv4MappedToIPv6) {
|
||||
IPAddress mappedClientIP = clientIP.MapToIPv4();
|
||||
|
||||
if (ForwardedHeadersOptions.KnownNetworks.Any(network => network.Contains(mappedClientIP))) {
|
||||
return HttpStatusCode.OK;
|
||||
}
|
||||
}
|
||||
|
||||
return ForwardedHeadersOptions.KnownNetworks.Any(network => network.Contains(clientIP)) ? HttpStatusCode.OK : HttpStatusCode.Forbidden;
|
||||
}
|
||||
|
||||
if (FailedAuthorizations.TryGetValue(clientIP, out byte attempts)) {
|
||||
if (attempts >= MaxFailedAuthorizationAttempts) {
|
||||
return HttpStatusCode.Forbidden;
|
||||
|
|
|
@ -148,12 +148,12 @@ namespace ArchiSteamFarm.IPC {
|
|||
app.UseRouting();
|
||||
#endif
|
||||
|
||||
// We want to protect our API with IPCPassword and additional security, this should be called after routing, so the middleware won't have to deal with API endpoints that do not exist
|
||||
app.UseWhen(context => context.Request.Path.StartsWithSegments("/Api", StringComparison.OrdinalIgnoreCase), appBuilder => appBuilder.UseMiddleware<ApiAuthenticationMiddleware>());
|
||||
|
||||
string? ipcPassword = ASF.GlobalConfig != null ? ASF.GlobalConfig.IPCPassword : GlobalConfig.DefaultIPCPassword;
|
||||
|
||||
if (!string.IsNullOrEmpty(ipcPassword)) {
|
||||
// We want to protect our API with IPCPassword, this should be called after routing, so the middleware won't have to deal with API endpoints that do not exist
|
||||
app.UseWhen(context => context.Request.Path.StartsWithSegments("/Api", StringComparison.OrdinalIgnoreCase), appBuilder => appBuilder.UseMiddleware<ApiAuthenticationMiddleware>());
|
||||
|
||||
// We want to apply CORS policy in order to allow userscripts and other third-party integrations to communicate with ASF API, this should be called before response compression, but can't be due to how our flow works
|
||||
// We apply CORS policy only with IPCPassword set as an extra authentication measure
|
||||
app.UseCors();
|
||||
|
@ -197,7 +197,8 @@ namespace ArchiSteamFarm.IPC {
|
|||
HashSet<IPNetwork>? knownNetworks = null;
|
||||
|
||||
if (knownNetworksTexts?.Count > 0) {
|
||||
knownNetworks = new HashSet<IPNetwork>(knownNetworksTexts.Count);
|
||||
// Use specified known networks
|
||||
knownNetworks = new HashSet<IPNetwork>();
|
||||
|
||||
foreach (string knownNetworkText in knownNetworksTexts) {
|
||||
string[] addressParts = knownNetworkText.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
|
Loading…
Reference in a new issue