Foxchat.NET/Foxchat.Chat/Middleware/ServerAuthenticationMiddleware.cs

86 lines
No EOL
3 KiB
C#

using Foxchat.Chat.Database;
using Foxchat.Chat.Database.Models;
using Foxchat.Core;
using Foxchat.Core.Extensions;
using Foxchat.Core.Federation;
using Microsoft.EntityFrameworkCore;
namespace Foxchat.Chat.Middleware;
public class ServerAuthenticationMiddleware(ILogger logger, ChatContext db, RequestSigningService requestSigningService)
: IMiddleware
{
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
{
var endpoint = ctx.GetEndpoint();
// Endpoints require server authentication by default, unless they have the [Unauthenticated] attribute.
var metadata = endpoint?.Metadata.GetMetadata<ServerUnauthenticatedAttribute>();
if (metadata != null)
{
await next(ctx);
return;
}
if (!ctx.ExtractRequestData(out var signature, out var domain, out var signatureData))
throw new ApiError.IncomingFederationError("This endpoint requires signed requests.");
var instance = await GetInstanceAsync(domain);
if (!requestSigningService.VerifySignature(instance.PublicKey, signature, signatureData))
throw new ApiError.IncomingFederationError("Signature is not valid.");
ctx.SetSignature(instance, signatureData);
await next(ctx);
}
private async Task<IdentityInstance> GetInstanceAsync(string domain)
{
return await db.IdentityInstances.FirstOrDefaultAsync(i => i.Domain == domain)
?? throw new ApiError.IncomingFederationError("Remote instance is not known.");
}
}
/// <summary>
/// Attribute to be put on controllers or methods to indicate that it does <i>not</i> require a signed request.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ServerUnauthenticatedAttribute : Attribute;
public static class HttpContextExtensions
{
private const string Key = "instance";
public static void SetSignature(this HttpContext ctx, IdentityInstance instance, SignatureData data)
{
ctx.Items.Add(Key, (instance, data));
}
public static (IdentityInstance?, SignatureData?) GetSignature(this HttpContext ctx)
{
try
{
var obj = ctx.GetSignatureOrThrow();
return (obj.Item1, obj.Item2);
}
catch
{
return (null, null);
}
}
public static (IdentityInstance, SignatureData) GetSignatureOrThrow(this HttpContext ctx)
{
if (!ctx.Items.TryGetValue(Key, out var obj))
throw new ApiError.AuthenticationError("No instance in HttpContext");
return ((IdentityInstance, SignatureData))obj!;
}
public static (IdentityInstance, SignatureData, string) GetSignatureWithUser(this HttpContext ctx)
{
var (instance, sig) = ctx.GetSignatureOrThrow();
if (sig.UserId == null) throw new ApiError.IncomingFederationError("This endpoint requires a user ID.");
return (instance, sig, sig.UserId);
}
}