feat(server)!: require a logged-in account for every channel request

Drop unauthenticated guests from channels: the allow_guests setting, the
/guests endpoints and the guest seat table are gone, and requests without an
account session get 401 account_required. Share links are unaffected.
Contract version is now 8.0.0.
This commit is contained in:
justme committed 2026-10-16 06:46:15 +08:00
1 parent b1ac11e1d7
commit c858bd2a09
31 files changed
+194 -358

No files matched your search

@@ -20,7 +20,8 @@ public class StreamingTests(ServerFixture s) : IClassFixture<ServerFixture>
ws.Options.AddSubProtocol($"wd.role.{who.Role}");
if (who.Token is not null) ws.Options.AddSubProtocol($"wd.auth.{B64(who.Token)}");
ws.Options.AddSubProtocol($"wd.ep.{B64(who.EndpointSecret)}");
if (who.Role == "owner") ws.Options.AddSubProtocol($"wd.account.{B64(Convert.FromBase64String(s.OwnerAccountToken))}");
string account = who.AccountToken ?? (who.Role == "owner" ? s.OwnerAccountToken : s.MemberAccountToken);
ws.Options.AddSubProtocol($"wd.account.{B64(Convert.FromBase64String(account))}");
var uri = new UriBuilder(s.BaseUri) { Scheme = "ws", Path = $"/api/v1/channels/{ch.Id}/ws", Query = $"from={from}" }.Uri;
using var cts = new CancellationTokenSource(Timeout);
@@ -106,23 +107,22 @@ public class StreamingTests(ServerFixture s) : IClassFixture<ServerFixture>
var ex = await Assert.ThrowsAsync<WebSocketException>(() => ConnectWs(ch, bad));
Assert.Contains("401", ex.Message);
// 未入席
// 未入席(已登录,但还不是成员)
var stranger = new Who("write", ch.WriteToken, new byte[32]);
var ex2 = await Assert.ThrowsAsync<WebSocketException>(() => ConnectWs(ch, stranger));
Assert.Contains("403", ex2.Message);
}
[Fact]
public async Task WebSocketNotifiesSeatChangesAndClosesWhenRevoked()
public async Task WebSocketNotifiesMemberChangesAndClosesWhenRevoked()
{
var ch = await s.CreateAsync();
await s.JoinAsync(ch, ch.Member);
string memberHash = B64(System.Security.Cryptography.SHA256.HashData(ch.Member.EndpointSecret));
using var member = await ConnectWs(ch, ch.Member);
// 所有者释放席位:订阅者先收到 seats 控制消息,随后被以 not_joined 关闭
using var release = await s.Http.SendAsync(s.Req(HttpMethod.Delete, $"/api/v1/channels/{ch.Id}/guests/{memberHash}", ch.Owner));
// 所有者撤销成员:订阅者先收到 members 控制消息,随后被以 not_joined 关闭
using var release = await s.Http.SendAsync(s.Req(HttpMethod.Delete, $"/api/v1/channels/{ch.Id}/invitations/fixture_member", ch.Owner));
Assert.Equal(HttpStatusCode.NoContent, release.StatusCode);
bool closed = false;