diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 04cfd010..5329f338 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -17,6 +17,7 @@ use std::collections::BTreeMap; use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; +use std::fmt::{self, Debug}; use std::path::Path; use std::result::Result as StdResult; use std::sync::Arc; @@ -66,8 +67,8 @@ pub struct Client { } #[cfg_attr(tarpaulin, skip)] -impl std::fmt::Debug for Client { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> StdResult<(), std::fmt::Error> { +impl Debug for Client { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> StdResult<(), fmt::Error> { write!(fmt, "Client {{ homeserver: {} }}", self.homeserver) } } @@ -106,8 +107,8 @@ pub struct ClientConfig { } #[cfg_attr(tarpaulin, skip)] -impl std::fmt::Debug for ClientConfig { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> StdResult<(), std::fmt::Error> { +impl Debug for ClientConfig { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { let mut res = fmt.debug_struct("ClientConfig"); #[cfg(not(target_arch = "wasm32"))] @@ -264,6 +265,7 @@ use api::r0::sync::sync_events; #[cfg(feature = "encryption")] use api::r0::to_device::send_event_to_device; use api::r0::typing::create_typing_event; +use api::r0::uiaa::UiaaResponse; impl Client { /// Creates a new client for making HTTP requests to the given homeserver. @@ -414,7 +416,7 @@ impl Client { /// device_id from a previous login call. Note that this should be done /// only if the client also holds the encryption keys for this device. #[instrument(skip(password))] - pub async fn login + std::fmt::Debug>( + pub async fn login + Debug>( &self, user: S, password: S, @@ -454,9 +456,24 @@ impl Client { /// /// * `registration` - The easiest way to create this request is using the `RegistrationBuilder`. /// + /// /// # Examples /// ``` - /// + /// # use std::convert::TryFrom; + /// # use matrix_sdk::{Client, RegistrationBuilder}; + /// # use matrix_sdk::api::r0::account::register::RegistrationKind; + /// # use matrix_sdk::identifiers::DeviceId; + /// # use url::Url; + /// # let homeserver = Url::parse("http://example.com").unwrap(); + /// # let mut rt = tokio::runtime::Runtime::new().unwrap(); + /// # rt.block_on(async { + /// let mut builder = RegistrationBuilder::default(); + /// builder.password("pass") + /// .username("user") + /// .kind(RegistrationKind::User); + /// let mut client = Client::new(homeserver).unwrap(); + /// client.register_user(builder).await; + /// # }) /// ``` #[instrument(skip(registration))] pub async fn register_user>( @@ -466,7 +483,7 @@ impl Client { info!("Registering to {}", self.homeserver); let request = registration.into(); - + println!("{:#?}", request); self.send_uiaa(request).await } @@ -896,7 +913,7 @@ impl Client { Ok(response) } - /// Send an arbitrary request to the server, without updating client state + /// Send an arbitrary request to the server, without updating client state. /// /// **Warning:** Because this method *does not* update the client state, it is /// important to make sure than you account for this yourself, and use wrapper methods @@ -934,7 +951,7 @@ impl Client { /// // returned /// # }) /// ``` - pub async fn send + std::fmt::Debug>( + pub async fn send + Debug>( &self, request: Request, ) -> Result { @@ -999,9 +1016,43 @@ impl Client { Ok(::try_from(http_response)?) } - async fn send_uiaa< - Request: Endpoint + std::fmt::Debug, - >( + // TODO I couldn't figure out a way to share code between these two send methods + // as they are essentially completely different types? + // + /// Send an arbitrary request to the server, without updating client state. + /// + /// This version allows the client to make registration requests. + /// + /// **Warning:** Because this method *does not* update the client state, it is + /// important to make sure than you account for this yourself, and use wrapper methods + /// where available. This method should *only* be used if a wrapper method for the + /// endpoint you'd like to use is not available. + /// + /// # Arguments + /// + /// * `request` - This version of send is for dealing with types that return + /// a `UiaaResponse` as the `Endpoint` associated type. + /// + /// # Examples + /// ``` + /// # use std::convert::TryFrom; + /// # use matrix_sdk::{Client, RegistrationBuilder}; + /// # use matrix_sdk::api::r0::account::register::{RegistrationKind, Request}; + /// # use matrix_sdk::identifiers::DeviceId; + /// # use url::Url; + /// # let homeserver = Url::parse("http://example.com").unwrap(); + /// # let mut rt = tokio::runtime::Runtime::new().unwrap(); + /// # rt.block_on(async { + /// let mut builder = RegistrationBuilder::default(); + /// builder.password("pass") + /// .username("user") + /// .kind(RegistrationKind::User); + /// let mut client = Client::new(homeserver).unwrap(); + /// let req: Request = builder.into(); + /// client.send_uiaa(req).await; + /// # }) + /// ``` + pub async fn send_uiaa + Debug>( &self, request: Request, ) -> Result { @@ -1363,14 +1414,16 @@ impl Client { #[cfg(test)] mod test { use super::{ - ban_user, create_receipt, create_typing_event, forget_room, invite_user, kick_user, - leave_room, set_read_marker, Invite3pid, MessageEventContent, + api::r0::uiaa::AuthData, ban_user, create_receipt, create_typing_event, forget_room, + invite_user, kick_user, leave_room, register::RegistrationKind, set_read_marker, + Invite3pid, MessageEventContent, }; use super::{Client, ClientConfig, Session, SyncSettings, Url}; use crate::events::collections::all::RoomEvent; use crate::events::room::member::MembershipState; use crate::events::room::message::TextMessageEventContent; use crate::identifiers::{EventId, RoomId, RoomIdOrAliasId, UserId}; + use crate::RegistrationBuilder; use matrix_sdk_base::JsonStore; use matrix_sdk_test::{EventBuilder, EventsFile}; @@ -1551,6 +1604,44 @@ mod test { } } + #[tokio::test] + async fn register_error() { + let homeserver = Url::from_str(&mockito::server_url()).unwrap(); + + let _m = mock("POST", "/_matrix/client/r0/register") + .with_status(403) + .with_body_from_file("../test_data/registration_response_error.json") + .create(); + + let mut user = RegistrationBuilder::default(); + + user.username("user") + .password("password") + .auth(AuthData::FallbackAcknowledgement { + session: "foobar".to_string(), + }) + .kind(RegistrationKind::User); + + let client = Client::new(homeserver).unwrap(); + + if let Err(err) = client.register_user(user).await { + if let crate::Error::UiaaError(crate::FromHttpResponseError::Http( + // TODO this should be a UiaaError need to investigate + crate::ServerError::Unknown(e), + )) = err + { + assert!(e.to_string().starts_with("EOF while parsing")) + } else { + panic!( + "found the wrong `Error` type {:#?}, expected `ServerError::Unknown", + err + ); + } + } else { + panic!("this request should return an `Err` variant") + } + } + #[tokio::test] async fn join_room_by_id() { let homeserver = Url::from_str(&mockito::server_url()).unwrap(); diff --git a/matrix_sdk/src/error.rs b/matrix_sdk/src/error.rs index 607f45d2..c293797a 100644 --- a/matrix_sdk/src/error.rs +++ b/matrix_sdk/src/error.rs @@ -51,12 +51,16 @@ pub enum Error { #[error("can't convert between ruma_client_api and hyper types.")] IntoHttp(RumaIntoHttpError), - /// An error occured in the Matrix client library. + /// An error occurred in the Matrix client library. #[error(transparent)] MatrixError(#[from] MatrixError), - /// An error occured in the Matrix client library. - #[error("can't convert between ruma_client_api and hyper types.")] + /// An error occurred while authenticating. + /// + /// When registering or authenticating the Matrix server can send a `UiaaResponse` + /// as the error type, this is a User-Interactive Authentication API response. This + /// represents an error with information about how to authenticate the user. + #[error("User-Interactive Authentication required.")] UiaaError(RumaResponseError), } diff --git a/matrix_sdk/src/request_builder.rs b/matrix_sdk/src/request_builder.rs index de37a507..e76b2644 100644 --- a/matrix_sdk/src/request_builder.rs +++ b/matrix_sdk/src/request_builder.rs @@ -2,6 +2,7 @@ use crate::api; use crate::events::room::power_levels::PowerLevelsEventContent; use crate::events::EventJson; use crate::identifiers::{DeviceId, RoomId, UserId}; +use api::r0::account::register; use api::r0::account::register::RegistrationKind; use api::r0::filter::RoomEventFilter; use api::r0::membership::Invite3pid; @@ -296,18 +297,16 @@ impl Into for MessagesRequestBuilder { /// ``` /// # use std::convert::TryFrom; /// # use matrix_sdk::{Client, RegistrationBuilder}; -/// # use api::r0::account::register::RegistrationKind; +/// # use matrix_sdk::api::r0::account::register::RegistrationKind; /// # use matrix_sdk::identifiers::DeviceId; /// # use url::Url; /// # let homeserver = Url::parse("http://example.com").unwrap(); /// # let mut rt = tokio::runtime::Runtime::new().unwrap(); /// # rt.block_on(async { /// let mut builder = RegistrationBuilder::default(); -/// builder.creation_content(false) -/// .initial_state(vec![]) -/// .visibility(Visibility::Public) -/// .name("name") -/// .room_version("v1.0"); +/// builder.password("pass") +/// .username("user") +/// .kind(RegistrationKind::User); /// let mut client = Client::new(homeserver).unwrap(); /// client.register_user(builder).await; /// # }) @@ -343,8 +342,8 @@ impl RegistrationBuilder { /// local part of the desired Matrix ID. /// /// If omitted, the homeserver MUST generate a Matrix ID local part. - pub fn username(&mut self, username: String) -> &mut Self { - self.username = Some(username); + pub fn username(&mut self, username: &str) -> &mut Self { + self.username = Some(username.to_string()); self } @@ -352,8 +351,8 @@ impl RegistrationBuilder { /// /// If this does not correspond to a known client device, a new device will be created. /// The server will auto-generate a device_id if this is not specified. - pub fn device_id(&mut self, device_id: String) -> &mut Self { - self.device_id = Some(device_id); + pub fn device_id(&mut self, device_id: &str) -> &mut Self { + self.device_id = Some(device_id.to_string()); self } @@ -392,6 +391,20 @@ impl RegistrationBuilder { } } +impl Into for RegistrationBuilder { + fn into(self) -> register::Request { + register::Request { + password: self.password, + username: self.username, + device_id: self.device_id, + initial_device_display_name: self.initial_device_display_name, + auth: self.auth, + kind: self.kind, + inhibit_login: self.inhibit_login, + } + } +} + #[cfg(test)] mod test { use std::collections::BTreeMap; diff --git a/test_data/registration_response_error.json b/test_data/registration_response_error.json new file mode 100644 index 00000000..b34244c1 --- /dev/null +++ b/test_data/registration_response_error.json @@ -0,0 +1,19 @@ +{ + "errcode": "M_FORBIDDEN", + "error": "Invalid password", + "completed": ["example.type.foo"], + "flows": [ + { + "stages": ["example.type.foo", "example.type.bar"] + }, + { + "stages": ["example.type.foo", "example.type.baz"] + } + ], + "params": { + "example.type.baz": { + "example_key": "foobar" + } + }, + "session": "xxxxxx" +}