diff --git a/src/api/client.rs b/src/api/client.rs index c4a39c9..96af400 100644 --- a/src/api/client.rs +++ b/src/api/client.rs @@ -1,10 +1,10 @@ -use crate::api::methods::{login::ValidLoginFlows, sync::SyncResponse}; +use crate::api::methods::{login::*, sync::SyncResponse}; use reqwest::{ header::{HeaderMap, HeaderValue, CONTENT_TYPE, USER_AGENT}, Client as reqwest_client, Response, StatusCode, }; use serde::Deserialize; -use serde_json::Value; +use serde_json::{json, Value}; use std::{ collections::HashMap, error::Error, @@ -175,7 +175,7 @@ impl Client { &self, method: MatrixHTTPMethod, path: &str, - content: Option, + content: Option, query_params: Option>, headers: Option, ) -> Result> { @@ -216,7 +216,7 @@ impl Client { request = request.headers(headers).query(&query_params); if let Some(content) = content { - request = request.body(content); + request = request.body(content.to_string()); } loop { @@ -302,6 +302,7 @@ impl Client { // Holds implementation for Section 5.4, Login impl Client { + /// Implementation of [Section 5.4.1 **GET** `/_matrix/client/r0/login`](https://matrix.org/docs/spec/client_server/r0.5.0#get-matrix-client-r0-login). pub fn login_flows(&self) -> Result> { Ok(self .send( @@ -313,6 +314,92 @@ impl Client { )? .json()?) } + + /// Implementation of password-based login for + /// [5.4.2 **POST** `/_matrix/client/r0/login`](https://matrix.org/docs/spec/client_server/r0.5.0#post-matrix-client-r0-login) + /// + /// If a specific device ID is specified, then the server will attempt to + /// use it. If an existing device ID is used, then any previously assigned + /// access tokens to that device ID may be invalidated. If the device ID + /// does not exist, then it will be created. If none is provided, then the + /// server will generate one. + /// + /// You may specify a device display name if the provided device ID is not + /// known. It is ignored if the device ID already exists. + pub fn login_password( + &self, + identifier: IdentifierType, + password: &str, + device_id: Option<&str>, + initial_device_display_name: Option<&str>, + ) -> Result> { + self.login( + json!({ + "type": "m.login.password", + "password": password, + }), + identifier, + device_id, + initial_device_display_name, + ) + } + + /// Implementation of token-based login for + /// [5.4.2 **POST** `/_matrix/client/r0/login`](https://matrix.org/docs/spec/client_server/r0.5.0#post-matrix-client-r0-login) + /// + /// If a specific device ID is specified, then the server will attempt to + /// use it. If an existing device ID is used, then any previously assigned + /// access tokens to that device ID may be invalidated. If the device ID + /// does not exist, then it will be created. If none is provided, then the + /// server will generate one. + /// + /// You may specify a device display name if the provided device ID is not + /// known. It is ignored if the device ID already exists. + pub fn login_token( + &self, + identifier: IdentifierType, + password: &str, + device_id: Option<&str>, + initial_device_display_name: Option<&str>, + ) -> Result> { + self.login( + json!({ + "type": "m.login.token", + "token": password, + }), + identifier, + device_id, + initial_device_display_name, + ) + } + + fn login( + &self, + body: Value, + identifier: IdentifierType, + device_id: Option<&str>, + initial_device_display_name: Option<&str>, + ) -> Result> { + let mut body = body; + body["identifier"] = json!(identifier); + + if let Some(id) = device_id { + body["device_id"] = json!(id); + if let Some(display_name) = initial_device_display_name { + body["initial_device_display_name"] = json!(display_name); + } + } + + Ok(self + .send( + MatrixHTTPMethod::Post, + "/_matrix/client/r0/login", + Some(body), + None, + None, + )? + .json()?) + } } #[cfg(test)] @@ -395,9 +482,10 @@ mod tests { assert_eq!(dbg!(resp)["hello"], "world"); }); + // Override 429 response with valid response after initial request + // returns a 429. This should really be done with a synchronization + // primitive (e.g. condvar) but I don't know how to do that. thread::sleep(time::Duration::from_secs(1)); - - // Override 429 response with valid response. let _m = mock("GET", "/hello") .with_body(r#"{"hello": "world"}"#) .create(); diff --git a/src/api/methods/login.rs b/src/api/methods/login.rs index 5b0df3f..fed85fd 100644 --- a/src/api/methods/login.rs +++ b/src/api/methods/login.rs @@ -1,20 +1,34 @@ -use serde::Deserialize; +use serde::{Deserialize, Serialize}; + +// 5.4.1 GET /_matrix/client/r0/login /// Response for 5.4.1: GET /_matrix/client/r0/login #[derive(Deserialize)] pub struct ValidLoginFlows { pub flows: Option, } - #[derive(Deserialize)] pub struct LoginFlow { pub r#type: Option, } -/// Request helper for 5.4.2: POST /_matrix/client/r0/login -pub struct LoginRequest {} +// 5.4.2 POST /_matrix/client/r0/login -/// Response object for a valid login from 5.4.2: POST /_matrix/client/r0/login +/// Known identifier types for authentication methods. See [Section 5.3.6](https://matrix.org/docs/spec/client_server/r0.5.0#identifier-types) +/// for more details. +#[derive(Deserialize, Serialize)] +pub enum IdentifierType { + #[serde(rename = "m.id.user")] + User, + #[serde(rename = "m.id.thirdparty")] + ThirdParty, + #[serde(rename = "m.id.phone")] + Phone +} + + +/// Response for a valid login from 5.4.2: POST /_matrix/client/r0/login +#[derive(Deserialize)] pub struct LoginResponse { pub user_id: Option, pub access_token: Option, @@ -23,4 +37,5 @@ pub struct LoginResponse { pub well_known: Option, } +#[derive(Deserialize)] pub struct DiscoveryInformation {}