1 Commits
Author SHA1 Message Date
clawbot 63d87a372e Enforce channel keys, invites, +s and +H on the IRC listener (closes #122)
check / check (push) Waiting to run
The IRC listener's JOIN now passes the channel key on, INVITE records
the invite, LIST and WHOIS hide +s channels from non-members with the
queries the HTTP API uses, and PRIVMSG to a +H channel gets 404.

The INVITE checks and the channel hashcash check move from the HTTP
handlers into the service, so both transports run the same code. An IRC
client cannot attach a stamp, so its PRIVMSG to a +H channel always
fails that check. The unfiltered channel list query, now unused, is
removed.

Model: opus-5-5
2026-10-20 13:47:38 +00:00
+81 -11
View File
@@ -1240,7 +1240,9 @@ func TestIntegrationModeModerated(t *testing.T) {
}
// TestIntegrationModeKey checks that JOIN takes the key of
// a +k channel as its second parameter.
// a +k channel as its second parameter, and that with
// several channels each key goes to the channel in the
// same position.
func TestIntegrationModeKey(t *testing.T) {
t.Parallel()
@@ -1249,19 +1251,25 @@ func TestIntegrationModeKey(t *testing.T) {
alice := env.dial(t)
alice.register("alice")
alice.joinAndDrain("#keyed")
alice.joinAndDrain("#vault")
// The IRC listener cannot set +k, so the test sets the
// key directly.
chID, err := env.database.GetChannelByName(
t.Context(), "#keyed",
)
if err != nil {
t.Fatalf("get channel: %v", err)
}
// keys directly.
for channel, key := range map[string]string{
"#keyed": "sesame",
"#vault": "barley",
} {
chID, err := env.database.GetChannelByName(
t.Context(), channel,
)
if err != nil {
t.Fatalf("get channel %s: %v", channel, err)
}
err = env.database.SetChannelKey(t.Context(), chID, "sesame")
if err != nil {
t.Fatalf("set channel key: %v", err)
err = env.database.SetChannelKey(t.Context(), chID, key)
if err != nil {
t.Fatalf("set key of %s: %v", channel, err)
}
}
bob := env.dial(t)
@@ -1274,6 +1282,16 @@ func TestIntegrationModeKey(t *testing.T) {
assertContains(
t, lines, "JOIN #keyed", "bob joins with the key",
)
carol := env.dial(t)
carol.register("carol")
lines = carol.sendAndExpect(
"JOIN #keyed,#vault sesame,barley", " 366 carol #vault ",
)
assertContains(t, lines, "JOIN #keyed", "carol joins #keyed")
assertContains(t, lines, "JOIN #vault", "carol joins #vault")
assertNotContains(t, lines, " 475 ", "carol's JOIN")
}
// TestIntegrationModeInviteOnly checks that INVITE lets a
@@ -1301,6 +1319,58 @@ func TestIntegrationModeInviteOnly(t *testing.T) {
)
}
// TestIntegrationModeInviteOnlyNeedsOperator checks that on
// a +i channel an INVITE from a member who is not a channel
// operator gets 482 and does not let the invited user in.
func TestIntegrationModeInviteOnlyNeedsOperator(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
alice := env.dial(t)
alice.register("alice")
alice.joinAndDrain("#invonly")
// Bob joins before +i is set, so he is a member but
// not an operator.
bob := env.dial(t)
bob.register("bob")
bob.joinAndDrain("#invonly")
alice.sendAndExpect("MODE #invonly +i", "+i")
carol := env.dial(t)
carol.register("carol")
lines := bob.sendAndExpect("INVITE carol #invonly", " 482 ")
assertContains(
t, lines, " 482 bob #invonly ", "bob's INVITE refused",
)
carol.sendAndExpect("JOIN #invonly", " 473 ")
}
// TestIntegrationInviteUserOnChannel checks that inviting a
// user who is already on the channel gets 443.
func TestIntegrationInviteUserOnChannel(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
alice := env.dial(t)
alice.register("alice")
alice.joinAndDrain("#room")
bob := env.dial(t)
bob.register("bob")
bob.joinAndDrain("#room")
lines := alice.sendAndExpect("INVITE bob #room", " 443 ")
assertContains(
t, lines, " 443 alice bob #room ", "alice's INVITE refused",
)
}
// TestIntegrationModeSecretList checks that LIST shows a +s
// channel only to its members.
func TestIntegrationModeSecretList(t *testing.T) {