FEP-171b, received (Hubzilla, Streams, Forte): an Add whose target is a collection of the actor's own server, and whose object is someone's Create, Update or Delete of their own object, is queued as that activity forwarded by the owner, so it is believed on its FEP-8b32 proof or as its origin has it, and shares its job with the plain forward Hubzilla also sends. Checked live against Hubzilla (unchanged, 20 checks). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
288 lines
13 KiB
C#
288 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, UndoHandler>()
|
|
.AddSingleton<IActivityHandler, LikeHandler>()
|
|
.AddSingleton<IActivityHandler, DislikeHandler>()
|
|
.AddSingleton<IActivityHandler, EmojiReactHandler>()
|
|
.AddSingleton<IActivityHandler, QuoteRequestHandler>()
|
|
.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.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();
|
|
});
|
|
});
|
|
}
|
|
|
|
}
|
|
}
|