client-api: Add custom variant to LoginInfo
This commit is contained in:
parent
1e48e97cf5
commit
e2386b7b64
@ -6,6 +6,8 @@ Breaking changes:
|
|||||||
* Make `r0::uiaa::ThirdpartyIdCredentials` an owned type and remove its `Incoming` equivalent
|
* Make `r0::uiaa::ThirdpartyIdCredentials` an owned type and remove its `Incoming` equivalent
|
||||||
* Previously, we had two fields of type `&'a [ThirdpartyIdCredentials<'a>]` and this kind of
|
* Previously, we had two fields of type `&'a [ThirdpartyIdCredentials<'a>]` and this kind of
|
||||||
nested borrowing can be very annoying
|
nested borrowing can be very annoying
|
||||||
|
* `LoginInfo` no longer implements `PartialEq` and `Eq` due to the custom variant that was added.
|
||||||
|
* `LoginInfo` converted to newtype variants.
|
||||||
|
|
||||||
Improvements:
|
Improvements:
|
||||||
|
|
||||||
@ -20,6 +22,10 @@ Improvements:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
* Add a `.data()` accessor method to `r0::uiaa::{AuthData, IncomingAuthData}`
|
* Add a `.data()` accessor method to `r0::uiaa::{AuthData, IncomingAuthData}`
|
||||||
|
* Allow to construct the custom `AuthData` variant with `IncomingAuthData::new` and then call
|
||||||
|
`IncomingAuthData::to_outgoing` on it.
|
||||||
|
* Add custom variant to `LoginInfo` which can be constructed with `IncomingLoginInfo::new` and
|
||||||
|
then call `IncomingLoginInfo::to_outgoing` on it.
|
||||||
|
|
||||||
# 0.12.3
|
# 0.12.3
|
||||||
|
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
//! [POST /_matrix/client/r0/login](https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-login)
|
//! [POST /_matrix/client/r0/login](https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-login)
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use ruma_api::ruma_api;
|
use ruma_api::ruma_api;
|
||||||
use ruma_identifiers::{DeviceId, DeviceIdBox, ServerNameBox, UserId};
|
use ruma_identifiers::{DeviceId, DeviceIdBox, ServerNameBox, UserId};
|
||||||
use ruma_serde::Outgoing;
|
use ruma_serde::{JsonObject, Outgoing};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{
|
||||||
|
de::{self, DeserializeOwned},
|
||||||
|
Deserialize, Deserializer, Serialize,
|
||||||
|
};
|
||||||
|
use serde_json::{value::RawValue as RawJsonValue, Value as JsonValue};
|
||||||
|
|
||||||
use crate::r0::uiaa::{IncomingUserIdentifier, UserIdentifier};
|
use crate::r0::uiaa::{IncomingUserIdentifier, UserIdentifier};
|
||||||
|
|
||||||
@ -77,26 +83,160 @@ impl Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The authentication mechanism.
|
/// The authentication mechanism.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Outgoing, Serialize)]
|
///
|
||||||
#[serde(tag = "type")]
|
/// To construct the custom `LoginInfo` variant you first have to construct
|
||||||
|
/// [`IncomingLoginInfo::new`] and then call [`IncomingLoginInfo::to_outgoing`] on it.
|
||||||
|
#[derive(Clone, Debug, Outgoing, Serialize)]
|
||||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
|
#[incoming_derive(!Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
pub enum LoginInfo<'a> {
|
pub enum LoginInfo<'a> {
|
||||||
/// An identifier and password are supplied to authenticate.
|
/// An identifier and password are supplied to authenticate.
|
||||||
#[serde(rename = "m.login.password")]
|
Password(Password<'a>),
|
||||||
Password {
|
|
||||||
/// Identification information for the user.
|
|
||||||
identifier: UserIdentifier<'a>,
|
|
||||||
|
|
||||||
/// The password.
|
|
||||||
password: &'a str,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Token-based login.
|
/// Token-based login.
|
||||||
#[serde(rename = "m.login.token")]
|
Token(Token<'a>),
|
||||||
Token {
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
_Custom(CustomLoginInfo<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IncomingLoginInfo {
|
||||||
|
/// Creates a new `IncomingLoginInfo` with the given `login_type` string, session and data.
|
||||||
|
///
|
||||||
|
/// Prefer to use the public variants of `IncomingLoginInfo` where possible; this constructor is
|
||||||
|
/// meant be used for unsupported authentication mechanisms only and does not allow setting
|
||||||
|
/// arbitrary data for supported ones.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if the `login_type` is known and serialization of `data` to the
|
||||||
|
/// corresponding `IncomingLoginInfo` variant fails.
|
||||||
|
pub fn new(login_type: &str, data: JsonObject) -> serde_json::Result<Self> {
|
||||||
|
Ok(match login_type {
|
||||||
|
"m.login.password" => Self::Password(serde_json::from_value(JsonValue::Object(data))?),
|
||||||
|
"m.login.token" => Self::Token(serde_json::from_value(JsonValue::Object(data))?),
|
||||||
|
_ => Self::_Custom(IncomingCustomLoginInfo {
|
||||||
|
login_type: login_type.into(),
|
||||||
|
extra: data,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert `IncomingLoginInfo` to `LoginInfo`.
|
||||||
|
pub fn to_outgoing(&self) -> LoginInfo<'_> {
|
||||||
|
match self {
|
||||||
|
Self::Password(a) => LoginInfo::Password(a.to_outgoing()),
|
||||||
|
Self::Token(a) => LoginInfo::Token(a.to_outgoing()),
|
||||||
|
Self::_Custom(a) => {
|
||||||
|
LoginInfo::_Custom(CustomLoginInfo { login_type: &a.login_type, extra: &a.extra })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for IncomingLoginInfo {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
fn from_raw_json_value<T: DeserializeOwned, E: de::Error>(
|
||||||
|
raw: &RawJsonValue,
|
||||||
|
) -> Result<T, E> {
|
||||||
|
serde_json::from_str(raw.get()).map_err(E::custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
let json = Box::<RawJsonValue>::deserialize(deserializer)?;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ExtractType<'a> {
|
||||||
|
#[serde(borrow, rename = "type")]
|
||||||
|
login_type: Cow<'a, str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let login_type = serde_json::from_str::<ExtractType<'_>>(json.get())
|
||||||
|
.map_err(de::Error::custom)?
|
||||||
|
.login_type;
|
||||||
|
|
||||||
|
match &*login_type {
|
||||||
|
"m.login.password" => from_raw_json_value(&json).map(Self::Password),
|
||||||
|
"m.login.token" => from_raw_json_value(&json).map(Self::Token),
|
||||||
|
_ => from_raw_json_value(&json).map(Self::_Custom),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An identifier and password to supply as authentication.
|
||||||
|
#[derive(Clone, Debug, Outgoing, Serialize)]
|
||||||
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
|
#[serde(tag = "type", rename = "m.login.password")]
|
||||||
|
pub struct Password<'a> {
|
||||||
|
/// Identification information for the user.
|
||||||
|
pub identifier: UserIdentifier<'a>,
|
||||||
|
|
||||||
|
/// The password.
|
||||||
|
pub password: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Password<'a> {
|
||||||
|
/// Creates a new `Password` with the given identifier and password.
|
||||||
|
pub fn new(identifier: UserIdentifier<'a>, password: &'a str) -> Self {
|
||||||
|
Self { identifier, password }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IncomingPassword {
|
||||||
|
/// Convert `IncomingPassword` to `Password`.
|
||||||
|
fn to_outgoing(&self) -> Password<'_> {
|
||||||
|
Password { identifier: self.identifier.to_outgoing(), password: &self.password }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A token to supply as authentication.
|
||||||
|
#[derive(Clone, Debug, Outgoing, Serialize)]
|
||||||
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
|
#[serde(tag = "type", rename = "m.login.token")]
|
||||||
|
pub struct Token<'a> {
|
||||||
/// The token.
|
/// The token.
|
||||||
token: &'a str,
|
pub token: &'a str,
|
||||||
},
|
}
|
||||||
|
|
||||||
|
impl<'a> Token<'a> {
|
||||||
|
/// Creates a new `Token` with the given token.
|
||||||
|
pub fn new(token: &'a str) -> Self {
|
||||||
|
Self { token }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IncomingToken {
|
||||||
|
/// Convert `IncomingToken` to `Token`.
|
||||||
|
fn to_outgoing(&self) -> Token<'_> {
|
||||||
|
Token { token: &self.token }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct CustomLoginInfo<'a> {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
login_type: &'a str,
|
||||||
|
#[serde(flatten)]
|
||||||
|
extra: &'a JsonObject,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct IncomingCustomLoginInfo {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
login_type: String,
|
||||||
|
#[serde(flatten)]
|
||||||
|
extra: JsonObject,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Outgoing for CustomLoginInfo<'_> {
|
||||||
|
type Incoming = IncomingCustomLoginInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Client configuration provided by the server.
|
/// Client configuration provided by the server.
|
||||||
@ -154,7 +294,7 @@ mod tests {
|
|||||||
use matches::assert_matches;
|
use matches::assert_matches;
|
||||||
use serde_json::{from_value as from_json_value, json};
|
use serde_json::{from_value as from_json_value, json};
|
||||||
|
|
||||||
use super::IncomingLoginInfo;
|
use super::{IncomingLoginInfo, IncomingPassword, IncomingToken};
|
||||||
use crate::r0::uiaa::IncomingUserIdentifier;
|
use crate::r0::uiaa::IncomingUserIdentifier;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -169,7 +309,7 @@ mod tests {
|
|||||||
"password": "ilovebananas"
|
"password": "ilovebananas"
|
||||||
}))
|
}))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
IncomingLoginInfo::Password { identifier: IncomingUserIdentifier::MatrixId(user), password }
|
IncomingLoginInfo::Password(IncomingPassword { identifier: IncomingUserIdentifier::MatrixId(user), password })
|
||||||
if user == "cheeky_monkey" && password == "ilovebananas"
|
if user == "cheeky_monkey" && password == "ilovebananas"
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -179,7 +319,7 @@ mod tests {
|
|||||||
"token": "1234567890abcdef"
|
"token": "1234567890abcdef"
|
||||||
}))
|
}))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
IncomingLoginInfo::Token { token }
|
IncomingLoginInfo::Token(IncomingToken { token })
|
||||||
if token == "1234567890abcdef"
|
if token == "1234567890abcdef"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -191,11 +331,11 @@ mod tests {
|
|||||||
use ruma_common::thirdparty::Medium;
|
use ruma_common::thirdparty::Medium;
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
use super::{LoginInfo, Request};
|
use super::{LoginInfo, Password, Request, Token};
|
||||||
use crate::r0::uiaa::UserIdentifier;
|
use crate::r0::uiaa::UserIdentifier;
|
||||||
|
|
||||||
let req: http::Request<Vec<u8>> = Request {
|
let req: http::Request<Vec<u8>> = Request {
|
||||||
login_info: LoginInfo::Token { token: "0xdeadbeef" },
|
login_info: LoginInfo::Token(Token { token: "0xdeadbeef" }),
|
||||||
device_id: None,
|
device_id: None,
|
||||||
initial_device_display_name: Some("test"),
|
initial_device_display_name: Some("test"),
|
||||||
}
|
}
|
||||||
@ -213,13 +353,13 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let req: http::Request<Vec<u8>> = Request {
|
let req: http::Request<Vec<u8>> = Request {
|
||||||
login_info: LoginInfo::Password {
|
login_info: LoginInfo::Password(Password {
|
||||||
identifier: UserIdentifier::ThirdPartyId {
|
identifier: UserIdentifier::ThirdPartyId {
|
||||||
address: "hello@example.com",
|
address: "hello@example.com",
|
||||||
medium: Medium::Email,
|
medium: Medium::Email,
|
||||||
},
|
},
|
||||||
password: "deadbeef",
|
password: "deadbeef",
|
||||||
},
|
}),
|
||||||
device_id: None,
|
device_id: None,
|
||||||
initial_device_display_name: Some("test"),
|
initial_device_display_name: Some("test"),
|
||||||
}
|
}
|
||||||
|
@ -294,7 +294,7 @@ impl IncomingAuthData {
|
|||||||
Self::RegistrationToken(a) => AuthData::RegistrationToken(a.to_outgoing()),
|
Self::RegistrationToken(a) => AuthData::RegistrationToken(a.to_outgoing()),
|
||||||
Self::FallbackAcknowledgement(a) => AuthData::FallbackAcknowledgement(a.to_outgoing()),
|
Self::FallbackAcknowledgement(a) => AuthData::FallbackAcknowledgement(a.to_outgoing()),
|
||||||
Self::_Custom(a) => AuthData::_Custom(CustomAuthData {
|
Self::_Custom(a) => AuthData::_Custom(CustomAuthData {
|
||||||
auth_type: a.auth_type.as_ref(),
|
auth_type: &a.auth_type,
|
||||||
session: a.session.as_deref(),
|
session: a.session.as_deref(),
|
||||||
extra: &a.extra,
|
extra: &a.extra,
|
||||||
}),
|
}),
|
||||||
@ -711,7 +711,7 @@ pub enum UserIdentifier<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IncomingUserIdentifier {
|
impl IncomingUserIdentifier {
|
||||||
fn to_outgoing(&self) -> UserIdentifier<'_> {
|
pub(crate) fn to_outgoing(&self) -> UserIdentifier<'_> {
|
||||||
match self {
|
match self {
|
||||||
Self::MatrixId(id) => UserIdentifier::MatrixId(id),
|
Self::MatrixId(id) => UserIdentifier::MatrixId(id),
|
||||||
Self::ThirdPartyId { address, medium } => {
|
Self::ThirdPartyId { address, medium } => {
|
||||||
|
@ -30,7 +30,7 @@ impl<C: HttpClient> Client<C> {
|
|||||||
let response = self
|
let response = self
|
||||||
.send_request(assign!(
|
.send_request(assign!(
|
||||||
login::Request::new(
|
login::Request::new(
|
||||||
LoginInfo::Password { identifier: UserIdentifier::MatrixId(user), password }
|
LoginInfo::Password(login::Password { identifier: UserIdentifier::MatrixId(user), password })
|
||||||
), {
|
), {
|
||||||
device_id,
|
device_id,
|
||||||
initial_device_display_name,
|
initial_device_display_name,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user