net10, the refactor finished, and federation that works
Build / Build (push) Successful in 28s
Deploy / privapub.thepra.dev (push) Successful in 19s

The tree had not compiled since its first commit: Group, DmGroup and
IGroupUsersService were referenced and never written, an IDE rename had
turned the user-settings DTO into the ViewAvatarServer enum, and the settings
were saved as an entity they no longer were.

Built now:
- Group (an ActivityPub Group actor with its own keys, members, invitation
  code and optional password) and DmGroup (a conversation), with
  /clientapi/group/{list,insert,update,join,leave,approve}.
- Posts and DMs: /clientapi/post/{list,insert,delete}, /clientapi/dm/{list,insert};
  DM recipients are local usernames or user@host handles resolved by WebFinger.
- Invitation sign-up and login against the group's invitation code, checking
  the password before any account is created.
- Federation: WebFinger, NodeInfo 2.0, actors at /peasants/{name} (Person,
  Group, and an Application instance actor) with SPKI keys, draft-cavage
  RSA-SHA256 HTTP signatures both ways, an inbox handling Follow (+Accept),
  Undo, Create, Delete and Update, an outbox, notes at /posts/{id}, and a
  persisted, retried, signed delivery queue. A post to a group is announced
  by the group to its followers (FEP-1b12).
- The unused, broken typed ActivityPub models are replaced by a renderer;
  NSign's HMAC setup, which could not federate, is gone.

Upgrade: net10.0, MongoDB.Entities 25.1 (instance DB API, Standard GUIDs),
Swashbuckle 10 / OpenApi 2, Serilog.AspNetCore 10, MailKit 4.18,
PasswordGenerator 3. The JWT keys are 64 bytes (IdentityModel 8 refuses
shorter for HS512). Production runs its own mongod on 127.0.0.1:27022, as
Sintopia's apps do, and deploys to privapub.thepra.dev from the build runner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-01 08:05:56 +02:00
1 parent 56c5396106
commit 075c22228a
78 files changed
+3419 -1091

No files matched your search

