Fix: incompatibility with reverse proxy forward auth providers

Signed-off-by: solidDoWant <fred.heinecke@yahoo.com>
This commit is contained in:
solidDoWant 2025-10-03 07:27:32 +00:00
parent ff6a69701f
commit 32e5aee9bf
No known key found for this signature in database
GPG key ID: 8FB1B42C043D666B
4 changed files with 63 additions and 2 deletions

View file

@ -27,6 +27,7 @@ function addContentType(ajaxOptions) {
export default function createAjaxRequest(originalAjaxOptions) { export default function createAjaxRequest(originalAjaxOptions) {
const requestXHR = new window.XMLHttpRequest(); const requestXHR = new window.XMLHttpRequest();
requestXHR.withCredentials = true; // Needed for CORS requests with cookies, which some reverse proxies with forward auth require
let aborted = false; let aborted = false;
let complete = false; let complete = false;

View file

@ -4,6 +4,7 @@ public class ServerOptions
{ {
public string UrlBase { get; set; } public string UrlBase { get; set; }
public string BindAddress { get; set; } public string BindAddress { get; set; }
public string AllowedCORSOrigins { get; set; } // TODO
public int? Port { get; set; } public int? Port { get; set; }
public bool? EnableSsl { get; set; } public bool? EnableSsl { get; set; }
public int? SslPort { get; set; } public int? SslPort { get; set; }

View file

@ -31,6 +31,7 @@ public interface IConfigFileProvider : IHandleAsync<ApplicationStartedEvent>,
void EnsureDefaultConfigFile(); void EnsureDefaultConfigFile();
string BindAddress { get; } string BindAddress { get; }
string AllowedCORSOrigins { get; }
int Port { get; } int Port { get; }
int SslPort { get; } int SslPort { get; }
bool EnableSsl { get; } bool EnableSsl { get; }
@ -174,6 +175,8 @@ public string BindAddress
} }
} }
public string AllowedCORSOrigins => _serverOptions.AllowedCORSOrigins ?? GetValue("AllowedCORSOrigins", "*");
public int Port => _serverOptions.Port ?? GetValueInt("Port", 7878); public int Port => _serverOptions.Port ?? GetValueInt("Port", 7878);
public int SslPort => _serverOptions.SslPort ?? GetValueInt("SslPort", 9898); public int SslPort => _serverOptions.SslPort ?? GetValueInt("SslPort", 9898);

View file

@ -4,6 +4,7 @@
using DryIoc; using DryIoc;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
@ -11,6 +12,7 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using NLog.Extensions.Logging; using NLog.Extensions.Logging;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@ -70,15 +72,18 @@ public void ConfigureServices(IServiceCollection services)
services.AddCors(options => services.AddCors(options =>
{ {
// Origin policy will be added after configuration is complete, because it depends on config file values
options.AddPolicy(VersionedApiControllerAttribute.API_CORS_POLICY, options.AddPolicy(VersionedApiControllerAttribute.API_CORS_POLICY,
builder => builder =>
builder.AllowAnyOrigin() builder
.AllowCredentials()
.AllowAnyMethod() .AllowAnyMethod()
.AllowAnyHeader()); .AllowAnyHeader());
options.AddPolicy("AllowGet", options.AddPolicy("AllowGet",
builder => builder =>
builder.AllowAnyOrigin() builder
.AllowCredentials()
.WithMethods("GET", "OPTIONS") .WithMethods("GET", "OPTIONS")
.AllowAnyHeader()); .AllowAnyHeader());
}); });
@ -194,6 +199,8 @@ public void ConfigureServices(IServiceCollection services)
services.AddAppAuthentication(); services.AddAppAuthentication();
services.ConfigureOptions<CORSOriginConfigurator>();
services.PostConfigure<ApiBehaviorOptions>(options => services.PostConfigure<ApiBehaviorOptions>(options =>
{ {
var builtInFactory = options.InvalidModelStateResponseFactory; var builtInFactory = options.InvalidModelStateResponseFactory;
@ -327,3 +334,52 @@ private void EnsureSingleInstance(bool isService, IStartupContext startupContext
} }
} }
} }
public class CORSOriginConfigurator : IPostConfigureOptions<CorsOptions>
{
private readonly IConfigFileProvider _configFileProvider;
private readonly NLog.Logger _logger;
public CORSOriginConfigurator(IConfigFileProvider configFileProvider, NLog.Logger logger)
{
_configFileProvider = configFileProvider;
_logger = logger;
}
public void PostConfigure(string name, CorsOptions options) => PostConfigure(options);
public void PostConfigure(CorsOptions options)
{
_logger.Info("Configuring CORS. Allowed origins: {0}", _configFileProvider.AllowedCORSOrigins);
options.GetPolicy(VersionedApiControllerAttribute.API_CORS_POLICY).IsOriginAllowed = CorsOriginCheck;
options.GetPolicy("AllowGet").IsOriginAllowed = CorsOriginCheck;
}
protected bool CorsOriginCheck(string requestOrigin)
{
var allowedOrigin = _configFileProvider.AllowedCORSOrigins;
if (allowedOrigin.Equals(CorsConstants.AnyOrigin, StringComparison.Ordinal))
{
return true;
}
if (string.IsNullOrWhiteSpace(requestOrigin))
{
return false;
}
var allowedOriginAsUri = new Uri(allowedOrigin);
Uri requestOriginAsUri;
try
{
requestOriginAsUri = new Uri(requestOrigin);
}
catch (UriFormatException)
{
return false;
}
// Compare the scheme and authority (host + port) of the two URIs, according to RFC 6454
return Uri.Compare(allowedOriginAsUri, requestOriginAsUri, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0;
}
}