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