matrix_sdk/authentication/matrix/
login_builder.rs1#[cfg(feature = "sso-login")]
20use std::future::Future;
21use std::future::IntoFuture;
22
23use matrix_sdk_common::boxed_into_future;
24use ruma::{
25 api::client::{session::login, uiaa::UserIdentifier},
26 assign,
27 serde::JsonObject,
28};
29use tracing::{info, instrument};
30
31use super::MatrixAuth;
32#[cfg(feature = "sso-login")]
33use crate::utils::local_server::LocalServerBuilder;
34use crate::{Result, config::RequestConfig};
35
36enum LoginMethod {
42 UserPassword {
44 id: UserIdentifier,
45 password: String,
46 },
47 Token(String),
49 Custom(login::v3::LoginInfo),
50}
51
52impl LoginMethod {
53 fn id(&self) -> Option<&UserIdentifier> {
54 match self {
55 LoginMethod::UserPassword { id, .. } => Some(id),
56 LoginMethod::Token(_) | LoginMethod::Custom(_) => None,
57 }
58 }
59
60 fn tracing_desc(&self) -> &'static str {
61 match self {
62 LoginMethod::UserPassword { .. } => "identifier and password",
63 LoginMethod::Token(_) => "token",
64 LoginMethod::Custom(_) => "custom",
65 }
66 }
67
68 fn into_login_info(self) -> login::v3::LoginInfo {
69 match self {
70 LoginMethod::UserPassword { id, password } => {
71 login::v3::LoginInfo::Password(login::v3::Password::new(id, password))
72 }
73 LoginMethod::Token(token) => login::v3::LoginInfo::Token(login::v3::Token::new(token)),
74 LoginMethod::Custom(login_info) => login_info,
75 }
76 }
77}
78
79#[allow(missing_debug_implementations)]
85pub struct LoginBuilder {
86 auth: MatrixAuth,
87 login_method: LoginMethod,
88 device_id: Option<String>,
89 initial_device_display_name: Option<String>,
90 request_refresh_token: bool,
91}
92
93impl LoginBuilder {
94 fn new(auth: MatrixAuth, login_method: LoginMethod) -> Self {
95 Self {
96 auth,
97 login_method,
98 device_id: None,
99 initial_device_display_name: None,
100 request_refresh_token: false,
101 }
102 }
103
104 pub(super) fn new_password(auth: MatrixAuth, id: UserIdentifier, password: String) -> Self {
105 Self::new(auth, LoginMethod::UserPassword { id, password })
106 }
107
108 pub(super) fn new_token(auth: MatrixAuth, token: String) -> Self {
109 Self::new(auth, LoginMethod::Token(token))
110 }
111
112 pub(super) fn new_custom(
113 auth: MatrixAuth,
114 login_type: &str,
115 data: JsonObject,
116 ) -> serde_json::Result<Self> {
117 let login_info = login::v3::LoginInfo::new(login_type, data)?;
118 Ok(Self::new(auth, LoginMethod::Custom(login_info)))
119 }
120
121 pub fn device_id(mut self, value: &str) -> Self {
128 self.device_id = Some(value.to_owned());
129 self
130 }
131
132 pub fn initial_device_display_name(mut self, value: &str) -> Self {
138 self.initial_device_display_name = Some(value.to_owned());
139 self
140 }
141
142 pub fn request_refresh_token(mut self) -> Self {
158 self.request_refresh_token = true;
159 self
160 }
161
162 #[instrument(
171 target = "matrix_sdk::client",
172 name = "login",
173 skip_all,
174 fields(method = self.login_method.tracing_desc()),
175 )]
176 pub async fn send(self) -> Result<login::v3::Response> {
177 let client = &self.auth.client;
178 let homeserver = client.homeserver();
179 info!(homeserver = homeserver.as_str(), identifier = ?self.login_method.id(), "Logging in");
180
181 let login_info = self.login_method.into_login_info();
182
183 let request = assign!(login::v3::Request::new(login_info.clone()), {
184 device_id: self.device_id.map(Into::into),
185 initial_device_display_name: self.initial_device_display_name,
186 refresh_token: self.request_refresh_token,
187 });
188
189 let response =
190 client.send(request).with_request_config(RequestConfig::short_retry()).await?;
191 self.auth
192 .receive_login_response(
193 &response,
194 #[cfg(feature = "e2e-encryption")]
195 Some(login_info),
196 )
197 .await?;
198
199 Ok(response)
200 }
201}
202
203impl IntoFuture for LoginBuilder {
204 type Output = Result<login::v3::Response>;
205 boxed_into_future!();
206
207 fn into_future(self) -> Self::IntoFuture {
208 Box::pin(self.send())
209 }
210}
211
212#[cfg(feature = "sso-login")]
217#[allow(missing_debug_implementations)]
218pub struct SsoLoginBuilder<F> {
219 auth: MatrixAuth,
220 use_sso_login_url: F,
221 device_id: Option<String>,
222 initial_device_display_name: Option<String>,
223 server_builder: Option<LocalServerBuilder>,
224 identity_provider_id: Option<String>,
225 request_refresh_token: bool,
226}
227
228#[cfg(feature = "sso-login")]
229impl<F, Fut> SsoLoginBuilder<F>
230where
231 F: FnOnce(String) -> Fut + Send,
232 Fut: Future<Output = Result<()>> + Send,
233{
234 pub(super) fn new(auth: MatrixAuth, use_sso_login_url: F) -> Self {
235 Self {
236 auth,
237 use_sso_login_url,
238 device_id: None,
239 initial_device_display_name: None,
240 server_builder: None,
241 identity_provider_id: None,
242 request_refresh_token: false,
243 }
244 }
245
246 pub fn device_id(mut self, value: &str) -> Self {
253 self.device_id = Some(value.to_owned());
254 self
255 }
256
257 pub fn initial_device_display_name(mut self, value: &str) -> Self {
263 self.initial_device_display_name = Some(value.to_owned());
264 self
265 }
266
267 pub fn server_builder(mut self, builder: LocalServerBuilder) -> Self {
273 self.server_builder = Some(builder);
274 self
275 }
276
277 pub fn identity_provider_id(mut self, value: &str) -> Self {
279 self.identity_provider_id = Some(value.to_owned());
280 self
281 }
282
283 pub fn request_refresh_token(mut self) -> Self {
299 self.request_refresh_token = true;
300 self
301 }
302
303 #[instrument(target = "matrix_sdk::client", name = "login", skip_all, fields(method = "sso"))]
312 pub async fn send(self) -> Result<login::v3::Response> {
313 use std::io::Error as IoError;
314
315 use serde::Deserialize;
316
317 let client = &self.auth.client;
318 let homeserver = client.homeserver();
319 info!(%homeserver, "Logging in");
320
321 #[derive(Deserialize)]
322 struct QueryParameters {
323 #[serde(rename = "loginToken")]
324 login_token: String,
325 }
326
327 let server_builder = self.server_builder.unwrap_or_default();
328 let (redirect_url, server_handle) = server_builder.spawn().await?;
329
330 let sso_url = self
331 .auth
332 .get_sso_login_url(redirect_url.as_str(), self.identity_provider_id.as_deref())
333 .await?;
334
335 (self.use_sso_login_url)(sso_url).await?;
336
337 let query_string =
338 server_handle.await.ok_or_else(|| IoError::other("Could not get the loginToken"))?;
339 let token = serde_html_form::from_str::<QueryParameters>(&query_string)
340 .map_err(IoError::other)?
341 .login_token;
342
343 let login_builder = LoginBuilder {
344 device_id: self.device_id,
345 initial_device_display_name: self.initial_device_display_name,
346 request_refresh_token: self.request_refresh_token,
347 ..LoginBuilder::new_token(self.auth, token)
348 };
349 login_builder.send().await
350 }
351}
352
353#[cfg(feature = "sso-login")]
354impl<F, Fut> IntoFuture for SsoLoginBuilder<F>
355where
356 F: FnOnce(String) -> Fut + Send + 'static,
357 Fut: Future<Output = Result<()>> + Send + 'static,
358{
359 type Output = Result<login::v3::Response>;
360 boxed_into_future!();
361
362 fn into_future(self) -> Self::IntoFuture {
363 Box::pin(self.send())
364 }
365}