From e77da0f91d80b7a6dc8269b7195c3f90823da9ef Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Tue, 23 Jan 2024 21:02:18 +0100 Subject: [PATCH] Implement .well-known/host-meta and .well-known/webfinger --- .../Controllers/WellKnownController.cs | 74 +++++++++++++++++++ .../Core/Federation/WebFinger/Types.cs | 13 ++-- Iceshrimp.Backend/Startup.cs | 2 + Iceshrimp.NET.sln.DotSettings | 3 +- 4 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 Iceshrimp.Backend/Controllers/WellKnownController.cs diff --git a/Iceshrimp.Backend/Controllers/WellKnownController.cs b/Iceshrimp.Backend/Controllers/WellKnownController.cs new file mode 100644 index 00000000..cbb12b01 --- /dev/null +++ b/Iceshrimp.Backend/Controllers/WellKnownController.cs @@ -0,0 +1,74 @@ +using Iceshrimp.Backend.Controllers.Schemas; +using Iceshrimp.Backend.Core.Configuration; +using Iceshrimp.Backend.Core.Database; +using Iceshrimp.Backend.Core.Database.Tables; +using Iceshrimp.Backend.Core.Federation.WebFinger; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; + +namespace Iceshrimp.Backend.Controllers; + +[ApiController] +[Route("/.well-known")] +public class WellKnownController(IOptions config, DatabaseContext db) : Controller { + [HttpGet("webfinger")] + [Produces("application/json")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(WebFingerResponse))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ErrorResponse))] + public async Task WebFinger([FromQuery] string resource) { + User? user; + if (resource.StartsWith("acct:")) { + var split = resource[5..].TrimStart('@').Split('@'); + if (split.Length > 2) return NotFound(); + if (split.Length == 2) { + List domains = [config.Value.AccountDomain, config.Value.WebDomain]; + if (!domains.Contains(split[1])) return NotFound(); + } + + user = await db.Users.FirstOrDefaultAsync(p => p.UsernameLower == split[0].ToLowerInvariant() && + p.Host == null); + } + else if (resource.StartsWith($"https://{config.Value.WebDomain}/users/")) { + var id = resource[$"https://{config.Value.WebDomain}/users/".Length..]; + user = await db.Users.FirstOrDefaultAsync(p => p.Id == id && p.Host == null); + } + else { + user = await db.Users.FirstOrDefaultAsync(p => p.UsernameLower == resource.ToLowerInvariant() && + p.Host == null); + } + + if (user == null) return NotFound(); + + var response = new WebFingerResponse { + Subject = $"acct:{user.Username}@{config.Value.AccountDomain}", + Links = [ + new WebFingerLink { + Rel = "self", + Type = "application/activity+json", + Href = $"https://{config.Value.WebDomain}/users/{user.Id}" + }, + new WebFingerLink { + Rel = "http://webfinger.net/rel/profile-page", + Type = "text/html", + Href = $"https://{config.Value.WebDomain}/@{user.Username}" + }, + new WebFingerLink { + Rel = "http://ostatus.org/schema/1.0/subscribe", + Template = $"https://{config.Value.WebDomain}/authorize-follow?acct={{uri}}" + } + ] + }; + + return Ok(response); + } + + [HttpGet("host-meta")] + [Produces("application/xrd+xml")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult HostMeta() { + //TODO: use a proper xml serializer for this + return + Content($$""""""); + } +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Federation/WebFinger/Types.cs b/Iceshrimp.Backend/Core/Federation/WebFinger/Types.cs index 7df782a2..6cfb517b 100644 --- a/Iceshrimp.Backend/Core/Federation/WebFinger/Types.cs +++ b/Iceshrimp.Backend/Core/Federation/WebFinger/Types.cs @@ -6,15 +6,16 @@ namespace Iceshrimp.Backend.Core.Federation.WebFinger; [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] public sealed class WebFingerLink { - [J("rel"), JR] public string Rel { get; set; } = null!; - [J("type")] public string? Type { get; set; } = null!; - [J("href")] public string? Href { get; set; } + [J("rel")] [JR] public string Rel { get; set; } = null!; + [J("type")] public string? Type { get; set; } + [J("href")] public string? Href { get; set; } + [J("template")] public string? Template { get; set; } } [SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")] [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] public sealed class WebFingerResponse { - [J("links"), JR] public List Links { get; set; } = null!; - [J("subject"), JR] public string Subject { get; set; } = null!; - [J("aliases")] public List Aliases { get; set; } = []; + [J("links")] [JR] public List Links { get; set; } = null!; + [J("subject")] [JR] public string Subject { get; set; } = null!; + [J("aliases")] public List Aliases { get; set; } = []; } \ No newline at end of file diff --git a/Iceshrimp.Backend/Startup.cs b/Iceshrimp.Backend/Startup.cs index 16b50248..5315f9db 100644 --- a/Iceshrimp.Backend/Startup.cs +++ b/Iceshrimp.Backend/Startup.cs @@ -70,4 +70,6 @@ if (app.Environment.IsDevelopment()) app.UseViteDevMiddleware(); app.Urls.Clear(); app.Urls.Add($"http://{instanceConfig.WebDomain}:{instanceConfig.ListenPort}"); +//TODO: init database, grab meta table data + app.Run(); \ No newline at end of file diff --git a/Iceshrimp.NET.sln.DotSettings b/Iceshrimp.NET.sln.DotSettings index fc0e9140..813b5c50 100644 --- a/Iceshrimp.NET.sln.DotSettings +++ b/Iceshrimp.NET.sln.DotSettings @@ -35,4 +35,5 @@ True True True - True \ No newline at end of file + True + True \ No newline at end of file