Files
SocialPub/PrivaPub/Middleware/SocialPubConfigurations.cs
T
thepraandClaude Opus 5.5 587997dd67
Build / Build (push) Successful in 8m37s
P7: Lemmy's Warn and Resolve{Flag}
- A Warn from a community's moderator about a persona's own post there becomes that persona's moderation_warning
  notification (Mastodon's AccountWarning, with the reason and the post), believed from the community's own server or
  from an account the community's moderators collection lists. Remote communities keep that collection's address
  (attributedTo) as ForeignAvatar.ModeratorsURL; it is read only when a warning needs it.
- A Resolve{Flag} for one of our reports (`/grunts/flag-<report id>`) is kept as the report's remote resolution when it
  comes from the server holding what was reported, or the community it was reported to; our own moderators'
  resolution is never replaced. The moderators' report list shows both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
2026-10-07 21:10:58 +02:00

295 lines
13 KiB
C#

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.ResponseCompression;
using OpenIddict.Validation.AspNetCore;
using PrivaPub.ClientModels;
using PrivaPub.Extensions;
using PrivaPub.Models;
using PrivaPub.Services;
using PrivaPub.StaticServices;
using System.Text.Json.Serialization;
using Microsoft.OpenApi;
using PrivaPub.Services.ClientToServer.Private;
using PrivaPub.Services.ClientToServer.Public;
using PrivaPub.Federation.Actors;
using PrivaPub.Federation.Outbox;
using PrivaPub.Federation.Signing;
using PrivaPub.Federation.Inbox;
using PrivaPub.Federation.Objects;
using PrivaPub.Federation.Moderation;
using PrivaPub.Federation.Inbox.Handlers;
using PrivaPub.Domain.Content;
using PrivaPub.Domain.Relationships;
using PrivaPub.Domain.Social;
using PrivaPub.Domain.Statuses;
using PrivaPub.Domain.Timelines;
using PrivaPub.Infrastructure.Http;
using PrivaPub.Infrastructure.Jobs;
using PrivaPub.Infrastructure.Geo;
using PrivaPub.Infrastructure.Statistics;
using Microsoft.Extensions.Options;
namespace PrivaPub.Middleware
{
public static class PrivaPubConfigurations
{
const string SchemeSelector = "PrivaPub";
public static IServiceCollection PrivaPubAppSettingsConfiguration(this IServiceCollection service, IConfiguration configuration)
{
return service
.Configure<MongoSettings>(configuration.GetSection(nameof(MongoSettings)))
.Configure<AppConfiguration>(configuration.GetSection(nameof(AppConfiguration)))
.Configure<PrivaPub.Infrastructure.RegistrationOptions>(configuration.GetSection("Registrations"));
}
public static IServiceCollection PrivaPubWorkersConfiguration(this IServiceCollection service)
{
return service;
//.AddHostedService<DiscussionsWorker>()
//.AddHostedService<GroupsCleanerWorker>()
//.AddHostedService<PoliciesCleanerWorker>();
}
public static IServiceCollection PrivaPubFederationConfiguration(this IServiceCollection service, IConfiguration configuration)
{
service.Configure<FederationOptions>(configuration.GetSection("Federation"));
service.AddHttpClient(FederationHttp.ClientName, (provider, client) =>
{
var baseAddress = provider.GetRequiredService<IOptionsMonitor<AppConfiguration>>().CurrentValue.BackendBaseAddress?.TrimEnd('/');
client.Timeout = FederationHttp.RequestTimeout;
client.DefaultRequestHeaders.UserAgent.ParseAdd($"PrivaPub/{BuildInfo.Ref} (+{baseAddress}/)");
})
.ConfigurePrimaryHttpMessageHandler(provider =>
SafeHttpHandlerFactory.Create(provider.GetRequiredService<IOptions<FederationOptions>>().Value, provider.GetRequiredService<IConnectedAddresses>()))
.AddHttpMessageHandler(provider => new EdgeHintsHandler(provider.GetRequiredService<IEdgeHints>()));
return service
.AddSingleton<IConnectedAddresses, ConnectedAddresses>()
.AddSingleton<IEdgeHints, EdgeHints>()
.AddSingleton<IFederationHttp, FederationHttp>()
.AddSingleton<IDomainBlocks, DomainBlocks>()
.AddSingleton<IContentRenderer, ContentRenderer>()
.AddSingleton<ILocalActorService, LocalActorService>()
.AddSingleton<IRemoteActorService, RemoteActorService>()
.AddSingleton<IDeliveryService, DeliveryService>()
.AddSingleton<IOutboxPublisher, OutboxPublisher>()
.AddSingleton<IGroupDistributor, GroupDistributor>()
.AddSingleton<ISignedFetchAuthorizer, SignedFetchAuthorizer>()
.AddSingleton<IFanout, Fanout>()
.AddSingleton<IInboxReceiver, InboxReceiver>()
.AddSingleton<IActivityHandler, FollowHandler>()
.AddSingleton<IActivityHandler, AcceptHandler>()
.AddSingleton<IActivityHandler, RejectHandler>()
.AddSingleton<IActivityHandler, ReplyApprovalHandler>()
.AddSingleton<IActivityHandler, ReplyRejectionHandler>()
.AddSingleton<IActivityHandler, UndoHandler>()
.AddSingleton<IActivityHandler, LikeHandler>()
.AddSingleton<IActivityHandler, DislikeHandler>()
.AddSingleton<IActivityHandler, EmojiReactHandler>()
.AddSingleton<IActivityHandler, QuoteRequestHandler>()
.AddSingleton<IActivityHandler, WarnHandler>()
.AddSingleton<IActivityHandler, ResolveHandler>()
.AddSingleton<IReactions, Reactions>()
.AddSingleton<LinkPreviews>()
.AddSingleton<ILinkPreviews>(services => services.GetRequiredService<LinkPreviews>())
.AddSingleton<IJobHandler>(services => services.GetRequiredService<LinkPreviews>())
.AddSingleton<IActivityHandler, JoinHandler>()
.AddSingleton<IActivityHandler, AnnounceHandler>()
.AddSingleton<IActivityHandler, FlagHandler>()
.AddSingleton<IActivityHandler, BlockHandler>()
.AddSingleton<IActivityHandler, LockHandler>()
.AddSingleton<Federation.Actors.IFeaturedPosts, Federation.Actors.FeaturedPosts>()
.AddSingleton<Federation.Actors.IWalls, Federation.Actors.Walls>()
.AddSingleton<IActivityHandler>(services => new FeaturedHandler(services.GetRequiredService<Federation.Actors.IFeaturedPosts>(), add: true,
services.GetRequiredService<Federation.Actors.IWalls>(), services.GetRequiredService<Federation.Actors.IRemoteActorService>(),
services.GetRequiredService<IJobQueue>()))
.AddSingleton<IActivityHandler>(services => new FeaturedHandler(services.GetRequiredService<Federation.Actors.IFeaturedPosts>(), add: false,
services.GetRequiredService<Federation.Actors.IWalls>(), services.GetRequiredService<Federation.Actors.IRemoteActorService>(),
services.GetRequiredService<IJobQueue>()))
.AddSingleton<IActivityHandler, MoveHandler>()
.AddSingleton<IActivityHandler, CreateHandler>()
.AddSingleton<IActivityHandler, DeleteHandler>()
.AddSingleton<IActivityHandler, UpdateHandler>()
.AddSingleton<IJobHandler, InboxProcessor>()
.AddSingleton<IRemotePosts, RemotePosts>()
.AddSingleton<IObjectRecords, ObjectRecords>()
.AddSingleton<IPollService, PollService>()
.AddSingleton<IQuoteService, QuoteService>()
.AddSingleton<IInteractionApprovals, InteractionApprovals>()
.AddSingleton<IParticipations, Participations>()
.AddSingleton<IJobHandler, PollRefreshJob>()
.AddSingleton<IJobHandler, RecoveryJob>()
.AddSingleton<IJobHandler, Federation.Actors.AccountCountsJob>()
.AddSingleton<IJobHandler, Federation.Actors.OutboxBackfill>()
.AddSingleton<IJobHandler, Federation.Actors.FollowingSynchronization>()
.AddSingleton<IJobHandler, PollCloseJob>()
.AddSingleton<IJobHandler, Domain.Media.ProcessMediaJob>()
.AddSingleton<IJobHandler, Domain.Portability.ExportArchiveJob>()
.AddSingleton<IJobHandler, Domain.Portability.ImportArchiveJob>()
.AddSingleton<IJobHandler, Domain.Statuses.PublishScheduledJob>()
.AddSingleton<InstanceDescriber>()
.AddSingleton<IJobHandler>(services => services.GetRequiredService<InstanceDescriber>())
.AddSingleton<IJobHandler, AncestorsJobHandler>()
.AddSingleton<IJobHandler, Federation.Inbox.RepliesJobHandler>()
.AddSingleton<IJobQueue, JobQueue>()
.AddSingleton<IHostCircuitBreaker, HostCircuitBreaker>()
.AddSingleton<IJobHandler, DeliveryJobHandler>()
.AddHostedService<JobWorker>()
.AddHostedService<Domain.Social.FollowResender>()
.AddSingleton<Federation.Relays.IRelays, Federation.Relays.Relays>()
.AddHostedService<Federation.Relays.RelaySubscriber>();
}
public static IServiceCollection PrivaPubStatisticsConfiguration(this IServiceCollection service, IConfiguration configuration)
{
// not federation: the DB-IP download only (GeoUpdater), plain HTTPS to a fixed host
service.AddHttpClient(GeoUpdater.ClientName, (provider, client) =>
{
var baseAddress = provider.GetRequiredService<IOptionsMonitor<AppConfiguration>>().CurrentValue.BackendBaseAddress?.TrimEnd('/');
client.Timeout = TimeSpan.FromMinutes(15);
client.DefaultRequestHeaders.UserAgent.ParseAdd($"PrivaPub/{BuildInfo.Ref} (+{baseAddress}/)");
});
// not federation either: the CDNs' published address ranges (CdnUpdater), plain HTTPS to the CDNs' own hosts
service.AddHttpClient(CdnUpdater.ClientName, (provider, client) =>
{
var baseAddress = provider.GetRequiredService<IOptionsMonitor<AppConfiguration>>().CurrentValue.BackendBaseAddress?.TrimEnd('/');
client.Timeout = TimeSpan.FromMinutes(2);
client.DefaultRequestHeaders.UserAgent.ParseAdd($"PrivaPub/{BuildInfo.Ref} (+{baseAddress}/)");
});
return service
.Configure<StatisticsOptions>(configuration.GetSection("Statistics"))
.AddSingleton<InteractionSalts>()
.AddSingleton<InteractionLedger>()
.AddSingleton<IInteractionLedger>(services => services.GetRequiredService<InteractionLedger>())
.AddHostedService(services => services.GetRequiredService<InteractionLedger>())
.AddSingleton<IJobHandler, RollupJob>()
.AddSingleton<IGeoLocator, DbIpLocator>()
.AddSingleton<ICdnRanges, CdnRanges>()
.AddHostedService<CdnUpdater>()
.AddSingleton<Domain.Statistics.ISelfLocation, Domain.Statistics.SelfLocation>()
.AddSingleton<Domain.Statistics.IServerPlaces, Domain.Statistics.ServerPlaces>()
.AddHostedService<GeoUpdater>()
.AddSingleton<Domain.Statistics.StatisticsQueries>()
.AddSingleton<IJobHandler, Federation.Crawler.CrawlPlanner>()
.AddSingleton<IJobHandler, Federation.Crawler.InstanceCrawler>()
.AddHostedService<StatisticsSchedule>();
}
public static IServiceCollection PrivaPubAuthServicesConfiguration(this IServiceCollection service, IConfiguration configuration)
{
return service
.AddAuthorization(options =>
{
options.AddPolicy(Policies.IsUser, Extensions.Extensions.IsUserPolicy());
options.AddPolicy(Policies.IsAdmin, Extensions.Extensions.IsAdminPolicy());
options.AddPolicy(Policies.IsModerator, Extensions.Extensions.IsModeratorPolicy());
})
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = SchemeSelector;
options.DefaultChallengeScheme = SchemeSelector;
options.DefaultScheme = SchemeSelector;
})
.AddPolicyScheme(SchemeSelector, SchemeSelector, options => options.ForwardDefaultSelector = context =>
context.Request.Path.StartsWithSegments("/api")
? OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme
: JwtBearerDefaults.AuthenticationScheme)
.AddPrivaPubAuth(configuration)
.Services
.AddSingleton<AuthTokenManager>()
.AddSingleton<IPasswordHasher, PasswordHasher>();
}
public static IServiceCollection PrivaPubInternalizationConfiguration(this IServiceCollection service, IConfiguration configuration)
{
return service
.AddLocalization()
.AddSingleton<RequestLocalizationOptionsService>();
}
public static IServiceCollection PrivaPubOptimizationConfiguration(this IServiceCollection service)
{
return service.AddResponseCompression(opts =>
{
opts.Providers.Add<BrotliCompressionProvider>();
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "application/octet-stream" });
});
}
public static IServiceCollection PrivaPubDataBaseConfiguration(this IServiceCollection service)
{
return service.AddSingleton<DbEntities>();
}
public static IServiceCollection PrivaPubServicesConfiguration(this IServiceCollection service)
{
return service
.AddTransient<IDataService, DataService>()
.AddTransient<IRootUsersService, RootUsersService>()
.AddScoped<IRootSessions, RootSessions>()
.AddScoped<IRootRemoval, RootRemoval>()
.AddTransient<IPublicAvatarUsersService, PublicAvatarUsersService>()
.AddTransient<IPrivateAvatarUsersService, PrivateAvatarUsersService>()
.AddTransient<IGroupUsersService, GroupUsersService>()
.AddTransient<IPostsService, PostsService>()
.AddTransient<IStatusService, StatusService>()
.AddTransient<IRelationshipService, RelationshipService>()
.AddTransient<IReportService, ReportService>()
.AddTransient<IFollowService, FollowService>()
.AddTransient<ITimelineService, TimelineService>()
.AddSingleton<AppConfigurationService>()
.AddHttpContextAccessor()
.AddMemoryCache()
.AddSingleton<IPasswordHasher, PasswordHasher>();
}
public static IServiceCollection PrivaPubMiddlewareConfiguration(this IServiceCollection service)
{
return service
.AddEndpointsApiExplorer()
.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please enter a valid token",
Name = "Authorization",
Type = SecuritySchemeType.Http,
BearerFormat = "JWT",
Scheme = "bearer"
});
c.AddSecurityRequirement(document => new OpenApiSecurityRequirement
{
[new OpenApiSecuritySchemeReference("Bearer", document)] = []
});
})
.AddRazorPages(options => options.RootDirectory = "/Web/Pages")
.Services
.AddControllers(options => { options.Filters.Add<OperationCancelledExceptionFilter>(); })
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.IgnoreReadOnlyFields = false;
options.JsonSerializerOptions.IgnoreReadOnlyProperties = false;
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
}).Services;
}
public static IServiceCollection PrivaPubCORSConfiguration(this IServiceCollection service)
{
return service.AddCors(options =>
{
options.DefaultPolicyName = "DefaultCORS";
options.AddDefaultPolicy(configure =>
{
configure.AllowAnyMethod()
.AllowAnyHeader()
.AllowAnyOrigin()
.WithExposedHeaders("Link", "X-RateLimit-Remaining", "X-RateLimit-Reset")
.DisallowCredentials();
});
});
}
}
}