69 lines
2.9 KiB
C#
69 lines
2.9 KiB
C#
using System.Collections.Concurrent;
|
|
|
|
namespace MoShiro.Backend.Application;
|
|
|
|
/// <summary>节点的一个属主:它报上来的 passkey 凭证 id,以及面板上对应的 passkey 名称和账户(对不上任何已注册的 passkey 时为空)。</summary>
|
|
public sealed record NodeOwner(string CredentialId, string? PasskeyName, string? Username);
|
|
|
|
/// <summary>
|
|
/// 节点的属主:Agent 和 Probe 握手时报上来的、安装时固定在节点本地的 passkey 凭证 id。
|
|
/// <b>属主的强制在节点本地</b>(没有属主的节点不运行,见 <c>OwnerGuard</c>),面板这边只是顺着凭证 id 反查出它绑定在哪个账户的哪把 passkey 上——
|
|
/// 以后给节点更多的接口和操作(extension 会话、加密内容)时,据此判断谁有资格用。上报的只是自述,真正的授权由节点自己对 passkey 签名的校验把关。
|
|
/// 在内存里:节点每次连上来都会重新报。
|
|
/// </summary>
|
|
public sealed class NodeOwnerService
|
|
{
|
|
private const int MaxOwners = 16;
|
|
private const int MaxCredentialIdLength = 256;
|
|
|
|
private readonly ConcurrentDictionary<string, string[]> _reported = new(StringComparer.Ordinal);
|
|
private readonly IAuthStore _auth;
|
|
|
|
public NodeOwnerService(IAuthStore auth)
|
|
{
|
|
_auth = auth;
|
|
}
|
|
|
|
public void Report(string nodeId, IReadOnlyList<string>? credentialIds)
|
|
{
|
|
string[] clean = credentialIds is null ? [] : [.. credentialIds.Where(id => id.Length is > 0 and <= MaxCredentialIdLength).Distinct(StringComparer.Ordinal).Take(MaxOwners)];
|
|
if (clean.Length == 0)
|
|
{
|
|
_reported.TryRemove(nodeId, out _);
|
|
}
|
|
else
|
|
{
|
|
_reported[nodeId] = clean;
|
|
}
|
|
}
|
|
|
|
public void Forget(string nodeId) => _reported.TryRemove(nodeId, out _);
|
|
|
|
/// <summary>每个节点的属主(只含报过的节点)。</summary>
|
|
public async ValueTask<IReadOnlyDictionary<string, IReadOnlyList<NodeOwner>>> ListAsync(CancellationToken ct)
|
|
{
|
|
var result = new Dictionary<string, IReadOnlyList<NodeOwner>>(StringComparer.Ordinal);
|
|
var users = new Dictionary<string, string?>(StringComparer.Ordinal);
|
|
foreach ((string nodeId, string[] ids) in _reported)
|
|
{
|
|
var owners = new List<NodeOwner>();
|
|
foreach (string id in ids)
|
|
{
|
|
PasskeyRecord? passkey = await _auth.FindPasskeyAsync(id, ct);
|
|
string? username = null;
|
|
if (passkey is not null && !users.TryGetValue(passkey.UserId, out username))
|
|
{
|
|
username = (await _auth.GetUserAsync(passkey.UserId, ct))?.Username;
|
|
users[passkey.UserId] = username;
|
|
}
|
|
|
|
owners.Add(new NodeOwner(id, passkey?.Name, username));
|
|
}
|
|
|
|
result[nodeId] = owners;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|