Authorize data sync on connected agent registration

This commit is contained in:
2026-09-22 16:47:52 +08:00
parent 49c07cca30
commit e26535781d
17 changed files with 194 additions and 110 deletions
@@ -42,12 +42,11 @@ public sealed class RemoteAgentHostedService(
using var client = new RemoteControlClient(remote);
var ledger = new RemoteTaskLedger(Path.Combine(options.DataDirectory, "remote-task-ledger.json"));
var eventQueue = remoteQueue ?? new RemoteEventQueue(Path.Combine(options.DataDirectory, "remote-event-queue.json"));
var dataQueue = options.EnableDataSync
? new RemoteDataBatchQueue(Path.Combine(options.DataDirectory, "remote-data-queue.json"), options.DataSyncQueueMaxItems, options.DataSyncQueueMaxBytes)
: null;
var syncState = options.EnableDataSync
? new RemoteDataSyncStateStore(Path.Combine(options.DataDirectory, "remote-data-sync-state.json"))
: null;
// A configured Agent connection is the data-sync authorization boundary.
// EnableDataSync remains only as a read-compatibility property for old service.json files.
var dataQueue = new RemoteDataBatchQueue(
Path.Combine(options.DataDirectory, "remote-data-queue.json"), options.DataSyncQueueMaxItems, options.DataSyncQueueMaxBytes);
var syncState = new RemoteDataSyncStateStore(Path.Combine(options.DataDirectory, "remote-data-sync-state.json"));
var blockedDataSyncAccounts = new HashSet<string>(StringComparer.Ordinal);
var lastDataSyncAt = DateTimeOffset.MinValue;
string? registeredActiveAccountId = null;
@@ -66,9 +65,9 @@ public sealed class RemoteAgentHostedService(
await DelayAsync(RetryDelay, stoppingToken);
continue;
}
var reporting = runtime.Reporting;
activeReporting = reporting;
var snapshot = await ReadSnapshotAsync(remote, stoppingToken);
var reporting = ConnectionAuthorizedReporting(runtime.Reporting, snapshot);
activeReporting = reporting;
var registrationNeedsRefresh = client.AuthState != RemoteAuthState.Authenticated
|| !string.Equals(registeredActiveAccountId, snapshot.ActiveAccountId, StringComparison.Ordinal)
|| registeredActiveAccountVerified != snapshot.ActiveAccountVerified
@@ -474,8 +473,9 @@ public sealed class RemoteAgentHostedService(
throw new ServiceException("InvalidPage", 500, "The node returned a non-advancing contact page.");
offset = next;
}
var allChats = scopes.Any(scope => scope.ChatId == "*");
var allowed = scopes.Select(scope => scope.ChatId).ToHashSet(StringComparer.Ordinal);
var items = all.Where(contact => allowed.Contains(contact.Id)).ToArray();
var items = allChats ? all.ToArray() : all.Where(contact => allowed.Contains(contact.Id)).ToArray();
var matchedScopeCount = scopes.Count(scope => all.Any(contact => string.Equals(contact.Id, scope.ChatId, StringComparison.Ordinal)));
var coverage = CreateCoverage(
matchedScopeCount == scopes.Count
@@ -531,6 +531,21 @@ public sealed class RemoteAgentHostedService(
string accountId, IReadOnlyList<RemoteReportingScope> scopes, CancellationToken cancellationToken)
{
var contacts = new Dictionary<string, ContactInfo>(StringComparer.Ordinal);
if (scopes.Any(scope => scope.ChatId == "*"))
{
for (var offset = 0; ;)
{
var page = await backend.ContactsAsync(accountId, null, null, 200, offset, cancellationToken);
foreach (var contact in page.Items)
contacts[contact.Id] = contact;
if (!page.HasMore) break;
var next = page.NextOffset ?? offset + page.Items.Count;
if (next <= offset)
throw new ServiceException("InvalidPage", 500, "The node returned a non-advancing contact page.");
offset = next;
}
return contacts.Values.ToArray();
}
foreach (var scope in scopes)
{
for (var offset = 0; ;)
@@ -556,6 +571,8 @@ public sealed class RemoteAgentHostedService(
private static bool SessionMatchesScope(SessionInfo session, RemoteReportingScope scope, IReadOnlyList<ContactInfo> contacts)
{
if (scope.ChatId == "*")
return true;
if (string.Equals(scope.ChatId, session.AutomationId, StringComparison.Ordinal) ||
string.Equals(scope.ChatId, session.Name, StringComparison.Ordinal))
return true;
@@ -721,6 +738,29 @@ public sealed class RemoteAgentHostedService(
}
}
private static ReportingConfig ConnectionAuthorizedReporting(ReportingConfig configured, BackendSnapshot snapshot)
{
var accounts = snapshot.Accounts
.Where(identity => identity.Verified)
.Select(identity => new AccountReportingConfig
{
AccountId = identity.AccountId,
Enabled = true,
AllowedChats =
[
new AllowedChat { Type = ReportingChatType.Group, ChatId = "*", Enabled = true, IdentityVerified = true },
new AllowedChat { Type = ReportingChatType.Private, ChatId = "*", Enabled = true, IdentityVerified = true }
]
})
.ToArray();
return configured with
{
Enabled = true,
ConfigVersion = Math.Max(1, configured.ConfigVersion),
Accounts = accounts
};
}
private static RemoteNodeRegistration CreateRegistration(RemoteAgentOptions remote, ReportingConfig reporting, BackendSnapshot snapshot) =>
new(remote.NodeId!, typeof(RemoteAgentHostedService).Assembly.GetName().Version?.ToString() ?? "dev",
RemoteProtocol.Version, ["heartbeat", "poll-tasks", "send-text", "read-sessions", "read-contacts", "read-messages", "db-messages", "db-merged", "report-message"], reporting.ConfigVersion,