From 63de18010ec1d33998e8bfdc6901490d2f714807 Mon Sep 17 00:00:00 2001 From: mikoto Date: Sat, 24 Feb 2024 08:01:44 +0000 Subject: [PATCH 01/10] WIP --- Cargo.toml | 20 +- crates/matrix/Cargo.toml | 8 +- .../src/admin/membership/joined_room.rs | 0 crates/matrix/src/admin/mod.rs | 3 +- crates/matrix/src/admin/resources/mod.rs | 3 - crates/matrix/src/admin/resources/user.rs | 269 ----------------- .../matrix/src/admin/{resources => }/room.rs | 0 crates/matrix/src/admin/session.rs | 0 .../admin/threepid/get_user_for_threepid.rs | 0 crates/matrix/src/admin/user.rs | 271 ++++++++++++++++++ crates/matrix/src/admin/user/get_user.rs | 11 + crates/matrix/src/admin/user/get_users.rs | 0 crates/matrix/src/admin/user/set_user.rs | 0 .../src/client/{resources => }/error.rs | 0 .../src/client/{resources => }/events.rs | 0 .../src/client/{resources => }/login.rs | 0 crates/matrix/src/client/mod.rs | 7 +- .../matrix/src/client/{resources => }/mxc.rs | 0 crates/matrix/src/client/resources/mod.rs | 6 - .../matrix/src/client/{resources => }/room.rs | 0 .../src/client/{resources => }/session.rs | 0 crates/matrix/src/error.rs | 7 - crates/matrix/src/http.rs | 177 ------------ crates/matrix/src/lib.rs | 28 +- .../src/{admin/resources => }/token/mod.rs | 0 .../resources => }/token/shared_secret.rs | 0 crates/server/Cargo.toml | 3 + 27 files changed, 318 insertions(+), 495 deletions(-) create mode 100644 crates/matrix/src/admin/membership/joined_room.rs delete mode 100644 crates/matrix/src/admin/resources/mod.rs delete mode 100644 crates/matrix/src/admin/resources/user.rs rename crates/matrix/src/admin/{resources => }/room.rs (100%) create mode 100644 crates/matrix/src/admin/session.rs create mode 100644 crates/matrix/src/admin/threepid/get_user_for_threepid.rs create mode 100644 crates/matrix/src/admin/user.rs create mode 100644 crates/matrix/src/admin/user/get_user.rs create mode 100644 crates/matrix/src/admin/user/get_users.rs create mode 100644 crates/matrix/src/admin/user/set_user.rs rename crates/matrix/src/client/{resources => }/error.rs (100%) rename crates/matrix/src/client/{resources => }/events.rs (100%) rename crates/matrix/src/client/{resources => }/login.rs (100%) rename crates/matrix/src/client/{resources => }/mxc.rs (100%) delete mode 100644 crates/matrix/src/client/resources/mod.rs rename crates/matrix/src/client/{resources => }/room.rs (100%) rename crates/matrix/src/client/{resources => }/session.rs (100%) delete mode 100644 crates/matrix/src/error.rs delete mode 100644 crates/matrix/src/http.rs rename crates/matrix/src/{admin/resources => }/token/mod.rs (100%) rename crates/matrix/src/{admin/resources => }/token/shared_secret.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 5fee52d..d14f1f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,15 @@ [workspace] -members = [ - "crates/core", - "crates/matrix", - "crates/server", - "crates/test" -] +members = ["crates/core", "crates/matrix", "crates/server", "crates/test"] default-members = ["crates/server"] resolver = "1" [workspace.dependencies] +async-trait = "0.1.74" +hex = "0.4.3" +hmac = "0.12.1" +serde_path_to_error = "0.1.14" +serde_qs = "0.12.0" +sha1 = "0.10.6" anyhow = "1.0.75" axum = { version = "0.7.4", features = ["tokio"] } chrono = { version = "0.4.34", features = ["serde"] } @@ -17,7 +18,12 @@ http = "0.2.11" mime = "0.3.17" openssl = { version = "0.10.63", features = ["vendored"] } openssl-sys = { version = "0.9.99", features = ["vendored"] } -reqwest = { version = "0.11.22", default-features = false, features = ["blocking", "json", "rustls", "multipart"] } +reqwest = { version = "0.11.22", default-features = false, features = [ + "blocking", + "json", + "rustls", + "multipart", +] } serde = "1.0.192" serde_json = "1.0.108" tokio = "1.34.0" diff --git a/crates/matrix/Cargo.toml b/crates/matrix/Cargo.toml index 27796bc..4304525 100644 --- a/crates/matrix/Cargo.toml +++ b/crates/matrix/Cargo.toml @@ -5,14 +5,8 @@ edition = "2021" publish = false [dependencies] -async-trait = "0.1.74" -hex = "0.4.3" -hmac = "0.12.1" -serde_path_to_error = "0.1.14" -serde_qs = "0.12.0" -sha1 = "0.10.6" ruma-events = { version = "0.27.11", features = ["html", "markdown"] } -ruma-common = { version = "0.12.1", features = ["rand"] } +ruma-common = { version = "0.12.0", features = ["api", "rand"], default_features = false } ruma-macros = "0.12.0" # Workspace Dependencies diff --git a/crates/matrix/src/admin/membership/joined_room.rs b/crates/matrix/src/admin/membership/joined_room.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/matrix/src/admin/mod.rs b/crates/matrix/src/admin/mod.rs index 6d0fe39..5fb5809 100644 --- a/crates/matrix/src/admin/mod.rs +++ b/crates/matrix/src/admin/mod.rs @@ -1 +1,2 @@ -pub mod resources; +// pub mod room; +mod user; diff --git a/crates/matrix/src/admin/resources/mod.rs b/crates/matrix/src/admin/resources/mod.rs deleted file mode 100644 index c29990d..0000000 --- a/crates/matrix/src/admin/resources/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod room; -pub mod token; -pub mod user; diff --git a/crates/matrix/src/admin/resources/user.rs b/crates/matrix/src/admin/resources/user.rs deleted file mode 100644 index e5fc0da..0000000 --- a/crates/matrix/src/admin/resources/user.rs +++ /dev/null @@ -1,269 +0,0 @@ -//! [User Admin API](https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#user-admin-api) -//! -//! To use it, you will need to authenticate by providing an `access_token` -//! for a server admin: see Admin API. - -use anyhow::Result; -use ruma_common::UserId; -use serde::{Deserialize, Serialize}; -use tracing::instrument; -use url::Url; - -use crate::{error::MatrixError, http::Client}; - -#[derive(Default)] -pub struct UserService; - -#[derive(Debug, Serialize, Deserialize)] -pub struct ExternalId { - pub auth_provider: String, - pub external_id: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ThreePid { - pub medium: String, - pub address: String, - pub added_at: u64, - pub validated_at: u64, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct User { - /// User name postfixed with Matrix instance Host - /// E.g. `@user:example.com` - pub name: String, - pub displayname: Option, - pub threepids: Vec, - pub avatar_url: Option, - pub is_guest: bool, - pub admin: bool, - pub deactivated: bool, - pub erased: bool, - pub shadow_banned: bool, - pub creation_ts: u64, - pub appservice_id: Option, - pub consent_server_notice_sent: Option, - pub consent_version: Option, - pub consent_ts: Option, - pub external_ids: Vec, - pub user_type: Option, - pub locked: bool, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct CreateUserBody { - pub password: String, - pub logout_devices: bool, - pub displayname: Option, - pub avatar_url: Option, - pub threepids: Vec, - pub external_ids: Vec, - pub admin: bool, - pub deactivated: bool, - pub user_type: Option, - pub locked: bool, -} - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct ListUsersQuery { - pub user_id: Option, - pub name: Option, - pub guests: Option, - pub admins: Option, - pub deactivated: Option, - pub limit: Option, - pub from: Option, -} - -/// Data type for the response of the `GET /_synapse/admin/v2/users` endpoint. -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct ListUser { - pub name: String, - pub user_type: Option, - pub is_guest: usize, - pub admin: usize, - pub deactivated: usize, - pub shadow_banned: bool, - pub avatar_url: Option, - pub creation_ts: u64, - pub last_seen_ts: Option, - pub erased: bool, - pub locked: bool, -} - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct ListUsersResponse { - pub users: Vec, - pub total: u64, - #[serde(default)] - pub next_token: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct UpdateUserBody { - pub password: String, - pub logout_devices: bool, - pub displayname: Option, - pub avatar_url: Option, - pub threepids: Vec, - pub external_ids: Vec, - pub admin: bool, - pub deactivated: bool, - pub user_type: Option, - pub locked: bool, -} - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct LoginAsUserBody { - pub valid_until_ms: Option, -} - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct LoginAsUserResponse { - pub access_token: String, -} - -pub struct QueryUserDataResponse { - pub name: String, - pub displayname: Option, - pub threepids: Vec, - pub avatar_url: Option, - pub is_guest: bool, - pub admin: bool, - pub deactivated: bool, - pub erased: bool, - pub shadow_banned: bool, - pub creation_ts: i64, - pub appservice_id: Option, - pub consent_server_notice_sent: Option, - pub consent_version: Option, - pub consent_ts: Option, - pub external_ids: Vec>, - pub user_type: Option, -} - -impl UserService { - /// This API returns information about a specific user account. - /// - /// Refer: https://matrix-org.github.io/synapse/v1.88/admin_api/user_admin_api.html#query-user-account - #[instrument(skip(client))] - pub async fn query_user_account(client: &Client, user_id: &UserId) -> Result { - let resp = client - .get(format!( - "/_synapse/admin/v2/users/{user_id}", - user_id = user_id - )) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// Allows an administrator to create a user account. - /// - /// Note that internally Synapse uses this same endpoint to modify an - /// existing user account, so this method will modify the existing user - /// if [`UserId`] matches. - /// - /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#create-or-modify-account - #[instrument(skip(client, body))] - pub async fn create(client: &Client, user_id: &UserId, body: CreateUserBody) -> Result { - let resp = client - .put_json( - format!("/_synapse/admin/v2/users/{user_id}", user_id = user_id), - &body, - ) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// Returns all local user accounts. By default, the response is ordered by - /// ascending user ID. - /// - /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#list-accounts - #[instrument(skip(client))] - pub async fn list(client: &Client, query: ListUsersQuery) -> Result { - let resp = client.get_query("/_synapse/admin/v2/users", &query).await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// Allows an administrator to modify a user account - /// - /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#create-or-modify-account - #[instrument(skip(client))] - pub async fn update(client: &Client, user_id: &UserId, body: UpdateUserBody) -> Result { - let resp = client - .put_json( - format!("/_synapse/admin/v2/users/{user_id}", user_id = user_id), - &body, - ) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// **Note: This API is disabled when MSC3861 is enabled. [See #15582][1]** - /// - /// Get an access token that can be used to authenticate as that user. - /// Useful for when admins wish to do actions on behalf of a user. - /// - /// An optional `valid_until_ms` field can be specified in the request body - /// as an integer timestamp that specifies when the token should expire. - /// - /// **By default tokens do not expire. Note that this API does not allow a - /// user to login as themselves (to create more tokens).** - /// - /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#login-as-a-user - /// - /// [1]: https://github.com/matrix-org/synapse/pull/15582 - #[instrument(skip(client))] - pub async fn login_as_user( - client: &Client, - user_id: &UserId, - body: LoginAsUserBody, - ) -> Result { - let resp = client - .post_json( - format!( - "/_synapse/admin/v1/users/{user_id}/login", - user_id = user_id - ), - &body, - ) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } -} diff --git a/crates/matrix/src/admin/resources/room.rs b/crates/matrix/src/admin/room.rs similarity index 100% rename from crates/matrix/src/admin/resources/room.rs rename to crates/matrix/src/admin/room.rs diff --git a/crates/matrix/src/admin/session.rs b/crates/matrix/src/admin/session.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/matrix/src/admin/threepid/get_user_for_threepid.rs b/crates/matrix/src/admin/threepid/get_user_for_threepid.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/matrix/src/admin/user.rs b/crates/matrix/src/admin/user.rs new file mode 100644 index 0000000..905b02f --- /dev/null +++ b/crates/matrix/src/admin/user.rs @@ -0,0 +1,271 @@ +//! [User Admin API](https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#user-admin-api) +//! +//! To use it, you will need to authenticate by providing an `access_token` +//! for a server admin: see Admin API. + +mod get_user; +mod get_users; +mod set_user; + +//use anyhow::Result; +//use ruma_common::UserId; +//use serde::{Deserialize, Serialize}; +//use tracing::instrument; +//use url::Url; + +//#[derive(Default)] +//pub struct UserService; + +//#[derive(Debug, Serialize, Deserialize)] +//pub struct ExternalId { +// pub auth_provider: String, +// pub external_id: String, +//} + +//#[derive(Debug, Serialize, Deserialize)] +//pub struct ThreePid { +// pub medium: String, +// pub address: String, +// pub added_at: u64, +// pub validated_at: u64, +//} + +//#[derive(Debug, Serialize, Deserialize)] +//pub struct User { +// /// User name postfixed with Matrix instance Host +// /// E.g. `@user:example.com` +// pub name: String, +// pub displayname: Option, +// pub threepids: Vec, +// pub avatar_url: Option, +// pub is_guest: bool, +// pub admin: bool, +// pub deactivated: bool, +// pub erased: bool, +// pub shadow_banned: bool, +// pub creation_ts: u64, +// pub appservice_id: Option, +// pub consent_server_notice_sent: Option, +// pub consent_version: Option, +// pub consent_ts: Option, +// pub external_ids: Vec, +// pub user_type: Option, +// pub locked: bool, +//} + +//#[derive(Debug, Serialize, Deserialize)] +//pub struct CreateUserBody { +// pub password: String, +// pub logout_devices: bool, +// pub displayname: Option, +// pub avatar_url: Option, +// pub threepids: Vec, +// pub external_ids: Vec, +// pub admin: bool, +// pub deactivated: bool, +// pub user_type: Option, +// pub locked: bool, +//} + +//#[derive(Debug, Default, Serialize, Deserialize)] +//pub struct ListUsersQuery { +// pub user_id: Option, +// pub name: Option, +// pub guests: Option, +// pub admins: Option, +// pub deactivated: Option, +// pub limit: Option, +// pub from: Option, +//} + +///// Data type for the response of the `GET /_synapse/admin/v2/users` endpoint. +//#[derive(Debug, Default, Serialize, Deserialize)] +//pub struct ListUser { +// pub name: String, +// pub user_type: Option, +// pub is_guest: usize, +// pub admin: usize, +// pub deactivated: usize, +// pub shadow_banned: bool, +// pub avatar_url: Option, +// pub creation_ts: u64, +// pub last_seen_ts: Option, +// pub erased: bool, +// pub locked: bool, +//} + +//#[derive(Debug, Default, Serialize, Deserialize)] +//pub struct ListUsersResponse { +// pub users: Vec, +// pub total: u64, +// #[serde(default)] +// pub next_token: Option, +//} + +//#[derive(Debug, Serialize, Deserialize)] +//pub struct UpdateUserBody { +// pub password: String, +// pub logout_devices: bool, +// pub displayname: Option, +// pub avatar_url: Option, +// pub threepids: Vec, +// pub external_ids: Vec, +// pub admin: bool, +// pub deactivated: bool, +// pub user_type: Option, +// pub locked: bool, +//} + +//#[derive(Debug, Default, Serialize, Deserialize)] +//pub struct LoginAsUserBody { +// pub valid_until_ms: Option, +//} + +//#[derive(Debug, Default, Serialize, Deserialize)] +//pub struct LoginAsUserResponse { +// pub access_token: String, +//} + +//pub struct QueryUserDataResponse { +// pub name: String, +// pub displayname: Option, +// pub threepids: Vec, +// pub avatar_url: Option, +// pub is_guest: bool, +// pub admin: bool, +// pub deactivated: bool, +// pub erased: bool, +// pub shadow_banned: bool, +// pub creation_ts: i64, +// pub appservice_id: Option, +// pub consent_server_notice_sent: Option, +// pub consent_version: Option, +// pub consent_ts: Option, +// pub external_ids: Vec>, +// pub user_type: Option, +//} + +//impl UserService { +// /// This API returns information about a specific user account. +// /// +// /// Refer: https://matrix-org.github.io/synapse/v1.88/admin_api/user_admin_api.html#query-user-account +// #[instrument(skip(client))] +// pub async fn query_user_account(client: &Client, user_id: &UserId) -> Result { +// let resp = client +// .get(format!( +// "/_synapse/admin/v2/users/{user_id}", +// user_id = user_id +// )) +// .await?; + +// if resp.status().is_success() { +// return Ok(resp.json().await?); +// } + +// let error = resp.json::().await?; + +// Err(anyhow::anyhow!(error.error)) +// } + +// /// Allows an administrator to create a user account. +// /// +// /// Note that internally Synapse uses this same endpoint to modify an +// /// existing user account, so this method will modify the existing user +// /// if [`UserId`] matches. +// /// +// /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#create-or-modify-account +// #[instrument(skip(client, body))] +// pub async fn create(client: &Client, user_id: &UserId, body: CreateUserBody) -> Result { +// let resp = client +// .put_json( +// format!("/_synapse/admin/v2/users/{user_id}", user_id = user_id), +// &body, +// ) +// .await?; + +// if resp.status().is_success() { +// return Ok(resp.json().await?); +// } + +// let error = resp.json::().await?; + +// Err(anyhow::anyhow!(error.error)) +// } + +// /// Returns all local user accounts. By default, the response is ordered by +// /// ascending user ID. +// /// +// /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#list-accounts +// #[instrument(skip(client))] +// pub async fn list(client: &Client, query: ListUsersQuery) -> Result { +// let resp = client.get_query("/_synapse/admin/v2/users", &query).await?; + +// if resp.status().is_success() { +// return Ok(resp.json().await?); +// } + +// let error = resp.json::().await?; + +// Err(anyhow::anyhow!(error.error)) +// } + +// /// Allows an administrator to modify a user account +// /// +// /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#create-or-modify-account +// #[instrument(skip(client))] +// pub async fn update(client: &Client, user_id: &UserId, body: UpdateUserBody) -> Result { +// let resp = client +// .put_json( +// format!("/_synapse/admin/v2/users/{user_id}", user_id = user_id), +// &body, +// ) +// .await?; + +// if resp.status().is_success() { +// return Ok(resp.json().await?); +// } + +// let error = resp.json::().await?; + +// Err(anyhow::anyhow!(error.error)) +// } + +// /// **Note: This API is disabled when MSC3861 is enabled. [See #15582][1]** +// /// +// /// Get an access token that can be used to authenticate as that user. +// /// Useful for when admins wish to do actions on behalf of a user. +// /// +// /// An optional `valid_until_ms` field can be specified in the request body +// /// as an integer timestamp that specifies when the token should expire. +// /// +// /// **By default tokens do not expire. Note that this API does not allow a +// /// user to login as themselves (to create more tokens).** +// /// +// /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#login-as-a-user +// /// +// /// [1]: https://github.com/matrix-org/synapse/pull/15582 +// #[instrument(skip(client))] +// pub async fn login_as_user( +// client: &Client, +// user_id: &UserId, +// body: LoginAsUserBody, +// ) -> Result { +// let resp = client +// .post_json( +// format!( +// "/_synapse/admin/v1/users/{user_id}/login", +// user_id = user_id +// ), +// &body, +// ) +// .await?; + +// if resp.status().is_success() { +// return Ok(resp.json().await?); +// } + +// let error = resp.json::().await?; + +// Err(anyhow::anyhow!(error.error)) +// } +//} diff --git a/crates/matrix/src/admin/user/get_user.rs b/crates/matrix/src/admin/user/get_user.rs new file mode 100644 index 0000000..c306f4e --- /dev/null +++ b/crates/matrix/src/admin/user/get_user.rs @@ -0,0 +1,11 @@ +use ruma_common::OwnedUserId; +use ruma_macros::request; + + + + +#[request(error = crate::Error)] +pub struct Request { + #[ruma_api(query)] + pub user_id: OwnedUserId, +} diff --git a/crates/matrix/src/admin/user/get_users.rs b/crates/matrix/src/admin/user/get_users.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/matrix/src/admin/user/set_user.rs b/crates/matrix/src/admin/user/set_user.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/matrix/src/client/resources/error.rs b/crates/matrix/src/client/error.rs similarity index 100% rename from crates/matrix/src/client/resources/error.rs rename to crates/matrix/src/client/error.rs diff --git a/crates/matrix/src/client/resources/events.rs b/crates/matrix/src/client/events.rs similarity index 100% rename from crates/matrix/src/client/resources/events.rs rename to crates/matrix/src/client/events.rs diff --git a/crates/matrix/src/client/resources/login.rs b/crates/matrix/src/client/login.rs similarity index 100% rename from crates/matrix/src/client/resources/login.rs rename to crates/matrix/src/client/login.rs diff --git a/crates/matrix/src/client/mod.rs b/crates/matrix/src/client/mod.rs index 6d0fe39..43420e9 100644 --- a/crates/matrix/src/client/mod.rs +++ b/crates/matrix/src/client/mod.rs @@ -1 +1,6 @@ -pub mod resources; +pub mod error; +pub mod events; +pub mod login; +pub mod mxc; +pub mod room; +pub mod session; diff --git a/crates/matrix/src/client/resources/mxc.rs b/crates/matrix/src/client/mxc.rs similarity index 100% rename from crates/matrix/src/client/resources/mxc.rs rename to crates/matrix/src/client/mxc.rs diff --git a/crates/matrix/src/client/resources/mod.rs b/crates/matrix/src/client/resources/mod.rs deleted file mode 100644 index 43420e9..0000000 --- a/crates/matrix/src/client/resources/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod error; -pub mod events; -pub mod login; -pub mod mxc; -pub mod room; -pub mod session; diff --git a/crates/matrix/src/client/resources/room.rs b/crates/matrix/src/client/room.rs similarity index 100% rename from crates/matrix/src/client/resources/room.rs rename to crates/matrix/src/client/room.rs diff --git a/crates/matrix/src/client/resources/session.rs b/crates/matrix/src/client/session.rs similarity index 100% rename from crates/matrix/src/client/resources/session.rs rename to crates/matrix/src/client/session.rs diff --git a/crates/matrix/src/error.rs b/crates/matrix/src/error.rs deleted file mode 100644 index 31d6786..0000000 --- a/crates/matrix/src/error.rs +++ /dev/null @@ -1,7 +0,0 @@ -use serde::Deserialize; - -#[derive(Clone, Debug, Deserialize)] -pub struct MatrixError { - pub errcode: String, - pub error: String, -} diff --git a/crates/matrix/src/http.rs b/crates/matrix/src/http.rs deleted file mode 100644 index 22db5c8..0000000 --- a/crates/matrix/src/http.rs +++ /dev/null @@ -1,177 +0,0 @@ -use std::str::from_utf8; - -use anyhow::{bail, Result}; -use reqwest::{ - header::{HeaderMap, HeaderValue, AUTHORIZATION}, - Client as HttpClient, Response, -}; -use serde::Serialize; -use url::Url; - -#[derive(Clone, Debug)] -pub struct Client { - client: HttpClient, - base_url: Url, - token: Option, - server_name: String, -} - -impl Client { - pub fn new>(url: S, server_name: S) -> Result { - let url = Url::parse(url.as_ref())?; - let server_name = server_name.as_ref().to_string(); - - Ok(Self { - client: HttpClient::new(), - base_url: url, - token: None, - server_name, - }) - } - - #[inline] - pub fn server_name(&self) -> &str { - &self.server_name - } - - /// Sets the token to be used for authentication with the server. - pub fn set_token(&mut self, token: impl Into) -> Result<()> { - let token = token.into(); - - if token.is_empty() { - self.token = None; - bail!("Token cannot be empty"); - } - - self.token = Some(token); - Ok(()) - } - - /// Clear the token for safety purposes. - pub fn clear_token(&mut self) { - self.token = None; - } - - pub async fn get(&self, path: impl AsRef) -> Result { - let url = self.build_url(path)?; - let headers = self.build_headers()?; - let response = self.client.get(url).headers(headers).send().await?; - - Ok(response) - } - - pub async fn get_query( - &self, - path: impl AsRef, - params: impl Serialize, - ) -> Result { - let url = self.build_url_with_params(path, params)?; - let headers = self.build_headers()?; - let response = self.client.get(url).headers(headers).send().await?; - - Ok(response) - } - - pub async fn put_json(&self, path: impl AsRef, body: &T) -> Result - where - T: Serialize, - { - let url = self.build_url(path)?; - let headers = self.build_headers()?; - let resp = self - .client - .put(url) - .json(body) - .headers(headers) - .send() - .await?; - - Ok(resp) - } - - pub async fn post(&self, path: impl AsRef) -> Result { - let url = self.build_url(path)?; - let headers = self.build_headers()?; - let resp = self.client.post(url).headers(headers).send().await?; - - Ok(resp) - } - - pub async fn post_json(&self, path: impl AsRef, body: &T) -> Result - where - T: Serialize, - { - let url = self.build_url(path)?; - let headers = self.build_headers()?; - let resp = self - .client - .post(url) - .json(body) - .headers(headers) - .send() - .await?; - - Ok(resp) - } - - pub async fn delete(&self, path: impl AsRef) -> Result { - let url = self.build_url(path)?; - let headers = self.build_headers()?; - let response = self.client.delete(url).headers(headers).send().await?; - - Ok(response) - } - - pub async fn delete_json(&self, path: impl AsRef, body: &T) -> Result - where - T: Serialize, - { - let url = self.build_url(path)?; - let headers = self.build_headers()?; - let resp = self - .client - .delete(url) - .json(body) - .headers(headers) - .send() - .await?; - - Ok(resp) - } - - fn build_headers(&self) -> Result { - let mut headers = HeaderMap::new(); - - if let Some(token) = &self.token { - headers.insert( - AUTHORIZATION, - HeaderValue::from_str(&format!("Bearer {}", token))?, - ); - } - - Ok(headers) - } - - #[inline] - fn build_url(&self, path: impl AsRef) -> Result { - let mut next = self.base_url.clone(); - - next.set_path(path.as_ref()); - - Ok(next) - } - - fn build_url_with_params(&self, path: impl AsRef, params: impl Serialize) -> Result { - let mut url = self.build_url(path)?; - let mut buff = Vec::new(); - let qs_ser = &mut serde_qs::Serializer::new(&mut buff); - - serde_path_to_error::serialize(¶ms, qs_ser)?; - - let params = from_utf8(buff.as_slice())?.to_string(); - - url.set_query(Some(¶ms)); - - Ok(url) - } -} diff --git a/crates/matrix/src/lib.rs b/crates/matrix/src/lib.rs index 0afb5f3..2d6ce8a 100644 --- a/crates/matrix/src/lib.rs +++ b/crates/matrix/src/lib.rs @@ -2,25 +2,19 @@ //! //! Reexports `matrix_sdk` and provides implementations on Matrix Admin API. -mod http; +// mod http; -mod error; +// pub mod filter; -pub mod filter; +// pub use http::Client; -pub use http::Client; - -/// Implementation on the Administrator API of Matrix -/// -/// Refer: https://matrix-org.github.io/synapse/latest/usage/administration/index.html -pub mod admin; +/// Ruma re-exports +// pub use ruma_common; +// pub use ruma_events; -/// Implementation on the Client API of Matrix -/// -/// Different to the Matrix SDK, no user state is kept in the Client instance, -/// this is equivalent to making cURL requests to the Matrix server. -pub mod client; +// mod api { +mod admin; + // mod client; +// } -/// Ruma re-exports -pub use ruma_common; -pub use ruma_events; +use ruma_common::api::error::MatrixError as Error; diff --git a/crates/matrix/src/admin/resources/token/mod.rs b/crates/matrix/src/token/mod.rs similarity index 100% rename from crates/matrix/src/admin/resources/token/mod.rs rename to crates/matrix/src/token/mod.rs diff --git a/crates/matrix/src/admin/resources/token/shared_secret.rs b/crates/matrix/src/token/shared_secret.rs similarity index 100% rename from crates/matrix/src/admin/resources/token/shared_secret.rs rename to crates/matrix/src/token/shared_secret.rs diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index 3dae89d..1f9348d 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -33,3 +33,6 @@ uuid = { workspace = true, features= ["serde"] } core = { path = "../core" } chrono = { version = "0.4.34", features = ["serde"] } mime = { git = "https://github.com/hyperium/mime", version = "0.4.0-a.0" } +ruma-api = "0.20.1" +ruma-common = { version = "0.12.1", default-features = false, features = ["api", "rand"] } +api = "0.2.0" From cd39d608a61d2ad6f91bc17021e3532705aa2ddf Mon Sep 17 00:00:00 2001 From: mikoto Date: Sat, 24 Feb 2024 16:37:33 +0000 Subject: [PATCH 02/10] feat: Ruma API macro to clear up HTTP boilerplate --- Cargo.toml | 2 +- crates/matrix/Cargo.toml | 6 +- crates/matrix/src/admin/user.rs | 274 +--------------------- crates/matrix/src/admin/user/get_user.rs | 59 ++++- crates/matrix/src/admin/user/get_users.rs | 97 ++++++++ crates/matrix/src/lib.rs | 20 +- 6 files changed, 162 insertions(+), 296 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d14f1f1..ea335e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] members = ["crates/core", "crates/matrix", "crates/server", "crates/test"] default-members = ["crates/server"] -resolver = "1" +resolver = "2" [workspace.dependencies] async-trait = "0.1.74" diff --git a/crates/matrix/Cargo.toml b/crates/matrix/Cargo.toml index 4304525..68b8b70 100644 --- a/crates/matrix/Cargo.toml +++ b/crates/matrix/Cargo.toml @@ -6,7 +6,7 @@ publish = false [dependencies] ruma-events = { version = "0.27.11", features = ["html", "markdown"] } -ruma-common = { version = "0.12.0", features = ["api", "rand"], default_features = false } +ruma-common = { version = "0.12.0", default_features = false, features = ["api", "rand"] } ruma-macros = "0.12.0" # Workspace Dependencies @@ -18,3 +18,7 @@ serde = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } url = { workspace = true, features = ["serde"] } + +[features] +client = [] +server = [] diff --git a/crates/matrix/src/admin/user.rs b/crates/matrix/src/admin/user.rs index 905b02f..eb59f60 100644 --- a/crates/matrix/src/admin/user.rs +++ b/crates/matrix/src/admin/user.rs @@ -1,271 +1,3 @@ -//! [User Admin API](https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#user-admin-api) -//! -//! To use it, you will need to authenticate by providing an `access_token` -//! for a server admin: see Admin API. - -mod get_user; -mod get_users; -mod set_user; - -//use anyhow::Result; -//use ruma_common::UserId; -//use serde::{Deserialize, Serialize}; -//use tracing::instrument; -//use url::Url; - -//#[derive(Default)] -//pub struct UserService; - -//#[derive(Debug, Serialize, Deserialize)] -//pub struct ExternalId { -// pub auth_provider: String, -// pub external_id: String, -//} - -//#[derive(Debug, Serialize, Deserialize)] -//pub struct ThreePid { -// pub medium: String, -// pub address: String, -// pub added_at: u64, -// pub validated_at: u64, -//} - -//#[derive(Debug, Serialize, Deserialize)] -//pub struct User { -// /// User name postfixed with Matrix instance Host -// /// E.g. `@user:example.com` -// pub name: String, -// pub displayname: Option, -// pub threepids: Vec, -// pub avatar_url: Option, -// pub is_guest: bool, -// pub admin: bool, -// pub deactivated: bool, -// pub erased: bool, -// pub shadow_banned: bool, -// pub creation_ts: u64, -// pub appservice_id: Option, -// pub consent_server_notice_sent: Option, -// pub consent_version: Option, -// pub consent_ts: Option, -// pub external_ids: Vec, -// pub user_type: Option, -// pub locked: bool, -//} - -//#[derive(Debug, Serialize, Deserialize)] -//pub struct CreateUserBody { -// pub password: String, -// pub logout_devices: bool, -// pub displayname: Option, -// pub avatar_url: Option, -// pub threepids: Vec, -// pub external_ids: Vec, -// pub admin: bool, -// pub deactivated: bool, -// pub user_type: Option, -// pub locked: bool, -//} - -//#[derive(Debug, Default, Serialize, Deserialize)] -//pub struct ListUsersQuery { -// pub user_id: Option, -// pub name: Option, -// pub guests: Option, -// pub admins: Option, -// pub deactivated: Option, -// pub limit: Option, -// pub from: Option, -//} - -///// Data type for the response of the `GET /_synapse/admin/v2/users` endpoint. -//#[derive(Debug, Default, Serialize, Deserialize)] -//pub struct ListUser { -// pub name: String, -// pub user_type: Option, -// pub is_guest: usize, -// pub admin: usize, -// pub deactivated: usize, -// pub shadow_banned: bool, -// pub avatar_url: Option, -// pub creation_ts: u64, -// pub last_seen_ts: Option, -// pub erased: bool, -// pub locked: bool, -//} - -//#[derive(Debug, Default, Serialize, Deserialize)] -//pub struct ListUsersResponse { -// pub users: Vec, -// pub total: u64, -// #[serde(default)] -// pub next_token: Option, -//} - -//#[derive(Debug, Serialize, Deserialize)] -//pub struct UpdateUserBody { -// pub password: String, -// pub logout_devices: bool, -// pub displayname: Option, -// pub avatar_url: Option, -// pub threepids: Vec, -// pub external_ids: Vec, -// pub admin: bool, -// pub deactivated: bool, -// pub user_type: Option, -// pub locked: bool, -//} - -//#[derive(Debug, Default, Serialize, Deserialize)] -//pub struct LoginAsUserBody { -// pub valid_until_ms: Option, -//} - -//#[derive(Debug, Default, Serialize, Deserialize)] -//pub struct LoginAsUserResponse { -// pub access_token: String, -//} - -//pub struct QueryUserDataResponse { -// pub name: String, -// pub displayname: Option, -// pub threepids: Vec, -// pub avatar_url: Option, -// pub is_guest: bool, -// pub admin: bool, -// pub deactivated: bool, -// pub erased: bool, -// pub shadow_banned: bool, -// pub creation_ts: i64, -// pub appservice_id: Option, -// pub consent_server_notice_sent: Option, -// pub consent_version: Option, -// pub consent_ts: Option, -// pub external_ids: Vec>, -// pub user_type: Option, -//} - -//impl UserService { -// /// This API returns information about a specific user account. -// /// -// /// Refer: https://matrix-org.github.io/synapse/v1.88/admin_api/user_admin_api.html#query-user-account -// #[instrument(skip(client))] -// pub async fn query_user_account(client: &Client, user_id: &UserId) -> Result { -// let resp = client -// .get(format!( -// "/_synapse/admin/v2/users/{user_id}", -// user_id = user_id -// )) -// .await?; - -// if resp.status().is_success() { -// return Ok(resp.json().await?); -// } - -// let error = resp.json::().await?; - -// Err(anyhow::anyhow!(error.error)) -// } - -// /// Allows an administrator to create a user account. -// /// -// /// Note that internally Synapse uses this same endpoint to modify an -// /// existing user account, so this method will modify the existing user -// /// if [`UserId`] matches. -// /// -// /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#create-or-modify-account -// #[instrument(skip(client, body))] -// pub async fn create(client: &Client, user_id: &UserId, body: CreateUserBody) -> Result { -// let resp = client -// .put_json( -// format!("/_synapse/admin/v2/users/{user_id}", user_id = user_id), -// &body, -// ) -// .await?; - -// if resp.status().is_success() { -// return Ok(resp.json().await?); -// } - -// let error = resp.json::().await?; - -// Err(anyhow::anyhow!(error.error)) -// } - -// /// Returns all local user accounts. By default, the response is ordered by -// /// ascending user ID. -// /// -// /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#list-accounts -// #[instrument(skip(client))] -// pub async fn list(client: &Client, query: ListUsersQuery) -> Result { -// let resp = client.get_query("/_synapse/admin/v2/users", &query).await?; - -// if resp.status().is_success() { -// return Ok(resp.json().await?); -// } - -// let error = resp.json::().await?; - -// Err(anyhow::anyhow!(error.error)) -// } - -// /// Allows an administrator to modify a user account -// /// -// /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#create-or-modify-account -// #[instrument(skip(client))] -// pub async fn update(client: &Client, user_id: &UserId, body: UpdateUserBody) -> Result { -// let resp = client -// .put_json( -// format!("/_synapse/admin/v2/users/{user_id}", user_id = user_id), -// &body, -// ) -// .await?; - -// if resp.status().is_success() { -// return Ok(resp.json().await?); -// } - -// let error = resp.json::().await?; - -// Err(anyhow::anyhow!(error.error)) -// } - -// /// **Note: This API is disabled when MSC3861 is enabled. [See #15582][1]** -// /// -// /// Get an access token that can be used to authenticate as that user. -// /// Useful for when admins wish to do actions on behalf of a user. -// /// -// /// An optional `valid_until_ms` field can be specified in the request body -// /// as an integer timestamp that specifies when the token should expire. -// /// -// /// **By default tokens do not expire. Note that this API does not allow a -// /// user to login as themselves (to create more tokens).** -// /// -// /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#login-as-a-user -// /// -// /// [1]: https://github.com/matrix-org/synapse/pull/15582 -// #[instrument(skip(client))] -// pub async fn login_as_user( -// client: &Client, -// user_id: &UserId, -// body: LoginAsUserBody, -// ) -> Result { -// let resp = client -// .post_json( -// format!( -// "/_synapse/admin/v1/users/{user_id}/login", -// user_id = user_id -// ), -// &body, -// ) -// .await?; - -// if resp.status().is_success() { -// return Ok(resp.json().await?); -// } - -// let error = resp.json::().await?; - -// Err(anyhow::anyhow!(error.error)) -// } -//} +pub mod get_user; +pub mod get_users; +pub mod set_user; diff --git a/crates/matrix/src/admin/user/get_user.rs b/crates/matrix/src/admin/user/get_user.rs index c306f4e..f19b5b1 100644 --- a/crates/matrix/src/admin/user/get_user.rs +++ b/crates/matrix/src/admin/user/get_user.rs @@ -1,11 +1,58 @@ -use ruma_common::OwnedUserId; -use ruma_macros::request; - - +use ruma_common::{ + api::{request, response, Metadata}, + metadata, + thirdparty::ThirdPartyIdentifier, + OwnedMxcUri, OwnedUserId, +}; +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: GET, + rate_limited: false, + authentication: AccessToken, + history: { + unstable => "/_synapse/admin/v2/users/:name", + } +}; #[request(error = crate::Error)] pub struct Request { - #[ruma_api(query)] - pub user_id: OwnedUserId, + #[ruma_api(path)] + pub name: OwnedUserId, +} + +#[response(error = crate::Error)] +pub struct Response { + pub name: OwnedUserId, + + pub displayname: Option, + + pub threepids: Vec, + + pub avatar_url: Option, + + pub admin: bool, + + pub deactivated: bool, + + pub erased: bool, + + pub shadow_banned: bool, + + pub creation_ts: u64, + + pub consent_server_notice_sent: Option, + + pub consent_ts: Option, + + pub external_ids: Vec, + + pub locked: bool, +} + +#[derive(Clone, Debug)] +pub struct ExternalId { + pub auth_provider: String, + + pub external_id: String, } diff --git a/crates/matrix/src/admin/user/get_users.rs b/crates/matrix/src/admin/user/get_users.rs index e69de29..eeab703 100644 --- a/crates/matrix/src/admin/user/get_users.rs +++ b/crates/matrix/src/admin/user/get_users.rs @@ -0,0 +1,97 @@ +use ruma_common::{ + api::{request, response, Metadata, Direction}, + thirdparty::ThirdPartyIdentifier, + OwnedMxcUri, OwnedUserId, metadata, +}; +use ruma_macros::{request, response}; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: GET, + rate_limited: false, + authentication: AccessToken, + history: { + unstable => "/_synapse/admin/v2/users", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[serde(skip_serializing_if = "Option::is_none")] + #[ruma_api(query)] + pub user_id: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[ruma_api(query)] + pub name: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[ruma_api(query)] + pub admins: Option, + + #[serde(skip_serializing_if = "ruma_common::serde::is_default")] + #[ruma_api(query)] + pub deactivated: bool, + + #[serde(skip_serializing_if = "Option::is_none")] + #[ruma_api(query)] + pub limit: Option, + + #[serde(skip_serializing_if = "ruma_common::serde::is_default")] + #[ruma_api(query)] + pub from: u64, + + #[serde(skip_serializing_if = "ruma_common::serde::is_default")] + #[ruma_api(query)] + pub order_by: OrderBy, + + #[serde(skip_serializing_if = "ruma_common::serde::is_default")] + #[ruma_api(query)] + pub dir: Direction, +} + +#[response(error = crate::Error)] +pub struct Response { + pub name: OwnedUserId, + + pub displayname: Option, + + pub threepids: Vec, + + pub avatar_url: Option, + + pub admin: bool, + + pub deactivated: bool, + + pub erased: bool, + + pub shadow_banned: bool, + + pub creation_ts: u64, + + pub locked: bool, +} + +#[derive(Clone, Debug, Default)] +#[allow(dead_code)] +pub enum OrderBy { + #[default] + Name, + + Admin, + + UserType, + + Deactivated, + + ShadowBanned, + + Displayname, + + AvatarUrl, + + CreationTs, + + LastSeenTs, +} diff --git a/crates/matrix/src/lib.rs b/crates/matrix/src/lib.rs index 2d6ce8a..16bf545 100644 --- a/crates/matrix/src/lib.rs +++ b/crates/matrix/src/lib.rs @@ -1,20 +1,6 @@ -//! Crate to centralize all Matrix dependencies. -//! -//! Reexports `matrix_sdk` and provides implementations on Matrix Admin API. - -// mod http; - +pub mod admin; +pub mod client; // pub mod filter; -// pub use http::Client; - -/// Ruma re-exports -// pub use ruma_common; -// pub use ruma_events; - -// mod api { -mod admin; - // mod client; -// } - +#[allow(unused_imports)] use ruma_common::api::error::MatrixError as Error; From 71b2d7cbca3e79dfebda6d4816bdb3568fb12eb7 Mon Sep 17 00:00:00 2001 From: mikoto Date: Sat, 24 Feb 2024 16:38:00 +0000 Subject: [PATCH 03/10] chore: removing bad imports --- crates/server/Cargo.toml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index 1f9348d..4917232 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -20,8 +20,8 @@ axum = { workspace = true, features = ["tokio"] } anyhow = { workspace = true } dotenv = { workspace = true } http = { workspace = true } -openssl = { workspace = true, features = ["vendored"] } -openssl-sys = { workspace = true, features = ["vendored"] } +# openssl = { workspace = true, features = ["vendored"] } +# openssl-sys = { workspace = true, features = ["vendored"] } serde = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] } tracing = { workspace = true } @@ -32,7 +32,3 @@ uuid = { workspace = true, features= ["serde"] } # Local Dependencies core = { path = "../core" } chrono = { version = "0.4.34", features = ["serde"] } -mime = { git = "https://github.com/hyperium/mime", version = "0.4.0-a.0" } -ruma-api = "0.20.1" -ruma-common = { version = "0.12.1", default-features = false, features = ["api", "rand"] } -api = "0.2.0" From b76417a31b91b250def3117b12c040fadd2b0adf Mon Sep 17 00:00:00 2001 From: mikoto Date: Sat, 24 Feb 2024 17:03:35 +0000 Subject: [PATCH 04/10] feat: reuse `User` type across all requests --- crates/matrix/src/admin/user.rs | 45 +++++++++++++++++++++++ crates/matrix/src/admin/user/get_user.rs | 44 ++++------------------ crates/matrix/src/admin/user/get_users.rs | 29 ++++----------- crates/matrix/src/admin/user/set_user.rs | 28 ++++++++++++++ 4 files changed, 88 insertions(+), 58 deletions(-) diff --git a/crates/matrix/src/admin/user.rs b/crates/matrix/src/admin/user.rs index eb59f60..20275af 100644 --- a/crates/matrix/src/admin/user.rs +++ b/crates/matrix/src/admin/user.rs @@ -1,3 +1,48 @@ +use ruma_common::{thirdparty::ThirdPartyIdentifier, OwnedMxcUri, OwnedUserId}; +use serde::{Deserialize, Serialize}; + pub mod get_user; pub mod get_users; pub mod set_user; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct User { + #[serde(rename = "name")] + pub user_id: OwnedUserId, + + pub displayname: Option, + + pub avatar_url: Option, + + pub threepids: Vec, + + pub external_ids: Vec, + + pub admin: bool, + + pub deactivated: bool, + + #[serde(skip_serializing)] + pub erased: bool, + + #[serde(skip_serializing)] + pub shadow_banned: bool, + + #[serde(skip_serializing)] + pub creation_ts: u64, + + #[serde(skip_serializing)] + pub consent_server_notice_sent: Option, + + #[serde(skip_serializing)] + pub consent_ts: Option, + + pub locked: bool, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ExternalId { + pub auth_provider: String, + + pub external_id: String, +} diff --git a/crates/matrix/src/admin/user/get_user.rs b/crates/matrix/src/admin/user/get_user.rs index f19b5b1..da302be 100644 --- a/crates/matrix/src/admin/user/get_user.rs +++ b/crates/matrix/src/admin/user/get_user.rs @@ -1,58 +1,28 @@ use ruma_common::{ api::{request, response, Metadata}, - metadata, - thirdparty::ThirdPartyIdentifier, - OwnedMxcUri, OwnedUserId, + metadata, OwnedUserId, }; +use super::User; + #[allow(dead_code)] const METADATA: Metadata = metadata! { method: GET, rate_limited: false, authentication: AccessToken, history: { - unstable => "/_synapse/admin/v2/users/:name", + unstable => "/_synapse/admin/v2/users/:user_id", } }; #[request(error = crate::Error)] pub struct Request { #[ruma_api(path)] - pub name: OwnedUserId, + pub user_id: OwnedUserId, } #[response(error = crate::Error)] pub struct Response { - pub name: OwnedUserId, - - pub displayname: Option, - - pub threepids: Vec, - - pub avatar_url: Option, - - pub admin: bool, - - pub deactivated: bool, - - pub erased: bool, - - pub shadow_banned: bool, - - pub creation_ts: u64, - - pub consent_server_notice_sent: Option, - - pub consent_ts: Option, - - pub external_ids: Vec, - - pub locked: bool, -} - -#[derive(Clone, Debug)] -pub struct ExternalId { - pub auth_provider: String, - - pub external_id: String, + #[ruma_api(body)] + pub user: User, } diff --git a/crates/matrix/src/admin/user/get_users.rs b/crates/matrix/src/admin/user/get_users.rs index eeab703..263fc66 100644 --- a/crates/matrix/src/admin/user/get_users.rs +++ b/crates/matrix/src/admin/user/get_users.rs @@ -1,9 +1,9 @@ use ruma_common::{ - api::{request, response, Metadata, Direction}, - thirdparty::ThirdPartyIdentifier, - OwnedMxcUri, OwnedUserId, metadata, + api::{request, response, Direction, Metadata}, + metadata, OwnedUserId, }; -use ruma_macros::{request, response}; + +use super::User; #[allow(dead_code)] const METADATA: Metadata = metadata! { @@ -52,25 +52,12 @@ pub struct Request { #[response(error = crate::Error)] pub struct Response { - pub name: OwnedUserId, - - pub displayname: Option, - - pub threepids: Vec, - - pub avatar_url: Option, - - pub admin: bool, - - pub deactivated: bool, - - pub erased: bool, - - pub shadow_banned: bool, + #[serde(flatten)] + users: Vec, - pub creation_ts: u64, + next_token: String, - pub locked: bool, + total: u64, } #[derive(Clone, Debug, Default)] diff --git a/crates/matrix/src/admin/user/set_user.rs b/crates/matrix/src/admin/user/set_user.rs index e69de29..83868f1 100644 --- a/crates/matrix/src/admin/user/set_user.rs +++ b/crates/matrix/src/admin/user/set_user.rs @@ -0,0 +1,28 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedUserId, +}; + +use super::User; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: PUT, + rate_limited: false, + authentication: AccessToken, + history: { + unstable => "/_synapse/admin/v2/users/:user_id", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[ruma_api(path)] + pub user_id: OwnedUserId, + + #[ruma_api(body)] + pub user: User, +} + +#[response(error = crate::Error)] +pub struct Response {} From e366ed68a75d46287aa48af3b6fd3264b1c0e10b Mon Sep 17 00:00:00 2001 From: mikoto Date: Sun, 25 Feb 2024 14:06:57 +0000 Subject: [PATCH 05/10] feat: admin migration complete, client wip --- crates/matrix/src/admin/mod.rs | 3 +- crates/matrix/src/admin/room.rs | 485 ++------------- crates/matrix/src/admin/room/delete_room.rs | 49 ++ .../forward_extremities/delete.rs} | 0 .../forward_extremities/get.rs} | 0 crates/matrix/src/admin/room/get_members.rs | 27 + crates/matrix/src/admin/room/get_room.rs | 28 + crates/matrix/src/admin/room/get_rooms.rs | 83 +++ crates/matrix/src/admin/room/get_state.rs | 36 ++ crates/matrix/src/admin/user.rs | 5 + .../matrix/src/admin/user/get_user_by_3pid.rs | 31 + crates/matrix/src/admin/user/get_users.rs | 1 - crates/matrix/src/client/room.rs | 555 ++++++++---------- .../room/ban_from_room.rs} | 0 crates/matrix/src/client/room/create_room.rs | 70 +++ crates/matrix/src/client/room/forget_room.rs | 0 .../matrix/src/client/room/invite_to_room.rs | 0 crates/matrix/src/client/room/join_room.rs | 0 .../matrix/src/client/room/kick_from_room.rs | 0 crates/matrix/src/client/room/leave_room.rs | 0 .../matrix/src/client/room/unban_from_room.rs | 0 crates/matrix/src/filter.rs | 212 ------- crates/matrix/src/lib.rs | 1 - 23 files changed, 619 insertions(+), 967 deletions(-) create mode 100644 crates/matrix/src/admin/room/delete_room.rs rename crates/matrix/src/admin/{membership/joined_room.rs => room/forward_extremities/delete.rs} (100%) rename crates/matrix/src/admin/{session.rs => room/forward_extremities/get.rs} (100%) create mode 100644 crates/matrix/src/admin/room/get_members.rs create mode 100644 crates/matrix/src/admin/room/get_room.rs create mode 100644 crates/matrix/src/admin/room/get_rooms.rs create mode 100644 crates/matrix/src/admin/room/get_state.rs create mode 100644 crates/matrix/src/admin/user/get_user_by_3pid.rs rename crates/matrix/src/{admin/threepid/get_user_for_threepid.rs => client/room/ban_from_room.rs} (100%) create mode 100644 crates/matrix/src/client/room/create_room.rs create mode 100644 crates/matrix/src/client/room/forget_room.rs create mode 100644 crates/matrix/src/client/room/invite_to_room.rs create mode 100644 crates/matrix/src/client/room/join_room.rs create mode 100644 crates/matrix/src/client/room/kick_from_room.rs create mode 100644 crates/matrix/src/client/room/leave_room.rs create mode 100644 crates/matrix/src/client/room/unban_from_room.rs delete mode 100644 crates/matrix/src/filter.rs diff --git a/crates/matrix/src/admin/mod.rs b/crates/matrix/src/admin/mod.rs index 5fb5809..2a77d65 100644 --- a/crates/matrix/src/admin/mod.rs +++ b/crates/matrix/src/admin/mod.rs @@ -1,2 +1,3 @@ -// pub mod room; mod user; +mod room; +// mod room; diff --git a/crates/matrix/src/admin/room.rs b/crates/matrix/src/admin/room.rs index 9b153c5..17117b9 100644 --- a/crates/matrix/src/admin/room.rs +++ b/crates/matrix/src/admin/room.rs @@ -1,474 +1,59 @@ -//! [Room Admin API](https://matrix-org.github.io/synapse/latest/admin_api/rooms.html) +//! reference: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html //! -//! To use it, you will need to authenticate by providing an `access_token` -//! for a server admin: see Admin API. - -use anyhow::Result; -use ruma_common::{serde::Raw, EventId, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId}; -use ruma_events::{AnyMessageLikeEvent, AnyStateEvent, AnyTimelineEvent}; -use serde::{Deserialize, Serialize}; -use tracing::instrument; - -use crate::{error::MatrixError, filter::RoomEventFilter, http::Client}; - -#[derive(Default)] -pub struct RoomService; - -#[derive(Default, Debug, Serialize)] -pub struct ListRoomQuery { - #[serde(skip_serializing_if = "Option::is_none")] - pub from: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub limit: Option, - - pub order_by: OrderBy, - - pub direction: Direction, - - #[serde(skip_serializing_if = "String::is_empty")] - pub search_term: String, -} - -#[derive(Debug, Default, Serialize)] -pub struct MessagesQuery { - #[serde(skip_serializing_if = "String::is_empty")] - pub from: String, - - #[serde(skip_serializing_if = "String::is_empty")] - pub to: String, - - #[serde(skip_serializing_if = "Option::is_none")] - pub limit: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub filter: Option, - - pub direction: Direction, -} +//! This module contains handlers for managing rooms. + +use ruma_common::{ + room::RoomType, EventEncryptionAlgorithm, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, + OwnedUserId, RoomVersionId, +}; +use ruma_events::room::{history_visibility::HistoryVisibility, join_rules::JoinRule}; +use serde::Deserialize; + +mod delete_room; +mod get_members; +pub mod get_room; +pub mod get_rooms; +mod get_state; + +#[derive(Clone, Debug, Deserialize)] +pub struct Room { + pub room_id: OwnedRoomId, -#[derive(Default, Debug, Serialize)] -pub struct TimestampToEventQuery { - #[serde(skip_serializing_if = "Option::is_none")] - pub ts: Option, + pub canonical_alias: Option, - pub direction: Direction, -} + pub avatar: Option, -#[derive(Default, Debug, Serialize)] -pub struct EventContextQuery { - #[serde(skip_serializing_if = "Option::is_none")] - pub limit: Option, + pub name: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub filter: Option, -} + pub joined_members: u64, -#[derive(Debug, Serialize)] -pub struct ReplaceRoomQuery { - #[serde(rename = "new_room_user_id")] - pub admin: OwnedUserId, + pub joined_local_members: u64, - #[serde(skip_serializing_if = "String::is_empty")] - pub room_name: String, + pub version: RoomVersionId, - #[serde(skip_serializing_if = "String::is_empty")] - pub message: String, -} + pub creator: OwnedUserId, -#[derive(Default, Debug, Serialize)] -pub struct DeleteQuery { - #[serde(flatten, skip_serializing_if = "Option::is_none")] - pub new_room: Option, + pub encryption: Option, - pub block: bool, + pub federatable: bool, - pub purge: bool, -} + pub public: bool, -#[derive(Debug, Deserialize)] -pub struct ListRoomResponse { - pub rooms: Vec, - pub offset: Option, - pub total_rooms: Option, - pub prev_batch: Option, - pub next_batch: Option, -} + pub join_rules: Option, -#[derive(Debug, Deserialize)] -pub struct MembersResponse { - pub members: Vec, - pub total: u64, -} + pub history_visibility: Option, -#[derive(Debug, Deserialize)] -pub struct State { - #[serde(rename = "type")] - pub kind: String, - pub state_key: String, - pub etc: Option, -} + pub state_events: u64, -#[derive(Debug, Deserialize)] -pub struct StateResponse { - pub state: Vec, -} + pub room_type: Option, -#[derive(Debug, Deserialize)] -pub struct Room { - /// Room ID postfixed with Matrix instance Host - /// E.g. `!room:example.com` - pub room_id: OwnedRoomId, - pub name: Option, - pub canonical_alias: Option, - pub joined_members: u64, - pub joined_local_members: u64, - pub version: Option, - pub creator: Option, - pub encryption: Option, - pub federatable: bool, - pub public: bool, - pub join_rules: Option, - pub guest_access: Option, - pub history_visibility: Option, - pub state_events: u64, - pub room_type: Option, #[serde(flatten)] pub details: Option, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct RoomDetails { - pub avatar: Option, pub topic: Option, - pub joined_local_devices: u64, - pub forgotten: bool, -} - -#[derive(Debug, Deserialize)] -pub struct GetEventsResponse { - pub chunk: Raw>, - pub start: String, - pub end: String, - pub state: Option>, -} - -#[derive(Debug, Deserialize)] -pub struct TimestampToEventResponse { - pub event_id: String, - pub origin_server_ts: u64, -} - -#[derive(Debug, Deserialize)] -pub struct ForwardExtremities { - pub event_id: String, - pub state_group: u64, - pub depth: u64, - pub received_ts: u64, -} - -#[derive(Debug, Deserialize)] -pub struct CheckForwardExtremitiesResponse { - pub count: u64, - pub result: Vec, -} - -#[derive(Debug, Deserialize)] -pub struct DeleteForwardExtremitiesResponse { - pub deleted: u64, -} - -#[derive(Debug, Deserialize)] -pub struct EventContextResponse { - pub start: String, - pub end: String, - pub events_before: Vec>, - pub event: Raw, - pub events_after: Vec>, - pub state: Vec>, -} - -#[derive(Debug, Deserialize)] -pub struct DeleteRoomResponse { - pub kicked_users: Vec, - pub failed_to_kick_users: Vec, - pub local_aliases: Vec, - pub new_room_id: Option, -} - -impl RoomService { - /// Returns information about a specific room - /// - /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#room-details-api - #[instrument(skip(client))] - pub async fn get_one(client: &Client, room_id: &RoomId) -> Result { - let resp = client - .get(format!( - "/_synapse/admin/v1/rooms/{room_id}", - room_id = room_id - )) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// Returns all rooms. By default, the response is ordered alphabetically by - /// room name - /// - /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#list-room-api - #[instrument(skip(client))] - pub async fn get_all(client: &Client, query: ListRoomQuery) -> Result { - let resp = client.get_query("/_synapse/admin/v1/rooms", &query).await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// Allows a server admin to get a list of all members of a room - /// - /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#room-members-api - #[instrument(skip(client))] - pub async fn get_members(client: &Client, room_id: &RoomId) -> Result { - let resp = client - .get(format!( - "/_synapse/admin/v1/rooms/{room_id}/members", - room_id = room_id - )) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// Allows a server admin to get all messages sent to a room in a given - /// timeframe - /// - /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#room-messages-api - #[instrument(skip(client))] - pub async fn get_state(client: &Client, room_id: &RoomId) -> Result { - let resp = client - .get(format!( - "/_synapse/admin/v1/rooms/{room_id}/state", - room_id = room_id - )) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// Allows a server admin to get the `event_id` of the closest event to the - /// given timestamp - /// - /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#room-timestamp-to-event-api - #[instrument(skip(client))] - pub async fn get_timestamp_to_event( - client: &Client, - room_id: &RoomId, - query: TimestampToEventQuery, - ) -> Result { - let resp = client - .get_query( - format!( - "/_synapse/admin/v1/rooms/{room_id}/timestamp_to_event", - room_id = room_id - ), - &query, - ) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - /// Allows a server admin to check the status of forward extremities for a - /// room - /// - /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#check-for-forward-extremities - #[instrument(skip(client))] - pub async fn check_forward_extremities( - client: &Client, - room_id: &RoomId, - ) -> Result { - let resp = client - .get(format!( - "/_synapse/admin/v1/rooms/{room_id}/forward_extremities", - room_id = room_id - )) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// Allows a server admin to delete forward extremities for a room - /// WARNING: Please ensure you know what you're doing and read the related issue [#1760](https://github.com/matrix-org/synapse/issues/1760) - /// - /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#delete-for-forward-extremities - #[instrument(skip(client))] - pub async fn delete_forward_extremities( - client: &Client, - room_id: &RoomId, - ) -> Result { - let resp = client - .delete(format!( - "/_synapse/admin/v1/rooms/{room_id}/forward_extremities", - room_id = room_id - )) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// allows server admins to remove rooms from the server and block these - /// rooms - /// - /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#delete-room-api - #[instrument(skip(client))] - pub async fn delete_room( - client: &Client, - room_id: &RoomId, - query: DeleteQuery, - ) -> Result { - let resp = client - .delete_json( - format!("/_synapse/admin/v1/rooms/{room_id}", room_id = room_id), - &query, - ) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } -} - -impl RoomService { - /// Allows a server admin to get a list of all state events in a room - /// - /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#room-state-api - #[instrument(skip(client))] - pub async fn get_room_events( - client: &Client, - room_id: &RoomId, - query: MessagesQuery, - ) -> Result { - let resp = client - .get_query( - format!( - "/_synapse/admin/v1/rooms/{room_id}/messages", - room_id = room_id - ), - &query, - ) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// This API lets a client find the context of an event. This is designed - /// primarily to investigate abuse reports. - /// - /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#event-context-api - #[instrument(skip(client))] - pub async fn get_event_context( - client: &Client, - room_id: &RoomId, - event_id: &EventId, - query: EventContextQuery, - ) -> Result { - let resp = client - .get_query( - format!( - "/_synapse/admin/v1/rooms/{room_id}/context/{event_id}", - room_id = room_id, - event_id = event_id, - ), - &query, - ) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } -} - -#[derive(Debug, Default, Clone, Serialize)] -pub enum Direction { - #[serde(rename = "f")] - #[default] - Forward, - #[serde(rename = "b")] - Backward, -} - -#[derive(Default, Debug, Serialize)] -#[serde(rename_all = "snake_case")] -pub enum OrderBy { - #[default] - Name, - CanonicalAlias, - JoinedMembers, - JoinedLocalMembers, - Version, - Creator, - Encryption, - Federatable, - Public, - JoinRules, - GuestAccess, - HistoryVisibility, - StateEvents, + pub forgotten: bool, } diff --git a/crates/matrix/src/admin/room/delete_room.rs b/crates/matrix/src/admin/room/delete_room.rs new file mode 100644 index 0000000..2774f1e --- /dev/null +++ b/crates/matrix/src/admin/room/delete_room.rs @@ -0,0 +1,49 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedRoomId, OwnedUserId, +}; +use serde::Serialize; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: DELETE, + rate_limited: false, + authentication: AccessToken, + history: { + unstable => "/_synapse/admin/v2/rooms/:room_id", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[ruma_api(path)] + pub room_id: OwnedRoomId, + + #[serde(flatten, skip_serializing_if = "Option::is_none")] + pub new_room: Option, + + #[serde(skip_serializing_if = "ruma_common::serde::is_default")] + pub block: bool, + + #[serde(skip_serializing_if = "ruma_common::serde::is_true")] + pub purge: bool, + + #[serde(skip_serializing_if = "ruma_common::serde::is_default")] + pub force_purge: bool, +} + +#[response(error = crate::Error)] +pub struct Response { + delete_id: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct NewRoomParams { + pub creator: OwnedUserId, + + #[serde(skip_serializing_if = "String::is_empty")] + pub name: String, + + #[serde(skip_serializing_if = "String::is_empty")] + pub message: String, +} diff --git a/crates/matrix/src/admin/membership/joined_room.rs b/crates/matrix/src/admin/room/forward_extremities/delete.rs similarity index 100% rename from crates/matrix/src/admin/membership/joined_room.rs rename to crates/matrix/src/admin/room/forward_extremities/delete.rs diff --git a/crates/matrix/src/admin/session.rs b/crates/matrix/src/admin/room/forward_extremities/get.rs similarity index 100% rename from crates/matrix/src/admin/session.rs rename to crates/matrix/src/admin/room/forward_extremities/get.rs diff --git a/crates/matrix/src/admin/room/get_members.rs b/crates/matrix/src/admin/room/get_members.rs new file mode 100644 index 0000000..79cd4e7 --- /dev/null +++ b/crates/matrix/src/admin/room/get_members.rs @@ -0,0 +1,27 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedRoomId, OwnedUserId, +}; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: GET, + rate_limited: false, + authentication: AccessToken, + history: { + unstable => "/_synapse/admin/v1/rooms/:room_id/members", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[ruma_api(path)] + pub room_id: OwnedRoomId, +} + +#[response(error = crate::Error)] +pub struct Response { + pub members: Vec, + + pub total: u64, +} diff --git a/crates/matrix/src/admin/room/get_room.rs b/crates/matrix/src/admin/room/get_room.rs new file mode 100644 index 0000000..b55a66b --- /dev/null +++ b/crates/matrix/src/admin/room/get_room.rs @@ -0,0 +1,28 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedRoomId, +}; + +use super::Room; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: GET, + rate_limited: false, + authentication: AccessToken, + history: { + unstable => "/_synapse/admin/v1/rooms/:room_id", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[ruma_api(path)] + pub room_id: OwnedRoomId, +} + +#[response(error = crate::Error)] +pub struct Response { + #[ruma_api(body)] + pub room: Room, +} diff --git a/crates/matrix/src/admin/room/get_rooms.rs b/crates/matrix/src/admin/room/get_rooms.rs new file mode 100644 index 0000000..5607c2b --- /dev/null +++ b/crates/matrix/src/admin/room/get_rooms.rs @@ -0,0 +1,83 @@ +use ruma_common::{ + api::{request, response, Metadata, Direction}, + metadata, +}; +use serde::Serialize; + +use super::Room; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: GET, + rate_limited: false, + authentication: AccessToken, + history: { + unstable => "/_synapse/admin/v1/rooms", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[serde(default)] + #[ruma_api(query)] + pub from: u64, + + #[serde(skip_serializing_if = "Option::is_none")] + #[ruma_api(query)] + pub limit: Option, + + #[ruma_api(query)] + pub order_by: OrderBy, + + #[ruma_api(query)] + pub direction: Direction, + + #[serde(skip_serializing_if = "String::is_empty")] + #[ruma_api(query)] + pub search_term: String, +} + +#[response(error = crate::Error)] +pub struct Response { + rooms: Vec, + + offset: u64, + + #[serde(rename = "total_rooms")] + total: u64, + + next_batch: Option, + + prev_batch: Option, +} + +#[derive(Clone, Default, Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum OrderBy { + #[default] + Name, + + CanonicalAlias, + + JoinedMembers, + + JoinedLocalMembers, + + Version, + + Creator, + + Encryption, + + Federatable, + + Public, + + JoinRules, + + GuestAccess, + + HistoryVisibility, + + StateEvents, +} diff --git a/crates/matrix/src/admin/room/get_state.rs b/crates/matrix/src/admin/room/get_state.rs new file mode 100644 index 0000000..6cf649b --- /dev/null +++ b/crates/matrix/src/admin/room/get_state.rs @@ -0,0 +1,36 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedRoomId, +}; +use serde::Deserialize; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: GET, + rate_limited: false, + authentication: AccessToken, + history: { + unstable => "/_synapse/admin/v1/rooms/:room_id/state", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[ruma_api(path)] + pub room_id: OwnedRoomId, +} + +#[response(error = crate::Error)] +pub struct Response { + pub state: Vec, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct State { + #[serde(rename = "type")] + pub kind: String, + + pub state_key: String, + + pub etc: bool, +} diff --git a/crates/matrix/src/admin/user.rs b/crates/matrix/src/admin/user.rs index 20275af..2e39665 100644 --- a/crates/matrix/src/admin/user.rs +++ b/crates/matrix/src/admin/user.rs @@ -1,7 +1,12 @@ +//! reference: https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html +//! +//! This module contains handlers for managing user accounts. + use ruma_common::{thirdparty::ThirdPartyIdentifier, OwnedMxcUri, OwnedUserId}; use serde::{Deserialize, Serialize}; pub mod get_user; +pub mod get_user_by_3pid; pub mod get_users; pub mod set_user; diff --git a/crates/matrix/src/admin/user/get_user_by_3pid.rs b/crates/matrix/src/admin/user/get_user_by_3pid.rs new file mode 100644 index 0000000..2b9e56c --- /dev/null +++ b/crates/matrix/src/admin/user/get_user_by_3pid.rs @@ -0,0 +1,31 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, thirdparty::Medium, +}; + +use super::User; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: GET, + rate_limited: false, + authentication: AccessToken, + history: { + unstable => "/_synapse/admin/v1/threepid/:medium/users/:address", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[ruma_api(path)] + pub medium: Medium, + + #[ruma_api(path)] + pub address: String, +} + +#[response(error = crate::Error)] +pub struct Response { + #[ruma_api(body)] + pub user: User, +} diff --git a/crates/matrix/src/admin/user/get_users.rs b/crates/matrix/src/admin/user/get_users.rs index 263fc66..40a926d 100644 --- a/crates/matrix/src/admin/user/get_users.rs +++ b/crates/matrix/src/admin/user/get_users.rs @@ -52,7 +52,6 @@ pub struct Request { #[response(error = crate::Error)] pub struct Response { - #[serde(flatten)] users: Vec, next_token: String, diff --git a/crates/matrix/src/client/room.rs b/crates/matrix/src/client/room.rs index 5d51035..9c28b1c 100644 --- a/crates/matrix/src/client/room.rs +++ b/crates/matrix/src/client/room.rs @@ -1,302 +1,253 @@ -use anyhow::Result; -use ruma_common::{serde::Raw, OwnedRoomId, OwnedUserId, RoomId, RoomOrAliasId}; -use ruma_events::{room::power_levels::RoomPowerLevelsEventContent, AnyInitialStateEvent}; -use serde::{Deserialize, Serialize}; -use tracing::instrument; - -use crate::error::MatrixError; - -#[derive(Debug, Serialize)] -#[serde(rename_all = "snake_case")] -pub enum RoomPreset { - PrivateChat, - PublicChat, - TrustedPrivateChat, -} - -#[derive(Default, Debug, Serialize)] -pub struct RoomCreationContent { - #[serde(rename = "m.federate")] - pub federate: bool, -} - -#[derive(Default, Debug, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum RoomVisibility { - Public, - #[default] - Private, -} - -#[derive(Default, Debug, Serialize)] -pub struct CreateRoomBody { - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub initial_state: Vec>, - - #[serde(skip_serializing_if = "Option::is_none")] - pub creation_content: Option, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub invite: Vec, - - pub is_direct: bool, - - #[serde(skip_serializing_if = "String::is_empty")] - pub name: String, - - #[serde(skip_serializing_if = "Option::is_none")] - pub power_override: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub preset: Option, - - #[serde(skip_serializing_if = "String::is_empty")] - pub room_alias_name: String, - - #[serde(skip_serializing_if = "String::is_empty")] - pub topic: String, - - #[serde(skip_serializing_if = "Option::is_none")] - pub visibility: Option, -} - -#[derive(Default, Debug, Serialize)] -pub struct JoinRoomBody { - #[serde(skip_serializing_if = "String::is_empty")] - pub reason: String, -} - -#[derive(Default, Debug, Serialize)] -pub struct ForgetRoomBody { - #[serde(skip_serializing_if = "String::is_empty")] - pub reason: String, -} - -#[derive(Default, Debug, Serialize)] -pub struct LeaveRoomBody { - #[serde(skip_serializing_if = "String::is_empty")] - pub reason: String, -} - -#[derive(Debug, Serialize)] -pub struct RoomKickOrBanBody { - #[serde(skip_serializing_if = "String::is_empty")] - pub reason: String, - - pub user_id: OwnedUserId, -} - -#[derive(Debug, Deserialize)] -pub struct CreateRoomResponse { - pub room_id: OwnedRoomId, -} - -#[derive(Debug, Deserialize)] -pub struct JoinRoomResponse { - pub room_id: OwnedRoomId, -} - -#[derive(Debug, Deserialize)] -pub struct LeaveRoomResponse {} - -#[derive(Debug, Deserialize)] -pub struct ForgetRoomResponse {} - -#[derive(Debug, Deserialize)] -pub struct RoomKickOrBanResponse {} - -pub struct RoomService; - -impl RoomService { - /// Create a new room with various configuration options. - /// - /// Refer: https://spec.matrix.org/v1.9/client-server-api/#creation - #[instrument(skip(client, access_token))] - pub async fn create( - client: &crate::http::Client, - access_token: impl Into, - body: CreateRoomBody, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .post_json("/_matrix/client/v3/createRoom", &body) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// Join a particular room, if we are allowed to participate. - /// - /// Refer: https://spec.matrix.org/v1.9/client-server-api/#joining-rooms - #[instrument(skip(client, access_token))] - pub async fn join( - client: &crate::http::Client, - access_token: impl Into, - alias_or_id: &RoomOrAliasId, - body: JoinRoomBody, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .post_json(format!("/_matrix/client/v3/join/{alias_or_id}"), &body) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// Leave a particular room. - /// They are still allowed to retrieve the history which they were - /// previously allowed to see. - /// - /// Refer: https://spec.matrix.org/v1.9/client-server-api/#leaving-rooms - #[instrument(skip(client, access_token))] - pub async fn leave( - client: &crate::http::Client, - access_token: impl Into, - room_id: &RoomId, - body: LeaveRoomBody, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .post_json(format!("/_matrix/client/v3/rooms/{room_id}/leave"), &body) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// Forget a particular room. - /// This will prevent the user from accessing the history of the room. - /// - /// Refer: https://spec.matrix.org/v1.9/client-server-api/#leaving-rooms - #[instrument(skip(client, access_token))] - pub async fn forget( - client: &crate::http::Client, - access_token: impl Into, - room_id: &RoomId, - body: ForgetRoomBody, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .post_json(format!("/_matrix/client/v3/rooms/{room_id}/forget"), &body) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// Kick a user from a particular room. - /// The caller must have the required power level in order to perform this - /// operation. - /// - /// Refer: https://spec.matrix.org/v1.9/client-server-api/#leaving-rooms - #[instrument(skip(client, access_token))] - pub async fn kick( - client: &crate::http::Client, - access_token: impl Into, - room_id: &RoomId, - body: RoomKickOrBanBody, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .post_json(format!("/_matrix/client/v3/rooms/{room_id}/kick"), &body) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// Ban a user from a particular room. - /// This will kick them too if they are still a member. - /// The caller must have the required power level in order to perform this - /// operation. - /// - /// Refer: https://spec.matrix.org/v1.9/client-server-api/#leaving-rooms - #[instrument(skip(client, access_token))] - pub async fn ban( - client: &crate::http::Client, - access_token: impl Into, - room_id: &RoomId, - body: RoomKickOrBanBody, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .post_json(format!("/_matrix/client/v3/rooms/{room_id}/ban"), &body) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// Unban a user from a particular room. - /// This will allow them to re-join or be re-invited. - /// The caller must have the required power level in order to perform this - /// operation. - /// - /// Refer: https://spec.matrix.org/v1.9/client-server-api/#banning-users-in-a-room - #[instrument(skip(client, access_token))] - pub async fn unban( - client: &crate::http::Client, - access_token: impl Into, - room_id: &RoomId, - body: RoomKickOrBanBody, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .post_json(format!("/_matrix/client/v3/rooms/{room_id}/unban"), &body) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } -} +mod create_room; +mod get_room; +mod join_room; +mod forget_room; +mod leave_room; +mod kick_from_room; +mod ban_from_room; +mod unban_from_room; + +//#[derive(Default, Debug, Serialize)] +//pub struct CreateRoomBody { +//} + +//#[derive(Default, Debug, Serialize)] +//pub struct JoinRoomBody { +// #[serde(skip_serializing_if = "String::is_empty")] +// pub reason: String, +//} + +//#[derive(Default, Debug, Serialize)] +//pub struct ForgetRoomBody { +// #[serde(skip_serializing_if = "String::is_empty")] +// pub reason: String, +//} + +//#[derive(Default, Debug, Serialize)] +//pub struct LeaveRoomBody { +// #[serde(skip_serializing_if = "String::is_empty")] +// pub reason: String, +//} + +//#[derive(Debug, Serialize)] +//pub struct RoomKickOrBanBody { +// #[serde(skip_serializing_if = "String::is_empty")] +// pub reason: String, + +// pub user_id: OwnedUserId, +//} + +//#[derive(Debug, Deserialize)] +//pub struct CreateRoomResponse { +// pub room_id: OwnedRoomId, +//} + +//#[derive(Debug, Deserialize)] +//pub struct JoinRoomResponse { +// pub room_id: OwnedRoomId, +//} + +//#[derive(Debug, Deserialize)] +//pub struct LeaveRoomResponse {} + +//#[derive(Debug, Deserialize)] +//pub struct ForgetRoomResponse {} + +//#[derive(Debug, Deserialize)] +//pub struct RoomKickOrBanResponse {} + +//pub struct RoomService; + +//impl RoomService { +// /// Create a new room with various configuration options. +// /// +// /// Refer: https://spec.matrix.org/v1.9/client-server-api/#creation +// #[instrument(skip(client, access_token))] +// pub async fn create( +// client: &crate::http::Client, +// access_token: impl Into, +// body: CreateRoomBody, +// ) -> Result { +// let mut tmp = (*client).clone(); +// tmp.set_token(access_token)?; + +// let resp = tmp +// .post_json("/_matrix/client/v3/createRoom", &body) +// .await?; + +// if resp.status().is_success() { +// return Ok(resp.json().await?); +// } + +// let error = resp.json::().await?; + +// Err(anyhow::anyhow!(error.error)) +// } + +// /// Join a particular room, if we are allowed to participate. +// /// +// /// Refer: https://spec.matrix.org/v1.9/client-server-api/#joining-rooms +// #[instrument(skip(client, access_token))] +// pub async fn join( +// client: &crate::http::Client, +// access_token: impl Into, +// alias_or_id: &RoomOrAliasId, +// body: JoinRoomBody, +// ) -> Result { +// let mut tmp = (*client).clone(); +// tmp.set_token(access_token)?; + +// let resp = tmp +// .post_json(format!("/_matrix/client/v3/join/{alias_or_id}"), +// &body) .await?; + +// if resp.status().is_success() { +// return Ok(resp.json().await?); +// } + +// let error = resp.json::().await?; + +// Err(anyhow::anyhow!(error.error)) +// } + +// /// Leave a particular room. +// /// They are still allowed to retrieve the history which they were +// /// previously allowed to see. +// /// +// /// Refer: https://spec.matrix.org/v1.9/client-server-api/#leaving-rooms +// #[instrument(skip(client, access_token))] +// pub async fn leave( +// client: &crate::http::Client, +// access_token: impl Into, +// room_id: &RoomId, +// body: LeaveRoomBody, +// ) -> Result { +// let mut tmp = (*client).clone(); +// tmp.set_token(access_token)?; + +// let resp = tmp +// .post_json(format!("/_matrix/client/v3/rooms/{room_id}/leave"), +// &body) .await?; + +// if resp.status().is_success() { +// return Ok(resp.json().await?); +// } + +// let error = resp.json::().await?; + +// Err(anyhow::anyhow!(error.error)) +// } + +// /// Forget a particular room. +// /// This will prevent the user from accessing the history of the room. +// /// +// /// Refer: https://spec.matrix.org/v1.9/client-server-api/#leaving-rooms +// #[instrument(skip(client, access_token))] +// pub async fn forget( +// client: &crate::http::Client, +// access_token: impl Into, +// room_id: &RoomId, +// body: ForgetRoomBody, +// ) -> Result { +// let mut tmp = (*client).clone(); +// tmp.set_token(access_token)?; + +// let resp = tmp +// .post_json(format!("/_matrix/client/v3/rooms/{room_id}/forget"), +// &body) .await?; + +// if resp.status().is_success() { +// return Ok(resp.json().await?); +// } + +// let error = resp.json::().await?; + +// Err(anyhow::anyhow!(error.error)) +// } + +// /// Kick a user from a particular room. +// /// The caller must have the required power level in order to perform this +// /// operation. +// /// +// /// Refer: https://spec.matrix.org/v1.9/client-server-api/#leaving-rooms +// #[instrument(skip(client, access_token))] +// pub async fn kick( +// client: &crate::http::Client, +// access_token: impl Into, +// room_id: &RoomId, +// body: RoomKickOrBanBody, +// ) -> Result { +// let mut tmp = (*client).clone(); +// tmp.set_token(access_token)?; + +// let resp = tmp +// .post_json(format!("/_matrix/client/v3/rooms/{room_id}/kick"), +// &body) .await?; + +// if resp.status().is_success() { +// return Ok(resp.json().await?); +// } + +// let error = resp.json::().await?; + +// Err(anyhow::anyhow!(error.error)) +// } + +// /// Ban a user from a particular room. +// /// This will kick them too if they are still a member. +// /// The caller must have the required power level in order to perform this +// /// operation. +// /// +// /// Refer: https://spec.matrix.org/v1.9/client-server-api/#leaving-rooms +// #[instrument(skip(client, access_token))] +// pub async fn ban( +// client: &crate::http::Client, +// access_token: impl Into, +// room_id: &RoomId, +// body: RoomKickOrBanBody, +// ) -> Result { +// let mut tmp = (*client).clone(); +// tmp.set_token(access_token)?; + +// let resp = tmp +// .post_json(format!("/_matrix/client/v3/rooms/{room_id}/ban"), +// &body) .await?; + +// if resp.status().is_success() { +// return Ok(resp.json().await?); +// } + +// let error = resp.json::().await?; + +// Err(anyhow::anyhow!(error.error)) +// } + +// /// Unban a user from a particular room. +// /// This will allow them to re-join or be re-invited. +// /// The caller must have the required power level in order to perform this +// /// operation. +// /// +// /// Refer: https://spec.matrix.org/v1.9/client-server-api/#banning-users-in-a-room +// #[instrument(skip(client, access_token))] +// pub async fn unban( +// client: &crate::http::Client, +// access_token: impl Into, +// room_id: &RoomId, +// body: RoomKickOrBanBody, +// ) -> Result { +// let mut tmp = (*client).clone(); +// tmp.set_token(access_token)?; + +// let resp = tmp +// .post_json(format!("/_matrix/client/v3/rooms/{room_id}/unban"), +// &body) .await?; + +// if resp.status().is_success() { +// return Ok(resp.json().await?); +// } + +// let error = resp.json::().await?; + +// Err(anyhow::anyhow!(error.error)) +// } +//} diff --git a/crates/matrix/src/admin/threepid/get_user_for_threepid.rs b/crates/matrix/src/client/room/ban_from_room.rs similarity index 100% rename from crates/matrix/src/admin/threepid/get_user_for_threepid.rs rename to crates/matrix/src/client/room/ban_from_room.rs diff --git a/crates/matrix/src/client/room/create_room.rs b/crates/matrix/src/client/room/create_room.rs new file mode 100644 index 0000000..73f9793 --- /dev/null +++ b/crates/matrix/src/client/room/create_room.rs @@ -0,0 +1,70 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedRoomId, OwnedUserId, +}; +use ruma_events::room::power_levels::RoomPowerLevels; +use serde::Serialize; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: POST, + rate_limited: false, + authentication: AccessToken, + history: { + 1.9 => "/_matrix/client/v3/createRoom", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[serde(skip_serializing_if = "Option::is_none")] + pub creation_content: Option, + + // #[serde(skip_serializing_if = "<[_]>::is_empty")] + // pub initial_state: Vec>, // TODO + #[serde(skip_serializing_if = "<[_]>::is_empty")] + pub invite: Vec, + + pub is_direct: bool, + + #[serde(skip_serializing_if = "String::is_empty")] + pub name: String, + + #[serde( + rename = "power_level_content_override", + skip_serializing_if = "Option::is_none" + )] + pub power_override: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub preset: RoomPreset, + + #[serde(rename = "alias", skip_serializing_if = "String::is_empty")] + pub room_alias_name: String, + + #[serde(skip_serializing_if = "String::is_empty")] + pub topic: String, +} + +#[response(error = crate::Error)] +pub struct Response { + room_id: OwnedRoomId, +} + +#[derive(Clone, Debug, Default, Serialize)] +pub struct RoomCreationContent { + #[serde(rename = "m.federate")] + pub federate: bool, +} + +#[derive(Clone, Debug, Default, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum RoomPreset { + PublicChat, + + PrivateChat, + + #[default] + TrustedPrivateChat, +} + diff --git a/crates/matrix/src/client/room/forget_room.rs b/crates/matrix/src/client/room/forget_room.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/matrix/src/client/room/invite_to_room.rs b/crates/matrix/src/client/room/invite_to_room.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/matrix/src/client/room/join_room.rs b/crates/matrix/src/client/room/join_room.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/matrix/src/client/room/kick_from_room.rs b/crates/matrix/src/client/room/kick_from_room.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/matrix/src/client/room/leave_room.rs b/crates/matrix/src/client/room/leave_room.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/matrix/src/client/room/unban_from_room.rs b/crates/matrix/src/client/room/unban_from_room.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/matrix/src/filter.rs b/crates/matrix/src/filter.rs deleted file mode 100644 index 3d4e398..0000000 --- a/crates/matrix/src/filter.rs +++ /dev/null @@ -1,212 +0,0 @@ -use anyhow::Result; -use ruma_common::{OwnedRoomId, OwnedUserId, UserId}; -use ruma_events::TimelineEventType; -use serde::{Deserialize, Serialize}; - -use crate::{error::MatrixError, Client}; - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub enum EventFormat { - #[default] - Client, - Federation, -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct Filter { - #[serde(skip_serializing_if = "Option::is_none", rename = "account_data")] - pub account: Option, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub event_fields: Vec, - - #[serde(skip_serializing_if = "Option::is_none")] - pub event_format: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub presence: Option, - - #[serde(skip_serializing_if = "Option::is_none", rename = "room")] - pub room: Option, -} - -impl Filter { - pub fn room_events(filter: RoomEventFilter) -> Self { - Self { - room: Some(RoomFilter { - timeline: Some(filter), - ..Default::default() - }), - ..Default::default() - } - } -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct EventFilter { - #[serde(skip_serializing_if = "Option::is_none")] - pub limit: Option, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub not_senders: Vec, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub not_types: Vec, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub senders: Vec, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub types: Vec, -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct RoomFilter { - #[serde(skip_serializing_if = "Option::is_none")] - pub account_data: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub ephemeral: Option, - - pub include_leave: bool, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub not_rooms: Vec, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub rooms: Vec, - - #[serde(skip_serializing_if = "Option::is_none")] - pub state: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub timeline: Option, -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct RoomEventFilter { - #[serde(skip_serializing_if = "Option::is_none")] - pub limit: Option, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub not_rooms: Vec, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub not_senders: Vec, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub not_types: Vec, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub rooms: Vec, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub senders: Vec, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub types: Vec, - - #[serde(skip_serializing_if = "Option::is_none", rename = "contains_url")] - pub include_urls: Option, - - pub include_redundant_members: bool, - - pub lazy_load_members: bool, - - pub unread_thread_notifications: bool, -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct StateFilter { - #[serde(skip_serializing_if = "Option::is_none")] - pub limit: Option, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub not_rooms: Vec, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub not_senders: Vec, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub not_types: Vec, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub rooms: Vec, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub senders: Vec, - - #[serde(skip_serializing_if = "<[_]>::is_empty")] - pub types: Vec, - - #[serde(skip_serializing_if = "Option::is_none", rename = "contains_url")] - pub include_urls: Option, - - pub include_redundant_members: bool, - - pub lazy_load_members: bool, - - pub unread_thread_notifications: bool, -} - -pub struct FilterService; - -#[derive(Debug, Deserialize)] -pub struct FilterResponse { - pub filter_id: String, -} - -impl FilterService { - pub async fn create( - client: &Client, - access_token: impl Into, - user_id: &UserId, - body: Filter, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .post_json( - format!( - "/_matrix/client/v3/user/{user_id}/filter", - user_id = user_id - ), - &body, - ) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - pub async fn get( - client: &Client, - access_token: impl Into, - user_id: &UserId, - filter_id: String, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .get(format!( - "/_matrix/client/v3/user/{user_id}/filter/{filter_id}", - user_id = user_id - )) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } -} diff --git a/crates/matrix/src/lib.rs b/crates/matrix/src/lib.rs index 16bf545..2b2848e 100644 --- a/crates/matrix/src/lib.rs +++ b/crates/matrix/src/lib.rs @@ -1,6 +1,5 @@ pub mod admin; pub mod client; -// pub mod filter; #[allow(unused_imports)] use ruma_common::api::error::MatrixError as Error; From e1d7acb3964eb80f1979d3cf8556c7f072a01851 Mon Sep 17 00:00:00 2001 From: mikoto Date: Tue, 27 Feb 2024 09:02:06 +0000 Subject: [PATCH 06/10] fix: client room API --- crates/matrix/src/client/room.rs | 253 +----------------- .../matrix/src/client/room/ban_from_room.rs | 28 ++ crates/matrix/src/client/room/create_room.rs | 20 +- crates/matrix/src/client/room/forget_room.rs | 26 ++ crates/matrix/src/client/room/get_rooms.rs | 70 +++++ crates/matrix/src/client/room/join_room.rs | 28 ++ .../matrix/src/client/room/kick_from_room.rs | 28 ++ crates/matrix/src/client/room/leave_room.rs | 26 ++ .../matrix/src/client/room/unban_from_room.rs | 28 ++ 9 files changed, 248 insertions(+), 259 deletions(-) create mode 100644 crates/matrix/src/client/room/get_rooms.rs diff --git a/crates/matrix/src/client/room.rs b/crates/matrix/src/client/room.rs index 9c28b1c..2cf5ffe 100644 --- a/crates/matrix/src/client/room.rs +++ b/crates/matrix/src/client/room.rs @@ -1,253 +1,8 @@ +mod ban_from_room; mod create_room; -mod get_room; -mod join_room; mod forget_room; -mod leave_room; +mod get_rooms; +mod join_room; mod kick_from_room; -mod ban_from_room; +mod leave_room; mod unban_from_room; - -//#[derive(Default, Debug, Serialize)] -//pub struct CreateRoomBody { -//} - -//#[derive(Default, Debug, Serialize)] -//pub struct JoinRoomBody { -// #[serde(skip_serializing_if = "String::is_empty")] -// pub reason: String, -//} - -//#[derive(Default, Debug, Serialize)] -//pub struct ForgetRoomBody { -// #[serde(skip_serializing_if = "String::is_empty")] -// pub reason: String, -//} - -//#[derive(Default, Debug, Serialize)] -//pub struct LeaveRoomBody { -// #[serde(skip_serializing_if = "String::is_empty")] -// pub reason: String, -//} - -//#[derive(Debug, Serialize)] -//pub struct RoomKickOrBanBody { -// #[serde(skip_serializing_if = "String::is_empty")] -// pub reason: String, - -// pub user_id: OwnedUserId, -//} - -//#[derive(Debug, Deserialize)] -//pub struct CreateRoomResponse { -// pub room_id: OwnedRoomId, -//} - -//#[derive(Debug, Deserialize)] -//pub struct JoinRoomResponse { -// pub room_id: OwnedRoomId, -//} - -//#[derive(Debug, Deserialize)] -//pub struct LeaveRoomResponse {} - -//#[derive(Debug, Deserialize)] -//pub struct ForgetRoomResponse {} - -//#[derive(Debug, Deserialize)] -//pub struct RoomKickOrBanResponse {} - -//pub struct RoomService; - -//impl RoomService { -// /// Create a new room with various configuration options. -// /// -// /// Refer: https://spec.matrix.org/v1.9/client-server-api/#creation -// #[instrument(skip(client, access_token))] -// pub async fn create( -// client: &crate::http::Client, -// access_token: impl Into, -// body: CreateRoomBody, -// ) -> Result { -// let mut tmp = (*client).clone(); -// tmp.set_token(access_token)?; - -// let resp = tmp -// .post_json("/_matrix/client/v3/createRoom", &body) -// .await?; - -// if resp.status().is_success() { -// return Ok(resp.json().await?); -// } - -// let error = resp.json::().await?; - -// Err(anyhow::anyhow!(error.error)) -// } - -// /// Join a particular room, if we are allowed to participate. -// /// -// /// Refer: https://spec.matrix.org/v1.9/client-server-api/#joining-rooms -// #[instrument(skip(client, access_token))] -// pub async fn join( -// client: &crate::http::Client, -// access_token: impl Into, -// alias_or_id: &RoomOrAliasId, -// body: JoinRoomBody, -// ) -> Result { -// let mut tmp = (*client).clone(); -// tmp.set_token(access_token)?; - -// let resp = tmp -// .post_json(format!("/_matrix/client/v3/join/{alias_or_id}"), -// &body) .await?; - -// if resp.status().is_success() { -// return Ok(resp.json().await?); -// } - -// let error = resp.json::().await?; - -// Err(anyhow::anyhow!(error.error)) -// } - -// /// Leave a particular room. -// /// They are still allowed to retrieve the history which they were -// /// previously allowed to see. -// /// -// /// Refer: https://spec.matrix.org/v1.9/client-server-api/#leaving-rooms -// #[instrument(skip(client, access_token))] -// pub async fn leave( -// client: &crate::http::Client, -// access_token: impl Into, -// room_id: &RoomId, -// body: LeaveRoomBody, -// ) -> Result { -// let mut tmp = (*client).clone(); -// tmp.set_token(access_token)?; - -// let resp = tmp -// .post_json(format!("/_matrix/client/v3/rooms/{room_id}/leave"), -// &body) .await?; - -// if resp.status().is_success() { -// return Ok(resp.json().await?); -// } - -// let error = resp.json::().await?; - -// Err(anyhow::anyhow!(error.error)) -// } - -// /// Forget a particular room. -// /// This will prevent the user from accessing the history of the room. -// /// -// /// Refer: https://spec.matrix.org/v1.9/client-server-api/#leaving-rooms -// #[instrument(skip(client, access_token))] -// pub async fn forget( -// client: &crate::http::Client, -// access_token: impl Into, -// room_id: &RoomId, -// body: ForgetRoomBody, -// ) -> Result { -// let mut tmp = (*client).clone(); -// tmp.set_token(access_token)?; - -// let resp = tmp -// .post_json(format!("/_matrix/client/v3/rooms/{room_id}/forget"), -// &body) .await?; - -// if resp.status().is_success() { -// return Ok(resp.json().await?); -// } - -// let error = resp.json::().await?; - -// Err(anyhow::anyhow!(error.error)) -// } - -// /// Kick a user from a particular room. -// /// The caller must have the required power level in order to perform this -// /// operation. -// /// -// /// Refer: https://spec.matrix.org/v1.9/client-server-api/#leaving-rooms -// #[instrument(skip(client, access_token))] -// pub async fn kick( -// client: &crate::http::Client, -// access_token: impl Into, -// room_id: &RoomId, -// body: RoomKickOrBanBody, -// ) -> Result { -// let mut tmp = (*client).clone(); -// tmp.set_token(access_token)?; - -// let resp = tmp -// .post_json(format!("/_matrix/client/v3/rooms/{room_id}/kick"), -// &body) .await?; - -// if resp.status().is_success() { -// return Ok(resp.json().await?); -// } - -// let error = resp.json::().await?; - -// Err(anyhow::anyhow!(error.error)) -// } - -// /// Ban a user from a particular room. -// /// This will kick them too if they are still a member. -// /// The caller must have the required power level in order to perform this -// /// operation. -// /// -// /// Refer: https://spec.matrix.org/v1.9/client-server-api/#leaving-rooms -// #[instrument(skip(client, access_token))] -// pub async fn ban( -// client: &crate::http::Client, -// access_token: impl Into, -// room_id: &RoomId, -// body: RoomKickOrBanBody, -// ) -> Result { -// let mut tmp = (*client).clone(); -// tmp.set_token(access_token)?; - -// let resp = tmp -// .post_json(format!("/_matrix/client/v3/rooms/{room_id}/ban"), -// &body) .await?; - -// if resp.status().is_success() { -// return Ok(resp.json().await?); -// } - -// let error = resp.json::().await?; - -// Err(anyhow::anyhow!(error.error)) -// } - -// /// Unban a user from a particular room. -// /// This will allow them to re-join or be re-invited. -// /// The caller must have the required power level in order to perform this -// /// operation. -// /// -// /// Refer: https://spec.matrix.org/v1.9/client-server-api/#banning-users-in-a-room -// #[instrument(skip(client, access_token))] -// pub async fn unban( -// client: &crate::http::Client, -// access_token: impl Into, -// room_id: &RoomId, -// body: RoomKickOrBanBody, -// ) -> Result { -// let mut tmp = (*client).clone(); -// tmp.set_token(access_token)?; - -// let resp = tmp -// .post_json(format!("/_matrix/client/v3/rooms/{room_id}/unban"), -// &body) .await?; - -// if resp.status().is_success() { -// return Ok(resp.json().await?); -// } - -// let error = resp.json::().await?; - -// Err(anyhow::anyhow!(error.error)) -// } -//} diff --git a/crates/matrix/src/client/room/ban_from_room.rs b/crates/matrix/src/client/room/ban_from_room.rs index e69de29..5e52971 100644 --- a/crates/matrix/src/client/room/ban_from_room.rs +++ b/crates/matrix/src/client/room/ban_from_room.rs @@ -0,0 +1,28 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedRoomId, OwnedUserId, +}; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: POST, + rate_limited: true, + authentication: AccessToken, + history: { + unstable => "/_matrix/client/v3/rooms/{room_id}/ban", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[ruma_api(path)] + pub room_id: OwnedRoomId, + + pub user_id: OwnedUserId, + + #[serde(skip_serializing_if = "String::is_empty")] + pub reason: String, +} + +#[response(error = crate::Error)] +pub struct Response {} diff --git a/crates/matrix/src/client/room/create_room.rs b/crates/matrix/src/client/room/create_room.rs index 73f9793..f27009a 100644 --- a/crates/matrix/src/client/room/create_room.rs +++ b/crates/matrix/src/client/room/create_room.rs @@ -1,14 +1,14 @@ use ruma_common::{ api::{request, response, Metadata}, - metadata, OwnedRoomId, OwnedUserId, + metadata, OwnedRoomId, OwnedUserId, serde::Raw, }; -use ruma_events::room::power_levels::RoomPowerLevels; +use ruma_events::{room::power_levels::RoomPowerLevels, AnyInitialStateEvent}; use serde::Serialize; #[allow(dead_code)] const METADATA: Metadata = metadata! { method: POST, - rate_limited: false, + rate_limited: true, authentication: AccessToken, history: { 1.9 => "/_matrix/client/v3/createRoom", @@ -20,8 +20,9 @@ pub struct Request { #[serde(skip_serializing_if = "Option::is_none")] pub creation_content: Option, - // #[serde(skip_serializing_if = "<[_]>::is_empty")] - // pub initial_state: Vec>, // TODO + #[serde(skip_serializing_if = "<[_]>::is_empty")] + pub initial_state: Vec>, + #[serde(skip_serializing_if = "<[_]>::is_empty")] pub invite: Vec, @@ -39,8 +40,8 @@ pub struct Request { #[serde(skip_serializing_if = "Option::is_none")] pub preset: RoomPreset, - #[serde(rename = "alias", skip_serializing_if = "String::is_empty")] - pub room_alias_name: String, + #[serde(rename = "room_alias_name", skip_serializing_if = "String::is_empty")] + pub alias: String, #[serde(skip_serializing_if = "String::is_empty")] pub topic: String, @@ -48,10 +49,10 @@ pub struct Request { #[response(error = crate::Error)] pub struct Response { - room_id: OwnedRoomId, + pub room_id: OwnedRoomId, } -#[derive(Clone, Debug, Default, Serialize)] +#[derive(Clone, Debug, Serialize)] pub struct RoomCreationContent { #[serde(rename = "m.federate")] pub federate: bool, @@ -67,4 +68,3 @@ pub enum RoomPreset { #[default] TrustedPrivateChat, } - diff --git a/crates/matrix/src/client/room/forget_room.rs b/crates/matrix/src/client/room/forget_room.rs index e69de29..1b72ee2 100644 --- a/crates/matrix/src/client/room/forget_room.rs +++ b/crates/matrix/src/client/room/forget_room.rs @@ -0,0 +1,26 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedRoomId, +}; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: POST, + rate_limited: true, + authentication: AccessToken, + history: { + unstable => "/_matrix/client/v3/rooms/{room_id}/forget", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[ruma_api(path)] + pub room_id: OwnedRoomId, + + #[serde(skip_serializing_if = "String::is_empty")] + pub reason: String, +} + +#[response(error = crate::Error)] +pub struct Response {} diff --git a/crates/matrix/src/client/room/get_rooms.rs b/crates/matrix/src/client/room/get_rooms.rs new file mode 100644 index 0000000..5208a1a --- /dev/null +++ b/crates/matrix/src/client/room/get_rooms.rs @@ -0,0 +1,70 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, + room::RoomType, + OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedServerName, +}; +use ruma_events::room::join_rules::JoinRule; +use serde::Deserialize; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: GET, + rate_limited: false, + authentication: None, + history: { + unstable => "/_matrix/client/v3/publicRooms", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[ruma_api(query)] + #[serde(skip_serializing_if = "Option::is_none")] + limit: Option, + + #[ruma_api(query)] + server: OwnedServerName, + + #[ruma_api(query)] + #[serde(skip_serializing_if = "String::is_empty")] + since: String, +} + +#[response(error = crate::Error)] +pub struct Response { + chunk: Vec, + + #[serde(skip_serializing_if = "Option::is_none")] + next_batch: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + prev_batch: Option, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Room { + #[serde(skip_serializing_if = "Option::is_none")] + pub avatar_url: Option, + + #[serde(rename = "canonical_alias", skip_serializing_if = "Option::is_none")] + pub alias: Option, + + pub join_rule: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + + #[serde(rename = "num_joined_members")] + pub members: Option, + + pub room_id: OwnedRoomId, + + #[serde(skip_serializing_if = "Option::is_none")] + pub room_type: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub topic: Option, + + pub world_readable: bool, +} diff --git a/crates/matrix/src/client/room/join_room.rs b/crates/matrix/src/client/room/join_room.rs index e69de29..1fdadd4 100644 --- a/crates/matrix/src/client/room/join_room.rs +++ b/crates/matrix/src/client/room/join_room.rs @@ -0,0 +1,28 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedRoomOrAliasId, OwnedRoomId, +}; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: POST, + rate_limited: true, + authentication: AccessToken, + history: { + unstable => "/_matrix/client/v3/join/{alias_or_id}", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[ruma_api(path)] + pub alias_or_id: OwnedRoomOrAliasId, + + #[serde(skip_serializing_if = "String::is_empty")] + pub reason: String, +} + +#[response(error = crate::Error)] +pub struct Response { + pub room_id: OwnedRoomId, +} diff --git a/crates/matrix/src/client/room/kick_from_room.rs b/crates/matrix/src/client/room/kick_from_room.rs index e69de29..c3079b9 100644 --- a/crates/matrix/src/client/room/kick_from_room.rs +++ b/crates/matrix/src/client/room/kick_from_room.rs @@ -0,0 +1,28 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedUserId, OwnedRoomId, +}; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: POST, + rate_limited: true, + authentication: AccessToken, + history: { + unstable => "/_matrix/client/v3/rooms/{room_id}/kick", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[ruma_api(path)] + pub room_id: OwnedRoomId, + + pub user_id: OwnedUserId, + + #[serde(skip_serializing_if = "String::is_empty")] + pub reason: String, +} + +#[response(error = crate::Error)] +pub struct Response {} diff --git a/crates/matrix/src/client/room/leave_room.rs b/crates/matrix/src/client/room/leave_room.rs index e69de29..6d50593 100644 --- a/crates/matrix/src/client/room/leave_room.rs +++ b/crates/matrix/src/client/room/leave_room.rs @@ -0,0 +1,26 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedRoomId, +}; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: POST, + rate_limited: true, + authentication: AccessToken, + history: { + unstable => "/_matrix/client/v3/rooms/{room_id}/leave", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[ruma_api(path)] + pub room_id: OwnedRoomId, + + #[serde(skip_serializing_if = "String::is_empty")] + pub reason: String, +} + +#[response(error = crate::Error)] +pub struct Response {} diff --git a/crates/matrix/src/client/room/unban_from_room.rs b/crates/matrix/src/client/room/unban_from_room.rs index e69de29..a34ace7 100644 --- a/crates/matrix/src/client/room/unban_from_room.rs +++ b/crates/matrix/src/client/room/unban_from_room.rs @@ -0,0 +1,28 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedRoomId, OwnedUserId, +}; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: POST, + rate_limited: true, + authentication: AccessToken, + history: { + unstable => "/_matrix/client/v3/rooms/{room_id}/unban", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[ruma_api(path)] + pub room_id: OwnedRoomId, + + pub user_id: OwnedUserId, + + #[serde(skip_serializing_if = "String::is_empty")] + pub reason: String, +} + +#[response(error = crate::Error)] +pub struct Response {} From 53ceed007fea74324bd0c474c2eb2268972f78b6 Mon Sep 17 00:00:00 2001 From: mikoto Date: Tue, 27 Feb 2024 09:06:07 +0000 Subject: [PATCH 07/10] docs --- crates/matrix/src/client/room.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/matrix/src/client/room.rs b/crates/matrix/src/client/room.rs index 2cf5ffe..ce0d312 100644 --- a/crates/matrix/src/client/room.rs +++ b/crates/matrix/src/client/room.rs @@ -1,3 +1,7 @@ +//! reference: https://spec.matrix.org/unstable/client-server-api/#rooms +//! +//! This module contains handlers to interact with rooms. + mod ban_from_room; mod create_room; mod forget_room; From c7cd019e8fe8961c585670b7b1cf01f1275256c7 Mon Sep 17 00:00:00 2001 From: mikoto Date: Tue, 27 Feb 2024 10:54:55 +0000 Subject: [PATCH 08/10] fix: client session API --- crates/matrix/src/client/session.rs | 49 +------- crates/matrix/src/client/session/get_flows.rs | 46 ++++++++ crates/matrix/src/client/session/login.rs | 108 ++++++++++++++++++ crates/matrix/src/client/session/register.rs | 108 ++++++++++++++++++ crates/matrix/src/client/session/whoami.rs | 24 ++++ 5 files changed, 289 insertions(+), 46 deletions(-) create mode 100644 crates/matrix/src/client/session/get_flows.rs create mode 100644 crates/matrix/src/client/session/login.rs create mode 100644 crates/matrix/src/client/session/register.rs create mode 100644 crates/matrix/src/client/session/whoami.rs diff --git a/crates/matrix/src/client/session.rs b/crates/matrix/src/client/session.rs index 1bad079..18b8249 100644 --- a/crates/matrix/src/client/session.rs +++ b/crates/matrix/src/client/session.rs @@ -1,46 +1,3 @@ -use anyhow::Result; -use ruma_common::OwnedUserId; -use serde::{Deserialize, Serialize}; -use tracing::instrument; - -use crate::error::MatrixError; - -#[derive(Debug, Serialize, Deserialize)] -pub struct Session { - pub device_id: String, - pub is_guest: bool, - pub user_id: OwnedUserId, -} - -impl Session { - /// Gets information about the owner of a given access token. - /// - /// Note that, as with the rest of the Client-Server API, Application - /// Services may masquerade as users within their namespace by giving a - /// user_id query parameter. In this situation, the server should verify - /// that the given user_id is registered by the appservice, and return it - /// in the response body. - /// - /// Refer: https://playground.matrix.org/#get-/_matrix/client/v3/account/whoami - #[instrument(skip(client, access_token))] - pub async fn get( - client: &crate::http::Client, - access_token: impl Into, - ) -> Result { - // Clones the client in order to temporally set a token for the `GET` - // request - let mut tmp = (*client).clone(); - - tmp.set_token(access_token)?; - - let resp = tmp.get("/_matrix/client/v3/account/whoami").await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } -} +mod login; +mod get_flows; +mod whoami; diff --git a/crates/matrix/src/client/session/get_flows.rs b/crates/matrix/src/client/session/get_flows.rs new file mode 100644 index 0000000..d3f6f7a --- /dev/null +++ b/crates/matrix/src/client/session/get_flows.rs @@ -0,0 +1,46 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, +}; +use serde::Deserialize; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: GET, + rate_limited: true, + authentication: None, + history: { + unstable => "/_matrix/client/v3/login", + } +}; + +#[request(error = crate::Error)] +pub struct Request {} + +#[response(error = crate::Error)] +pub struct Response { + body: Vec, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct LoginFlow { + #[serde(skip_serializing_if = "Option::is_none")] + pub get_login_token: Option, + + pub kind: LoginType, +} + +#[derive(Clone, Debug, Deserialize)] +pub enum LoginType { + #[serde(rename = "m.login.password")] + Password, + + #[serde(rename = "m.login.token")] + Token, + + #[serde(rename = "m.login.sso")] + Sso, + + #[serde(rename = "m.login.application_service")] + ApplicationService, +} diff --git a/crates/matrix/src/client/session/login.rs b/crates/matrix/src/client/session/login.rs new file mode 100644 index 0000000..889401b --- /dev/null +++ b/crates/matrix/src/client/session/login.rs @@ -0,0 +1,108 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedMxcUri, thirdparty::Medium, OwnedDeviceId, OwnedUserId, +}; +use serde::{Serialize, Deserialize}; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: POST, + rate_limited: true, + authentication: None, + history: { + unstable => "/_matrix/client/v3/login", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[serde(flatten, rename = "type")] + pub kind: LoginType, + + #[serde(skip_serializing_if = "Option::is_none")] + pub identifier: Option, + + #[serde( + rename = "initial_device_display_name", + skip_serializing_if = "String::is_empty" + )] + pub device_name: String, + + #[serde(skip_serializing_if = "Option::is_none")] + pub refresh_token: Option, +} + +#[response(error = crate::Error)] +pub struct Response { + pub access_token: String, + + pub device_id: OwnedDeviceId, + + #[serde(skip_serializing_if = "Option::is_none")] + pub expires_in_ms: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub refresh_token: Option, + + pub user_id: OwnedUserId, + + #[serde(skip_serializing_if = "Option::is_none")] + pub well_known: Option, +} + +#[derive(Clone, Debug, Serialize)] +pub struct IdentityProvider { + pub id: String, + + #[serde(skip_serializing_if = "String::is_empty")] + pub name: String, + + #[serde(skip_serializing_if = "Option::is_none")] + pub icon: Option, +} + +#[derive(Clone, Debug, Serialize)] +#[serde(tag = "type")] +pub enum LoginType { + #[serde(rename = "m.login.password")] + Password { password: String }, + + #[serde(rename = "m.login.token")] + Token { token: String }, + + #[serde(rename = "m.login.sso")] + Sso { + #[serde(skip_serializing_if = "<[_]>::is_empty")] + identity_providers: Vec, + }, + + #[serde(rename = "m.login.application_service")] + ApplicationService, +} + +#[derive(Clone, Debug, Serialize)] +#[serde(tag = "type")] +pub enum Identifier { + #[serde(rename = "m.id.user")] + User { user: String }, + + #[serde(rename = "m.id.thirdparty")] + ThirdParty { medium: Medium, address: String }, + + #[serde(rename = "m.id.phone")] + Phone { country: String, phone: String }, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct BaseUrl { + pub base_url: url::Url, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct WellKnown { + #[serde(rename = "m.homeserver")] + pub homeserver: BaseUrl, + + #[serde(rename = "m.identity_server")] + pub identity_server: BaseUrl, +} diff --git a/crates/matrix/src/client/session/register.rs b/crates/matrix/src/client/session/register.rs new file mode 100644 index 0000000..e662413 --- /dev/null +++ b/crates/matrix/src/client/session/register.rs @@ -0,0 +1,108 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedMxcUri, thirdparty::Medium, OwnedDeviceId, OwnedUserId, +}; +use serde::{Serialize, Deserialize}; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: POST, + rate_limited: true, + authentication: None, + history: { + unstable => "", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[serde(flatten, rename = "type")] + pub kind: LoginType, + + #[serde(skip_serializing_if = "Option::is_none")] + pub identifier: Option, + + #[serde( + rename = "initial_device_display_name", + skip_serializing_if = "String::is_empty" + )] + pub device_name: String, + + #[serde(skip_serializing_if = "Option::is_none")] + pub refresh_token: Option, +} + +#[response(error = crate::Error)] +pub struct Response { + pub access_token: String, + + pub device_id: OwnedDeviceId, + + #[serde(skip_serializing_if = "Option::is_none")] + pub expires_in_ms: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub refresh_token: Option, + + pub user_id: OwnedUserId, + + #[serde(skip_serializing_if = "Option::is_none")] + pub well_known: Option, +} + +#[derive(Clone, Debug, Serialize)] +pub struct IdentityProvider { + pub id: String, + + #[serde(skip_serializing_if = "String::is_empty")] + pub name: String, + + #[serde(skip_serializing_if = "Option::is_none")] + pub icon: Option, +} + +#[derive(Clone, Debug, Serialize)] +#[serde(tag = "type")] +pub enum LoginType { + #[serde(rename = "m.login.password")] + Password { password: String }, + + #[serde(rename = "m.login.token")] + Token { token: String }, + + #[serde(rename = "m.login.sso")] + Sso { + #[serde(skip_serializing_if = "<[_]>::is_empty")] + identity_providers: Vec, + }, + + #[serde(rename = "m.login.application_service")] + ApplicationService, +} + +#[derive(Clone, Debug, Serialize)] +#[serde(tag = "type")] +pub enum Identifier { + #[serde(rename = "m.id.user")] + User { user: String }, + + #[serde(rename = "m.id.thirdparty")] + ThirdParty { medium: Medium, address: String }, + + #[serde(rename = "m.id.phone")] + Phone { country: String, phone: String }, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct BaseUrl { + pub base_url: url::Url, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct WellKnown { + #[serde(rename = "m.homeserver")] + pub homeserver: BaseUrl, + + #[serde(rename = "m.identity_server")] + pub identity_server: BaseUrl, +} diff --git a/crates/matrix/src/client/session/whoami.rs b/crates/matrix/src/client/session/whoami.rs new file mode 100644 index 0000000..5913bfb --- /dev/null +++ b/crates/matrix/src/client/session/whoami.rs @@ -0,0 +1,24 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedDeviceId, OwnedUserId, +}; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: GET, + rate_limited: true, + authentication: AccessToken, + history: { + unstable => "/_matrix/client/v3/account/whoami", + } +}; + +#[request(error = crate::Error)] +pub struct Request {} + +#[response(error = crate::Error)] +pub struct Response { + pub device_id: OwnedDeviceId, + + pub user_id: OwnedUserId, +} From 87f60df3963609629c0a4e46cf022c31281c89a9 Mon Sep 17 00:00:00 2001 From: mikoto Date: Tue, 27 Feb 2024 14:19:26 +0000 Subject: [PATCH 09/10] Matrix API finished --- crates/matrix/Cargo.toml | 10 +- crates/matrix/src/admin/mod.rs | 8 +- crates/matrix/src/admin/room.rs | 4 +- crates/matrix/src/admin/session.rs | 45 +++ crates/matrix/src/admin/session/get_nonce.rs | 22 ++ crates/matrix/src/admin/session/register.rs | 43 +++ crates/matrix/src/admin/user.rs | 4 +- crates/matrix/src/client/error.rs | 9 - crates/matrix/src/client/events.rs | 310 ------------------ crates/matrix/src/client/login.rs | 93 ------ crates/matrix/src/client/mod.rs | 8 +- crates/matrix/src/client/mxc.rs | 184 ----------- crates/matrix/src/client/room.rs | 4 +- crates/matrix/src/client/session.rs | 8 +- crates/matrix/src/client/session/get_flows.rs | 2 +- crates/matrix/src/client/session/register.rs | 71 +--- .../src/client/session/username_available.rs | 24 ++ crates/matrix/src/lib.rs | 8 + 18 files changed, 177 insertions(+), 680 deletions(-) create mode 100644 crates/matrix/src/admin/session.rs create mode 100644 crates/matrix/src/admin/session/get_nonce.rs create mode 100644 crates/matrix/src/admin/session/register.rs delete mode 100644 crates/matrix/src/client/error.rs delete mode 100644 crates/matrix/src/client/events.rs delete mode 100644 crates/matrix/src/client/login.rs delete mode 100644 crates/matrix/src/client/mxc.rs create mode 100644 crates/matrix/src/client/session/username_available.rs diff --git a/crates/matrix/Cargo.toml b/crates/matrix/Cargo.toml index 68b8b70..8d24aca 100644 --- a/crates/matrix/Cargo.toml +++ b/crates/matrix/Cargo.toml @@ -6,18 +6,22 @@ publish = false [dependencies] ruma-events = { version = "0.27.11", features = ["html", "markdown"] } -ruma-common = { version = "0.12.0", default_features = false, features = ["api", "rand"] } +ruma-common = { version = "0.12.0", default_features = false, features = [ + "api", + "rand", +] } ruma-macros = "0.12.0" # Workspace Dependencies -anyhow = { workspace = true } -chrono = { workspace = true, features = ["serde"] } mime = { workspace = true } reqwest = { workspace = true, features = ["json"] } serde = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } +sha1 = { workspace = true } url = { workspace = true, features = ["serde"] } +hex = { workspace = true } +hmac = { workspace = true } [features] client = [] diff --git a/crates/matrix/src/admin/mod.rs b/crates/matrix/src/admin/mod.rs index 2a77d65..dcb275b 100644 --- a/crates/matrix/src/admin/mod.rs +++ b/crates/matrix/src/admin/mod.rs @@ -1,3 +1,7 @@ -mod user; +//! This module is the root of the admin API. +//! +//! reference: https://matrix-org.github.io/synapse/latest/usage/administration/admin_api/index.html + mod room; -// mod room; +mod session; +mod user; diff --git a/crates/matrix/src/admin/room.rs b/crates/matrix/src/admin/room.rs index 17117b9..39b3a22 100644 --- a/crates/matrix/src/admin/room.rs +++ b/crates/matrix/src/admin/room.rs @@ -1,6 +1,6 @@ -//! reference: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html -//! //! This module contains handlers for managing rooms. +//! +//! reference: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html use ruma_common::{ room::RoomType, EventEncryptionAlgorithm, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, diff --git a/crates/matrix/src/admin/session.rs b/crates/matrix/src/admin/session.rs new file mode 100644 index 0000000..88cabf2 --- /dev/null +++ b/crates/matrix/src/admin/session.rs @@ -0,0 +1,45 @@ +//! This module contains handlers for user registration. +//! +//! reference: https://matrix-org.github.io/synapse/latest/admin_api/register_api.html + +use hmac::Mac; + +mod get_nonce; +mod register; + +#[derive(Clone, Debug)] +pub struct Hmac { + inner: Vec, +} + +impl Hmac { + fn new( + shared_secret: &str, + nonce: &str, + username: &str, + password: &str, + admin: bool, + ) -> Result { + let mut mac = hmac::Hmac::::new_from_slice(shared_secret.as_bytes())?; + let admin = match admin { + true => "admin", + false => "notadmin", + }; + + mac.update( + &[nonce, username, password, admin] + .map(str::as_bytes) + .join(&0x00), + ); + + let result = mac.finalize().into_bytes(); + + Ok(Self { + inner: result.to_vec(), + }) + } + + fn get(&self) -> String { + hex::encode(&self.inner) + } +} diff --git a/crates/matrix/src/admin/session/get_nonce.rs b/crates/matrix/src/admin/session/get_nonce.rs new file mode 100644 index 0000000..d5f5c00 --- /dev/null +++ b/crates/matrix/src/admin/session/get_nonce.rs @@ -0,0 +1,22 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, +}; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: GET, + rate_limited: false, + authentication: AccessToken, + history: { + unstable => "/_synapse/admin/v1/register", + } +}; + +#[request(error = crate::Error)] +pub struct Request {} + +#[response(error = crate::Error)] +pub struct Response { + nonce: String, +} diff --git a/crates/matrix/src/admin/session/register.rs b/crates/matrix/src/admin/session/register.rs new file mode 100644 index 0000000..c71d8f0 --- /dev/null +++ b/crates/matrix/src/admin/session/register.rs @@ -0,0 +1,43 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedDeviceId, OwnedServerName, OwnedUserId, +}; + +use super::Hmac; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: GET, + rate_limited: false, + authentication: AccessToken, + history: { + unstable => "/_synapse/admin/v1/register", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + pub nonce: String, + + pub username: String, + + pub password: String, + + #[serde(skip_deserializing_if = "String::is_empty")] + pub displayname: String, + + pub admin: bool, + + pub hmac: Hmac, +} + +#[response(error = crate::Error)] +pub struct Response { + pub access_token: String, + + pub user_id: OwnedUserId, + + pub home_server: OwnedServerName, + + pub device_id: OwnedDeviceId, +} diff --git a/crates/matrix/src/admin/user.rs b/crates/matrix/src/admin/user.rs index 2e39665..3417bb5 100644 --- a/crates/matrix/src/admin/user.rs +++ b/crates/matrix/src/admin/user.rs @@ -1,6 +1,6 @@ -//! reference: https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html +//! This module contains handlers for managing users. //! -//! This module contains handlers for managing user accounts. +//! reference: https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html use ruma_common::{thirdparty::ThirdPartyIdentifier, OwnedMxcUri, OwnedUserId}; use serde::{Deserialize, Serialize}; diff --git a/crates/matrix/src/client/error.rs b/crates/matrix/src/client/error.rs deleted file mode 100644 index 872754b..0000000 --- a/crates/matrix/src/client/error.rs +++ /dev/null @@ -1,9 +0,0 @@ -use serde::Deserialize; - -#[derive(Clone, Debug, Deserialize)] -pub struct MatrixError { - #[serde(rename = "errcode")] - pub error_code: String, - #[serde(rename = "error")] - pub error: String, -} diff --git a/crates/matrix/src/client/events.rs b/crates/matrix/src/client/events.rs deleted file mode 100644 index 2953b2b..0000000 --- a/crates/matrix/src/client/events.rs +++ /dev/null @@ -1,310 +0,0 @@ -use anyhow::Result; -use ruma_common::{serde::Raw, EventId, OwnedEventId, OwnedTransactionId, RoomId}; - -use ruma_events::{ - relation::RelationType, AnyMessageLikeEvent, AnyStateEvent, AnyStateEventContent, - AnyTimelineEvent, MessageLikeEventContent, MessageLikeEventType, StateEventContent, - StateEventType, -}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use tracing::instrument; - -use crate::{admin::resources::room::Direction, error::MatrixError, Client}; - -pub struct EventsService; - -#[derive(Debug, Default, Clone, Serialize)] -pub struct GetMessagesQuery { - #[serde(skip_serializing_if = "Option::is_none")] - pub from: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub to: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub limit: Option, - - pub dir: Direction, - - #[serde(skip_serializing_if = "String::is_empty")] - pub filter: String, -} - -#[derive(Debug, Default, Clone, Serialize)] -pub struct GetRelationsQuery { - #[serde(skip_serializing_if = "Option::is_none")] - pub from: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub to: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub limit: Option, - - pub dir: Direction, -} - -#[derive(Debug, Deserialize)] -pub struct GetMessagesResponse { - pub chunk: Vec>, - pub start: String, - pub end: String, - pub state: Option>>, -} - -#[derive(Debug, Deserialize)] -#[serde(transparent)] -pub struct GetStateResponse(pub Vec>); - -#[derive(Debug, Deserialize)] -pub struct GetRelationsResponse { - pub chunk: Vec>, - pub prev_batch: Option, - pub next_batch: Option, -} - -#[derive(Debug, Default, Serialize)] -pub struct SendRedactionBody { - #[serde(skip_serializing_if = "String::is_empty")] - pub reason: String, -} - -#[derive(Debug, Deserialize)] -pub struct SendMessageResponse { - pub event_id: OwnedEventId, -} - -#[derive(Debug, Deserialize)] -pub struct SendStateResponse { - pub event_id: OwnedEventId, -} - -#[derive(Debug, Deserialize)] -pub struct SendRedactionResponse { - pub event_id: OwnedEventId, -} - -impl EventsService { - #[instrument(skip(client, access_token))] - pub async fn get_event( - client: &Client, - access_token: impl Into, - room_id: &RoomId, - event_id: &EventId, - ) -> Result> { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .get(format!( - "/_matrix/client/v3/rooms/{room_id}/event/{event_id}", - room_id = room_id, - event_id = event_id, - )) - .await?; - - Ok(resp.json().await?) - } - - #[instrument(skip(client, access_token))] - pub async fn get_messages( - client: &Client, - access_token: impl Into, - room_id: &RoomId, - query: GetMessagesQuery, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .get_query( - format!( - "/_matrix/client/v3/rooms/{room_id}/messages", - room_id = room_id, - ), - &query, - ) - .await?; - - Ok(resp.json().await?) - } - - #[instrument(skip(client, access_token))] - pub async fn get_state( - client: &Client, - access_token: impl Into, - room_id: &RoomId, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .get(format!( - "/_matrix/client/v3/rooms/{room_id}/state", - room_id = room_id, - )) - .await?; - - Ok(resp.json().await?) - } - - #[instrument(skip(client, access_token))] - pub async fn get_state_content( - client: &Client, - access_token: impl Into, - room_id: &RoomId, - event_type: StateEventType, - state_key: Option, - ) -> Result> { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let mut path = format!( - "/_matrix/client/v3/rooms/{room_id}/state/{event_type}", - room_id = room_id, - event_type = event_type, - ); - - if let Some(state_key) = state_key { - path.push_str(&format!("/{state_key}", state_key = state_key)) - } - - let resp = tmp.get(path).await?; - - Ok(resp.json().await?) - } - - #[instrument(skip(client, access_token))] - pub async fn get_relations( - client: &Client, - access_token: impl Into, - room_id: &RoomId, - event_id: &EventId, - rel_type: Option>, - event_type: Option, - query: GetRelationsQuery, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let mut path = format!( - "/_matrix/client/v3/rooms/{room_id}/relations/{event_id}", - room_id = room_id, - event_id = event_id, - ); - - if let Some(rel_type) = rel_type { - path.push_str(&format!( - "/{rel_type}", - rel_type = rel_type - .as_ref() - .map_or("m.in_reply_to".into(), ToString::to_string) - )); - - if let Some(event_type) = event_type { - path.push_str(&format!("/{event_type}", event_type = event_type)) - } - } - - let resp = tmp.get_query(path, &query).await?; - - Ok(resp.json().await?) - } - - #[instrument(skip(client, access_token, body))] - pub async fn send_message( - client: &Client, - access_token: impl Into, - room_id: &RoomId, - txn_id: OwnedTransactionId, - body: T, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .put_json( - format!( - "/_matrix/client/v3/rooms/{room_id}/send/{event_type}/{txn_id}", - room_id = room_id, - event_type = body.event_type(), - txn_id = txn_id, - ), - &body, - ) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - #[instrument(skip(client, access_token, body))] - pub async fn send_state( - client: &Client, - access_token: impl Into, - room_id: &RoomId, - state_key: Option, - body: T, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let mut path = format!( - "/_matrix/client/v3/rooms/{room_id}/state/{event_type}", - room_id = room_id, - event_type = body.event_type(), - ); - - if let Some(state_key) = state_key { - path.push_str(&format!("/{state_key}", state_key = state_key)) - } - - let resp = tmp.put_json(path, &body).await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - #[instrument(skip(client, access_token, body))] - pub async fn send_redaction( - client: &Client, - access_token: impl Into, - room_id: &RoomId, - event_id: &EventId, - txn_id: OwnedTransactionId, - body: SendRedactionBody, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .put_json( - format!( - "/_matrix/client/v3/rooms/{room_id}/redact/{event_id}/{txn_id}", - room_id = room_id, - event_id = event_id, - txn_id = txn_id, - ), - &body, - ) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } -} diff --git a/crates/matrix/src/client/login.rs b/crates/matrix/src/client/login.rs deleted file mode 100644 index 6a65fba..0000000 --- a/crates/matrix/src/client/login.rs +++ /dev/null @@ -1,93 +0,0 @@ -use anyhow::Result; -use serde::{Deserialize, Serialize}; -use tracing::instrument; - -use crate::{error::MatrixError, http::Client}; - -#[derive(Debug, Deserialize)] -pub struct LoginCredentials { - pub access_token: String, -} - -#[derive(Debug, Serialize)] -pub struct LoginCredentialsPayload { - pub r#type: &'static str, - pub user: String, - pub password: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct LoginFlow { - pub get_login_token: Option, - #[serde(rename = "type")] - pub kind: String, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(transparent)] -pub struct LoginFlows { - pub flows: Vec, -} - -pub struct Login; - -impl Login { - /// Retrieves an access token by logging in with Username and Password - /// - /// This is equivalent to executing: - /// - /// ```ignore - /// curl -sS -d '{"type":"m.login.password", "user":"X", "password":"Y"}' http://server:port/_matrix/client/v3/login - /// ``` - #[instrument(skip(client, username, password))] - pub async fn login_credentials( - client: &Client, - username: impl AsRef, - password: impl AsRef, - ) -> Result { - let resp = client - .post_json( - "/_matrix/client/v3/login", - &LoginCredentialsPayload { - r#type: "m.login.password", - user: username.as_ref().to_string(), - password: password.as_ref().to_string(), - }, - ) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - #[instrument(skip(client))] - pub async fn get_login_flows(client: &Client) -> Result { - let resp = client.get("/_matrix/client/v3/login").await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - #[instrument(skip(client))] - pub async fn redirect_sso(client: &Client) -> Result { - let resp = client.get("/_matrix/client/v3/login").await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } -} diff --git a/crates/matrix/src/client/mod.rs b/crates/matrix/src/client/mod.rs index 43420e9..196920e 100644 --- a/crates/matrix/src/client/mod.rs +++ b/crates/matrix/src/client/mod.rs @@ -1,6 +1,6 @@ -pub mod error; -pub mod events; -pub mod login; -pub mod mxc; +//! This module is the root of the client-server API. +//! +//! reference: https://spec.matrix.org/unstable/client-server-api + pub mod room; pub mod session; diff --git a/crates/matrix/src/client/mxc.rs b/crates/matrix/src/client/mxc.rs deleted file mode 100644 index 7dc669a..0000000 --- a/crates/matrix/src/client/mxc.rs +++ /dev/null @@ -1,184 +0,0 @@ -use std::str::FromStr; - -use anyhow::Result; -use mime::Mime; -use ruma_common::{MxcUri, OwnedMxcUri}; -use serde::{de, Deserialize, Deserializer, Serialize}; -use tracing::instrument; - -use chrono::{serde::ts_microseconds_option, DateTime, Utc}; - -use crate::error::MatrixError; - -fn parse_mime_opt<'de, D>(d: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - Option::<&str>::deserialize(d)? - .map(::from_str) - .transpose() - .map_err(de::Error::custom) -} - -#[derive(Debug, Serialize)] -pub struct GetPreviewUrlQuery { - pub url: url::Url, - pub ts: DateTime, -} - -#[derive(Debug, Deserialize)] -pub struct CreateMxcUriResponse { - pub content_uri: String, - - #[serde(with = "ts_microseconds_option")] - pub unused_expires_at: Option>, -} - -#[derive(Debug, Deserialize)] -pub struct GetPreviewUrlResponse { - #[serde(rename = "matrix:image_size")] - pub image_size: Option, - - #[serde(rename = "og:description")] - pub description: Option, - - #[serde(rename = "og:image")] - pub image: Option, - - #[serde(rename = "og:image:height")] - pub height: Option, - - #[serde(rename = "og:image:width")] - pub width: Option, - - #[serde(rename = "og:image:type", deserialize_with = "parse_mime_opt")] - pub kind: Option, - - #[serde(rename = "og:title")] - pub title: Option, -} - -#[derive(Debug, Deserialize)] -pub struct GetConfigResponse { - #[serde(rename = "m.upload.size")] - pub upload_size: Option, -} - -#[derive(Debug, Serialize)] -pub enum ResizeMethod { - Crop, - Scale, -} - -pub struct MxcService; - -#[derive(Debug, Deserialize)] -pub struct MxcError { - #[serde(flatten)] - pub inner: MatrixError, - - pub retry_after_ms: u64, -} - -impl MxcService { - /// Creates a new `MxcUri`, independently of the content being uploaded - /// - /// Refer: https://spec.matrix.org/v1.9/client-server-api/#post_matrixmediav1create - #[instrument(skip(client, access_token))] - pub async fn create( - client: &crate::http::Client, - access_token: impl Into, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp.post("/_matrix/media/v1/create").await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.inner.error)) - } - - /// Retrieve the configuration of the content repository - /// - /// Refer: https://spec.matrix.org/v1.9/client-server-api/#get_matrixmediav3config - #[instrument(skip(client, access_token))] - pub async fn get_config( - client: &crate::http::Client, - access_token: impl Into, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp.get("/_matrix/media/v3/config").await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.inner.error)) - } - - /// Retrieve a URL to download content from the content repository, - /// optionally replacing the name of the file. - /// - /// Refer: https://spec.matrix.org/v1.9/client-server-api/#get_matrixmediav3downloadservernamemediaid - #[instrument(skip(client, access_token))] - pub async fn get_download_url( - client: &crate::http::Client, - access_token: impl Into, - mxc_uri: &MxcUri, - mut base_url: url::Url, - file_name: Option, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let (server_name, media_id) = mxc_uri.parts().unwrap(); - - let mut path = format!( - "/_matrix/media/v3/download/{server_name}/{media_id}", - server_name = server_name, - media_id = media_id, - ); - - if let Some(file_name) = file_name { - path.push_str(&format!("/{file_name}", file_name = file_name)) - } - - base_url.set_path(&path); - - Ok(base_url) - } - - /// - /// - /// Refer: https://spec.matrix.org/v1.9/client-server-api/#get_matrixmediav3preview_url - #[instrument(skip(client, access_token))] - pub async fn get_preview( - client: &crate::http::Client, - access_token: impl Into, - query: GetPreviewUrlQuery, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .get_query("/_matrix/media/v3/preview_url".to_string(), &query) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.inner.error)) - } -} diff --git a/crates/matrix/src/client/room.rs b/crates/matrix/src/client/room.rs index ce0d312..27a797f 100644 --- a/crates/matrix/src/client/room.rs +++ b/crates/matrix/src/client/room.rs @@ -1,6 +1,6 @@ -//! reference: https://spec.matrix.org/unstable/client-server-api/#rooms -//! //! This module contains handlers to interact with rooms. +//! +//! reference: https://spec.matrix.org/unstable/client-server-api/#rooms mod ban_from_room; mod create_room; diff --git a/crates/matrix/src/client/session.rs b/crates/matrix/src/client/session.rs index 18b8249..18328e9 100644 --- a/crates/matrix/src/client/session.rs +++ b/crates/matrix/src/client/session.rs @@ -1,3 +1,9 @@ -mod login; +//! This module contains handlers for user sessions. +//! +//! reference: https://spec.matrix.org/unstable/client-server-api/#client-authentication + mod get_flows; +mod login; +mod register; +mod username_available; mod whoami; diff --git a/crates/matrix/src/client/session/get_flows.rs b/crates/matrix/src/client/session/get_flows.rs index d3f6f7a..14e9a29 100644 --- a/crates/matrix/src/client/session/get_flows.rs +++ b/crates/matrix/src/client/session/get_flows.rs @@ -19,7 +19,7 @@ pub struct Request {} #[response(error = crate::Error)] pub struct Response { - body: Vec, + pub body: Vec, } #[derive(Clone, Debug, Deserialize)] diff --git a/crates/matrix/src/client/session/register.rs b/crates/matrix/src/client/session/register.rs index e662413..505e8e9 100644 --- a/crates/matrix/src/client/session/register.rs +++ b/crates/matrix/src/client/session/register.rs @@ -1,8 +1,7 @@ use ruma_common::{ api::{request, response, Metadata}, - metadata, OwnedMxcUri, thirdparty::Medium, OwnedDeviceId, OwnedUserId, + metadata, OwnedDeviceId, OwnedUserId, }; -use serde::{Serialize, Deserialize}; #[allow(dead_code)] const METADATA: Metadata = metadata! { @@ -10,17 +9,15 @@ const METADATA: Metadata = metadata! { rate_limited: true, authentication: None, history: { - unstable => "", + unstable => "/_matrix/client/v3/register", } }; #[request(error = crate::Error)] pub struct Request { - #[serde(flatten, rename = "type")] - pub kind: LoginType, + pub username: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub identifier: Option, + pub password: String, #[serde( rename = "initial_device_display_name", @@ -45,64 +42,4 @@ pub struct Response { pub refresh_token: Option, pub user_id: OwnedUserId, - - #[serde(skip_serializing_if = "Option::is_none")] - pub well_known: Option, -} - -#[derive(Clone, Debug, Serialize)] -pub struct IdentityProvider { - pub id: String, - - #[serde(skip_serializing_if = "String::is_empty")] - pub name: String, - - #[serde(skip_serializing_if = "Option::is_none")] - pub icon: Option, -} - -#[derive(Clone, Debug, Serialize)] -#[serde(tag = "type")] -pub enum LoginType { - #[serde(rename = "m.login.password")] - Password { password: String }, - - #[serde(rename = "m.login.token")] - Token { token: String }, - - #[serde(rename = "m.login.sso")] - Sso { - #[serde(skip_serializing_if = "<[_]>::is_empty")] - identity_providers: Vec, - }, - - #[serde(rename = "m.login.application_service")] - ApplicationService, -} - -#[derive(Clone, Debug, Serialize)] -#[serde(tag = "type")] -pub enum Identifier { - #[serde(rename = "m.id.user")] - User { user: String }, - - #[serde(rename = "m.id.thirdparty")] - ThirdParty { medium: Medium, address: String }, - - #[serde(rename = "m.id.phone")] - Phone { country: String, phone: String }, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct BaseUrl { - pub base_url: url::Url, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct WellKnown { - #[serde(rename = "m.homeserver")] - pub homeserver: BaseUrl, - - #[serde(rename = "m.identity_server")] - pub identity_server: BaseUrl, } diff --git a/crates/matrix/src/client/session/username_available.rs b/crates/matrix/src/client/session/username_available.rs new file mode 100644 index 0000000..e380f6f --- /dev/null +++ b/crates/matrix/src/client/session/username_available.rs @@ -0,0 +1,24 @@ +use ruma_common::{ + api::{request, response, Metadata}, + metadata, +}; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: POST, + rate_limited: true, + authentication: None, + history: { + unstable => "/_matrix/client/v3/register/available", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + pub username: String, +} + +#[response(error = crate::Error)] +pub struct Response { + pub available: bool, +} diff --git a/crates/matrix/src/lib.rs b/crates/matrix/src/lib.rs index 2b2848e..ace15fb 100644 --- a/crates/matrix/src/lib.rs +++ b/crates/matrix/src/lib.rs @@ -1,3 +1,11 @@ +//! This library deals with forwarding Matrix requests to the server. +//! Comments have been used sparingly as the specification contains all the technical details. + +//! We rely on `ruma` to abstract away the boilerplate introduced by HTTP requests, +//! without sacrificing flexibility by defining our own request and response types. +//! +//! reference: https://docs.ruma.io/ruma_common/api/index.html + pub mod admin; pub mod client; From b1b42aec270d719a5782b0d8ea12886450fb7b7e Mon Sep 17 00:00:00 2001 From: mikoto Date: Wed, 28 Feb 2024 17:25:55 +0000 Subject: [PATCH 10/10] feat: handler addition, one to rule them all --- crates/core/src/lib.rs | 14 +- crates/core/src/{account => session}/error.rs | 0 crates/core/src/{account => session}/mod.rs | 0 crates/core/src/{account => session}/model.rs | 0 .../core/src/{account => session}/service.rs | 0 crates/matrix/Cargo.toml | 11 +- crates/matrix/src/admin/mod.rs | 6 +- crates/matrix/src/admin/room.rs | 6 +- crates/matrix/src/admin/room/delete_room.rs | 4 +- crates/matrix/src/admin/room/get_rooms.rs | 12 +- crates/matrix/src/admin/session.rs | 8 +- crates/matrix/src/admin/session/get_nonce.rs | 2 +- crates/matrix/src/client/room/create_room.rs | 2 +- crates/matrix/src/lib.rs | 56 ++++++ crates/matrix/src/token/mod.rs | 1 - crates/matrix/src/token/shared_secret.rs | 173 ------------------ 16 files changed, 90 insertions(+), 205 deletions(-) rename crates/core/src/{account => session}/error.rs (100%) rename crates/core/src/{account => session}/mod.rs (100%) rename crates/core/src/{account => session}/model.rs (100%) rename crates/core/src/{account => session}/service.rs (100%) delete mode 100644 crates/matrix/src/token/mod.rs delete mode 100644 crates/matrix/src/token/shared_secret.rs diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index afc2a0a..f0f9183 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,4 +1,7 @@ -pub mod account; +//! This library deals with our core logic, such as authorizing user interactions, +//! forwarding regular events and constructing custom requests. + +pub mod session; pub mod auth; pub mod error; pub mod mail; @@ -9,14 +12,11 @@ pub use error::{Error, HttpStatusCode, Result}; use mail::service::MailService; use room::service::RoomService; +use tokio::sync::mpsc::Receiver; use url::Url; use std::{fmt::Debug, str::FromStr, sync::Arc}; -use matrix::Client as MatrixAdminClient; - -use self::{account::service::AccountService, auth::service::AuthService}; - pub mod env { pub const COMMUNE_SYNAPSE_HOST: &str = "COMMUNE_SYNAPSE_HOST"; pub const COMMUNE_SYNAPSE_ADMIN_TOKEN: &str = "COMMUNE_SYNAPSE_ADMIN_TOKEN"; @@ -92,9 +92,7 @@ impl CommuneConfig { } pub struct Commune { - pub account: Arc, - pub auth: Arc, - pub room: Arc, + } impl Commune { diff --git a/crates/core/src/account/error.rs b/crates/core/src/session/error.rs similarity index 100% rename from crates/core/src/account/error.rs rename to crates/core/src/session/error.rs diff --git a/crates/core/src/account/mod.rs b/crates/core/src/session/mod.rs similarity index 100% rename from crates/core/src/account/mod.rs rename to crates/core/src/session/mod.rs diff --git a/crates/core/src/account/model.rs b/crates/core/src/session/model.rs similarity index 100% rename from crates/core/src/account/model.rs rename to crates/core/src/session/model.rs diff --git a/crates/core/src/account/service.rs b/crates/core/src/session/service.rs similarity index 100% rename from crates/core/src/account/service.rs rename to crates/core/src/session/service.rs diff --git a/crates/matrix/Cargo.toml b/crates/matrix/Cargo.toml index 8d24aca..82ee55f 100644 --- a/crates/matrix/Cargo.toml +++ b/crates/matrix/Cargo.toml @@ -5,12 +5,16 @@ edition = "2021" publish = false [dependencies] -ruma-events = { version = "0.27.11", features = ["html", "markdown"] } +ruma-events = { version = "0.27.11", default_features = false, features = [ + "html", + "markdown", +] } ruma-common = { version = "0.12.0", default_features = false, features = [ "api", "rand", ] } -ruma-macros = "0.12.0" +ruma-macros = { version = "0.12.0", default_features = false } +ruma-client = { version = "0.12.0", default_features = false } # Workspace Dependencies mime = { workspace = true } @@ -22,6 +26,9 @@ sha1 = { workspace = true } url = { workspace = true, features = ["serde"] } hex = { workspace = true } hmac = { workspace = true } +http.workspace = true +bytes = "1.5.0" +async-trait = "0.1.77" [features] client = [] diff --git a/crates/matrix/src/admin/mod.rs b/crates/matrix/src/admin/mod.rs index dcb275b..75a9d6d 100644 --- a/crates/matrix/src/admin/mod.rs +++ b/crates/matrix/src/admin/mod.rs @@ -2,6 +2,6 @@ //! //! reference: https://matrix-org.github.io/synapse/latest/usage/administration/admin_api/index.html -mod room; -mod session; -mod user; +pub mod room; +pub mod session; +pub mod user; diff --git a/crates/matrix/src/admin/room.rs b/crates/matrix/src/admin/room.rs index 39b3a22..38a3738 100644 --- a/crates/matrix/src/admin/room.rs +++ b/crates/matrix/src/admin/room.rs @@ -9,11 +9,11 @@ use ruma_common::{ use ruma_events::room::{history_visibility::HistoryVisibility, join_rules::JoinRule}; use serde::Deserialize; -mod delete_room; -mod get_members; +pub mod delete_room; +pub mod get_members; pub mod get_room; pub mod get_rooms; -mod get_state; +pub mod get_state; #[derive(Clone, Debug, Deserialize)] pub struct Room { diff --git a/crates/matrix/src/admin/room/delete_room.rs b/crates/matrix/src/admin/room/delete_room.rs index 2774f1e..578a334 100644 --- a/crates/matrix/src/admin/room/delete_room.rs +++ b/crates/matrix/src/admin/room/delete_room.rs @@ -22,19 +22,17 @@ pub struct Request { #[serde(flatten, skip_serializing_if = "Option::is_none")] pub new_room: Option, - #[serde(skip_serializing_if = "ruma_common::serde::is_default")] pub block: bool, #[serde(skip_serializing_if = "ruma_common::serde::is_true")] pub purge: bool, - #[serde(skip_serializing_if = "ruma_common::serde::is_default")] pub force_purge: bool, } #[response(error = crate::Error)] pub struct Response { - delete_id: String, + pub delete_id: String, } #[derive(Clone, Debug, Serialize)] diff --git a/crates/matrix/src/admin/room/get_rooms.rs b/crates/matrix/src/admin/room/get_rooms.rs index 5607c2b..08a792c 100644 --- a/crates/matrix/src/admin/room/get_rooms.rs +++ b/crates/matrix/src/admin/room/get_rooms.rs @@ -1,5 +1,5 @@ use ruma_common::{ - api::{request, response, Metadata, Direction}, + api::{request, response, Direction, Metadata}, metadata, }; use serde::Serialize; @@ -39,16 +39,16 @@ pub struct Request { #[response(error = crate::Error)] pub struct Response { - rooms: Vec, + pub rooms: Vec, - offset: u64, + pub offset: u64, #[serde(rename = "total_rooms")] - total: u64, + pub total: u64, - next_batch: Option, + pub next_batch: Option, - prev_batch: Option, + pub prev_batch: Option, } #[derive(Clone, Default, Debug, Serialize)] diff --git a/crates/matrix/src/admin/session.rs b/crates/matrix/src/admin/session.rs index 88cabf2..84ee0f7 100644 --- a/crates/matrix/src/admin/session.rs +++ b/crates/matrix/src/admin/session.rs @@ -4,8 +4,8 @@ use hmac::Mac; -mod get_nonce; -mod register; +pub mod get_nonce; +pub mod register; #[derive(Clone, Debug)] pub struct Hmac { @@ -13,7 +13,7 @@ pub struct Hmac { } impl Hmac { - fn new( + pub fn new( shared_secret: &str, nonce: &str, username: &str, @@ -39,7 +39,7 @@ impl Hmac { }) } - fn get(&self) -> String { + pub fn get(&self) -> String { hex::encode(&self.inner) } } diff --git a/crates/matrix/src/admin/session/get_nonce.rs b/crates/matrix/src/admin/session/get_nonce.rs index d5f5c00..0388987 100644 --- a/crates/matrix/src/admin/session/get_nonce.rs +++ b/crates/matrix/src/admin/session/get_nonce.rs @@ -18,5 +18,5 @@ pub struct Request {} #[response(error = crate::Error)] pub struct Response { - nonce: String, + pub nonce: String, } diff --git a/crates/matrix/src/client/room/create_room.rs b/crates/matrix/src/client/room/create_room.rs index f27009a..140bfed 100644 --- a/crates/matrix/src/client/room/create_room.rs +++ b/crates/matrix/src/client/room/create_room.rs @@ -11,7 +11,7 @@ const METADATA: Metadata = metadata! { rate_limited: true, authentication: AccessToken, history: { - 1.9 => "/_matrix/client/v3/createRoom", + unstable => "/_matrix/client/v3/createRoom", } }; diff --git a/crates/matrix/src/lib.rs b/crates/matrix/src/lib.rs index ace15fb..1ae34a5 100644 --- a/crates/matrix/src/lib.rs +++ b/crates/matrix/src/lib.rs @@ -9,5 +9,61 @@ pub mod admin; pub mod client; +use async_trait::async_trait; +use bytes::{Bytes, BytesMut}; +use ruma_client::{DefaultConstructibleHttpClient, HttpClient, HttpClientExt}; + #[allow(unused_imports)] use ruma_common::api::error::MatrixError as Error; + +#[derive(Debug)] +pub struct Handle { + inner: reqwest::Client, +} + +impl Handle { + pub async fn new() { + self.send_matrix_request(, access_token, for_versions, request) + } + + pub async fn dispatch(&self) { + self.send_matrix_request(, access_token, for_versions, request) + } +} + +#[async_trait] +impl HttpClient for Handle { + type RequestBody = BytesMut; + type ResponseBody = Bytes; + type Error = reqwest::Error; + + async fn send_http_request( + &self, + req: http::Request, + ) -> Result, reqwest::Error> { + let req = req.map(|body| body.freeze()).try_into()?; + let mut res = self.inner.execute(req).await?; + + let mut http_builder = http::Response::builder() + .status(res.status()) + .version(res.version()); + std::mem::swap( + http_builder + .headers_mut() + .expect("http::response::Builder to be usable"), + res.headers_mut(), + ); + + Ok(http_builder + .body(res.bytes().await?) + .expect("http::Response construction to work")) + } +} + +impl DefaultConstructibleHttpClient for Handle { + fn default() -> Self { + Self { + inner: reqwest::Client::new(), + } + } +} diff --git a/crates/matrix/src/token/mod.rs b/crates/matrix/src/token/mod.rs deleted file mode 100644 index 24b2441..0000000 --- a/crates/matrix/src/token/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod shared_secret; diff --git a/crates/matrix/src/token/shared_secret.rs b/crates/matrix/src/token/shared_secret.rs deleted file mode 100644 index 1bba14c..0000000 --- a/crates/matrix/src/token/shared_secret.rs +++ /dev/null @@ -1,173 +0,0 @@ -//! [Shared-Secret Registration API](https://matrix-org.github.io/synapse/latest/admin_api/register_api.html#) -//! -//! # Important -//! -//! This API is disabled when MSC3861 is enabled. See [#15582](https://github.com/matrix-org/synapse/pull/15582) -//! -//! This API allows for the creation of users in an administrative and -//! non-interactive way. This is generally used for bootstrapping a Synapse -//! instance with administrator accounts. -//! -//! To authenticate yourself to the server, you will need both the shared secret -//! (registration_shared_secret in the homeserver configuration), and a one-time -//! nonce. If the registration shared secret is not configured, this API is not -//! enabled. - -use anyhow::Result; -use hex; -use hmac::{Hmac, Mac}; -use serde::{Deserialize, Serialize}; -use sha1::Sha1; - -use crate::{error::MatrixError, http::Client}; - -type HmacSha1 = Hmac; - -#[derive(Debug, Serialize, Deserialize)] -pub struct Nonce { - pub nonce: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct SharedSecretRegistrationDto { - pub nonce: String, - pub username: String, - pub displayname: Option, - pub password: String, - pub admin: bool, - /// The MAC is the hex digest output of the HMAC-SHA1 algorithm, with the - /// key being the shared secret and the content being the nonce, user, - /// password, either the string "admin" or "notadmin", and optionally the - /// user_type each separated by NULs. - pub mac: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct SharedSecretRegistration { - pub access_token: String, - pub user_id: String, - pub home_server: String, - pub device_id: String, -} - -impl SharedSecretRegistration { - /// Fetches the `Nonce` from the server. - /// - /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/register_api.html#shared-secret-registration - pub async fn get_nonce(client: &Client) -> Result { - let resp = client.get("/_synapse/admin/v1/register").await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// Creates the [`SharedSecretRegistration`] instance. - /// - /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/register_api.html#shared-secret-registration - pub async fn create(client: &Client, dto: SharedSecretRegistrationDto) -> Result { - let resp = client - .post_json("/_synapse/admin/v1/register", &dto) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - /// Generates the MAC. - /// - /// # Inspiration - /// - /// This implementation is inspired by the following Python code from the - /// Synapse documentation on `Shared-Secret Registration`. - /// - /// ```python - /// import hmac, hashlib - /// - /// def generate_mac(nonce, user, password, admin=False, user_type=None): - /// - /// mac = hmac.new( - /// key=shared_secret, - /// digestmod=hashlib.sha1, - /// ) - /// - /// mac.update(nonce.encode('utf8')) - /// mac.update(b"\x00") - /// mac.update(user.encode('utf8')) - /// mac.update(b"\x00") - /// mac.update(password.encode('utf8')) - /// mac.update(b"\x00") - /// mac.update(b"admin" if admin else b"notadmin") - /// if user_type: - /// mac.update(b"\x00") - /// mac.update(user_type.encode('utf8')) - /// - /// return mac.hexdigest() - /// ``` - /// [Source](https://matrix-org.github.io/synapse/latest/admin_api/register_api.html#shared-secret-registration) - pub fn generate_mac>( - shared_secret: S, - nonce: S, - user: S, - password: S, - admin: bool, - user_type: Option, - ) -> Result { - let mut mac = HmacSha1::new_from_slice(shared_secret.as_ref().as_bytes())?; - - mac.update(nonce.as_ref().as_bytes()); - mac.update(b"\x00"); - - mac.update(user.as_ref().as_bytes()); - mac.update(b"\x00"); - - mac.update(password.as_ref().as_bytes()); - mac.update(b"\x00"); - - if admin { - mac.update("admin".as_bytes()); - } else { - mac.update("notadmin".as_bytes()); - } - - if let Some(user_type) = user_type { - mac.update(b"\x00"); - mac.update(user_type.as_ref().as_bytes()); - } - - let result = mac.finalize(); - let code_bytes = result.into_bytes(); - - Ok(hex::encode(code_bytes)) - } -} - -#[cfg(test)] -mod test { - use super::SharedSecretRegistration; - - #[test] - fn generates_mac_accordingly() { - let want = "c272fb1c287c795ff5ce238c4dba57cf95db5eff"; - let have = SharedSecretRegistration::generate_mac( - "m@;wYOUOh0f:CH5XA65sJB1^q01~DmIriOysRImot,OR_vzN&B", - "1234567890", - "groot", - "imroot!1234", - true, - None, - ) - .unwrap(); - - assert_eq!(have, want); - } -}