matrix-mcp: auto-join only on accounts that live on the swarm homeserver
This commit is contained in:
parent
852d98e8dd
commit
513530ae35
2 changed files with 53 additions and 10 deletions
|
|
@ -35,10 +35,12 @@ is what an agent reads at call time. They fall into four groups:
|
||||||
`join_room`. `resolve_invite` accepts or rejects a pending invite and is the
|
`join_room`. `resolve_invite` accepts or rejects a pending invite and is the
|
||||||
path for invites; `join_room` is for a public room the agent wasn't invited to.
|
path for invites; `join_room` is for a public room the agent wasn't invited to.
|
||||||
`list_invites` gives the inviter and the topic, so an agent can judge an
|
`list_invites` gives the inviter and the topic, so an agent can judge an
|
||||||
invite before it answers. The daemon joins an invite from a user of the
|
invite before it answers. On an account that lives on the swarm
|
||||||
agent's own homeserver by itself, with an info log line and no todo. An invite
|
homeserver, the daemon joins an invite from a user of that same server by itself,
|
||||||
from any other homeserver stays a todo that names the inviter as an external
|
with an info log line and no todo. Every other invite stays a todo that names the
|
||||||
matrix user, and the agent decides. If an automatic join fails, the invite
|
inviter as an external matrix user, and the agent decides: that covers invites from
|
||||||
|
other homeservers and every invite to a linked account on a public server such as
|
||||||
|
matrix.org. If an automatic join fails, the invite
|
||||||
falls back to a todo too.
|
falls back to a todo too.
|
||||||
- **Receipts:** `mark_read` advances the read receipt.
|
- **Receipts:** `mark_read` advances the read receipt.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ pub async fn sweep_invites<S: BuildHasher>(
|
||||||
// from the unread sweep's `<room>` key) so the two never collide; the
|
// from the unread sweep's `<room>` key) so the two never collide; the
|
||||||
// harness coalesces the wake iff the summary is new or changed, so
|
// harness coalesces the wake iff the summary is new or changed, so
|
||||||
// re-upserting an unchanged invite every tick is a cheap no-op.
|
// re-upserting an unchanged invite every tick is a cheap no-op.
|
||||||
let own_server = client.user_id().map(|u| u.server_name().to_owned());
|
let own_server = swarm_server(client);
|
||||||
for room in ¤t {
|
for room in ¤t {
|
||||||
let room_id = room.room_id().to_owned();
|
let room_id = room.room_id().to_owned();
|
||||||
let inviter = room
|
let inviter = room
|
||||||
|
|
@ -139,8 +139,8 @@ pub async fn sweep_invites<S: BuildHasher>(
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|d| d.inviter)
|
.and_then(|d| d.inviter)
|
||||||
.map(|m| m.user_id().to_owned());
|
.map(|m| m.user_id().to_owned());
|
||||||
// An invite from the swarm's own homeserver is trusted: join it
|
// An invite from a user of the swarm's own homeserver, to an account
|
||||||
// without waking the agent. A failed join falls through to the todo
|
// that lives on it, is trusted: join it without waking the agent. A failed join falls through to the todo
|
||||||
// below, so the invite is never lost.
|
// below, so the invite is never lost.
|
||||||
if is_local(own_server.as_deref(), inviter.as_deref()) {
|
if is_local(own_server.as_deref(), inviter.as_deref()) {
|
||||||
match room.join().await {
|
match room.join().await {
|
||||||
|
|
@ -179,8 +179,25 @@ pub async fn sweep_invites<S: BuildHasher>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether `inviter` is a user of the agent's own homeserver. No known
|
/// The swarm homeserver's server name when `client` is an account on it, else
|
||||||
/// inviter or no known own server counts as external.
|
/// `None`. The swarm homeserver is the daemon-wide `HIVE_MATRIX_URL`; a linked
|
||||||
|
/// account on another server (matrix.org, catgirl.cloud, ...) is not on it, so
|
||||||
|
/// its same-server inviters are strangers, not swarm members.
|
||||||
|
fn swarm_server(client: &Client) -> Option<matrix_sdk::ruma::OwnedServerName> {
|
||||||
|
let swarm = reqwest::Url::parse(&crate::paths::homeserver_url()?).ok()?;
|
||||||
|
same_homeserver(&swarm, &client.homeserver())
|
||||||
|
.then(|| client.user_id().map(|u| u.server_name().to_owned()))
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether two homeserver URLs point at the same host and port.
|
||||||
|
fn same_homeserver(a: &reqwest::Url, b: &reqwest::Url) -> bool {
|
||||||
|
a.host_str() == b.host_str() && a.port_or_known_default() == b.port_or_known_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether `inviter` is a user of the swarm homeserver `own` (the account's
|
||||||
|
/// server when the account is on it, else `None`). No known inviter or no
|
||||||
|
/// swarm server counts as external.
|
||||||
fn is_local(own: Option<&ServerName>, inviter: Option<&UserId>) -> bool {
|
fn is_local(own: Option<&ServerName>, inviter: Option<&UserId>) -> bool {
|
||||||
matches!((own, inviter), (Some(own), Some(u)) if u.server_name() == own)
|
matches!((own, inviter), (Some(own), Some(u)) if u.server_name() == own)
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +214,7 @@ pub(crate) fn invite_key(room_id: &matrix_sdk::ruma::RoomId) -> String {
|
||||||
mod invite_tests {
|
mod invite_tests {
|
||||||
use matrix_sdk::ruma::{server_name, user_id};
|
use matrix_sdk::ruma::{server_name, user_id};
|
||||||
|
|
||||||
use super::is_local;
|
use super::{is_local, same_homeserver};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn only_an_inviter_on_the_own_homeserver_is_local() {
|
fn only_an_inviter_on_the_own_homeserver_is_local() {
|
||||||
|
|
@ -216,4 +233,28 @@ mod invite_tests {
|
||||||
assert!(!is_local(Some(own), None));
|
assert!(!is_local(Some(own), None));
|
||||||
assert!(!is_local(None, Some(user_id!("@mara:pr1ma.darkest.space"))));
|
assert!(!is_local(None, Some(user_id!("@mara:pr1ma.darkest.space"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn an_account_is_on_the_swarm_homeserver_only_by_host_and_port() {
|
||||||
|
let u = |s: &str| reqwest::Url::parse(s).expect("url");
|
||||||
|
let swarm = u("https://matrix.pr1ma.darkest.space");
|
||||||
|
assert!(same_homeserver(
|
||||||
|
&swarm,
|
||||||
|
&u("https://matrix.pr1ma.darkest.space/")
|
||||||
|
));
|
||||||
|
assert!(same_homeserver(
|
||||||
|
&swarm,
|
||||||
|
&u("https://matrix.pr1ma.darkest.space:443")
|
||||||
|
));
|
||||||
|
// A public server is a different host, and a different port is a
|
||||||
|
// different server.
|
||||||
|
assert!(!same_homeserver(&swarm, &u("https://catgirl.cloud")));
|
||||||
|
assert!(!same_homeserver(
|
||||||
|
&swarm,
|
||||||
|
&u("https://matrix.pr1ma.darkest.space:8448")
|
||||||
|
));
|
||||||
|
// An external account gets no swarm server, so even its own
|
||||||
|
// same-server inviter is not local.
|
||||||
|
assert!(!is_local(None, Some(user_id!("@x:catgirl.cloud"))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue