🐛 Fix web finger parsing
This commit is contained in:
@@ -5,7 +5,7 @@ using System.Text.RegularExpressions;
|
|||||||
|
|
||||||
namespace DysonNetwork.Sphere.ActivityPub;
|
namespace DysonNetwork.Sphere.ActivityPub;
|
||||||
|
|
||||||
public class ActivityPubDiscoveryService(
|
public partial class ActivityPubDiscoveryService(
|
||||||
AppDatabase db,
|
AppDatabase db,
|
||||||
IHttpClientFactory httpClientFactory,
|
IHttpClientFactory httpClientFactory,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
@@ -15,7 +15,7 @@ public class ActivityPubDiscoveryService(
|
|||||||
private string Domain => configuration["ActivityPub:Domain"] ?? "localhost";
|
private string Domain => configuration["ActivityPub:Domain"] ?? "localhost";
|
||||||
private HttpClient HttpClient => httpClientFactory.CreateClient();
|
private HttpClient HttpClient => httpClientFactory.CreateClient();
|
||||||
|
|
||||||
private static readonly Regex HandlePattern = new(@"^@?(\w+)@([\w.-]+)$", RegexOptions.Compiled);
|
private static readonly Regex HandlePattern = HandleRegex();
|
||||||
|
|
||||||
public async Task<SnFediverseActor?> DiscoverActorAsync(string query)
|
public async Task<SnFediverseActor?> DiscoverActorAsync(string query)
|
||||||
{
|
{
|
||||||
@@ -48,30 +48,21 @@ public class ActivityPubDiscoveryService(
|
|||||||
return localResults;
|
return localResults;
|
||||||
|
|
||||||
var (username, domain) = ParseHandle(query);
|
var (username, domain) = ParseHandle(query);
|
||||||
if (username != null && domain != null)
|
if (username == null || domain == null) return localResults;
|
||||||
{
|
{
|
||||||
var actorUri = await GetActorUriFromWebfingerAsync(username, domain);
|
var actorUri = await GetActorUriFromWebfingerAsync(username, domain);
|
||||||
if (actorUri != null)
|
if (actorUri == null) return localResults;
|
||||||
{
|
var remoteActor = await FetchAndStoreActorAsync(actorUri);
|
||||||
var remoteActor = await FetchAndStoreActorAsync(actorUri);
|
if (remoteActor == null || localResults.Any(a => a.Uri == actorUri)) return localResults;
|
||||||
if (remoteActor != null && !localResults.Any(a => a.Uri == actorUri))
|
var combined = new List<SnFediverseActor>(localResults) { remoteActor };
|
||||||
{
|
return combined.Take(limit).ToList();
|
||||||
var combined = new List<SnFediverseActor>(localResults) { remoteActor };
|
|
||||||
return combined.Take(limit).ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return localResults;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private (string? username, string? domain) ParseHandle(string query)
|
private (string? username, string? domain) ParseHandle(string query)
|
||||||
{
|
{
|
||||||
var match = HandlePattern.Match(query.Trim());
|
var match = HandlePattern.Match(query.Trim());
|
||||||
if (!match.Success)
|
return !match.Success ? (null, null) : (match.Groups[1].Value, match.Groups[2].Value);
|
||||||
return (null, null);
|
|
||||||
|
|
||||||
return (match.Groups[1].Value, match.Groups[2].Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string?> GetActorUriFromWebfingerAsync(string username, string domain)
|
private async Task<string?> GetActorUriFromWebfingerAsync(string username, string domain)
|
||||||
@@ -92,6 +83,8 @@ public class ActivityPubDiscoveryService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var json = await response.Content.ReadAsStringAsync();
|
var json = await response.Content.ReadAsStringAsync();
|
||||||
|
logger.LogDebug("Webfinger response from {Url}: {Json}", webfingerUrl, json);
|
||||||
|
|
||||||
var webfingerData = JsonSerializer.Deserialize<WebFingerResponse>(json);
|
var webfingerData = JsonSerializer.Deserialize<WebFingerResponse>(json);
|
||||||
|
|
||||||
if (webfingerData?.Links == null)
|
if (webfingerData?.Links == null)
|
||||||
@@ -100,9 +93,14 @@ public class ActivityPubDiscoveryService(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.LogDebug("Found {LinkCount} links in Webfinger response", webfingerData.Links.Count);
|
||||||
|
foreach (var link in webfingerData.Links)
|
||||||
|
{
|
||||||
|
logger.LogDebug("Link: rel={Rel}, type={Type}, href={Href}", link.Rel, link.Type, link.Href);
|
||||||
|
}
|
||||||
|
|
||||||
var selfLink = webfingerData.Links.FirstOrDefault(l =>
|
var selfLink = webfingerData.Links.FirstOrDefault(l =>
|
||||||
l.Rel == "self" &&
|
l is { Rel: "self", Type: "application/activity+json" });
|
||||||
l.Type == "application/activity+json");
|
|
||||||
|
|
||||||
if (selfLink == null)
|
if (selfLink == null)
|
||||||
{
|
{
|
||||||
@@ -203,21 +201,15 @@ public class ActivityPubDiscoveryService(
|
|||||||
return actorUri.Split('/').Last();
|
return actorUri.Split('/').Last();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? ExtractAvatarUrl(object? iconData)
|
private static string? ExtractAvatarUrl(object? iconData)
|
||||||
{
|
{
|
||||||
if (iconData == null)
|
return iconData switch
|
||||||
return null;
|
|
||||||
|
|
||||||
if (iconData is JsonElement element)
|
|
||||||
{
|
{
|
||||||
if (element.ValueKind == JsonValueKind.String)
|
null => null,
|
||||||
return element.GetString();
|
JsonElement { ValueKind: JsonValueKind.String } element => element.GetString(),
|
||||||
|
JsonElement element when element.TryGetProperty("url", out var urlElement) => urlElement.GetString(),
|
||||||
if (element.TryGetProperty("url", out var urlElement))
|
_ => iconData.ToString()
|
||||||
return urlElement.GetString();
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return iconData.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? ExtractImageUrl(object? imageData)
|
private string? ExtractImageUrl(object? imageData)
|
||||||
@@ -225,31 +217,30 @@ public class ActivityPubDiscoveryService(
|
|||||||
return ExtractAvatarUrl(imageData);
|
return ExtractAvatarUrl(imageData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? ExtractPublicKeyId(object? publicKeyData)
|
private static string? ExtractPublicKeyId(object? publicKeyData)
|
||||||
{
|
{
|
||||||
if (publicKeyData == null)
|
return publicKeyData switch
|
||||||
return null;
|
{
|
||||||
|
null => null,
|
||||||
if (publicKeyData is JsonElement element && element.TryGetProperty("id", out var idElement))
|
JsonElement element when element.TryGetProperty("id", out var idElement) => idElement.GetString(),
|
||||||
return idElement.GetString();
|
Dictionary<string, object> dict when dict.TryGetValue("id", out var idValue) => idValue.ToString(),
|
||||||
|
_ => null
|
||||||
if (publicKeyData is Dictionary<string, object> dict && dict.TryGetValue("id", out var idValue))
|
};
|
||||||
return idValue?.ToString();
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? ExtractPublicKeyPem(object? publicKeyData)
|
private static string? ExtractPublicKeyPem(object? publicKeyData)
|
||||||
{
|
{
|
||||||
if (publicKeyData == null)
|
return publicKeyData switch
|
||||||
return null;
|
{
|
||||||
|
null => null,
|
||||||
if (publicKeyData is JsonElement element && element.TryGetProperty("publicKeyPem", out var pemElement))
|
JsonElement element when element.TryGetProperty("publicKeyPem", out var pemElement) =>
|
||||||
return pemElement.GetString();
|
pemElement.GetString(),
|
||||||
|
Dictionary<string, object> dict when dict.TryGetValue("publicKeyPem", out var pemValue) => pemValue
|
||||||
if (publicKeyData is Dictionary<string, object> dict && dict.TryGetValue("publicKeyPem", out var pemValue))
|
.ToString(),
|
||||||
return pemValue?.ToString();
|
_ => null
|
||||||
|
};
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex(@"^@?(\w+)@([\w.-]+)$", RegexOptions.Compiled)]
|
||||||
|
private static partial Regex HandleRegex();
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ namespace DysonNetwork.Sphere.ActivityPub;
|
|||||||
public class ActivityPubFollowController(
|
public class ActivityPubFollowController(
|
||||||
AppDatabase db,
|
AppDatabase db,
|
||||||
ActivityPubDeliveryService deliveryService,
|
ActivityPubDeliveryService deliveryService,
|
||||||
ActivityPubDiscoveryService discoveryService,
|
ActivityPubDiscoveryService discSrv,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
ILogger<ActivityPubFollowController> logger
|
ILogger<ActivityPubFollowController> logger
|
||||||
) : ControllerBase
|
) : ControllerBase
|
||||||
@@ -161,7 +161,7 @@ public class ActivityPubFollowController(
|
|||||||
if (string.IsNullOrWhiteSpace(query))
|
if (string.IsNullOrWhiteSpace(query))
|
||||||
return BadRequest(new { error = "Query is required" });
|
return BadRequest(new { error = "Query is required" });
|
||||||
|
|
||||||
var actors = await discoveryService.SearchActorsAsync(query, limit, includeRemoteDiscovery: true);
|
var actors = await discSrv.SearchActorsAsync(query, limit, includeRemoteDiscovery: true);
|
||||||
|
|
||||||
return Ok(actors);
|
return Ok(actors);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.ActivityPub;
|
namespace DysonNetwork.Sphere.ActivityPub;
|
||||||
|
|
||||||
@@ -68,13 +69,21 @@ public class WebFingerController(
|
|||||||
|
|
||||||
public class WebFingerResponse
|
public class WebFingerResponse
|
||||||
{
|
{
|
||||||
|
[JsonPropertyName("subject")]
|
||||||
public string Subject { get; set; } = null!;
|
public string Subject { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("links")]
|
||||||
public List<WebFingerLink> Links { get; set; } = [];
|
public List<WebFingerLink> Links { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public class WebFingerLink
|
public class WebFingerLink
|
||||||
{
|
{
|
||||||
|
[JsonPropertyName("rel")]
|
||||||
public string Rel { get; set; } = null!;
|
public string Rel { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("type")]
|
||||||
public string Type { get; set; } = null!;
|
public string Type { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("href")]
|
||||||
public string Href { get; set; } = null!;
|
public string Href { get; set; } = null!;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKestrelServerLimits_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1e2e5dfcafad4407b569dd5df56a2fbf274e00_003Fa4_003F39445f62_003FKestrelServerLimits_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKestrelServerLimits_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1e2e5dfcafad4407b569dd5df56a2fbf274e00_003Fa4_003F39445f62_003FKestrelServerLimits_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKnownResamplers_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fef3339e864a448e2b1ec6fa7bbf4c6661fee00_003Fb3_003Fcdb3e080_003FKnownResamplers_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKnownResamplers_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fef3339e864a448e2b1ec6fa7bbf4c6661fee00_003Fb3_003Fcdb3e080_003FKnownResamplers_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AListenOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fafff40a572554b579e7307e8ac16b014279a00_003Fc7_003Ff445744a_003FListenOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AListenOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fafff40a572554b579e7307e8ac16b014279a00_003Fc7_003Ff445744a_003FListenOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AList_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F19674998a88c4c42be3692b32f3379d21046510_003F1f_003F4c54500e_003FList_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALivekitRoom_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F82666257d5ad47354add7af860f66dd85df55ec93e92e8a45891b9bff7bf80ac_003FLivekitRoom_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALivekitRoom_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F82666257d5ad47354add7af860f66dd85df55ec93e92e8a45891b9bff7bf80ac_003FLivekitRoom_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALocalDate_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F9bab6c3f4ee252ba1a9d0707f963a846da4f248fa52e9ff43e789149728a4_003FLocalDate_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALocalDate_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F9bab6c3f4ee252ba1a9d0707f963a846da4f248fa52e9ff43e789149728a4_003FLocalDate_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMailboxAddress_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8e03e47c46b7469f97abc40667cbcf9b133000_003Fa6_003F83324248_003FMailboxAddress_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMailboxAddress_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8e03e47c46b7469f97abc40667cbcf9b133000_003Fa6_003F83324248_003FMailboxAddress_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
|||||||
Reference in New Issue
Block a user