IPC: Add optional SRI support for ASF-ui

In theory, this is required only in specific proxy/CDN solutions accessing ASF data over http that would somehow want to transform the responses

https://github.com/JustArchiNET/ASF-ui/pull/1470
This commit is contained in:
Archi 2021-07-04 18:51:35 +02:00
parent dcacdd802c
commit f58a9be02a
No known key found for this signature in database
GPG key ID: 6B138B4C64555AEA

View file

@ -41,10 +41,12 @@ using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Headers;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
@ -70,35 +72,60 @@ namespace ArchiSteamFarm.IPC {
throw new ArgumentNullException(nameof(env));
}
// The order of dependency injection is super important, doing things in wrong order will break everything
// https://docs.microsoft.com/aspnet/core/fundamentals/middleware
// This one is easy, it's always in the beginning
if (Debugging.IsUserDebugging) {
app.UseDeveloperExceptionPage();
}
// The order of dependency injection matters, pay attention to it
// Add support for proxies, this one comes usually after developer exception page, but could be before
app.UseForwardedHeaders();
// TODO: Try to get rid of this workaround for missing PathBase feature, https://github.com/aspnet/AspNetCore/issues/5898
// Add support for response compression - must be called before static files as we want to compress those as well
app.UseResponseCompression();
// It's not apparent when UsePathBase() should be called, but definitely before we get down to static files
// TODO: Maybe eventually we can get rid of this, https://github.com/aspnet/AspNetCore/issues/5898
PathString pathBase = Configuration.GetSection("Kestrel").GetValue<PathString>("PathBase");
if (!string.IsNullOrEmpty(pathBase) && (pathBase != "/")) {
app.UsePathBase(pathBase);
}
// Add support for proxies
app.UseForwardedHeaders();
// Add support for response compression
app.UseResponseCompression();
// Add support for websockets used in /Api/NLog
app.UseWebSockets();
// We're using index for URL routing in our static files so re-execute all non-API calls on /
// The default HTML file (usually index.html) is responsible for IPC GUI routing, so re-execute all non-API calls on /
// This must be called before default files, because we don't know the exact file name that will be used for index page
app.UseWhen(context => !context.Request.Path.StartsWithSegments("/Api", StringComparison.OrdinalIgnoreCase), appBuilder => appBuilder.UseStatusCodePagesWithReExecute("/"));
// We need static files support for IPC GUI
// Add support for default root path redirection (GET / -> GET /index.html), must come before static files
app.UseDefaultFiles();
app.UseStaticFiles();
// Add support for static files (e.g. HTML, CSS and JS from IPC GUI)
app.UseStaticFiles(
new StaticFileOptions {
OnPrepareResponse = context => {
// Add support for SRI-protected static files
if (context.File.Exists && !context.File.IsDirectory && !string.IsNullOrEmpty(context.File.Name)) {
string extension = Path.GetExtension(context.File.Name);
switch (extension.ToUpperInvariant()) {
case ".CSS":
case ".JS":
ResponseHeaders headers = context.Context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue {
NoTransform = true
};
break;
}
}
}
}
);
// Use routing for our API controllers, this should be called once we're done with all the static files mess
#if !NETFRAMEWORK
app.UseRouting();
#endif
@ -106,25 +133,28 @@ namespace ArchiSteamFarm.IPC {
string? ipcPassword = ASF.GlobalConfig != null ? ASF.GlobalConfig.IPCPassword : GlobalConfig.DefaultIPCPassword;
if (!string.IsNullOrEmpty(ipcPassword)) {
// We need ApiAuthenticationMiddleware for 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
// We apply CORS policy only with IPCPassword set as extra authentication measure
// 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();
}
// Add support for mapping controllers
// Add support for websockets that we use e.g. in /Api/NLog
app.UseWebSockets();
// Finally register proper API endpoints once we're done with routing
#if NETFRAMEWORK
app.UseMvcWithDefaultRoute();
#else
app.UseEndpoints(endpoints => endpoints.MapControllers());
#endif
// Use swagger for automatic API documentation generation
// Add support for swagger, responsible for automatic API documentation generation, this should be on the end, once we're done with API
app.UseSwagger();
// Use friendly swagger UI
// Add support for swagger UI, this should be after swagger, obviously
app.UseSwaggerUI(
options => {
options.DisplayRequestDuration();
@ -140,9 +170,10 @@ namespace ArchiSteamFarm.IPC {
throw new ArgumentNullException(nameof(services));
}
// The order of dependency injection matters, pay attention to it
// The order of dependency injection is super important, doing things in wrong order will break everything
// Order in Configure() method is a good start
// Add support for custom reverse proxy endpoints
// Prepare knownNetworks that we'll use in a second
HashSet<string>? knownNetworksTexts = Configuration.GetSection("Kestrel:KnownNetworks").Get<HashSet<string>>();
HashSet<IPNetwork>? knownNetworks = null;
@ -182,12 +213,13 @@ namespace ArchiSteamFarm.IPC {
string? ipcPassword = ASF.GlobalConfig != null ? ASF.GlobalConfig.IPCPassword : GlobalConfig.DefaultIPCPassword;
// Add CORS to allow userscripts and third-party apps
if (!string.IsNullOrEmpty(ipcPassword)) {
// We want to apply CORS policy in order to allow userscripts and other third-party integrations to communicate with ASF API
// We apply CORS policy only with IPCPassword set as an extra authentication measure
services.AddCors(options => options.AddDefaultPolicy(policyBuilder => policyBuilder.AllowAnyOrigin()));
}
// Add swagger documentation generation
// Add support for swagger, responsible for automatic API documentation generation
services.AddSwaggerGen(
options => {
options.AddSecurityDefinition(
@ -244,7 +276,7 @@ namespace ArchiSteamFarm.IPC {
}
);
// Add Newtonsoft.Json support for SwaggerGen, this one must be executed after AddSwaggerGen()
// Add support for Newtonsoft.Json in swagger, this one must be executed after AddSwaggerGen()
services.AddSwaggerGenNewtonsoftSupport();
// We need MVC for /Api, but we're going to use only a small subset of all available features