//! Module for [User-Interactive Authentication API][uiaa] types. //! //! [uiaa]: https://matrix.org/docs/spec/client_server/r0.6.1#user-interactive-authentication-api use std::{borrow::Cow, fmt}; use bytes::BufMut; use ruma_api::{ error::{IntoHttpError, ResponseDeserializationError}, EndpointError, OutgoingResponse, }; use ruma_common::thirdparty::Medium; use ruma_identifiers::{ClientSecret, SessionId}; use ruma_serde::{JsonObject, Outgoing, StringEnum}; use serde::{ de::{self, DeserializeOwned}, Deserialize, Deserializer, Serialize, }; use serde_json::{ from_slice as from_json_slice, value::RawValue as RawJsonValue, Value as JsonValue, }; use crate::error::{Error as MatrixError, ErrorBody}; pub mod authorize_fallback; mod user_serde; /// Information for one authentication stage. /// /// To construct the custom `AuthData` variant you first have to construct [`IncomingAuthData::new`] /// and then call [`IncomingAuthData::to_outgoing`] on it. #[derive(Clone, Debug, Outgoing, Serialize)] #[non_exhaustive] #[incoming_derive(!Deserialize)] #[serde(untagged)] pub enum AuthData<'a> { /// Password-based authentication (`m.login.password`). Password(Password<'a>), /// Google ReCaptcha 2.0 authentication (`m.login.recaptcha`). ReCaptcha(ReCaptcha<'a>), /// Token-based authentication (`m.login.token`). Token(Token<'a>), /// OAuth2-based authentication (`m.login.oauth2`). OAuth2(OAuth2<'a>), /// Email-based authentication (`m.login.email.identity`). EmailIdentity(EmailIdentity<'a>), /// Phone number-based authentication (`m.login.msisdn`). Msisdn(Msisdn<'a>), /// Dummy authentication (`m.login.dummy`). Dummy(Dummy<'a>), /// Registration token-based authentication (`org.matrix.msc3231.login.registration_token`). #[cfg(feature = "unstable-pre-spec")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] RegistrationToken(RegistrationToken<'a>), /// Fallback acknowledgement. FallbackAcknowledgement(FallbackAcknowledgement<'a>), #[doc(hidden)] _Custom(CustomAuthData<'a>), } impl<'a> AuthData<'a> { /// Creates a new `AuthData::FallbackAcknowledgement` with the given session key. pub fn fallback_acknowledgement(session: &'a str) -> Self { Self::FallbackAcknowledgement(FallbackAcknowledgement::new(session)) } /// Returns the value of the `type` field, if it exists. pub fn auth_type(&self) -> Option { match self { Self::Password(_) => Some(AuthType::Password), Self::ReCaptcha(_) => Some(AuthType::ReCaptcha), Self::Token(_) => Some(AuthType::Token), Self::OAuth2(_) => Some(AuthType::OAuth2), Self::EmailIdentity(_) => Some(AuthType::EmailIdentity), Self::Msisdn(_) => Some(AuthType::Msisdn), Self::Dummy(_) => Some(AuthType::Dummy), #[cfg(feature = "unstable-pre-spec")] Self::RegistrationToken(_) => Some(AuthType::RegistrationToken), Self::FallbackAcknowledgement(_) => None, Self::_Custom(c) => Some(AuthType::_Custom(c.auth_type.to_owned())), } } /// Returns the value of the `session` field, if it exists. pub fn session(&self) -> Option<&'a str> { match self { Self::Password(x) => x.session, Self::ReCaptcha(x) => x.session, Self::Token(x) => x.session, Self::OAuth2(x) => x.session, Self::EmailIdentity(x) => x.session, Self::Msisdn(x) => x.session, Self::Dummy(x) => x.session, #[cfg(feature = "unstable-pre-spec")] Self::RegistrationToken(x) => x.session, Self::FallbackAcknowledgement(x) => Some(x.session), Self::_Custom(x) => x.session, } } /// Returns the associated data. /// /// The returned JSON object won't contain the `type` and `session` fields, use /// [`.auth_type()`][Self::auth_type] / [`.session()`](Self::session) to access those. /// /// Prefer to use the public variants of `AuthData` where possible; this method is meant to be /// used for custom auth types only. pub fn data(&self) -> Cow<'_, JsonObject> { fn serialize(obj: T) -> JsonObject { match serde_json::to_value(obj).expect("auth data serialization to succeed") { JsonValue::Object(obj) => obj, _ => panic!("all auth data variants must serialize to objects"), } } match self { Self::Password(x) => Cow::Owned(serialize(Password { identifier: x.identifier.clone(), password: x.password, session: None, })), Self::ReCaptcha(x) => { Cow::Owned(serialize(ReCaptcha { response: x.response, session: None })) } Self::Token(x) => { Cow::Owned(serialize(Token { token: x.token, txn_id: x.txn_id, session: None })) } Self::OAuth2(x) => Cow::Owned(serialize(OAuth2 { uri: x.uri, session: None })), Self::EmailIdentity(x) => Cow::Owned(serialize(EmailIdentity { thirdparty_id_creds: x.thirdparty_id_creds, session: None, })), Self::Msisdn(x) => Cow::Owned(serialize(Msisdn { thirdparty_id_creds: x.thirdparty_id_creds, session: None, })), #[cfg(feature = "unstable-pre-spec")] Self::RegistrationToken(x) => { Cow::Owned(serialize(RegistrationToken { token: x.token, session: None })) } // Dummy and fallback acknowledgement have no associated data Self::Dummy(_) | Self::FallbackAcknowledgement(_) => Cow::Owned(JsonObject::default()), Self::_Custom(c) => Cow::Borrowed(c.extra), } } } impl IncomingAuthData { /// Creates a new `IncomingAuthData` with the given `auth_type` string, session and data. /// /// Prefer to use the public variants of `IncomingAuthData` where possible; this constructor is /// meant be used for unsupported message types only and does not allow setting arbitrary /// data for supported ones. This method can't construct the `RegistrationToken` variant. /// /// # Errors /// /// Returns an error if the `auth_type` is known and serialization of `data` to the /// corresponding `IncomingAuthData` variant fails. pub fn new( auth_type: &str, session: Option, data: JsonObject, ) -> serde_json::Result { fn deserialize_variant( session: Option, mut obj: JsonObject, ) -> serde_json::Result { if let Some(session) = session { obj.insert("session".into(), session.into()); } serde_json::from_value(JsonValue::Object(obj)) } Ok(match auth_type { "m.login.password" => Self::Password(deserialize_variant(session, data)?), "m.login.recaptcha" => Self::ReCaptcha(deserialize_variant(session, data)?), "m.login.token" => Self::Token(deserialize_variant(session, data)?), "m.login.oauth2" => Self::OAuth2(deserialize_variant(session, data)?), "m.login.email.identity" => Self::EmailIdentity(deserialize_variant(session, data)?), "m.login.msisdn" => Self::Msisdn(deserialize_variant(session, data)?), "m.login.dummy" => Self::Dummy(deserialize_variant(session, data)?), #[cfg(feature = "unstable-pre-spec")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] "org.matrix.msc3231.login.registration_token" => { Self::RegistrationToken(deserialize_variant(session, data)?) } _ => Self::_Custom(IncomingCustomAuthData { auth_type: auth_type.into(), session, extra: data, }), }) } /// Returns the value of the `type` field, if it exists. pub fn auth_type(&self) -> Option { match self { Self::Password(_) => Some(AuthType::Password), Self::ReCaptcha(_) => Some(AuthType::ReCaptcha), Self::Token(_) => Some(AuthType::Token), Self::OAuth2(_) => Some(AuthType::OAuth2), Self::EmailIdentity(_) => Some(AuthType::EmailIdentity), Self::Msisdn(_) => Some(AuthType::Msisdn), Self::Dummy(_) => Some(AuthType::Dummy), #[cfg(feature = "unstable-pre-spec")] Self::RegistrationToken(_) => Some(AuthType::RegistrationToken), Self::FallbackAcknowledgement(_) => None, Self::_Custom(c) => Some(AuthType::_Custom(c.auth_type.clone())), } } /// Returns the value of the `session` field, if it exists. pub fn session(&self) -> Option<&str> { match self { Self::Password(x) => x.session.as_deref(), Self::ReCaptcha(x) => x.session.as_deref(), Self::Token(x) => x.session.as_deref(), Self::OAuth2(x) => x.session.as_deref(), Self::EmailIdentity(x) => x.session.as_deref(), Self::Msisdn(x) => x.session.as_deref(), Self::Dummy(x) => x.session.as_deref(), #[cfg(feature = "unstable-pre-spec")] Self::RegistrationToken(x) => x.session.as_deref(), Self::FallbackAcknowledgement(x) => Some(&x.session), Self::_Custom(x) => x.session.as_deref(), } } /// Returns the associated data. /// /// The returned JSON object won't contain the `type` and `session` fields, use /// [`.auth_type()`][Self::auth_type] / [`.session()`](Self::session) to access those. /// /// Prefer to use the public variants of `AuthData` where possible; this method is meant to be /// used for custom auth types only. pub fn data(&self) -> Cow<'_, JsonObject> { fn serialize(obj: T) -> JsonObject { match serde_json::to_value(obj).expect("auth data serialization to succeed") { JsonValue::Object(obj) => obj, _ => panic!("all auth data variants must serialize to objects"), } } match self { Self::Password(x) => Cow::Owned(serialize(Password { identifier: x.identifier.to_outgoing(), password: &x.password, session: None, })), Self::ReCaptcha(x) => { Cow::Owned(serialize(ReCaptcha { response: &x.response, session: None })) } Self::Token(x) => { Cow::Owned(serialize(Token { token: &x.token, txn_id: &x.txn_id, session: None })) } Self::OAuth2(x) => Cow::Owned(serialize(OAuth2 { uri: &x.uri, session: None })), Self::EmailIdentity(x) => Cow::Owned(serialize(EmailIdentity { thirdparty_id_creds: &x.thirdparty_id_creds, session: None, })), Self::Msisdn(x) => Cow::Owned(serialize(Msisdn { thirdparty_id_creds: &x.thirdparty_id_creds, session: None, })), #[cfg(feature = "unstable-pre-spec")] Self::RegistrationToken(x) => { Cow::Owned(serialize(RegistrationToken { token: &x.token, session: None })) } // Dummy and fallback acknowledgement have no associated data Self::Dummy(_) | Self::FallbackAcknowledgement(_) => Cow::Owned(JsonObject::default()), Self::_Custom(c) => Cow::Borrowed(&c.extra), } } /// Convert `IncomingAuthData` to `AuthData`. pub fn to_outgoing(&self) -> AuthData<'_> { match self { Self::Password(a) => AuthData::Password(a.to_outgoing()), Self::ReCaptcha(a) => AuthData::ReCaptcha(a.to_outgoing()), Self::Token(a) => AuthData::Token(a.to_outgoing()), Self::OAuth2(a) => AuthData::OAuth2(a.to_outgoing()), Self::EmailIdentity(a) => AuthData::EmailIdentity(a.to_outgoing()), Self::Msisdn(a) => AuthData::Msisdn(a.to_outgoing()), Self::Dummy(a) => AuthData::Dummy(a.to_outgoing()), #[cfg(feature = "unstable-pre-spec")] Self::RegistrationToken(a) => AuthData::RegistrationToken(a.to_outgoing()), Self::FallbackAcknowledgement(a) => AuthData::FallbackAcknowledgement(a.to_outgoing()), Self::_Custom(a) => AuthData::_Custom(CustomAuthData { auth_type: a.auth_type.as_ref(), session: a.session.as_deref(), extra: &a.extra, }), } } } impl<'de> Deserialize<'de> for IncomingAuthData { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { fn from_raw_json_value( raw: &RawJsonValue, ) -> Result { serde_json::from_str(raw.get()).map_err(E::custom) } let json = Box::::deserialize(deserializer)?; #[derive(Deserialize)] struct ExtractType<'a> { #[serde(borrow, rename = "type")] auth_type: Option>, } let auth_type = serde_json::from_str::>(json.get()) .map_err(de::Error::custom)? .auth_type; match auth_type.as_deref() { Some("m.login.password") => from_raw_json_value(&json).map(Self::Password), Some("m.login.recaptcha") => from_raw_json_value(&json).map(Self::ReCaptcha), Some("m.login.token") => from_raw_json_value(&json).map(Self::Token), Some("m.login.oauth2") => from_raw_json_value(&json).map(Self::OAuth2), Some("m.login.email.identity") => from_raw_json_value(&json).map(Self::EmailIdentity), Some("m.login.msisdn") => from_raw_json_value(&json).map(Self::Msisdn), Some("m.login.dummy") => from_raw_json_value(&json).map(Self::Dummy), #[cfg(feature = "unstable-pre-spec")] Some("org.matrix.msc3231.login.registration_token") => { from_raw_json_value(&json).map(Self::RegistrationToken) } None => from_raw_json_value(&json).map(Self::FallbackAcknowledgement), Some(_) => from_raw_json_value(&json).map(Self::_Custom), } } } /// The type of an authentication stage. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, StringEnum)] #[non_exhaustive] pub enum AuthType { /// Password-based authentication (`m.login.password`). #[ruma_enum(rename = "m.login.password")] Password, /// Google ReCaptcha 2.0 authentication (`m.login.recaptcha`). #[ruma_enum(rename = "m.login.recaptcha")] ReCaptcha, /// Token-based authentication (`m.login.token`). #[ruma_enum(rename = "m.login.token")] Token, /// OAuth2-based authentication (`m.login.oauth2`). #[ruma_enum(rename = "m.login.oauth2")] OAuth2, /// Email-based authentication (`m.login.email.identity`). #[ruma_enum(rename = "m.login.email.identity")] EmailIdentity, /// Phone number-based authentication (`m.login.msisdn`). #[ruma_enum(rename = "m.login.msisdn")] Msisdn, /// Dummy authentication (`m.login.dummy`). #[ruma_enum(rename = "m.login.dummy")] Dummy, /// Registration token-based authentication (`org.matrix.msc3231.login.registration_token`). #[cfg(feature = "unstable-pre-spec")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] #[ruma_enum(rename = "org.matrix.msc3231.login.registration_token")] RegistrationToken, #[doc(hidden)] _Custom(String), } /// Data for password-based UIAA flow. /// /// See [the spec] for how to use this. /// /// [the spec]: https://matrix.org/docs/spec/client_server/r0.6.1#password-based #[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> { /// One of the user's identifiers. pub identifier: UserIdentifier<'a>, /// The plaintext password. pub password: &'a str, /// The value of the session key given by the homeserver, if any. pub session: Option<&'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, session: None } } } impl IncomingPassword { /// Convert `IncomingPassword` to `Password`. fn to_outgoing(&self) -> Password<'_> { Password { identifier: self.identifier.to_outgoing(), password: &self.password, session: self.session.as_deref(), } } } /// Data for ReCaptcha UIAA flow. /// /// See [the spec] for how to use this. /// /// [the spec]: https://matrix.org/docs/spec/client_server/r0.6.1#google-recaptcha #[derive(Clone, Debug, Outgoing, Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[serde(tag = "type", rename = "m.login.recaptcha")] pub struct ReCaptcha<'a> { /// The captcha response. pub response: &'a str, /// The value of the session key given by the homeserver, if any. pub session: Option<&'a str>, } impl<'a> ReCaptcha<'a> { /// Creates a new `ReCaptcha` with the given response string. pub fn new(response: &'a str) -> Self { Self { response, session: None } } } impl IncomingReCaptcha { /// Convert `IncomingReCaptcha` to `ReCaptcha`. fn to_outgoing(&self) -> ReCaptcha<'_> { ReCaptcha { response: &self.response, session: self.session.as_deref() } } } /// Data for token-based UIAA flow. /// /// See [the spec] for how to use this. /// /// [the spec]: https://matrix.org/docs/spec/client_server/r0.6.1#token-based #[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 login token. pub token: &'a str, /// The transaction ID. pub txn_id: &'a str, /// The value of the session key given by the homeserver, if any. pub session: Option<&'a str>, } impl<'a> Token<'a> { /// Creates a new `Token` with the given token and transaction ID. pub fn new(token: &'a str, txn_id: &'a str) -> Self { Self { token, txn_id, session: None } } } impl IncomingToken { /// Convert `IncomingToken` to `Token`. fn to_outgoing(&self) -> Token<'_> { Token { token: &self.token, txn_id: &self.txn_id, session: self.session.as_deref() } } } /// Data for OAuth2-based UIAA flow. /// /// See [the spec] for how to use this. /// /// [the spec]: https://matrix.org/docs/spec/client_server/r0.6.1#oauth2-based #[derive(Clone, Debug, Outgoing, Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[serde(tag = "type", rename = "m.login.oauth2")] pub struct OAuth2<'a> { /// Authorization Request URI or service selection URI. pub uri: &'a str, /// The value of the session key given by the homeserver, if any. pub session: Option<&'a str>, } impl<'a> OAuth2<'a> { /// Creates a new `OAuth2` with the given URI. pub fn new(uri: &'a str) -> Self { Self { uri, session: None } } } impl IncomingOAuth2 { /// Convert `IncomingOAuth2` to `OAuth2`. fn to_outgoing(&self) -> OAuth2<'_> { OAuth2 { uri: &self.uri, session: self.session.as_deref() } } } /// Data for Email-based UIAA flow. /// /// See [the spec] for how to use this. /// /// [the spec]: https://matrix.org/docs/spec/client_server/r0.6.1#email-based-identity-homeserver #[derive(Clone, Debug, Outgoing, Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[serde(tag = "type", rename = "m.login.email.identity")] pub struct EmailIdentity<'a> { /// Thirdparty identifier credentials. #[serde(rename = "threepidCreds")] pub thirdparty_id_creds: &'a [ThirdpartyIdCredentials], /// The value of the session key given by the homeserver, if any. pub session: Option<&'a str>, } impl IncomingEmailIdentity { /// Convert `IncomingEmailIdentity` to `EmailIdentity`. fn to_outgoing(&self) -> EmailIdentity<'_> { EmailIdentity { thirdparty_id_creds: &self.thirdparty_id_creds, session: self.session.as_deref(), } } } /// Data for phone number-based UIAA flow. /// /// See [the spec] for how to use this. /// /// [the spec]: https://matrix.org/docs/spec/client_server/r0.6.1#phone-number-msisdn-based-identity-homeserver #[derive(Clone, Debug, Outgoing, Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[serde(tag = "type", rename = "m.login.msisdn")] pub struct Msisdn<'a> { /// Thirdparty identifier credentials. #[serde(rename = "threepidCreds")] pub thirdparty_id_creds: &'a [ThirdpartyIdCredentials], /// The value of the session key given by the homeserver, if any. pub session: Option<&'a str>, } impl IncomingMsisdn { /// Convert `IncomingMsisdn` to `Msisdn`. fn to_outgoing(&self) -> Msisdn<'_> { Msisdn { thirdparty_id_creds: &self.thirdparty_id_creds, session: self.session.as_deref() } } } /// Data for dummy UIAA flow. /// /// See [the spec] for how to use this. /// /// [the spec]: https://matrix.org/docs/spec/client_server/r0.6.1#dummy-auth #[derive(Clone, Debug, Default, Outgoing, Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[serde(tag = "type", rename = "m.login.dummy")] pub struct Dummy<'a> { /// The value of the session key given by the homeserver, if any. pub session: Option<&'a str>, } impl Dummy<'_> { /// Creates an empty `Dummy`. pub fn new() -> Self { Self::default() } } impl IncomingDummy { /// Convert from `IncomingDummy` to `Dummy`. fn to_outgoing(&self) -> Dummy<'_> { Dummy { session: self.session.as_deref() } } } /// Data for registration token-based UIAA flow. /// /// See [MSC3231] for how to use this. /// /// [MSC3231]: https://github.com/matrix-org/matrix-doc/pull/3231 #[derive(Clone, Debug, Outgoing, Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[cfg(feature = "unstable-pre-spec")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] #[serde(tag = "type", rename = "org.matrix.msc3231.login.registration_token")] pub struct RegistrationToken<'a> { /// The registration token. pub token: &'a str, /// The value of the session key given by the homeserver, if any. pub session: Option<&'a str>, } #[cfg(feature = "unstable-pre-spec")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] impl<'a> RegistrationToken<'a> { /// Creates a new `RegistrationToken` with the given token. pub fn new(token: &'a str) -> Self { Self { token, session: None } } } #[cfg(feature = "unstable-pre-spec")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] impl IncomingRegistrationToken { /// Convert from `IncomingRegistrationToken` to `RegistrationToken`. fn to_outgoing(&self) -> RegistrationToken<'_> { RegistrationToken { token: &self.token, session: self.session.as_deref() } } } /// Data for UIAA fallback acknowledgement. /// /// See [the spec] for how to use this. /// /// [the spec]: https://matrix.org/docs/spec/client_server/r0.6.1#fallback #[derive(Clone, Debug, Outgoing, Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct FallbackAcknowledgement<'a> { /// The value of the session key given by the homeserver. pub session: &'a str, } impl<'a> FallbackAcknowledgement<'a> { /// Creates a new `FallbackAcknowledgement` with the given session key. pub fn new(session: &'a str) -> Self { Self { session } } } impl IncomingFallbackAcknowledgement { /// Convert from `IncomingFallbackAcknowledgement` to `FallbackAcknowledgement`. fn to_outgoing(&self) -> FallbackAcknowledgement<'_> { FallbackAcknowledgement { session: &self.session } } } #[doc(hidden)] #[derive(Clone, Debug, Serialize)] #[non_exhaustive] pub struct CustomAuthData<'a> { #[serde(rename = "type")] auth_type: &'a str, session: Option<&'a str>, #[serde(flatten)] extra: &'a JsonObject, } #[doc(hidden)] #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct IncomingCustomAuthData { #[serde(rename = "type")] auth_type: String, session: Option, #[serde(flatten)] extra: JsonObject, } impl Outgoing for CustomAuthData<'_> { type Incoming = IncomingCustomAuthData; } /// Identification information for the user. #[derive(Clone, Debug, PartialEq, Eq, Outgoing, Serialize)] #[serde(from = "user_serde::IncomingUserIdentifier", into = "user_serde::UserIdentifier<'_>")] #[allow(clippy::exhaustive_enums)] pub enum UserIdentifier<'a> { /// Either a fully qualified Matrix user ID, or just the localpart (as part of the 'identifier' /// field). MatrixId(&'a str), /// Third party identifier (as part of the 'identifier' field). ThirdPartyId { /// Third party identifier for the user. address: &'a str, /// The medium of the identifier. medium: Medium, }, /// Same as third-party identification with medium == msisdn, but with a non-canonicalised /// phone number. PhoneNumber { /// The country that the phone number is from. country: &'a str, /// The phone number. phone: &'a str, }, } impl IncomingUserIdentifier { fn to_outgoing(&self) -> UserIdentifier<'_> { match self { Self::MatrixId(id) => UserIdentifier::MatrixId(id), Self::ThirdPartyId { address, medium } => { UserIdentifier::ThirdPartyId { address, medium: medium.clone() } } Self::PhoneNumber { country, phone } => UserIdentifier::PhoneNumber { country, phone }, } } } /// Credentials for thirdparty authentification (e.g. email / phone number). #[derive(Clone, Debug, Deserialize, Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct ThirdpartyIdCredentials { /// Identity server session ID. pub sid: Box, /// Identity server client secret. pub client_secret: Box, /// Identity server URL. pub id_server: String, /// Identity server access token. pub id_access_token: String, } impl ThirdpartyIdCredentials { /// Creates a new `ThirdpartyIdCredentials` with the given session ID, client secret, identity /// server address and access token. pub fn new( sid: Box, client_secret: Box, id_server: String, id_access_token: String, ) -> Self { Self { sid, client_secret, id_server, id_access_token } } } /// Information about available authentication flows and status for User-Interactive Authenticiation /// API. #[derive(Clone, Debug, Deserialize, Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct UiaaInfo { /// List of authentication flows available for this endpoint. pub flows: Vec, /// List of stages in the current flow completed by the client. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub completed: Vec, /// Authentication parameters required for the client to complete authentication. /// /// To create a `Box`, use `serde_json::value::to_raw_value`. pub params: Box, /// Session key for client to use to complete authentication. #[serde(skip_serializing_if = "Option::is_none")] pub session: Option, /// Authentication-related errors for previous request returned by homeserver. #[serde(flatten, skip_serializing_if = "Option::is_none")] pub auth_error: Option, } impl UiaaInfo { /// Creates a new `UiaaInfo` with the given flows and parameters. pub fn new(flows: Vec, params: Box) -> Self { Self { flows, completed: Vec::new(), params, session: None, auth_error: None } } } /// Description of steps required to authenticate via the User-Interactive Authentication API. #[derive(Clone, Debug, Default, Deserialize, Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct AuthFlow { /// Ordered list of stages required to complete authentication. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub stages: Vec, } impl AuthFlow { /// Creates a new `AuthFlow` with the given stages. /// /// To create an empty `AuthFlow`, use `AuthFlow::default()`. pub fn new(stages: Vec) -> Self { Self { stages } } } /// Contains either a User-Interactive Authentication API response body or a Matrix error. #[derive(Clone, Debug)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub enum UiaaResponse { /// User-Interactive Authentication API response AuthResponse(UiaaInfo), /// Matrix error response MatrixError(MatrixError), } impl fmt::Display for UiaaResponse { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::AuthResponse(_) => write!(f, "User-Interactive Authentication required."), Self::MatrixError(err) => write!(f, "{}", err), } } } impl From for UiaaResponse { fn from(error: MatrixError) -> Self { Self::MatrixError(error) } } impl EndpointError for UiaaResponse { fn try_from_http_response>( response: http::Response, ) -> Result { if response.status() == http::StatusCode::UNAUTHORIZED { Ok(UiaaResponse::AuthResponse(from_json_slice(response.body().as_ref())?)) } else { MatrixError::try_from_http_response(response).map(From::from) } } } impl std::error::Error for UiaaResponse {} impl OutgoingResponse for UiaaResponse { fn try_into_http_response( self, ) -> Result, IntoHttpError> { match self { UiaaResponse::AuthResponse(authentication_info) => http::Response::builder() .header(http::header::CONTENT_TYPE, "application/json") .status(&http::StatusCode::UNAUTHORIZED) .body(ruma_serde::json_to_buf(&authentication_info)?) .map_err(Into::into), UiaaResponse::MatrixError(error) => error.try_into_http_response(), } } }