68 lines
3.1 KiB
C#
68 lines
3.1 KiB
C#
using Carter;
|
||
using MediatR;
|
||
using Knot.Shared.Kernel.Constants;
|
||
using Microsoft.AspNetCore.Builder;
|
||
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.Modules.Settings.Application.Settings.Abstractions;
|
||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||
|
||
namespace Host.Endpoints;
|
||
|
||
/// <summary>
|
||
/// Эндпоинты федерации для межсерверного взаимодействия.
|
||
/// </summary>
|
||
public sealed class FederationEndpoints : ICarterModule
|
||
{
|
||
public void AddRoutes(IEndpointRouteBuilder app)
|
||
{
|
||
var group = app.MapGroup(Routes.ApiFederation);
|
||
|
||
group.MapPost("/handshake", async ([FromBody] HandshakeRequest request, ISender sender, CancellationToken ct) =>
|
||
{
|
||
var result = await sender.Send(new HandshakeFederationCommand(request), ct);
|
||
return result.IsSuccess ? Results.Ok(result.Value) : Results.BadRequest(new { error = result.Error.Description });
|
||
});
|
||
|
||
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));
|
||
|
||
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();
|
||
}
|
||
});
|
||
}
|
||
}
|