+23 -88
View File
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.ResponseCompression;
using PrivaPub.ClientModels;
@@ -8,13 +8,10 @@ using PrivaPub.Services;
using PrivaPub.StaticServices;
using System.Text.Json.Serialization;
using NSign.Providers;
using NSign;
using NSign.Signatures;
using NSign.Client;
using System.Text;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi;
using PrivaPub.Services.ClientToServer.Private;
using PrivaPub.Services.ClientToServer.Public;
using PrivaPub.Services.Federation;
namespace PrivaPub.Middleware
{
@@ -33,74 +30,19 @@ namespace PrivaPub.Middleware
//.AddHostedService<GroupsCleanerWorker>()
//.AddHostedService<PoliciesCleanerWorker>();
}
public static IServiceCollection PrivaPubHTTPSignature(this IServiceCollection service, IConfiguration configuration)
public static IServiceCollection PrivaPubFederationConfiguration(this IServiceCollection service)
{
//HTTP CLIENT
service.Configure<AddDigestOptions>(options => options.WithHash(AddDigestOptions.Hash.Sha256))
.ConfigureMessageSigningOptions(options =>
{
options.SignatureName = "PrivaPub";
options
.WithMandatoryComponent(SignatureComponent.Path)
.WithMandatoryComponent(SignatureComponent.RequestTarget)
.SetParameters = signatureParams => signatureParams.WithKeyId("keyId");
})
.Services.Configure<SignatureVerificationOptions>(options =>
{
})
.AddHttpClient<ActivityPubClient>(nameof(ActivityPubClient))
.ConfigureHttpClient(httpClient =>
{
httpClient.DefaultRequestHeaders.Accept.Add(new("application/ld+json"));
})
.AddDigestAndSigningHandlers()
//.AddSignatureVerifiationHandler()
.Services
.AddSingleton<ISigner>(new HmacSha256SignatureProvider(Encoding.UTF8.GetBytes(configuration["AppConfiguration:Jwt:Key"])));
//MESSAGE RESPONSE
return service;
//.Configure<RequestSignatureVerificationOptions>(options =>
//{
// options.SignaturesToVerify.Add("sample");
// options.RequiredSignatureComponents.Add(SignatureComponent.Path);
// options.RequiredSignatureComponents.Add(SignatureComponent.Method);
// options.RequiredSignatureComponents.Add(SignatureComponent.Digest);
//})
//.AddSignatureVerification(serviceProvider =>
//{
// var memoryCache = serviceProvider.GetRequiredService<IMemoryCache>();
// //var httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
// //httpContextAccessor.HttpContext.Request.
// var cert = memoryCache.GetOrCreate("PrivaPub", (cacheEntry) => Extensions.Extensions.GetX509Certificate2("PrivaPubCert"));
// return new RsaPkcs15Sha256SignatureProvider(cert, "anon");
//})
//.ConfigureMessageSigningOptions(options =>
//{
// options.WithMandatoryComponent(SignatureComponent.Path)
// .WithMandatoryComponent(SignatureComponent.Method)
// .WithMandatoryComponent(SignatureComponent.Digest)
// .WithOptionalComponent(new HttpHeaderDictionaryStructuredComponent(NSign.Constants.Headers.Signature, "sample", bindRequest: true));
// options.SignatureName = "resp";
// options.SetParameters = (sigParams) =>
// {
// sigParams.WithCreatedNow();
// };
//})
//.ValidateOnStart()
//.Services
//.AddHttpClient("ActivityPub", (serviceProvider, client) =>
//{
// client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("NSignSample", "0.1-beta"));
//}).Services;
//.AddSingleton<ISigner>(new RsaPssSha512SignatureProvider(
// new X509Certificate2(@"path\to\certificate.pfx", "PasswordForPfx"),
// "my-cert"));
service.AddHttpClient(RemoteActorService.HttpClientName, client =>
{
client.Timeout = TimeSpan.FromSeconds(20);
client.DefaultRequestHeaders.UserAgent.ParseAdd($"PrivaPub/{BuildInfo.Ref}");
});
return service
.AddSingleton<ILocalActorService, LocalActorService>()
.AddSingleton<IRemoteActorService, RemoteActorService>()
.AddSingleton<IDeliveryService, DeliveryService>()
.AddTransient<IInboxService, InboxService>()
.AddHostedService<DeliveryWorker>();
}
public static IServiceCollection PrivaPubAuthServicesConfiguration(this IServiceCollection service, IConfiguration configuration)
{
@@ -149,6 +91,9 @@ namespace PrivaPub.Middleware
.AddTransient<IDataService, DataService>()
.AddTransient<IRootUsersService, RootUsersService>()
.AddTransient<IPublicAvatarUsersService, PublicAvatarUsersService>()
.AddTransient<IPrivateAvatarUsersService, PrivateAvatarUsersService>()
.AddTransient<IGroupUsersService, GroupUsersService>()
.AddTransient<IPostsService, PostsService>()
.AddSingleton<AppConfigurationService>()
.AddHttpContextAccessor()
.AddMemoryCache()
@@ -161,28 +106,18 @@ namespace PrivaPub.Middleware
.AddEndpointsApiExplorer()
.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("Bearer", new()
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please enter a valid token",
Name = "Authorization",
Type = SecuritySchemeType.Http,
BearerFormat = "JWT",
Scheme = "Bearer"
Scheme = "bearer"
});
c.AddSecurityRequirement(new()
c.AddSecurityRequirement(document => new OpenApiSecurityRequirement
{
{
new()
{
Reference = new()
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[]{}
}
[new OpenApiSecuritySchemeReference("Bearer", document)] = []
});
})
.AddControllers(options => { options.Filters.Add<OperationCancelledExceptionFilter>(); })