[backend/asp] Refactor middleware stack
This commit splits the request pipeline conditionally instead of invoking every middleware in the stack. It also simplifies middleware instantiation by using runtime discovery, allowing for Plugins to add Middleware.
This commit is contained in:
parent
70c692e1cb
commit
705e061f74
15 changed files with 467 additions and 411 deletions
|
@ -1,6 +1,8 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading.RateLimiting;
|
using System.Threading.RateLimiting;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
using Iceshrimp.AssemblyUtils;
|
||||||
using Iceshrimp.Backend.Components.PublicPreview.Attributes;
|
using Iceshrimp.Backend.Components.PublicPreview.Attributes;
|
||||||
using Iceshrimp.Backend.Components.PublicPreview.Renderers;
|
using Iceshrimp.Backend.Components.PublicPreview.Renderers;
|
||||||
using Iceshrimp.Backend.Controllers.Federation;
|
using Iceshrimp.Backend.Controllers.Federation;
|
||||||
|
@ -9,6 +11,7 @@ using Iceshrimp.Backend.Controllers.Web.Renderers;
|
||||||
using Iceshrimp.Backend.Core.Configuration;
|
using Iceshrimp.Backend.Core.Configuration;
|
||||||
using Iceshrimp.Backend.Core.Database;
|
using Iceshrimp.Backend.Core.Database;
|
||||||
using Iceshrimp.Backend.Core.Federation.WebFinger;
|
using Iceshrimp.Backend.Core.Federation.WebFinger;
|
||||||
|
using Iceshrimp.Backend.Core.Helpers;
|
||||||
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
|
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
|
||||||
using Iceshrimp.Backend.Core.Middleware;
|
using Iceshrimp.Backend.Core.Middleware;
|
||||||
using Iceshrimp.Backend.Core.Services;
|
using Iceshrimp.Backend.Core.Services;
|
||||||
|
@ -30,8 +33,6 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using AuthenticationMiddleware = Iceshrimp.Backend.Core.Middleware.AuthenticationMiddleware;
|
|
||||||
using AuthorizationMiddleware = Iceshrimp.Backend.Core.Middleware.AuthorizationMiddleware;
|
|
||||||
using NoteRenderer = Iceshrimp.Backend.Controllers.Web.Renderers.NoteRenderer;
|
using NoteRenderer = Iceshrimp.Backend.Controllers.Web.Renderers.NoteRenderer;
|
||||||
using NotificationRenderer = Iceshrimp.Backend.Controllers.Web.Renderers.NotificationRenderer;
|
using NotificationRenderer = Iceshrimp.Backend.Controllers.Web.Renderers.NotificationRenderer;
|
||||||
using UserRenderer = Iceshrimp.Backend.Controllers.Web.Renderers.UserRenderer;
|
using UserRenderer = Iceshrimp.Backend.Controllers.Web.Renderers.UserRenderer;
|
||||||
|
@ -68,10 +69,6 @@ public static class ServiceExtensions
|
||||||
.AddScoped<BiteService>()
|
.AddScoped<BiteService>()
|
||||||
.AddScoped<ImportExportService>()
|
.AddScoped<ImportExportService>()
|
||||||
.AddScoped<UserProfileMentionsResolver>()
|
.AddScoped<UserProfileMentionsResolver>()
|
||||||
.AddScoped<AuthorizedFetchMiddleware>()
|
|
||||||
.AddScoped<InboxValidationMiddleware>()
|
|
||||||
.AddScoped<AuthenticationMiddleware>()
|
|
||||||
.AddScoped<ErrorHandlerMiddleware>()
|
|
||||||
.AddScoped<Controllers.Mastodon.Renderers.UserRenderer>()
|
.AddScoped<Controllers.Mastodon.Renderers.UserRenderer>()
|
||||||
.AddScoped<Controllers.Mastodon.Renderers.NoteRenderer>()
|
.AddScoped<Controllers.Mastodon.Renderers.NoteRenderer>()
|
||||||
.AddScoped<Controllers.Mastodon.Renderers.NotificationRenderer>()
|
.AddScoped<Controllers.Mastodon.Renderers.NotificationRenderer>()
|
||||||
|
@ -100,16 +97,10 @@ public static class ServiceExtensions
|
||||||
.AddSingleton<QueueService>()
|
.AddSingleton<QueueService>()
|
||||||
.AddSingleton<ObjectStorageService>()
|
.AddSingleton<ObjectStorageService>()
|
||||||
.AddSingleton<EventService>()
|
.AddSingleton<EventService>()
|
||||||
.AddSingleton<RequestBufferingMiddleware>()
|
|
||||||
.AddSingleton<AuthorizationMiddleware>()
|
|
||||||
.AddSingleton<RequestVerificationMiddleware>()
|
|
||||||
.AddSingleton<RequestDurationMiddleware>()
|
|
||||||
.AddSingleton<FederationSemaphoreMiddleware>()
|
|
||||||
.AddSingleton<PushService>()
|
.AddSingleton<PushService>()
|
||||||
.AddSingleton<StreamingService>()
|
.AddSingleton<StreamingService>()
|
||||||
.AddSingleton<ImageProcessor>()
|
.AddSingleton<ImageProcessor>()
|
||||||
.AddSingleton<RazorViewRenderService>()
|
.AddSingleton<RazorViewRenderService>()
|
||||||
.AddSingleton<BlazorSsrHandoffMiddleware>()
|
|
||||||
.AddSingleton<MfmRenderer>()
|
.AddSingleton<MfmRenderer>()
|
||||||
.AddSingleton<MatcherPolicy, PublicPreviewRouteMatcher>()
|
.AddSingleton<MatcherPolicy, PublicPreviewRouteMatcher>()
|
||||||
.AddSingleton<PolicyService>();
|
.AddSingleton<PolicyService>();
|
||||||
|
@ -139,6 +130,21 @@ public static class ServiceExtensions
|
||||||
services.AddHostedService<PushService>(provider => provider.GetRequiredService<PushService>());
|
services.AddHostedService<PushService>(provider => provider.GetRequiredService<PushService>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void AddMiddleware(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
var types = PluginLoader
|
||||||
|
.Assemblies.Prepend(Assembly.GetExecutingAssembly())
|
||||||
|
.SelectMany(p => AssemblyLoader.GetImplementationsOfInterface(p, typeof(IMiddlewareService)));
|
||||||
|
|
||||||
|
foreach (var type in types)
|
||||||
|
{
|
||||||
|
if (type.GetProperty(nameof(IMiddlewareService.Lifetime))?.GetValue(null) is not ServiceLifetime lifetime)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
services.Add(new ServiceDescriptor(type, type, lifetime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration)
|
public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Iceshrimp.Backend.Core.Configuration;
|
using Iceshrimp.Backend.Core.Configuration;
|
||||||
using Iceshrimp.Backend.Core.Database;
|
using Iceshrimp.Backend.Core.Database;
|
||||||
|
@ -32,6 +34,10 @@ public static class WebApplicationExtensions
|
||||||
.UseMiddleware<BlazorSsrHandoffMiddleware>();
|
.UseMiddleware<BlazorSsrHandoffMiddleware>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevents conditional middleware from being invoked on non-matching requests
|
||||||
|
private static IApplicationBuilder UseMiddleware<T>(this IApplicationBuilder app) where T : IConditionalMiddleware
|
||||||
|
=> app.UseWhen(T.Predicate, builder => UseMiddlewareExtensions.UseMiddleware<T>(builder));
|
||||||
|
|
||||||
public static IApplicationBuilder UseOpenApiWithOptions(this WebApplication app)
|
public static IApplicationBuilder UseOpenApiWithOptions(this WebApplication app)
|
||||||
{
|
{
|
||||||
app.MapSwagger("/openapi/{documentName}.{extension:regex(^(json|ya?ml)$)}")
|
app.MapSwagger("/openapi/{documentName}.{extension:regex(^(json|ya?ml)$)}")
|
||||||
|
@ -306,3 +312,31 @@ public static class WebApplicationExtensions
|
||||||
static extern int chmod(string pathname, int mode);
|
static extern int chmod(string pathname, int mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface IConditionalMiddleware
|
||||||
|
{
|
||||||
|
public static abstract bool Predicate(HttpContext ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IMiddlewareService : IMiddleware
|
||||||
|
{
|
||||||
|
public static abstract ServiceLifetime Lifetime { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ConditionalMiddleware<T> : IConditionalMiddleware where T : Attribute
|
||||||
|
{
|
||||||
|
[SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "Intended behavior")]
|
||||||
|
private static readonly ConcurrentDictionary<Endpoint, bool> Cache = [];
|
||||||
|
|
||||||
|
public static bool Predicate(HttpContext ctx)
|
||||||
|
=> ctx.GetEndpoint() is { } endpoint && Cache.GetOrAdd(endpoint, e => GetAttribute(e) != null);
|
||||||
|
|
||||||
|
private static T? GetAttribute(Endpoint? endpoint)
|
||||||
|
=> endpoint?.Metadata.GetMetadata<T>();
|
||||||
|
|
||||||
|
private static T? GetAttribute(HttpContext ctx)
|
||||||
|
=> GetAttribute(ctx.GetEndpoint());
|
||||||
|
|
||||||
|
protected static T GetAttributeOrFail(HttpContext ctx)
|
||||||
|
=> GetAttribute(ctx) ?? throw new Exception("Failed to get middleware filter attribute");
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
using Iceshrimp.Backend.Controllers.Mastodon.Attributes;
|
using Iceshrimp.Backend.Controllers.Mastodon.Attributes;
|
||||||
using Iceshrimp.Backend.Core.Database;
|
using Iceshrimp.Backend.Core.Database;
|
||||||
using Iceshrimp.Backend.Core.Database.Tables;
|
using Iceshrimp.Backend.Core.Database.Tables;
|
||||||
|
using Iceshrimp.Backend.Core.Extensions;
|
||||||
using Iceshrimp.Backend.Core.Helpers;
|
using Iceshrimp.Backend.Core.Helpers;
|
||||||
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
|
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
|
||||||
using Iceshrimp.Backend.Core.Services;
|
using Iceshrimp.Backend.Core.Services;
|
||||||
|
@ -9,15 +10,19 @@ using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Core.Middleware;
|
namespace Iceshrimp.Backend.Core.Middleware;
|
||||||
|
|
||||||
public class AuthenticationMiddleware(DatabaseContext db, UserService userSvc, MfmConverter mfmConverter) : IMiddleware
|
public class AuthenticationMiddleware(
|
||||||
|
DatabaseContext db,
|
||||||
|
UserService userSvc,
|
||||||
|
MfmConverter mfmConverter
|
||||||
|
) : ConditionalMiddleware<AuthenticateAttribute>, IMiddlewareService
|
||||||
{
|
{
|
||||||
|
public static ServiceLifetime Lifetime => ServiceLifetime.Scoped;
|
||||||
|
|
||||||
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
||||||
{
|
{
|
||||||
var endpoint = ctx.GetEndpoint();
|
var endpoint = ctx.GetEndpoint();
|
||||||
var attribute = endpoint?.Metadata.GetMetadata<AuthenticateAttribute>();
|
var attribute = GetAttributeOrFail(ctx);
|
||||||
|
|
||||||
if (attribute != null)
|
|
||||||
{
|
|
||||||
var isBlazorSsr = endpoint?.Metadata.GetMetadata<RootComponentMetadata>() != null;
|
var isBlazorSsr = endpoint?.Metadata.GetMetadata<RootComponentMetadata>() != null;
|
||||||
if (isBlazorSsr)
|
if (isBlazorSsr)
|
||||||
{
|
{
|
||||||
|
@ -104,7 +109,6 @@ public class AuthenticationMiddleware(DatabaseContext db, UserService userSvc, M
|
||||||
userSvc.UpdateSessionMetadata(session);
|
userSvc.UpdateSessionMetadata(session);
|
||||||
ctx.SetSession(session);
|
ctx.SetSession(session);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
await next(ctx);
|
await next(ctx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
using Iceshrimp.Backend.Controllers.Mastodon.Attributes;
|
using Iceshrimp.Backend.Controllers.Mastodon.Attributes;
|
||||||
|
using Iceshrimp.Backend.Core.Extensions;
|
||||||
using Iceshrimp.Backend.Core.Helpers;
|
using Iceshrimp.Backend.Core.Helpers;
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Core.Middleware;
|
namespace Iceshrimp.Backend.Core.Middleware;
|
||||||
|
|
||||||
public class AuthorizationMiddleware : IMiddleware
|
public class AuthorizationMiddleware(RequestDelegate next) : ConditionalMiddleware<AuthorizeAttribute>
|
||||||
{
|
{
|
||||||
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
public async Task InvokeAsync(HttpContext ctx)
|
||||||
{
|
{
|
||||||
var endpoint = ctx.GetEndpoint();
|
var endpoint = ctx.GetEndpoint();
|
||||||
var attribute = endpoint?.Metadata.GetMetadata<AuthorizeAttribute>();
|
var attribute = GetAttributeOrFail(ctx);
|
||||||
|
|
||||||
if (attribute != null)
|
|
||||||
{
|
|
||||||
ctx.Response.Headers.CacheControl = "private, no-store";
|
ctx.Response.Headers.CacheControl = "private, no-store";
|
||||||
var isMastodon = endpoint?.Metadata.GetMetadata<MastodonApiControllerAttribute>() != null;
|
var isMastodon = endpoint?.Metadata.GetMetadata<MastodonApiControllerAttribute>() != null;
|
||||||
|
|
||||||
|
@ -38,7 +37,6 @@ public class AuthorizationMiddleware : IMiddleware
|
||||||
if (attribute.ModeratorRole && session.User is { IsAdmin: false, IsModerator: false })
|
if (attribute.ModeratorRole && session.User is { IsAdmin: false, IsModerator: false })
|
||||||
throw GracefulException.Forbidden("This action is outside the authorized scopes");
|
throw GracefulException.Forbidden("This action is outside the authorized scopes");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
await next(ctx);
|
await next(ctx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Net;
|
||||||
using Iceshrimp.Backend.Core.Configuration;
|
using Iceshrimp.Backend.Core.Configuration;
|
||||||
using Iceshrimp.Backend.Core.Database;
|
using Iceshrimp.Backend.Core.Database;
|
||||||
using Iceshrimp.Backend.Core.Database.Tables;
|
using Iceshrimp.Backend.Core.Database.Tables;
|
||||||
|
using Iceshrimp.Backend.Core.Extensions;
|
||||||
using Iceshrimp.Backend.Core.Federation.Cryptography;
|
using Iceshrimp.Backend.Core.Federation.Cryptography;
|
||||||
using Iceshrimp.Backend.Core.Services;
|
using Iceshrimp.Backend.Core.Services;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
@ -21,14 +22,18 @@ public class AuthorizedFetchMiddleware(
|
||||||
ActivityPub.FederationControlService fedCtrlSvc,
|
ActivityPub.FederationControlService fedCtrlSvc,
|
||||||
ILogger<AuthorizedFetchMiddleware> logger,
|
ILogger<AuthorizedFetchMiddleware> logger,
|
||||||
IHostApplicationLifetime appLifetime
|
IHostApplicationLifetime appLifetime
|
||||||
) : IMiddleware
|
) : ConditionalMiddleware<AuthorizedFetchAttribute>, IMiddlewareService
|
||||||
{
|
{
|
||||||
|
public static ServiceLifetime Lifetime => ServiceLifetime.Scoped;
|
||||||
|
|
||||||
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
||||||
{
|
{
|
||||||
var attribute = ctx.GetEndpoint()?.Metadata.GetMetadata<AuthorizedFetchAttribute>();
|
if (!config.Value.AuthorizedFetch)
|
||||||
|
|
||||||
if (attribute != null && config.Value.AuthorizedFetch)
|
|
||||||
{
|
{
|
||||||
|
await next(ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ctx.Response.Headers.CacheControl = "private, no-store";
|
ctx.Response.Headers.CacheControl = "private, no-store";
|
||||||
|
|
||||||
var request = ctx.Request;
|
var request = ctx.Request;
|
||||||
|
@ -121,7 +126,6 @@ public class AuthorizedFetchMiddleware(
|
||||||
throw new GracefulException(HttpStatusCode.Forbidden, "Request signature validation failed");
|
throw new GracefulException(HttpStatusCode.Forbidden, "Request signature validation failed");
|
||||||
|
|
||||||
ctx.SetActor(key.User);
|
ctx.SetActor(key.User);
|
||||||
}
|
|
||||||
|
|
||||||
await next(ctx);
|
await next(ctx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Iceshrimp.Backend.Core.Extensions;
|
||||||
using Microsoft.AspNetCore.Components.Endpoints;
|
using Microsoft.AspNetCore.Components.Endpoints;
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Core.Middleware;
|
namespace Iceshrimp.Backend.Core.Middleware;
|
||||||
|
|
||||||
public class BlazorSsrHandoffMiddleware : IMiddleware
|
public class BlazorSsrHandoffMiddleware(RequestDelegate next) : ConditionalMiddleware<BlazorSsrAttribute>
|
||||||
{
|
{
|
||||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
public async Task InvokeAsync(HttpContext context)
|
||||||
{
|
{
|
||||||
var attribute = context.GetEndpoint()
|
var attribute = context.GetEndpoint()
|
||||||
?.Metadata.GetMetadata<RootComponentMetadata>()
|
?.Metadata.GetMetadata<RootComponentMetadata>()
|
||||||
?.Type.GetCustomAttributes<RazorSsrAttribute>()
|
?.Type.GetCustomAttributes<BlazorSsrAttribute>()
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
if (attribute != null)
|
if (attribute != null)
|
||||||
|
@ -34,4 +35,4 @@ public class BlazorSsrHandoffMiddleware : IMiddleware
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RazorSsrAttribute : Attribute;
|
public class BlazorSsrAttribute : Attribute;
|
|
@ -15,11 +15,13 @@ namespace Iceshrimp.Backend.Core.Middleware;
|
||||||
|
|
||||||
public class ErrorHandlerMiddleware(
|
public class ErrorHandlerMiddleware(
|
||||||
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor")]
|
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor")]
|
||||||
IOptionsSnapshot<Config.SecuritySection> options,
|
IOptionsMonitor<Config.SecuritySection> options,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
RazorViewRenderService razor
|
RazorViewRenderService razor
|
||||||
) : IMiddleware
|
) : IMiddlewareService
|
||||||
{
|
{
|
||||||
|
public static ServiceLifetime Lifetime => ServiceLifetime.Singleton;
|
||||||
|
|
||||||
private static readonly XmlSerializer XmlSerializer = new(typeof(ErrorResponse));
|
private static readonly XmlSerializer XmlSerializer = new(typeof(ErrorResponse));
|
||||||
|
|
||||||
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
||||||
|
@ -37,7 +39,7 @@ public class ErrorHandlerMiddleware(
|
||||||
type = type[..(type.IndexOf('>') + 1)];
|
type = type[..(type.IndexOf('>') + 1)];
|
||||||
|
|
||||||
var logger = loggerFactory.CreateLogger(type);
|
var logger = loggerFactory.CreateLogger(type);
|
||||||
var verbosity = options.Value.ExceptionVerbosity;
|
var verbosity = options.CurrentValue.ExceptionVerbosity;
|
||||||
|
|
||||||
if (ctx.Response.HasStarted)
|
if (ctx.Response.HasStarted)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Iceshrimp.Backend.Core.Configuration;
|
using Iceshrimp.Backend.Core.Configuration;
|
||||||
|
using Iceshrimp.Backend.Core.Extensions;
|
||||||
using Iceshrimp.Backend.Core.Helpers;
|
using Iceshrimp.Backend.Core.Helpers;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
@ -8,8 +9,10 @@ namespace Iceshrimp.Backend.Core.Middleware;
|
||||||
public class FederationSemaphoreMiddleware(
|
public class FederationSemaphoreMiddleware(
|
||||||
IOptions<Config.PerformanceSection> config,
|
IOptions<Config.PerformanceSection> config,
|
||||||
IHostApplicationLifetime appLifetime
|
IHostApplicationLifetime appLifetime
|
||||||
) : IMiddleware
|
) : ConditionalMiddleware<FederationSemaphoreAttribute>, IMiddlewareService
|
||||||
{
|
{
|
||||||
|
public static ServiceLifetime Lifetime => ServiceLifetime.Singleton;
|
||||||
|
|
||||||
private readonly SemaphorePlus _semaphore = new(Math.Max(config.Value.FederationRequestHandlerConcurrency, 1));
|
private readonly SemaphorePlus _semaphore = new(Math.Max(config.Value.FederationRequestHandlerConcurrency, 1));
|
||||||
|
|
||||||
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
||||||
|
@ -20,13 +23,6 @@ public class FederationSemaphoreMiddleware(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var attribute = ctx.GetEndpoint()?.Metadata.GetMetadata<FederationSemaphoreAttribute>();
|
|
||||||
if (attribute == null)
|
|
||||||
{
|
|
||||||
await next(ctx);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var cts = CancellationTokenSource
|
var cts = CancellationTokenSource
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.Net.Http.Headers;
|
||||||
using Iceshrimp.Backend.Core.Configuration;
|
using Iceshrimp.Backend.Core.Configuration;
|
||||||
using Iceshrimp.Backend.Core.Database;
|
using Iceshrimp.Backend.Core.Database;
|
||||||
using Iceshrimp.Backend.Core.Database.Tables;
|
using Iceshrimp.Backend.Core.Database.Tables;
|
||||||
|
using Iceshrimp.Backend.Core.Extensions;
|
||||||
using Iceshrimp.Backend.Core.Federation.ActivityStreams;
|
using Iceshrimp.Backend.Core.Federation.ActivityStreams;
|
||||||
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
|
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
|
||||||
using Iceshrimp.Backend.Core.Federation.Cryptography;
|
using Iceshrimp.Backend.Core.Federation.Cryptography;
|
||||||
|
@ -25,16 +26,14 @@ public class InboxValidationMiddleware(
|
||||||
ActivityPub.FederationControlService fedCtrlSvc,
|
ActivityPub.FederationControlService fedCtrlSvc,
|
||||||
ILogger<InboxValidationMiddleware> logger,
|
ILogger<InboxValidationMiddleware> logger,
|
||||||
IHostApplicationLifetime appLifetime
|
IHostApplicationLifetime appLifetime
|
||||||
) : IMiddleware
|
) : ConditionalMiddleware<InboxValidationAttribute>, IMiddlewareService
|
||||||
{
|
{
|
||||||
|
public static ServiceLifetime Lifetime => ServiceLifetime.Scoped;
|
||||||
|
|
||||||
private static readonly JsonSerializerSettings JsonSerializerSettings =
|
private static readonly JsonSerializerSettings JsonSerializerSettings =
|
||||||
new() { DateParseHandling = DateParseHandling.None };
|
new() { DateParseHandling = DateParseHandling.None };
|
||||||
|
|
||||||
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
||||||
{
|
|
||||||
var attribute = ctx.GetEndpoint()?.Metadata.GetMetadata<InboxValidationAttribute>();
|
|
||||||
|
|
||||||
if (attribute != null)
|
|
||||||
{
|
{
|
||||||
var request = ctx.Request;
|
var request = ctx.Request;
|
||||||
var ct = appLifetime.ApplicationStopping;
|
var ct = appLifetime.ApplicationStopping;
|
||||||
|
@ -250,7 +249,6 @@ public class InboxValidationMiddleware(
|
||||||
throw new GracefulException(HttpStatusCode.Forbidden, "Request signature validation failed");
|
throw new GracefulException(HttpStatusCode.Forbidden, "Request signature validation failed");
|
||||||
|
|
||||||
ctx.SetActor(key.User);
|
ctx.SetActor(key.User);
|
||||||
}
|
|
||||||
|
|
||||||
await next(ctx);
|
await next(ctx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
|
using Iceshrimp.Backend.Core.Extensions;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Core.Middleware;
|
namespace Iceshrimp.Backend.Core.Middleware;
|
||||||
|
|
||||||
public class RequestBufferingMiddleware : IMiddleware
|
[UsedImplicitly]
|
||||||
|
public class RequestBufferingMiddleware(RequestDelegate next) : ConditionalMiddleware<EnableRequestBufferingAttribute>
|
||||||
{
|
{
|
||||||
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
[UsedImplicitly]
|
||||||
|
public async Task InvokeAsync(HttpContext ctx)
|
||||||
{
|
{
|
||||||
var attribute = ctx.GetEndpoint()?.Metadata.GetMetadata<EnableRequestBufferingAttribute>();
|
var attr = GetAttributeOrFail(ctx);
|
||||||
if (attribute != null) ctx.Request.EnableBuffering(attribute.MaxLength);
|
ctx.Request.EnableBuffering(attr.MaxLength);
|
||||||
await next(ctx);
|
await next(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||||
public class EnableRequestBufferingAttribute(long maxLength) : Attribute
|
public class EnableRequestBufferingAttribute(long maxLength) : Attribute
|
||||||
{
|
{
|
||||||
internal long MaxLength = maxLength;
|
internal readonly long MaxLength = maxLength;
|
||||||
}
|
}
|
|
@ -1,11 +1,14 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Iceshrimp.Backend.Core.Extensions;
|
using Iceshrimp.Backend.Core.Extensions;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Core.Middleware;
|
namespace Iceshrimp.Backend.Core.Middleware;
|
||||||
|
|
||||||
public class RequestDurationMiddleware : IMiddleware
|
[UsedImplicitly]
|
||||||
|
public class RequestDurationMiddleware(RequestDelegate next)
|
||||||
{
|
{
|
||||||
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
[UsedImplicitly]
|
||||||
|
public async Task InvokeAsync(HttpContext ctx)
|
||||||
{
|
{
|
||||||
if (ctx.GetEndpoint()?.Metadata.GetMetadata<HideRequestDuration>() == null)
|
if (ctx.GetEndpoint()?.Metadata.GetMetadata<HideRequestDuration>() == null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Iceshrimp.Backend.Core.Configuration;
|
using Iceshrimp.Backend.Core.Configuration;
|
||||||
|
using Iceshrimp.Backend.Core.Extensions;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Core.Middleware;
|
namespace Iceshrimp.Backend.Core.Middleware;
|
||||||
|
@ -7,8 +8,10 @@ public class RequestVerificationMiddleware(
|
||||||
IOptions<Config.InstanceSection> config,
|
IOptions<Config.InstanceSection> config,
|
||||||
IHostEnvironment environment,
|
IHostEnvironment environment,
|
||||||
ILogger<RequestVerificationMiddleware> logger
|
ILogger<RequestVerificationMiddleware> logger
|
||||||
) : IMiddleware
|
) : IMiddlewareService
|
||||||
{
|
{
|
||||||
|
public static ServiceLifetime Lifetime => ServiceLifetime.Singleton;
|
||||||
|
|
||||||
private readonly bool _isDevelopment = environment.IsDevelopment();
|
private readonly bool _isDevelopment = environment.IsDevelopment();
|
||||||
|
|
||||||
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||||
<PackageReference Include="System.Text.Json" Version="9.0.0" />
|
<PackageReference Include="System.Text.Json" Version="9.0.0" />
|
||||||
<PackageReference Include="Ulid" Version="1.3.4" />
|
<PackageReference Include="Ulid" Version="1.3.4" />
|
||||||
<PackageReference Include="Iceshrimp.AssemblyUtils" Version="1.0.1" />
|
<PackageReference Include="Iceshrimp.AssemblyUtils" Version="1.0.2" />
|
||||||
<PackageReference Include="Iceshrimp.MimeTypes" Version="1.0.1" />
|
<PackageReference Include="Iceshrimp.MimeTypes" Version="1.0.1" />
|
||||||
<PackageReference Include="Iceshrimp.WebPush" Version="2.1.0" />
|
<PackageReference Include="Iceshrimp.WebPush" Version="2.1.0" />
|
||||||
<PackageReference Include="NetVips" Version="3.0.0" />
|
<PackageReference Include="NetVips" Version="3.0.0" />
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
@using Microsoft.AspNetCore.Components.Routing
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
@inject IOptions<Config.InstanceSection> Instance
|
@inject IOptions<Config.InstanceSection> Instance
|
||||||
@preservewhitespace true
|
@preservewhitespace true
|
||||||
@attribute [RazorSsr]
|
@attribute [BlazorSsr]
|
||||||
@inherits AsyncComponentBase
|
@inherits AsyncComponentBase
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
|
@ -37,6 +37,7 @@ builder.Services.AddResponseCompression();
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
builder.Services.AddRazorComponents();
|
builder.Services.AddRazorComponents();
|
||||||
builder.Services.AddAntiforgery(o => o.Cookie.Name = "CSRF-Token");
|
builder.Services.AddAntiforgery(o => o.Cookie.Name = "CSRF-Token");
|
||||||
|
builder.Services.AddMiddleware();
|
||||||
|
|
||||||
builder.Services.AddServices(builder.Configuration);
|
builder.Services.AddServices(builder.Configuration);
|
||||||
builder.Services.ConfigureServices(builder.Configuration);
|
builder.Services.ConfigureServices(builder.Configuration);
|
||||||
|
|
Loading…
Add table
Reference in a new issue