Implement .well-known/host-meta and .well-known/webfinger
This commit is contained in:
parent
b930c60fd9
commit
e77da0f91d
4 changed files with 85 additions and 7 deletions
74
Iceshrimp.Backend/Controllers/WellKnownController.cs
Normal file
74
Iceshrimp.Backend/Controllers/WellKnownController.cs
Normal file
|
@ -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.InstanceSection> config, DatabaseContext db) : Controller {
|
||||
[HttpGet("webfinger")]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(WebFingerResponse))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ErrorResponse))]
|
||||
public async Task<IActionResult> 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<string> 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($$"""<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" type="application/xrd+xml" template="https://{{config.Value.WebDomain}}/.well-known/webfinger?resource={uri}"/></XRD>""");
|
||||
}
|
||||
}
|
|
@ -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<WebFingerLink> Links { get; set; } = null!;
|
||||
[J("subject"), JR] public string Subject { get; set; } = null!;
|
||||
[J("aliases")] public List<string> Aliases { get; set; } = [];
|
||||
[J("links")] [JR] public List<WebFingerLink> Links { get; set; } = null!;
|
||||
[J("subject")] [JR] public string Subject { get; set; } = null!;
|
||||
[J("aliases")] public List<string> Aliases { get; set; } = [];
|
||||
}
|
|
@ -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();
|
|
@ -35,4 +35,5 @@
|
|||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Iceshrimp/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Iceshrimp/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=webfinger/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
Loading…
Add table
Reference in a new issue