From e6b347f90fddd6fcab2c9809dcbdddc1a745a81e Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Mon, 3 Feb 2025 22:34:13 +0100 Subject: [PATCH] Can get current user identity --- Cargo.lock | 382 ++++++++++++++++++++++++++++-- Cargo.toml | 5 +- src/app_config.rs | 2 +- src/extractors/client_auth.rs | 24 +- src/main.rs | 1 + src/server/api/account.rs | 23 ++ src/server/{api.rs => api/mod.rs} | 2 + src/server/mod.rs | 7 +- src/user.rs | 18 ++ 9 files changed, 441 insertions(+), 23 deletions(-) create mode 100644 src/server/api/account.rs rename src/server/{api.rs => api/mod.rs} (93%) diff --git a/Cargo.lock b/Cargo.lock index fb2b556..53e72a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -390,6 +390,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "as_variant" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38fa22307249f86fb7fad906fcae77f2564caeb56d7209103c551cd1cf4798f" + [[package]] name = "askama" version = "0.12.1" @@ -434,6 +440,34 @@ dependencies = [ "nom", ] +[[package]] +name = "assign" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f093eed78becd229346bf859eec0aa4dd7ddde0757287b2b4107a1f09c80002" + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-trait" version = "0.1.85" @@ -800,6 +834,12 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "const_panic" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2459fc9262a1aa204eb4b5764ad4f189caec88aea9634389c0a25f8be7f6265e" + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -919,6 +959,12 @@ dependencies = [ "cipher", ] +[[package]] +name = "date_header" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c03c416ed1a30fbb027ef484ba6ab6f80e1eada675e1a2b92fd673c045a1f1d" + [[package]] name = "der" version = "0.7.9" @@ -1281,16 +1327,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.0-rc.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a78f88e84d239c7f2619ae8b091603c26208e1cb322571f5a29d6806f56ee5e" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" dependencies = [ "cfg-if", - "js-sys", "libc", - "rustix", "wasi 0.13.3+wasi-0.2.2", - "wasm-bindgen", "windows-targets", ] @@ -1763,6 +1806,7 @@ checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown 0.15.2", + "serde", ] [[package]] @@ -1814,6 +1858,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "js_int" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d937f95470b270ce8b8950207715d71aa8e153c0d44c6684d59397ed4949160a" +dependencies = [ + "serde", +] + +[[package]] +name = "js_option" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68421373957a1593a767013698dbf206e2b221eefe97a44d98d18672ff38423c" +dependencies = [ + "serde", +] + [[package]] name = "jwt-simple" version = "0.12.11" @@ -1854,6 +1916,26 @@ dependencies = [ "signature", ] +[[package]] +name = "konst" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4381b9b00c55f251f2ebe9473aef7c117e96828def1a7cb3bd3f0f903c6894e9" +dependencies = [ + "const_panic", + "konst_kernel", + "typewit", +] + +[[package]] +name = "konst_kernel" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4b1eb7788f3824c629b1116a7a9060d6e898c358ebff59070093d51103dcc3c" +dependencies = [ + "typewit", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -1940,6 +2022,12 @@ version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "matrix_gateway" version = "0.1.0" @@ -1961,7 +2049,8 @@ dependencies = [ "light-openid", "log", "mime_guess", - "rand 0.9.0-beta.3", + "rand 0.9.0", + "ruma", "rust-embed", "rust-s3", "serde", @@ -2375,6 +2464,15 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.93" @@ -2426,12 +2524,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0-beta.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fccbfebb3972a41a31c605a59207d9fba5489b9a87d9d87024cb6df73a32ec7" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ - "rand_chacha 0.9.0-beta.1", - "rand_core 0.9.0-beta.1", + "rand_chacha 0.9.0", + "rand_core 0.9.0", "zerocopy 0.8.14", ] @@ -2447,12 +2545,12 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.9.0-beta.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16da77124f4ee9fabd55ce6540866e9101431863b4876de58b68797f331adf2" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.0-beta.1", + "rand_core 0.9.0", ] [[package]] @@ -2466,11 +2564,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.0-beta.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98fa0b8309344136abe6244130311e76997e546f76fae8054422a7539b43df7" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" dependencies = [ - "getrandom 0.3.0-rc.0", + "getrandom 0.3.1", "zerocopy 0.8.14", ] @@ -2633,6 +2731,149 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ruma" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d6fea33e3d17b9e009fefb3f175ca7fd40b1e7d1e72444478fd1b28611eb50a" +dependencies = [ + "assign", + "js_int", + "js_option", + "ruma-client", + "ruma-client-api", + "ruma-common", + "ruma-events", + "web-time", +] + +[[package]] +name = "ruma-client" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df765f1917f28ef0bf307b19c2c845be4fc2bb77f76e00b1eafbfa8921f7952" +dependencies = [ + "as_variant", + "assign", + "async-stream", + "bytes", + "futures-core", + "http 1.2.0", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "ruma-client-api", + "ruma-common", + "serde_html_form", + "tracing", +] + +[[package]] +name = "ruma-client-api" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23989b539eceeaad01ba089ad307788f90a29bac2e5f730ff0a523eeae3fa1d7" +dependencies = [ + "as_variant", + "assign", + "bytes", + "date_header", + "http 1.2.0", + "js_int", + "js_option", + "maplit", + "ruma-common", + "ruma-events", + "serde", + "serde_html_form", + "serde_json", + "thiserror 2.0.11", + "url", + "web-time", +] + +[[package]] +name = "ruma-common" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1058c04b8dd62f4fba71c9f65112fb79bc332438d11aefe1e8edf67b7fb58a98" +dependencies = [ + "as_variant", + "base64 0.22.1", + "bytes", + "form_urlencoded", + "http 1.2.0", + "indexmap", + "js_int", + "konst", + "percent-encoding", + "rand 0.8.5", + "regex", + "ruma-identifiers-validation", + "ruma-macros", + "serde", + "serde_html_form", + "serde_json", + "thiserror 2.0.11", + "time", + "tracing", + "url", + "uuid", + "web-time", + "wildmatch", +] + +[[package]] +name = "ruma-events" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1b8e15942e35ba56004429bc0845f481281f903e86957973a08ec08f8d06f0" +dependencies = [ + "as_variant", + "indexmap", + "js_int", + "js_option", + "percent-encoding", + "regex", + "ruma-common", + "ruma-identifiers-validation", + "ruma-macros", + "serde", + "serde_json", + "thiserror 2.0.11", + "tracing", + "url", + "web-time", + "wildmatch", +] + +[[package]] +name = "ruma-identifiers-validation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ad674b5e5368c53a2c90fde7dac7e30747004aaf7b1827b72874a25fc06d4d8" +dependencies = [ + "js_int", + "thiserror 2.0.11", +] + +[[package]] +name = "ruma-macros" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1182e83ee5cd10121974f163337b16af68a93eedfc7cdbdbd52307ac7e1d743" +dependencies = [ + "cfg-if", + "proc-macro-crate", + "proc-macro2", + "quote", + "ruma-identifiers-validation", + "serde", + "syn", + "toml", +] + [[package]] name = "rust-embed" version = "8.5.0" @@ -2897,6 +3138,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_html_form" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2de91cf02bbc07cde38891769ccd5d4f073d22a40683aa4bc7a95781aaa2c4" +dependencies = [ + "form_urlencoded", + "indexmap", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_json" version = "1.0.137" @@ -2909,6 +3163,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3285,6 +3548,40 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.5.2" @@ -3320,9 +3617,21 @@ checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.33" @@ -3350,6 +3659,21 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "typewit" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb77c29baba9e4d3a6182d51fa75e3215c7fd1dab8f4ea9d107c716878e55fc0" +dependencies = [ + "typewit_proc_macros", +] + +[[package]] +name = "typewit_proc_macros" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" + [[package]] name = "unicase" version = "2.8.1" @@ -3393,6 +3717,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -3578,6 +3903,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wildmatch" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ce1ab1f8c62655ebe1350f589c61e505cf94d385bc6a12899442d9081e71fd" + [[package]] name = "winapi-util" version = "0.1.9" @@ -3708,6 +4049,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen-rt" version = "0.33.0" diff --git a/Cargo.toml b/Cargo.toml index 769eca6..cb42f5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ actix-web = "4" actix-session = { version = "0.10.1", features = ["redis-session"] } light-openid = "1.0.2" thiserror = "2.0.11" -rand = "0.9.0-beta.3" +rand = "0.9.0" rust-embed = "8.5.0" mime_guess = "2.0.5" askama = "0.12.1" @@ -29,4 +29,5 @@ jwt-simple = { version = "0.12.11", default-features=false, features=["pure-rust actix-remote-ip = "0.1.0" bytes = "1.9.0" sha2 = "0.11.0-pre.4" -base16ct = "0.2.0" \ No newline at end of file +base16ct = "0.2.0" +ruma = { version = "0.12.0", features = ["client-api-c", "client-ext-client-api", "client-hyper-native-tls", "rand"] } \ No newline at end of file diff --git a/src/app_config.rs b/src/app_config.rs index 8eb8789..c809113 100644 --- a/src/app_config.rs +++ b/src/app_config.rs @@ -20,7 +20,7 @@ pub struct AppConfig { /// Matrix API origin #[clap(short, long, env, default_value = "http://127.0.0.1:8448")] - pub matrix_api: String, + pub matrix_homeserver: String, /// Redis connection hostname #[clap(long, env, default_value = "localhost")] diff --git a/src/extractors/client_auth.rs b/src/extractors/client_auth.rs index 2321361..468b1e6 100644 --- a/src/extractors/client_auth.rs +++ b/src/extractors/client_auth.rs @@ -1,4 +1,5 @@ -use crate::user::{APIClient, APIClientID, UserConfig, UserID}; +use crate::server::HttpFailure; +use crate::user::{APIClient, APIClientID, RumaClient, UserConfig, UserID}; use crate::utils::curr_time; use actix_remote_ip::RemoteIP; use actix_web::dev::Payload; @@ -6,13 +7,14 @@ use actix_web::{FromRequest, HttpRequest}; use bytes::Bytes; use jwt_simple::common::VerificationOptions; use jwt_simple::prelude::{Duration, HS256Key, MACLike}; +use ruma::api::{IncomingResponse, OutgoingRequest}; use sha2::{Digest, Sha256}; use std::net::IpAddr; use std::str::FromStr; pub struct APIClientAuth { pub user: UserConfig, - client: APIClient, + pub client: APIClient, pub payload: Option>, } @@ -94,7 +96,7 @@ impl APIClientAuth { // Decode JWT let key = HS256Key::from_bytes(client.secret.as_bytes()); let mut verif = VerificationOptions::default(); - verif.max_validity = Some(Duration::from_mins(15)); + verif.max_validity = Some(Duration::from_mins(20)); let claims = match key.verify_token::(jwt_token, Some(verif)) { Ok(t) => t, Err(e) => { @@ -178,6 +180,22 @@ impl APIClientAuth { user, }) } + + /// Get an instance of Matrix client + pub async fn client(&self) -> anyhow::Result { + self.user.matrix_client().await + } + + /// Send request to matrix server + pub async fn send_request, E: IncomingResponse>( + &self, + request: R, + ) -> anyhow::Result { + match self.client().await?.send_request(request).await { + Ok(e) => Ok(e), + Err(e) => Err(HttpFailure::MatrixClientError(e.to_string())), + } + } } impl FromRequest for APIClientAuth { diff --git a/src/main.rs b/src/main.rs index 55a3e65..cb7d58a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,6 +48,7 @@ async fn main() -> std::io::Result<()> { // API routes .route("/api", web::get().to(api::api_home)) .route("/api", web::post().to(api::api_home)) + .route("/api/account/whoami", web::get().to(api::account::who_am_i)) }) .bind(&AppConfig::get().listen_address)? .run() diff --git a/src/server/api/account.rs b/src/server/api/account.rs new file mode 100644 index 0000000..36d4731 --- /dev/null +++ b/src/server/api/account.rs @@ -0,0 +1,23 @@ +use crate::extractors::client_auth::APIClientAuth; +use crate::server::HttpResult; +use actix_web::HttpResponse; +use ruma::api::client::account; +use ruma::DeviceId; + +#[derive(serde::Serialize)] +struct WhoAmIResponse { + user_id: String, + device_id: Option, +} + +/// Get current user identity +pub async fn who_am_i(auth: APIClientAuth) -> HttpResult { + let res = auth + .send_request(account::whoami::v3::Request::default()) + .await?; + + Ok(HttpResponse::Ok().json(WhoAmIResponse { + user_id: res.user_id.to_string(), + device_id: res.device_id.as_deref().map(DeviceId::to_string), + })) +} diff --git a/src/server/api.rs b/src/server/api/mod.rs similarity index 93% rename from src/server/api.rs rename to src/server/api/mod.rs index 5f41922..499de08 100644 --- a/src/server/api.rs +++ b/src/server/api/mod.rs @@ -2,6 +2,8 @@ use crate::extractors::client_auth::APIClientAuth; use crate::server::HttpResult; use actix_web::HttpResponse; +pub mod account; + /// API Home route pub async fn api_home(auth: APIClientAuth) -> HttpResult { Ok(HttpResponse::Ok().body(format!("Welcome user {}!", auth.user.user_id.0))) diff --git a/src/server/mod.rs b/src/server/mod.rs index 5fbdb77..9833631 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,6 +1,7 @@ use actix_web::http::StatusCode; use actix_web::{HttpResponse, ResponseError}; use std::error::Error; +use std::fmt::Debug; pub mod api; pub mod web_ui; @@ -21,6 +22,10 @@ pub enum HttpFailure { FetchUserConfig(anyhow::Error), #[error("an unspecified internal error occurred: {0}")] InternalError(#[from] anyhow::Error), + #[error("a matrix api client error occurred: {0}")] + MatrixApiClientError(#[from] ruma::api::client::Error), + #[error("a matrix client error occurred: {0}")] + MatrixClientError(String), } impl ResponseError for HttpFailure { @@ -37,4 +42,4 @@ impl ResponseError for HttpFailure { } } -pub type HttpResult = std::result::Result; +pub type HttpResult = Result; diff --git a/src/user.rs b/src/user.rs index c995753..f2c035e 100644 --- a/src/user.rs +++ b/src/user.rs @@ -8,10 +8,15 @@ use crate::app_config::AppConfig; use crate::constants::TOKEN_LEN; use crate::utils::{curr_time, format_time, rand_str}; +type HttpClient = ruma::client::http_client::HyperNativeTls; +pub type RumaClient = ruma::Client; + #[derive(Error, Debug)] pub enum UserError { #[error("failed to fetch user configuration: {0}")] FetchUserConfig(S3Error), + #[error("missing matrix token")] + MissingMatrixToken, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] @@ -220,4 +225,17 @@ impl UserConfig { pub fn find_client_by_id_mut(&mut self, id: &APIClientID) -> Option<&mut APIClient> { self.clients.iter_mut().find(|c| &c.id == id) } + + /// Get a matrix client instance for the current user + pub async fn matrix_client(&self) -> anyhow::Result { + if self.matrix_token.is_empty() { + return Err(UserError::MissingMatrixToken.into()); + } + + Ok(ruma::Client::builder() + .homeserver_url(AppConfig::get().matrix_homeserver.to_string()) + .access_token(Some(self.matrix_token.clone())) + .build() + .await?) + } }