Stop data sync after account authorization revoke

This commit is contained in:
2026-09-22 13:02:55 +08:00
parent 51c9524437
commit e8a8b2f926
6 changed files with 159 additions and 12 deletions
@@ -335,6 +335,51 @@ public sealed class RemoteControlClientTests
Assert.All(handler.Requests, request => Assert.Equal("Bearer node-token", request.Authorization));
}
[Fact]
public async Task FlushDropsAllQueuedBatchesAndBlocksAccountAfterRevocation()
{
var directory = Directory.CreateTempSubdirectory("wxagent-revoke-");
try
{
var config = new ReportingConfig
{
Enabled = true,
ConfigVersion = 1,
Accounts = [new AccountReportingConfig
{
AccountId = "account-a", Enabled = true,
AllowedChats = [new AllowedChat { Type = ReportingChatType.Private, ChatId = "chat-a", Enabled = true, IdentityVerified = true }]
}]
};
var queue = new RemoteDataBatchQueue(Path.Combine(directory.FullName, "queue.json"));
Assert.True(queue.Enqueue(config, SyncBatch("batch-1")).Accepted);
Assert.True(queue.Enqueue(config, SyncBatch("batch-2")).Accepted);
var handler = new RevokedBatchHandler();
using var http = new HttpClient(handler);
using var client = new RemoteControlClient(new RemoteAgentOptions
{
AuthAddress = "http://127.0.0.1:8090",
Token = "node-token",
NodeId = "node-1"
}, http);
await client.RegisterAsync(new RemoteNodeRegistration("node-1", "test", RemoteProtocol.Version, ["db-messages"], 1, []));
var blockedAccounts = new HashSet<string>(StringComparer.Ordinal);
var confirmed = await client.FlushDataBatchesAsync(queue, config, blockedAccounts);
Assert.Empty(confirmed);
Assert.Contains("account-a", blockedAccounts);
Assert.Empty(queue.Pending());
Assert.Empty(new RemoteDataBatchQueue(Path.Combine(directory.FullName, "queue.json")).Pending());
Assert.Equal(1, handler.BatchRequests);
}
finally
{
directory.Delete(true);
}
}
[Fact]
public async Task MissingRemoteCredentialsAreRejectedWithoutNetworkAccess()
{
@@ -345,6 +390,38 @@ public sealed class RemoteControlClientTests
Assert.Empty(handler.Requests);
}
private static RemoteSyncBatch SyncBatch(string batchId) => new(
"node-1", "account-a", batchId, "generation-a", "messages", 1, "{}", "{\"chat-a\\u001fmessage/a.db\":1}",
"hash-" + batchId, "complete", [],
[new RemoteSyncMessage(batchId, "chat-a", ReportingChatType.Private, batchId, "incoming", "text", "queued secret",
DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, "wx-1", "payload-" + batchId)]);
private sealed class RevokedBatchHandler : HttpMessageHandler
{
public int BatchRequests { get; private set; }
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.RequestUri!.AbsolutePath == "/v1/data/batches")
{
BatchRequests++;
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Forbidden)
{
Content = new StringContent("{\"error\":{\"code\":\"AccountNotAuthorized\",\"message\":\"revoked\"}}", Encoding.UTF8, "application/json")
});
}
if (request.RequestUri.AbsolutePath == "/v1/nodes/register")
{
var body = JsonSerializer.Serialize(new RemoteNodeRegistrationResponse("node-1", RemoteNodeStatus.Online, true, "register"), RemoteJson.Options);
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(body, Encoding.UTF8, "application/json")
});
}
throw new InvalidOperationException("Unexpected request " + request.RequestUri.AbsolutePath);
}
}
private sealed class RecordingHandler : HttpMessageHandler
{
public List<RecordedRequest> Requests { get; } = [];