#![cfg_attr(not(target_family = "wasm"), deny(clippy::future_not_send))]
#[cfg(feature = "sso-login")]
use std::future::Future;
use std::future::IntoFuture;
use matrix_sdk_common::boxed_into_future;
use ruma::{
api::client::{session::login, uiaa::UserIdentifier},
assign,
serde::JsonObject,
};
use tracing::{info, instrument};
use super::MatrixAuth;
#[cfg(feature = "sso-login")]
use crate::utils::local_server::LocalServerBuilder;
use crate::{Result, config::RequestConfig};
enum LoginMethod {
UserPassword {
id: UserIdentifier,
password: String,
},
Token(String),
Custom(login::v3::LoginInfo),
}
impl LoginMethod {
fn id(&self) -> Option<&UserIdentifier> {
match self {
LoginMethod::UserPassword { id, .. } => Some(id),
LoginMethod::Token(_) | LoginMethod::Custom(_) => None,
}
}
fn tracing_desc(&self) -> &'static str {
match self {
LoginMethod::UserPassword { .. } => "identifier and password",
LoginMethod::Token(_) => "token",
LoginMethod::Custom(_) => "custom",
}
}
fn into_login_info(self) -> login::v3::LoginInfo {
match self {
LoginMethod::UserPassword { id, password } => {
login::v3::LoginInfo::Password(login::v3::Password::new(id, password))
}
LoginMethod::Token(token) => login::v3::LoginInfo::Token(login::v3::Token::new(token)),
LoginMethod::Custom(login_info) => login_info,
}
}
}
#[allow(missing_debug_implementations)]
pub struct LoginBuilder {
auth: MatrixAuth,
login_method: LoginMethod,
device_id: Option<String>,
initial_device_display_name: Option<String>,
request_refresh_token: bool,
}
impl LoginBuilder {
fn new(auth: MatrixAuth, login_method: LoginMethod) -> Self {
Self {
auth,
login_method,
device_id: None,
initial_device_display_name: None,
request_refresh_token: false,
}
}
pub(super) fn new_password(auth: MatrixAuth, id: UserIdentifier, password: String) -> Self {
Self::new(auth, LoginMethod::UserPassword { id, password })
}
pub(super) fn new_token(auth: MatrixAuth, token: String) -> Self {
Self::new(auth, LoginMethod::Token(token))
}
pub(super) fn new_custom(
auth: MatrixAuth,
login_type: &str,
data: JsonObject,
) -> serde_json::Result<Self> {
let login_info = login::v3::LoginInfo::new(login_type, data)?;
Ok(Self::new(auth, LoginMethod::Custom(login_info)))
}
pub fn device_id(mut self, value: &str) -> Self {
self.device_id = Some(value.to_owned());
self
}
pub fn initial_device_display_name(mut self, value: &str) -> Self {
self.initial_device_display_name = Some(value.to_owned());
self
}
pub fn request_refresh_token(mut self) -> Self {
self.request_refresh_token = true;
self
}
#[instrument(
target = "matrix_sdk::client",
name = "login",
skip_all,
fields(method = self.login_method.tracing_desc()),
)]
pub async fn send(self) -> Result<login::v3::Response> {
let client = &self.auth.client;
let homeserver = client.homeserver();
info!(homeserver = homeserver.as_str(), identifier = ?self.login_method.id(), "Logging in");
let login_info = self.login_method.into_login_info();
let request = assign!(login::v3::Request::new(login_info.clone()), {
device_id: self.device_id.map(Into::into),
initial_device_display_name: self.initial_device_display_name,
refresh_token: self.request_refresh_token,
});
let response =
client.send(request).with_request_config(RequestConfig::short_retry()).await?;
self.auth
.receive_login_response(
&response,
#[cfg(feature = "e2e-encryption")]
Some(login_info),
)
.await?;
Ok(response)
}
}
impl IntoFuture for LoginBuilder {
type Output = Result<login::v3::Response>;
boxed_into_future!();
fn into_future(self) -> Self::IntoFuture {
Box::pin(self.send())
}
}
#[cfg(feature = "sso-login")]
#[allow(missing_debug_implementations)]
pub struct SsoLoginBuilder<F> {
auth: MatrixAuth,
use_sso_login_url: F,
device_id: Option<String>,
initial_device_display_name: Option<String>,
server_builder: Option<LocalServerBuilder>,
identity_provider_id: Option<String>,
request_refresh_token: bool,
}
#[cfg(feature = "sso-login")]
impl<F, Fut> SsoLoginBuilder<F>
where
F: FnOnce(String) -> Fut + Send,
Fut: Future<Output = Result<()>> + Send,
{
pub(super) fn new(auth: MatrixAuth, use_sso_login_url: F) -> Self {
Self {
auth,
use_sso_login_url,
device_id: None,
initial_device_display_name: None,
server_builder: None,
identity_provider_id: None,
request_refresh_token: false,
}
}
pub fn device_id(mut self, value: &str) -> Self {
self.device_id = Some(value.to_owned());
self
}
pub fn initial_device_display_name(mut self, value: &str) -> Self {
self.initial_device_display_name = Some(value.to_owned());
self
}
pub fn server_builder(mut self, builder: LocalServerBuilder) -> Self {
self.server_builder = Some(builder);
self
}
pub fn identity_provider_id(mut self, value: &str) -> Self {
self.identity_provider_id = Some(value.to_owned());
self
}
pub fn request_refresh_token(mut self) -> Self {
self.request_refresh_token = true;
self
}
#[instrument(target = "matrix_sdk::client", name = "login", skip_all, fields(method = "sso"))]
pub async fn send(self) -> Result<login::v3::Response> {
use std::io::Error as IoError;
use serde::Deserialize;
let client = &self.auth.client;
let homeserver = client.homeserver();
info!(%homeserver, "Logging in");
#[derive(Deserialize)]
struct QueryParameters {
#[serde(rename = "loginToken")]
login_token: String,
}
let server_builder = self.server_builder.unwrap_or_default();
let (redirect_url, server_handle) = server_builder.spawn().await?;
let sso_url = self
.auth
.get_sso_login_url(redirect_url.as_str(), self.identity_provider_id.as_deref())
.await?;
(self.use_sso_login_url)(sso_url).await?;
let query_string =
server_handle.await.ok_or_else(|| IoError::other("Could not get the loginToken"))?;
let token = serde_html_form::from_str::<QueryParameters>(&query_string)
.map_err(IoError::other)?
.login_token;
let login_builder = LoginBuilder {
device_id: self.device_id,
initial_device_display_name: self.initial_device_display_name,
request_refresh_token: self.request_refresh_token,
..LoginBuilder::new_token(self.auth, token)
};
login_builder.send().await
}
}
#[cfg(feature = "sso-login")]
impl<F, Fut> IntoFuture for SsoLoginBuilder<F>
where
F: FnOnce(String) -> Fut + Send + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
type Output = Result<login::v3::Response>;
boxed_into_future!();
fn into_future(self) -> Self::IntoFuture {
Box::pin(self.send())
}
}