From 5abac19b72b812231d3d0fa3fde9127dc3e24c43 Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 2 Jun 2020 07:55:24 -0400 Subject: [PATCH] request_builder/async_client: add register endpoint and RegistrationBuilder for making the request --- matrix_sdk/src/client.rs | 92 ++++++++++++++++++++++++++ matrix_sdk/src/error.rs | 11 ++++ matrix_sdk/src/lib.rs | 2 +- matrix_sdk/src/request_builder.rs | 106 +++++++++++++++++++++++++++++- 4 files changed, 209 insertions(+), 2 deletions(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index c88e097f..04cfd010 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -246,6 +246,7 @@ impl SyncSettings { } } +use api::r0::account::register; #[cfg(feature = "encryption")] use api::r0::keys::{claim_keys, get_keys, upload_keys, KeyAlgorithm}; use api::r0::membership::{ @@ -447,6 +448,28 @@ impl Client { Ok(self.base_client.restore_login(session).await?) } + /// Register a user to the server. + /// + /// # Arguments + /// + /// * `registration` - The easiest way to create this request is using the `RegistrationBuilder`. + /// + /// # Examples + /// ``` + /// + /// ``` + #[instrument(skip(registration))] + pub async fn register_user>( + &self, + registration: R, + ) -> Result { + info!("Registering to {}", self.homeserver); + + let request = registration.into(); + + self.send_uiaa(request).await + } + /// Join a room by `RoomId`. /// /// Returns a `join_room_by_id::Response` consisting of the @@ -976,6 +999,75 @@ impl Client { Ok(::try_from(http_response)?) } + async fn send_uiaa< + Request: Endpoint + std::fmt::Debug, + >( + &self, + request: Request, + ) -> Result { + let request: http::Request> = request.try_into()?; + let url = request.uri(); + let path_and_query = url.path_and_query().unwrap(); + let mut url = self.homeserver.clone(); + + url.set_path(path_and_query.path()); + url.set_query(path_and_query.query()); + + trace!("Doing request {:?}", url); + + let request_builder = match Request::METADATA.method { + HttpMethod::GET => self.http_client.get(url), + HttpMethod::POST => { + let body = request.body().clone(); + self.http_client + .post(url) + .body(body) + .header(reqwest::header::CONTENT_TYPE, "application/json") + } + HttpMethod::PUT => { + let body = request.body().clone(); + self.http_client + .put(url) + .body(body) + .header(reqwest::header::CONTENT_TYPE, "application/json") + } + HttpMethod::DELETE => unimplemented!(), + _ => panic!("Unsuported method"), + }; + + let request_builder = if Request::METADATA.requires_authentication { + let session = self.base_client.session().read().await; + + if let Some(session) = session.as_ref() { + let header_value = format!("Bearer {}", &session.access_token); + request_builder.header(AUTHORIZATION, header_value) + } else { + return Err(Error::AuthenticationRequired); + } + } else { + request_builder + }; + let mut response = request_builder.send().await?; + + trace!("Got response: {:?}", response); + + let status = response.status(); + let mut http_builder = HttpResponse::builder().status(status); + let headers = http_builder.headers_mut().unwrap(); + + for (k, v) in response.headers_mut().drain() { + if let Some(key) = k { + headers.insert(key, v); + } + } + let body = response.bytes().await?.as_ref().to_owned(); + let http_response = http_builder.body(body).unwrap(); + + let uiaa: Result<_> = ::try_from(http_response).map_err(Into::into); + + Ok(uiaa?) + } + /// Synchronize the client's state with the latest state on the server. /// /// If a `StateStore` is provided and this is the initial sync state will diff --git a/matrix_sdk/src/error.rs b/matrix_sdk/src/error.rs index 2670a989..607f45d2 100644 --- a/matrix_sdk/src/error.rs +++ b/matrix_sdk/src/error.rs @@ -20,6 +20,7 @@ use thiserror::Error; use matrix_sdk_base::Error as MatrixError; +use crate::api::r0::uiaa::UiaaResponse as UiaaError; use crate::api::Error as RumaClientError; use crate::FromHttpResponseError as RumaResponseError; use crate::IntoHttpError as RumaIntoHttpError; @@ -53,6 +54,16 @@ pub enum Error { /// An error occured 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.")] + UiaaError(RumaResponseError), +} + +impl From> for Error { + fn from(error: RumaResponseError) -> Self { + Self::UiaaError(error) + } } impl From> for Error { diff --git a/matrix_sdk/src/lib.rs b/matrix_sdk/src/lib.rs index 3834a7f4..579a74d7 100644 --- a/matrix_sdk/src/lib.rs +++ b/matrix_sdk/src/lib.rs @@ -51,7 +51,7 @@ mod error; mod request_builder; pub use client::{Client, ClientConfig, SyncSettings}; pub use error::{Error, Result}; -pub use request_builder::{MessagesRequestBuilder, RoomBuilder}; +pub use request_builder::{MessagesRequestBuilder, RegistrationBuilder, RoomBuilder}; #[cfg(not(target_arch = "wasm32"))] pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/matrix_sdk/src/request_builder.rs b/matrix_sdk/src/request_builder.rs index d7c52615..de37a507 100644 --- a/matrix_sdk/src/request_builder.rs +++ b/matrix_sdk/src/request_builder.rs @@ -1,7 +1,8 @@ use crate::api; use crate::events::room::power_levels::PowerLevelsEventContent; use crate::events::EventJson; -use crate::identifiers::{RoomId, UserId}; +use crate::identifiers::{DeviceId, RoomId, UserId}; +use api::r0::account::register::RegistrationKind; use api::r0::filter::RoomEventFilter; use api::r0::membership::Invite3pid; use api::r0::message::get_message_events::{self, Direction}; @@ -9,6 +10,7 @@ use api::r0::room::{ create_room::{self, CreationContent, InitialStateEvent, RoomPreset}, Visibility, }; +use api::r0::uiaa::AuthData; use crate::js_int::UInt; @@ -288,6 +290,108 @@ impl Into for MessagesRequestBuilder { } } +/// A builder used to register users. +/// +/// # Examples +/// ``` +/// # use std::convert::TryFrom; +/// # use matrix_sdk::{Client, RegistrationBuilder}; +/// # use 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"); +/// let mut client = Client::new(homeserver).unwrap(); +/// client.register_user(builder).await; +/// # }) +/// ``` +#[derive(Clone, Debug, Default)] +pub struct RegistrationBuilder { + password: Option, + username: Option, + device_id: Option, + initial_device_display_name: Option, + auth: Option, + kind: Option, + inhibit_login: bool, +} + +impl RegistrationBuilder { + /// Create a `RegistrationBuilder` builder to make a `register::Request`. + /// + /// The `room_id` and `from`` fields **need to be set** to create the request. + pub fn new() -> Self { + Self::default() + } + + /// The desired password for the account. + /// + /// May be empty for accounts that should not be able to log in again + /// with a password, e.g., for guest or application service accounts. + pub fn password(&mut self, password: &str) -> &mut Self { + self.password = Some(password.to_string()); + self + } + + /// 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); + self + } + + /// ID of the client device. + /// + /// 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); + self + } + + /// A display name to assign to the newly-created device. + /// + /// Ignored if `device_id` corresponds to a known device. + pub fn initial_device_display_name(&mut self, initial_device_display_name: &str) -> &mut Self { + self.initial_device_display_name = Some(initial_device_display_name.to_string()); + self + } + + /// Additional authentication information for the user-interactive authentication API. + /// + /// Note that this information is not used to define how the registered user should be + /// authenticated, but is instead used to authenticate the register call itself. + /// It should be left empty, or omitted, unless an earlier call returned an response + /// with status code 401. + pub fn auth(&mut self, auth: AuthData) -> &mut Self { + self.auth = Some(auth); + self + } + + /// Kind of account to register + /// + /// Defaults to `User` if omitted. + pub fn kind(&mut self, kind: RegistrationKind) -> &mut Self { + self.kind = Some(kind); + self + } + + /// If `true`, an `access_token` and `device_id` should not be returned + /// from this call, therefore preventing an automatic login. + pub fn inhibit_login(&mut self, inhibit_login: bool) -> &mut Self { + self.inhibit_login = inhibit_login; + self + } +} + #[cfg(test)] mod test { use std::collections::BTreeMap;