Структура, доп модули, федерация, документация

This commit is contained in:
Халимов Рустам
2026-03-27 00:55:01 +03:00
parent 7cb6ac61dd
commit 7ef73b414c
64 changed files with 3080 additions and 133 deletions

View File

@@ -6,11 +6,15 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using System;
using Host.Application.Federation.Commands;
using Knot.Modules.Federation.Application.Abstractions;
using Knot.Shared.Kernel.Storage;
using Knot.Shared.Kernel.Configuration;
namespace Host.Endpoints;
/// <summary>
/// Регистрация эндпоинтов федерации.
/// Эндпоинты федерации для межсерверного взаимодействия.
/// </summary>
public sealed class FederationEndpoints : ICarterModule
{
@@ -18,24 +22,45 @@ public sealed class FederationEndpoints : ICarterModule
{
var group = app.MapGroup(Routes.ApiFederation);
group.MapPost("/handshake", async ([FromBody] Host.Application.Federation.Commands.HandshakeRequest request, ISender sender, CancellationToken ct) =>
group.MapPost("/handshake", async ([FromBody] HandshakeRequest request, ISender sender, CancellationToken ct) =>
{
var result = await sender.Send(new Host.Application.Federation.Commands.HandshakeFederationCommand(request), ct);
if (result.IsSuccess)
{
return Results.Ok(result.Value);
}
var result = await sender.Send(new HandshakeFederationCommand(request), ct);
return result.IsSuccess ? Results.Ok(result.Value) : Results.BadRequest(new { error = result.Error.Description });
});
if (result.Error.Code == "Unauthorized")
{
return Results.Forbid();
}
if (result.Error.Code == Knot.Shared.Kernel.Constants.Errors.DisabledByAdmin)
{
return Results.StatusCode(503);
}
group.MapPost("/inbound", async ([FromBody] FederationMessagePacket packet, ISender sender, CancellationToken ct) =>
{
var result = await sender.Send(new InboundFederationCommand(packet), ct);
return result.IsSuccess ? Results.Accepted() : Results.BadRequest(new { error = result.Error.Description });
});
group.MapGet("/resolve/{username}", async (string username, ISender sender, CancellationToken ct) =>
{
var result = await sender.Send(new ResolveUserCommand(username), ct);
return result.IsSuccess ? Results.Ok(result.Value) : Results.NotFound(new { error = result.Error.Description });
});
group.MapGet("/proxy/{id}", async (string id, [FromHeader(Name = "X-Knot-Signature")] string signature, [FromHeader(Name = "X-Knot-Domain")] string senderDomain, IFileStorageService storage, ISettingsService settingsService, CancellationToken ct) =>
{
// 1. Валидация подписи (упрощенно)
var settings = await settingsService.GetSettingsAsync(ct);
var senderConfig = settings.Federation.AllowedDomains.FirstOrDefault(d => d.Domain.Equals(senderDomain, StringComparison.OrdinalIgnoreCase));
return Results.BadRequest(new { error = result.Error.Description });
if (senderConfig == null || string.IsNullOrEmpty(senderConfig.PublicKey))
return Results.Forbid();
// В реальном коде здесь проверка RSA подписи параметров запроса (URL + Domain)
// 2. Стриминг файла первоисточника
try
{
var (stream, contentType, fileName) = await storage.DownloadFileAsync(id);
return Results.File(stream, contentType, fileName, enableRangeProcessing: true);
}
catch
{
return Results.NotFound();
}
});
}
}