ruma_client_api/uiaa/
auth_data.rs

1//! Authentication data types for the different [`AuthType`]s.
2
3use std::{borrow::Cow, fmt};
4
5use ruma_common::{
6    OwnedClientSecret, OwnedSessionId, OwnedUserId, serde::JsonObject, thirdparty::Medium,
7};
8use serde::{Deserialize, Serialize, de::DeserializeOwned};
9use serde_json::Value as JsonValue;
10
11mod data_serde;
12
13use super::AuthType;
14use crate::PrivOwnedStr;
15
16/// Information for one authentication stage.
17#[derive(Clone, Serialize)]
18#[non_exhaustive]
19#[serde(untagged)]
20pub enum AuthData {
21    /// Password-based authentication (`m.login.password`).
22    Password(Password),
23
24    /// Google ReCaptcha 2.0 authentication (`m.login.recaptcha`).
25    ReCaptcha(ReCaptcha),
26
27    /// Email-based authentication (`m.login.email.identity`).
28    EmailIdentity(EmailIdentity),
29
30    /// Phone number-based authentication (`m.login.msisdn`).
31    Msisdn(Msisdn),
32
33    /// Dummy authentication (`m.login.dummy`).
34    Dummy(Dummy),
35
36    /// Registration token-based authentication (`m.login.registration_token`).
37    RegistrationToken(RegistrationToken),
38
39    /// Fallback acknowledgement.
40    FallbackAcknowledgement(FallbackAcknowledgement),
41
42    /// Terms of service (`m.login.terms`).
43    ///
44    /// This type is only valid during account registration.
45    Terms(Terms),
46
47    /// OAuth 2.0 (`m.oauth`).
48    ///
49    /// This type is only valid with the cross-signing keys upload endpoint, after logging in with
50    /// the OAuth 2.0 API.
51    OAuth(OAuth),
52
53    /// Unsupported authentication type.
54    #[doc(hidden)]
55    _Custom(CustomAuthData),
56}
57
58impl AuthData {
59    /// Creates a new `AuthData` with the given `auth_type` string, session and data.
60    ///
61    /// Prefer to use the public variants of `AuthData` where possible; this constructor is meant to
62    /// be used for unsupported authentication types only and does not allow setting arbitrary
63    /// data for supported ones.
64    ///
65    /// # Errors
66    ///
67    /// Returns an error if the `auth_type` is known and serialization of `data` to the
68    /// corresponding `AuthData` variant fails.
69    pub fn new(
70        auth_type: &str,
71        session: Option<String>,
72        data: JsonObject,
73    ) -> serde_json::Result<Self> {
74        fn deserialize_variant<T: DeserializeOwned>(
75            session: Option<String>,
76            mut obj: JsonObject,
77        ) -> serde_json::Result<T> {
78            if let Some(session) = session {
79                obj.insert("session".into(), session.into());
80            }
81            serde_json::from_value(JsonValue::Object(obj))
82        }
83
84        Ok(match auth_type {
85            "m.login.password" => Self::Password(deserialize_variant(session, data)?),
86            "m.login.recaptcha" => Self::ReCaptcha(deserialize_variant(session, data)?),
87            "m.login.email.identity" => Self::EmailIdentity(deserialize_variant(session, data)?),
88            "m.login.msisdn" => Self::Msisdn(deserialize_variant(session, data)?),
89            "m.login.dummy" => Self::Dummy(deserialize_variant(session, data)?),
90            "m.registration_token" => Self::RegistrationToken(deserialize_variant(session, data)?),
91            "m.login.terms" => Self::Terms(deserialize_variant(session, data)?),
92            "m.oauth" | "org.matrix.cross_signing_reset" => {
93                Self::OAuth(deserialize_variant(session, data)?)
94            }
95            _ => {
96                Self::_Custom(CustomAuthData { auth_type: auth_type.into(), session, extra: data })
97            }
98        })
99    }
100
101    /// Creates a new `AuthData::FallbackAcknowledgement` with the given session key.
102    pub fn fallback_acknowledgement(session: String) -> Self {
103        Self::FallbackAcknowledgement(FallbackAcknowledgement::new(session))
104    }
105
106    /// Returns the value of the `type` field, if it exists.
107    pub fn auth_type(&self) -> Option<AuthType> {
108        match self {
109            Self::Password(_) => Some(AuthType::Password),
110            Self::ReCaptcha(_) => Some(AuthType::ReCaptcha),
111            Self::EmailIdentity(_) => Some(AuthType::EmailIdentity),
112            Self::Msisdn(_) => Some(AuthType::Msisdn),
113            Self::Dummy(_) => Some(AuthType::Dummy),
114            Self::RegistrationToken(_) => Some(AuthType::RegistrationToken),
115            Self::FallbackAcknowledgement(_) => None,
116            Self::Terms(_) => Some(AuthType::Terms),
117            Self::OAuth(_) => Some(AuthType::OAuth),
118            Self::_Custom(c) => Some(AuthType::_Custom(PrivOwnedStr(c.auth_type.as_str().into()))),
119        }
120    }
121
122    /// Returns the value of the `session` field, if it exists.
123    pub fn session(&self) -> Option<&str> {
124        match self {
125            Self::Password(x) => x.session.as_deref(),
126            Self::ReCaptcha(x) => x.session.as_deref(),
127            Self::EmailIdentity(x) => x.session.as_deref(),
128            Self::Msisdn(x) => x.session.as_deref(),
129            Self::Dummy(x) => x.session.as_deref(),
130            Self::RegistrationToken(x) => x.session.as_deref(),
131            Self::FallbackAcknowledgement(x) => Some(&x.session),
132            Self::Terms(x) => x.session.as_deref(),
133            Self::OAuth(x) => x.session.as_deref(),
134            Self::_Custom(x) => x.session.as_deref(),
135        }
136    }
137
138    /// Returns the associated data.
139    ///
140    /// The returned JSON object won't contain the `type` and `session` fields, use
141    /// [`.auth_type()`][Self::auth_type] / [`.session()`](Self::session) to access those.
142    ///
143    /// Prefer to use the public variants of `AuthData` where possible; this method is meant to be
144    /// used for custom auth types only.
145    pub fn data(&self) -> Cow<'_, JsonObject> {
146        fn serialize<T: Serialize>(obj: T) -> JsonObject {
147            match serde_json::to_value(obj).expect("auth data serialization to succeed") {
148                JsonValue::Object(obj) => obj,
149                _ => panic!("all auth data variants must serialize to objects"),
150            }
151        }
152
153        match self {
154            Self::Password(x) => Cow::Owned(serialize(Password {
155                identifier: x.identifier.clone(),
156                password: x.password.clone(),
157                session: None,
158            })),
159            Self::ReCaptcha(x) => {
160                Cow::Owned(serialize(ReCaptcha { response: x.response.clone(), session: None }))
161            }
162            Self::EmailIdentity(x) => Cow::Owned(serialize(EmailIdentity {
163                thirdparty_id_creds: x.thirdparty_id_creds.clone(),
164                session: None,
165            })),
166            Self::Msisdn(x) => Cow::Owned(serialize(Msisdn {
167                thirdparty_id_creds: x.thirdparty_id_creds.clone(),
168                session: None,
169            })),
170            Self::RegistrationToken(x) => {
171                Cow::Owned(serialize(RegistrationToken { token: x.token.clone(), session: None }))
172            }
173            // These types have no associated data.
174            Self::Dummy(_) | Self::FallbackAcknowledgement(_) | Self::Terms(_) | Self::OAuth(_) => {
175                Cow::Owned(JsonObject::default())
176            }
177            Self::_Custom(c) => Cow::Borrowed(&c.extra),
178        }
179    }
180}
181
182impl fmt::Debug for AuthData {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        // Print `Password { .. }` instead of `Password(Password { .. })`
185        match self {
186            Self::Password(inner) => inner.fmt(f),
187            Self::ReCaptcha(inner) => inner.fmt(f),
188            Self::EmailIdentity(inner) => inner.fmt(f),
189            Self::Msisdn(inner) => inner.fmt(f),
190            Self::Dummy(inner) => inner.fmt(f),
191            Self::RegistrationToken(inner) => inner.fmt(f),
192            Self::FallbackAcknowledgement(inner) => inner.fmt(f),
193            Self::Terms(inner) => inner.fmt(f),
194            Self::OAuth(inner) => inner.fmt(f),
195            Self::_Custom(inner) => inner.fmt(f),
196        }
197    }
198}
199
200/// Data for password-based UIAA flow.
201///
202/// See [the spec] for how to use this.
203///
204/// [the spec]: https://spec.matrix.org/latest/client-server-api/#password-based
205#[derive(Clone, Deserialize, Serialize)]
206#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
207#[serde(tag = "type", rename = "m.login.password")]
208pub struct Password {
209    /// One of the user's identifiers.
210    pub identifier: UserIdentifier,
211
212    /// The plaintext password.
213    pub password: String,
214
215    /// The value of the session key given by the homeserver, if any.
216    pub session: Option<String>,
217}
218
219impl Password {
220    /// Creates a new `Password` with the given identifier and password.
221    pub fn new(identifier: UserIdentifier, password: String) -> Self {
222        Self { identifier, password, session: None }
223    }
224}
225
226impl fmt::Debug for Password {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        let Self { identifier, password: _, session } = self;
229        f.debug_struct("Password")
230            .field("identifier", identifier)
231            .field("session", session)
232            .finish_non_exhaustive()
233    }
234}
235
236/// Data for ReCaptcha UIAA flow.
237///
238/// See [the spec] for how to use this.
239///
240/// [the spec]: https://spec.matrix.org/latest/client-server-api/#google-recaptcha
241#[derive(Clone, Deserialize, Serialize)]
242#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
243#[serde(tag = "type", rename = "m.login.recaptcha")]
244pub struct ReCaptcha {
245    /// The captcha response.
246    pub response: String,
247
248    /// The value of the session key given by the homeserver, if any.
249    pub session: Option<String>,
250}
251
252impl ReCaptcha {
253    /// Creates a new `ReCaptcha` with the given response string.
254    pub fn new(response: String) -> Self {
255        Self { response, session: None }
256    }
257}
258
259impl fmt::Debug for ReCaptcha {
260    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261        let Self { response: _, session } = self;
262        f.debug_struct("ReCaptcha").field("session", session).finish_non_exhaustive()
263    }
264}
265
266/// Data for Email-based UIAA flow.
267///
268/// See [the spec] for how to use this.
269///
270/// [the spec]: https://spec.matrix.org/latest/client-server-api/#email-based-identity--homeserver
271#[derive(Clone, Debug, Deserialize, Serialize)]
272#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
273#[serde(tag = "type", rename = "m.login.email.identity")]
274pub struct EmailIdentity {
275    /// Thirdparty identifier credentials.
276    #[serde(rename = "threepid_creds")]
277    pub thirdparty_id_creds: ThirdpartyIdCredentials,
278
279    /// The value of the session key given by the homeserver, if any.
280    pub session: Option<String>,
281}
282
283/// Data for phone number-based UIAA flow.
284///
285/// See [the spec] for how to use this.
286///
287/// [the spec]: https://spec.matrix.org/latest/client-server-api/#phone-numbermsisdn-based-identity--homeserver
288#[derive(Clone, Debug, Deserialize, Serialize)]
289#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
290#[serde(tag = "type", rename = "m.login.msisdn")]
291pub struct Msisdn {
292    /// Thirdparty identifier credentials.
293    #[serde(rename = "threepid_creds")]
294    pub thirdparty_id_creds: ThirdpartyIdCredentials,
295
296    /// The value of the session key given by the homeserver, if any.
297    pub session: Option<String>,
298}
299
300/// Data for dummy UIAA flow.
301///
302/// See [the spec] for how to use this.
303///
304/// [the spec]: https://spec.matrix.org/latest/client-server-api/#dummy-auth
305#[derive(Clone, Debug, Default, Deserialize, Serialize)]
306#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
307#[serde(tag = "type", rename = "m.login.dummy")]
308pub struct Dummy {
309    /// The value of the session key given by the homeserver, if any.
310    pub session: Option<String>,
311}
312
313impl Dummy {
314    /// Creates an empty `Dummy`.
315    pub fn new() -> Self {
316        Self::default()
317    }
318}
319
320/// Data for registration token-based UIAA flow.
321///
322/// See [the spec] for how to use this.
323///
324/// [the spec]: https://spec.matrix.org/latest/client-server-api/#token-authenticated-registration
325#[derive(Clone, Deserialize, Serialize)]
326#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
327#[serde(tag = "type", rename = "m.login.registration_token")]
328pub struct RegistrationToken {
329    /// The registration token.
330    pub token: String,
331
332    /// The value of the session key given by the homeserver, if any.
333    pub session: Option<String>,
334}
335
336impl RegistrationToken {
337    /// Creates a new `RegistrationToken` with the given token.
338    pub fn new(token: String) -> Self {
339        Self { token, session: None }
340    }
341}
342
343impl fmt::Debug for RegistrationToken {
344    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345        let Self { token: _, session } = self;
346        f.debug_struct("RegistrationToken").field("session", session).finish_non_exhaustive()
347    }
348}
349
350/// Data for UIAA fallback acknowledgement.
351///
352/// See [the spec] for how to use this.
353///
354/// [the spec]: https://spec.matrix.org/latest/client-server-api/#fallback
355#[derive(Clone, Debug, Deserialize, Serialize)]
356#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
357pub struct FallbackAcknowledgement {
358    /// The value of the session key given by the homeserver.
359    pub session: String,
360}
361
362impl FallbackAcknowledgement {
363    /// Creates a new `FallbackAcknowledgement` with the given session key.
364    pub fn new(session: String) -> Self {
365        Self { session }
366    }
367}
368
369/// Data for terms of service flow.
370///
371/// This type is only valid during account registration.
372///
373/// See [the spec] for how to use this.
374///
375/// [the spec]: https://spec.matrix.org/latest/client-server-api/#terms-of-service-at-registration
376#[derive(Clone, Debug, Default, Deserialize, Serialize)]
377#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
378#[serde(tag = "type", rename = "m.login.terms")]
379pub struct Terms {
380    /// The value of the session key given by the homeserver, if any.
381    pub session: Option<String>,
382}
383
384impl Terms {
385    /// Creates an empty `Terms`.
386    pub fn new() -> Self {
387        Self::default()
388    }
389}
390
391/// Data for an OAuth 2.0-based UIAA flow.
392///
393/// See [MSC4312] for how to use this.
394///
395/// [MSC4312]: https://github.com/matrix-org/matrix-spec-proposals/pull/4312
396#[derive(Clone, Debug, Default, Deserialize, Serialize)]
397#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
398#[serde(tag = "type", rename = "m.oauth")]
399pub struct OAuth {
400    /// The value of the session key given by the homeserver, if any.
401    pub session: Option<String>,
402}
403
404impl OAuth {
405    /// Construct an empty `OAuth`.
406    pub fn new() -> Self {
407        Self::default()
408    }
409}
410
411/// Data for an unsupported authentication type.
412#[doc(hidden)]
413#[derive(Clone, Deserialize, Serialize)]
414#[non_exhaustive]
415pub struct CustomAuthData {
416    /// The type of authentication.
417    #[serde(rename = "type")]
418    auth_type: String,
419
420    /// The value of the session key given by the homeserver, if any.
421    session: Option<String>,
422
423    /// Extra data.
424    #[serde(flatten)]
425    extra: JsonObject,
426}
427
428impl fmt::Debug for CustomAuthData {
429    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430        let Self { auth_type, session, extra: _ } = self;
431        f.debug_struct("CustomAuthData")
432            .field("auth_type", auth_type)
433            .field("session", session)
434            .finish_non_exhaustive()
435    }
436}
437
438/// Identification information for the user.
439#[derive(Clone, Debug, PartialEq, Eq)]
440#[allow(clippy::exhaustive_enums)]
441pub enum UserIdentifier {
442    /// Either a fully qualified Matrix user ID, or just the localpart (as part of the 'identifier'
443    /// field).
444    UserIdOrLocalpart(String),
445
446    /// An email address.
447    Email {
448        /// The email address.
449        address: String,
450    },
451
452    /// A phone number in the MSISDN format.
453    Msisdn {
454        /// The phone number according to the [E.164] numbering plan.
455        ///
456        /// [E.164]: https://www.itu.int/rec/T-REC-E.164-201011-I/en
457        number: String,
458    },
459
460    /// A phone number as a separate country code and phone number.
461    ///
462    /// The homeserver will be responsible for canonicalizing this to the MSISDN format.
463    PhoneNumber {
464        /// The country that the phone number is from.
465        ///
466        /// This is a two-letter uppercase [ISO-3166-1 alpha-2] country code.
467        ///
468        /// [ISO-3166-1 alpha-2]: https://www.iso.org/iso-3166-country-codes.html
469        country: String,
470
471        /// The phone number.
472        phone: String,
473    },
474
475    /// Unsupported `m.id.thirdpartyid`.
476    #[doc(hidden)]
477    _CustomThirdParty(CustomThirdPartyId),
478}
479
480impl UserIdentifier {
481    /// Creates a new `UserIdentifier` from the given third party identifier.
482    pub fn third_party_id(medium: Medium, address: String) -> Self {
483        match medium {
484            Medium::Email => Self::Email { address },
485            Medium::Msisdn => Self::Msisdn { number: address },
486            _ => Self::_CustomThirdParty(CustomThirdPartyId { medium, address }),
487        }
488    }
489
490    /// Get this `UserIdentifier` as a third party identifier if it is one.
491    pub fn as_third_party_id(&self) -> Option<(&Medium, &str)> {
492        match self {
493            Self::Email { address } => Some((&Medium::Email, address)),
494            Self::Msisdn { number } => Some((&Medium::Msisdn, number)),
495            Self::_CustomThirdParty(CustomThirdPartyId { medium, address }) => {
496                Some((medium, address))
497            }
498            _ => None,
499        }
500    }
501}
502
503impl From<OwnedUserId> for UserIdentifier {
504    fn from(id: OwnedUserId) -> Self {
505        Self::UserIdOrLocalpart(id.into())
506    }
507}
508
509impl From<&OwnedUserId> for UserIdentifier {
510    fn from(id: &OwnedUserId) -> Self {
511        Self::UserIdOrLocalpart(id.as_str().to_owned())
512    }
513}
514
515/// Data for an unsupported third-party ID.
516#[doc(hidden)]
517#[derive(Clone, Debug, PartialEq, Eq)]
518#[non_exhaustive]
519pub struct CustomThirdPartyId {
520    /// The kind of the third-party ID.
521    medium: Medium,
522
523    /// The third-party ID.
524    address: String,
525}
526
527/// Credentials for third-party authentication (e.g. email / phone number).
528#[derive(Clone, Deserialize, Serialize)]
529#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
530pub struct ThirdpartyIdCredentials {
531    /// Identity server (or homeserver) session ID.
532    pub sid: OwnedSessionId,
533
534    /// Identity server (or homeserver) client secret.
535    pub client_secret: OwnedClientSecret,
536
537    /// Identity server URL.
538    #[serde(skip_serializing_if = "Option::is_none")]
539    pub id_server: Option<String>,
540
541    /// Identity server access token.
542    #[serde(skip_serializing_if = "Option::is_none")]
543    pub id_access_token: Option<String>,
544}
545
546impl ThirdpartyIdCredentials {
547    /// Creates a new `ThirdpartyIdCredentials` with the given session ID and client secret.
548    pub fn new(sid: OwnedSessionId, client_secret: OwnedClientSecret) -> Self {
549        Self { sid, client_secret, id_server: None, id_access_token: None }
550    }
551}
552
553impl fmt::Debug for ThirdpartyIdCredentials {
554    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
555        let Self { sid, client_secret: _, id_server, id_access_token } = self;
556        f.debug_struct("ThirdpartyIdCredentials")
557            .field("sid", sid)
558            .field("id_server", id_server)
559            .field("id_access_token", id_access_token)
560            .finish_non_exhaustive()
561    }
562}