679 lines
24 KiB
Rust
679 lines
24 KiB
Rust
use std::{
|
|
collections::HashMap,
|
|
sync::{Arc, RwLock as StdRwLock},
|
|
};
|
|
|
|
use matrix_sdk::{
|
|
encryption::BackupDownloadStrategy,
|
|
oidc::{
|
|
registrations::{ClientId, OidcRegistrations, OidcRegistrationsError},
|
|
types::{
|
|
client_credentials::ClientCredentials,
|
|
errors::ClientErrorCode::AccessDenied,
|
|
iana::oauth::OAuthClientAuthenticationMethod,
|
|
oidc::ApplicationType,
|
|
registration::{ClientMetadata, Localized, VerifiedClientMetadata},
|
|
requests::{GrantType, Prompt},
|
|
},
|
|
AuthorizationResponse, Oidc, OidcError,
|
|
},
|
|
reqwest::StatusCode,
|
|
AuthSession, ClientBuildError as MatrixClientBuildError, HttpError, RumaApiError,
|
|
};
|
|
use ruma::{
|
|
api::error::{DeserializationError, FromHttpResponseError},
|
|
OwnedUserId,
|
|
};
|
|
use tokio::sync::RwLock as AsyncRwLock;
|
|
use url::Url;
|
|
use zeroize::Zeroize;
|
|
|
|
use super::{client::Client, client_builder::ClientBuilder};
|
|
use crate::{
|
|
client::ClientSessionDelegate,
|
|
client_builder::{CertificateBytes, ClientBuildError},
|
|
error::ClientError,
|
|
};
|
|
|
|
#[derive(uniffi::Object)]
|
|
pub struct AuthenticationService {
|
|
base_path: String,
|
|
passphrase: Option<String>,
|
|
user_agent: Option<String>,
|
|
client: AsyncRwLock<Option<Client>>,
|
|
homeserver_details: StdRwLock<Option<Arc<HomeserverLoginDetails>>>,
|
|
oidc_configuration: Option<OidcConfiguration>,
|
|
custom_sliding_sync_proxy: StdRwLock<Option<String>>,
|
|
cross_process_refresh_lock_id: Option<String>,
|
|
session_delegate: Option<Arc<dyn ClientSessionDelegate>>,
|
|
additional_root_certificates: Vec<CertificateBytes>,
|
|
proxy: Option<String>,
|
|
}
|
|
|
|
impl Drop for AuthenticationService {
|
|
fn drop(&mut self) {
|
|
self.passphrase.zeroize();
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
|
#[uniffi(flat_error)]
|
|
pub enum AuthenticationError {
|
|
#[error("A successful call to configure_homeserver must be made first.")]
|
|
ClientMissing,
|
|
|
|
#[error("The supplied server name is invalid.")]
|
|
InvalidServerName,
|
|
#[error(transparent)]
|
|
ServerUnreachable(HttpError),
|
|
#[error(transparent)]
|
|
WellKnownLookupFailed(RumaApiError),
|
|
#[error(transparent)]
|
|
WellKnownDeserializationError(DeserializationError),
|
|
#[error("The homeserver doesn't provide a trusted sliding sync proxy in its well-known configuration.")]
|
|
SlidingSyncNotAvailable,
|
|
|
|
#[error("Login was successful but is missing a valid Session to configure the file store.")]
|
|
SessionMissing,
|
|
#[error("Failed to use the supplied base path.")]
|
|
InvalidBasePath,
|
|
|
|
#[error(
|
|
"The homeserver doesn't provide an authentication issuer in its well-known configuration."
|
|
)]
|
|
OidcNotSupported,
|
|
#[error("Unable to use OIDC as no client metadata has been supplied.")]
|
|
OidcMetadataMissing,
|
|
#[error("Unable to use OIDC as the supplied client metadata is invalid.")]
|
|
OidcMetadataInvalid,
|
|
#[error("The supplied callback URL used to complete OIDC is invalid.")]
|
|
OidcCallbackUrlInvalid,
|
|
#[error("The OIDC login was cancelled by the user.")]
|
|
OidcCancelled,
|
|
#[error("An error occurred with OIDC: {message}")]
|
|
OidcError { message: String },
|
|
|
|
#[error("An error occurred: {message}")]
|
|
Generic { message: String },
|
|
}
|
|
|
|
impl From<anyhow::Error> for AuthenticationError {
|
|
fn from(e: anyhow::Error) -> AuthenticationError {
|
|
AuthenticationError::Generic { message: e.to_string() }
|
|
}
|
|
}
|
|
|
|
impl From<ClientBuildError> for AuthenticationError {
|
|
fn from(e: ClientBuildError) -> AuthenticationError {
|
|
match e {
|
|
ClientBuildError::Sdk(MatrixClientBuildError::InvalidServerName) => {
|
|
AuthenticationError::InvalidServerName
|
|
}
|
|
|
|
ClientBuildError::Sdk(MatrixClientBuildError::Http(e)) => {
|
|
AuthenticationError::ServerUnreachable(e)
|
|
}
|
|
|
|
ClientBuildError::Sdk(MatrixClientBuildError::AutoDiscovery(
|
|
FromHttpResponseError::Server(e),
|
|
)) => AuthenticationError::WellKnownLookupFailed(e),
|
|
|
|
ClientBuildError::Sdk(MatrixClientBuildError::AutoDiscovery(
|
|
FromHttpResponseError::Deserialization(e),
|
|
)) => AuthenticationError::WellKnownDeserializationError(e),
|
|
|
|
_ => AuthenticationError::Generic { message: e.to_string() },
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<OidcRegistrationsError> for AuthenticationError {
|
|
fn from(e: OidcRegistrationsError) -> AuthenticationError {
|
|
match e {
|
|
OidcRegistrationsError::InvalidBasePath => AuthenticationError::InvalidBasePath,
|
|
_ => AuthenticationError::OidcError { message: e.to_string() },
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<OidcError> for AuthenticationError {
|
|
fn from(e: OidcError) -> AuthenticationError {
|
|
AuthenticationError::OidcError { message: e.to_string() }
|
|
}
|
|
}
|
|
|
|
/// The configuration to use when authenticating with OIDC.
|
|
#[derive(uniffi::Record)]
|
|
pub struct OidcConfiguration {
|
|
/// The name of the client that will be shown during OIDC authentication.
|
|
pub client_name: Option<String>,
|
|
/// The redirect URI that will be used when OIDC authentication is
|
|
/// successful.
|
|
pub redirect_uri: String,
|
|
/// A URI that contains information about the client.
|
|
pub client_uri: Option<String>,
|
|
/// A URI that contains the client's logo.
|
|
pub logo_uri: Option<String>,
|
|
/// A URI that contains the client's terms of service.
|
|
pub tos_uri: Option<String>,
|
|
/// A URI that contains the client's privacy policy.
|
|
pub policy_uri: Option<String>,
|
|
/// An array of e-mail addresses of people responsible for this client.
|
|
pub contacts: Option<Vec<String>>,
|
|
|
|
/// Pre-configured registrations for use with issuers that don't support
|
|
/// dynamic client registration.
|
|
pub static_registrations: HashMap<String, String>,
|
|
}
|
|
|
|
/// The data required to authenticate against an OIDC server.
|
|
#[derive(uniffi::Object)]
|
|
pub struct OidcAuthenticationData {
|
|
/// The underlying URL for authentication.
|
|
url: Url,
|
|
/// A unique identifier for the request, used to ensure the response
|
|
/// originated from the authentication issuer.
|
|
state: String,
|
|
}
|
|
|
|
#[uniffi::export]
|
|
impl OidcAuthenticationData {
|
|
/// The login URL to use for authentication.
|
|
pub fn login_url(&self) -> String {
|
|
self.url.to_string()
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Object)]
|
|
pub struct HomeserverLoginDetails {
|
|
url: String,
|
|
sliding_sync_proxy: Option<String>,
|
|
supports_oidc_login: bool,
|
|
supports_password_login: bool,
|
|
}
|
|
|
|
#[uniffi::export]
|
|
impl HomeserverLoginDetails {
|
|
/// The URL of the currently configured homeserver.
|
|
pub fn url(&self) -> String {
|
|
self.url.clone()
|
|
}
|
|
|
|
/// The URL of the discovered or manually set sliding sync proxy,
|
|
/// if any.
|
|
pub fn sliding_sync_proxy(&self) -> Option<String> {
|
|
self.sliding_sync_proxy.clone()
|
|
}
|
|
|
|
/// Whether the current homeserver supports login using OIDC.
|
|
pub fn supports_oidc_login(&self) -> bool {
|
|
self.supports_oidc_login
|
|
}
|
|
|
|
/// Whether the current homeserver supports the password login flow.
|
|
pub fn supports_password_login(&self) -> bool {
|
|
self.supports_password_login
|
|
}
|
|
}
|
|
|
|
#[uniffi::export(async_runtime = "tokio")]
|
|
impl AuthenticationService {
|
|
/// Creates a new service to authenticate a user with.
|
|
#[uniffi::constructor]
|
|
// TODO: This has too many arguments, even clippy agrees. Many of these methods are the same as
|
|
// for the `ClientBuilder`. We should let people pass in a `ClientBuilder` and possibly convert
|
|
// this to a builder pattern as well.
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn new(
|
|
base_path: String,
|
|
passphrase: Option<String>,
|
|
user_agent: Option<String>,
|
|
additional_root_certificates: Vec<Vec<u8>>,
|
|
proxy: Option<String>,
|
|
oidc_configuration: Option<OidcConfiguration>,
|
|
custom_sliding_sync_proxy: Option<String>,
|
|
session_delegate: Option<Box<dyn ClientSessionDelegate>>,
|
|
cross_process_refresh_lock_id: Option<String>,
|
|
) -> Arc<Self> {
|
|
Arc::new(AuthenticationService {
|
|
base_path,
|
|
passphrase,
|
|
user_agent,
|
|
client: AsyncRwLock::new(None),
|
|
homeserver_details: StdRwLock::new(None),
|
|
oidc_configuration,
|
|
custom_sliding_sync_proxy: StdRwLock::new(custom_sliding_sync_proxy),
|
|
session_delegate: session_delegate.map(Into::into),
|
|
cross_process_refresh_lock_id,
|
|
additional_root_certificates,
|
|
proxy,
|
|
})
|
|
}
|
|
|
|
pub fn homeserver_details(&self) -> Option<Arc<HomeserverLoginDetails>> {
|
|
self.homeserver_details.read().unwrap().clone()
|
|
}
|
|
|
|
/// Updates the service to authenticate with the homeserver for the
|
|
/// specified address.
|
|
pub async fn configure_homeserver(
|
|
&self,
|
|
server_name_or_homeserver_url: String,
|
|
) -> Result<(), AuthenticationError> {
|
|
let mut builder = self.new_client_builder();
|
|
builder = builder.server_name_or_homeserver_url(server_name_or_homeserver_url);
|
|
|
|
let client = builder.build_inner().await?;
|
|
let details = self.details_from_client(&client).await?;
|
|
|
|
// Make sure there's a sliding sync proxy available.
|
|
if self.custom_sliding_sync_proxy.read().unwrap().is_none()
|
|
&& details.sliding_sync_proxy().is_none()
|
|
{
|
|
return Err(AuthenticationError::SlidingSyncNotAvailable);
|
|
}
|
|
|
|
*self.client.write().await = Some(client);
|
|
*self.homeserver_details.write().unwrap() = Some(Arc::new(details));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Performs a password login using the current homeserver.
|
|
pub async fn login(
|
|
&self,
|
|
username: String,
|
|
password: String,
|
|
initial_device_name: Option<String>,
|
|
device_id: Option<String>,
|
|
) -> Result<Arc<Client>, AuthenticationError> {
|
|
let client_guard = self.client.read().await;
|
|
let Some(client) = client_guard.as_ref() else {
|
|
return Err(AuthenticationError::ClientMissing);
|
|
};
|
|
|
|
// Login and ask the server for the full user ID as this could be different from
|
|
// the username that was entered.
|
|
client.login(username, password, initial_device_name, device_id).await.map_err(
|
|
|e| match e {
|
|
ClientError::Generic { msg } => AuthenticationError::Generic { message: msg },
|
|
},
|
|
)?;
|
|
let whoami = client.whoami().await?;
|
|
let session =
|
|
client.inner.matrix_auth().session().ok_or(AuthenticationError::SessionMissing)?;
|
|
|
|
drop(client_guard);
|
|
self.finalize_client(session, whoami.user_id).await
|
|
}
|
|
|
|
/// Requests the URL needed for login in a web view using OIDC. Once the web
|
|
/// view has succeeded, call `login_with_oidc_callback` with the callback it
|
|
/// returns.
|
|
pub async fn url_for_oidc_login(
|
|
&self,
|
|
) -> Result<Arc<OidcAuthenticationData>, AuthenticationError> {
|
|
let client_guard = self.client.read().await;
|
|
let Some(client) = client_guard.as_ref() else {
|
|
return Err(AuthenticationError::ClientMissing);
|
|
};
|
|
|
|
let oidc = client.inner.oidc();
|
|
|
|
let issuer = match oidc.fetch_authentication_issuer().await {
|
|
Ok(issuer) => issuer,
|
|
Err(error) => {
|
|
if error
|
|
.as_client_api_error()
|
|
.is_some_and(|err| err.status_code == StatusCode::NOT_FOUND)
|
|
{
|
|
return Err(AuthenticationError::OidcNotSupported);
|
|
} else {
|
|
return Err(AuthenticationError::ServerUnreachable(error));
|
|
}
|
|
}
|
|
};
|
|
|
|
let Some(oidc_configuration) = &self.oidc_configuration else {
|
|
return Err(AuthenticationError::OidcMetadataMissing);
|
|
};
|
|
|
|
let redirect_url = Url::parse(&oidc_configuration.redirect_uri)
|
|
.map_err(|_e| AuthenticationError::OidcMetadataInvalid)?;
|
|
|
|
self.configure_oidc(&oidc, issuer, oidc_configuration).await?;
|
|
|
|
let mut data_builder = oidc.login(redirect_url, None)?;
|
|
// TODO: Add a check for the Consent prompt when MAS is updated.
|
|
data_builder = data_builder.prompt(vec![Prompt::Consent]);
|
|
let data = data_builder.build().await?;
|
|
|
|
Ok(Arc::new(OidcAuthenticationData { url: data.url, state: data.state }))
|
|
}
|
|
|
|
/// Completes the OIDC login process.
|
|
pub async fn login_with_oidc_callback(
|
|
&self,
|
|
authentication_data: Arc<OidcAuthenticationData>,
|
|
callback_url: String,
|
|
) -> Result<Arc<Client>, AuthenticationError> {
|
|
let client_guard = self.client.read().await;
|
|
let Some(client) = client_guard.as_ref() else {
|
|
return Err(AuthenticationError::ClientMissing);
|
|
};
|
|
|
|
let oidc = client.inner.oidc();
|
|
|
|
let url =
|
|
Url::parse(&callback_url).map_err(|_| AuthenticationError::OidcCallbackUrlInvalid)?;
|
|
|
|
let response = AuthorizationResponse::parse_uri(&url)
|
|
.map_err(|_| AuthenticationError::OidcCallbackUrlInvalid)?;
|
|
|
|
let code = match response {
|
|
AuthorizationResponse::Success(code) => code,
|
|
AuthorizationResponse::Error(err) => {
|
|
if err.error.error == AccessDenied {
|
|
// The user cancelled the login in the web view.
|
|
return Err(AuthenticationError::OidcCancelled);
|
|
}
|
|
return Err(AuthenticationError::OidcError {
|
|
message: err.error.error.to_string(),
|
|
});
|
|
}
|
|
};
|
|
|
|
if code.state != authentication_data.state {
|
|
return Err(AuthenticationError::OidcCallbackUrlInvalid);
|
|
};
|
|
|
|
oidc.finish_authorization(code).await?;
|
|
|
|
oidc.finish_login()
|
|
.await
|
|
.map_err(|e| AuthenticationError::OidcError { message: e.to_string() })?;
|
|
|
|
let user_id = client.inner.user_id().unwrap().to_owned();
|
|
let session =
|
|
client.inner.oidc().full_session().ok_or(AuthenticationError::SessionMissing)?;
|
|
|
|
drop(client_guard);
|
|
self.finalize_client(session, user_id).await
|
|
}
|
|
}
|
|
|
|
impl AuthenticationService {
|
|
/// A new client builder pre-configured with the service's base path and
|
|
/// user agent if specified
|
|
fn new_client_builder(&self) -> Arc<ClientBuilder> {
|
|
let mut builder = ClientBuilder::new().base_path(self.base_path.clone());
|
|
|
|
if let Some(user_agent) = self.user_agent.clone() {
|
|
builder = builder.user_agent(user_agent);
|
|
}
|
|
|
|
if let Some(proxy) = &self.proxy {
|
|
builder = builder.proxy(proxy.to_owned())
|
|
}
|
|
|
|
builder = builder.add_root_certificates(self.additional_root_certificates.clone());
|
|
|
|
builder
|
|
}
|
|
|
|
/// Get the homeserver login details from a client.
|
|
async fn details_from_client(
|
|
&self,
|
|
client: &Client,
|
|
) -> Result<HomeserverLoginDetails, AuthenticationError> {
|
|
let supports_oidc_login = client.inner.oidc().fetch_authentication_issuer().await.is_ok();
|
|
let supports_password_login = client.supports_password_login().await.ok().unwrap_or(false);
|
|
let sliding_sync_proxy = client.sliding_sync_proxy().map(|proxy_url| proxy_url.to_string());
|
|
let url = client.homeserver();
|
|
|
|
Ok(HomeserverLoginDetails {
|
|
url,
|
|
sliding_sync_proxy,
|
|
supports_oidc_login,
|
|
supports_password_login,
|
|
})
|
|
}
|
|
|
|
/// Handle any necessary configuration in order for login via OIDC to
|
|
/// succeed. This includes performing dynamic client registration against
|
|
/// the homeserver's issuer or restoring a previous registration if one has
|
|
/// been stored.
|
|
async fn configure_oidc(
|
|
&self,
|
|
oidc: &Oidc,
|
|
issuer: String,
|
|
configuration: &OidcConfiguration,
|
|
) -> Result<(), AuthenticationError> {
|
|
if oidc.client_credentials().is_some() {
|
|
tracing::info!("OIDC is already configured.");
|
|
return Ok(());
|
|
};
|
|
|
|
let oidc_metadata = self.oidc_metadata(configuration)?;
|
|
|
|
if self.load_client_registration(oidc, issuer.clone(), oidc_metadata.clone()).await {
|
|
tracing::info!("OIDC configuration loaded from disk.");
|
|
return Ok(());
|
|
}
|
|
|
|
tracing::info!("Registering this client for OIDC.");
|
|
let registration_response =
|
|
oidc.register_client(&issuer, oidc_metadata.clone(), None).await?;
|
|
|
|
// The format of the credentials changes according to the client metadata that
|
|
// was sent. Public clients only get a client ID.
|
|
let credentials =
|
|
ClientCredentials::None { client_id: registration_response.client_id.clone() };
|
|
oidc.restore_registered_client(issuer, oidc_metadata, credentials);
|
|
|
|
tracing::info!("Persisting OIDC registration data.");
|
|
self.store_client_registration(oidc).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Stores the current OIDC dynamic client registration so it can be re-used
|
|
/// if we ever log in via the same issuer again.
|
|
async fn store_client_registration(&self, oidc: &Oidc) -> Result<(), AuthenticationError> {
|
|
let issuer = Url::parse(oidc.issuer().ok_or(AuthenticationError::OidcNotSupported)?)
|
|
.map_err(|_| AuthenticationError::OidcError {
|
|
message: String::from("Failed to parse issuer URL."),
|
|
})?;
|
|
let client_id = oidc
|
|
.client_credentials()
|
|
.ok_or(AuthenticationError::OidcError {
|
|
message: String::from("Missing client registration."),
|
|
})?
|
|
.client_id()
|
|
.to_owned();
|
|
|
|
let metadata = oidc.client_metadata().ok_or(AuthenticationError::OidcError {
|
|
message: String::from("Missing client metadata."),
|
|
})?;
|
|
|
|
let registrations = OidcRegistrations::new(
|
|
&self.base_path,
|
|
metadata.clone(),
|
|
self.oidc_static_registrations(),
|
|
)?;
|
|
registrations.set_and_write_client_id(ClientId(client_id), issuer)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Attempts to load an existing OIDC dynamic client registration for the
|
|
/// currently configured issuer.
|
|
async fn load_client_registration(
|
|
&self,
|
|
oidc: &Oidc,
|
|
issuer: String,
|
|
oidc_metadata: VerifiedClientMetadata,
|
|
) -> bool {
|
|
let Ok(issuer_url) = Url::parse(&issuer) else {
|
|
tracing::error!("Failed to parse {issuer:?}");
|
|
return false;
|
|
};
|
|
let Some(registrations) = OidcRegistrations::new(
|
|
&self.base_path,
|
|
oidc_metadata.clone(),
|
|
self.oidc_static_registrations(),
|
|
)
|
|
.ok() else {
|
|
return false;
|
|
};
|
|
let Some(client_id) = registrations.client_id(&issuer_url) else {
|
|
return false;
|
|
};
|
|
|
|
oidc.restore_registered_client(
|
|
issuer,
|
|
oidc_metadata,
|
|
ClientCredentials::None { client_id: client_id.0 },
|
|
);
|
|
|
|
true
|
|
}
|
|
|
|
/// Creates and verifies OIDC client metadata for the supplied OIDC
|
|
/// configuration.
|
|
fn oidc_metadata(
|
|
&self,
|
|
configuration: &OidcConfiguration,
|
|
) -> Result<VerifiedClientMetadata, AuthenticationError> {
|
|
let redirect_uri = Url::parse(&configuration.redirect_uri)
|
|
.map_err(|_| AuthenticationError::OidcCallbackUrlInvalid)?;
|
|
let client_name =
|
|
configuration.client_name.as_ref().map(|n| Localized::new(n.to_owned(), []));
|
|
let client_uri = configuration.client_uri.localized_url()?;
|
|
let logo_uri = configuration.logo_uri.localized_url()?;
|
|
let policy_uri = configuration.policy_uri.localized_url()?;
|
|
let tos_uri = configuration.tos_uri.localized_url()?;
|
|
let contacts = configuration.contacts.clone();
|
|
|
|
ClientMetadata {
|
|
application_type: Some(ApplicationType::Native),
|
|
redirect_uris: Some(vec![redirect_uri]),
|
|
grant_types: Some(vec![GrantType::RefreshToken, GrantType::AuthorizationCode]),
|
|
// A native client shouldn't use authentication as the credentials could be intercepted.
|
|
token_endpoint_auth_method: Some(OAuthClientAuthenticationMethod::None),
|
|
// The server should display the following fields when getting the user's consent.
|
|
client_name,
|
|
contacts,
|
|
client_uri,
|
|
logo_uri,
|
|
policy_uri,
|
|
tos_uri,
|
|
..Default::default()
|
|
}
|
|
.validate()
|
|
.map_err(|_| AuthenticationError::OidcMetadataInvalid)
|
|
}
|
|
|
|
fn oidc_static_registrations(&self) -> HashMap<Url, ClientId> {
|
|
let registrations = self
|
|
.oidc_configuration
|
|
.as_ref()
|
|
.map(|c| c.static_registrations.clone())
|
|
.unwrap_or_default();
|
|
registrations
|
|
.iter()
|
|
.filter_map(|(issuer, client_id)| {
|
|
let Ok(issuer) = Url::parse(issuer) else {
|
|
tracing::error!("Failed to parse {:?}", issuer);
|
|
return None;
|
|
};
|
|
Some((issuer, ClientId(client_id.clone())))
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
/// Creates a new client to setup the store path now the user ID is known.
|
|
async fn finalize_client(
|
|
&self,
|
|
session: impl Into<AuthSession>,
|
|
user_id: OwnedUserId,
|
|
) -> Result<Arc<Client>, AuthenticationError> {
|
|
// Take ownership of the client. This means that further attempts to
|
|
// `finalize_client` may fail, but we want to make sure that there
|
|
// aren't two clients at any point later.
|
|
let Some(client) = self.client.write().await.take() else {
|
|
return Err(AuthenticationError::ClientMissing);
|
|
};
|
|
|
|
let homeserver_url = client.homeserver();
|
|
|
|
let sliding_sync_proxy = self
|
|
.custom_sliding_sync_proxy
|
|
.read()
|
|
.unwrap()
|
|
.clone()
|
|
.or_else(|| client.sliding_sync_proxy().map(|url| url.to_string()));
|
|
|
|
// Wait for the parent client to finish running its initialization tasks.
|
|
client.inner.encryption().wait_for_e2ee_initialization_tasks().await;
|
|
|
|
// Drop the parent client. Both clients shouldn't be alive at the same time, or
|
|
// it may cause issues (when trying to initialize encryption-related tasks at
|
|
// the same time).
|
|
drop(client);
|
|
|
|
// Construct the final client.
|
|
let mut client = self
|
|
.new_client_builder()
|
|
.passphrase(self.passphrase.clone())
|
|
.homeserver_url(homeserver_url)
|
|
.sliding_sync_proxy(sliding_sync_proxy)
|
|
.auto_enable_cross_signing(true)
|
|
.backup_download_strategy(BackupDownloadStrategy::AfterDecryptionFailure)
|
|
.auto_enable_backups(true)
|
|
.username(user_id.to_string());
|
|
|
|
if let Some(proxy) = &self.proxy {
|
|
client = client.proxy(proxy.to_owned())
|
|
}
|
|
|
|
if let Some(id) = &self.cross_process_refresh_lock_id {
|
|
let Some(ref session_delegate) = self.session_delegate else {
|
|
return Err(AuthenticationError::OidcError {
|
|
message: "cross-process refresh lock requires session delegate".to_owned(),
|
|
});
|
|
};
|
|
client = client
|
|
.enable_cross_process_refresh_lock_inner(id.clone(), session_delegate.clone());
|
|
} else if let Some(ref session_delegate) = self.session_delegate {
|
|
client = client.set_session_delegate_inner(session_delegate.clone());
|
|
}
|
|
|
|
let client = client.build_inner().await?;
|
|
|
|
// Restore the client using the session from the login request.
|
|
client.restore_session_inner(session).await?;
|
|
|
|
Ok(Arc::new(client))
|
|
}
|
|
}
|
|
|
|
trait OptionExt {
|
|
/// Convenience method to convert a string to a URL and returns it as a
|
|
/// Localized URL. No localization is actually performed.
|
|
fn localized_url(&self) -> Result<Option<Localized<Url>>, AuthenticationError>;
|
|
}
|
|
|
|
impl OptionExt for Option<String> {
|
|
fn localized_url(&self) -> Result<Option<Localized<Url>>, AuthenticationError> {
|
|
self.as_deref()
|
|
.map(|uri| -> Result<Localized<Url>, AuthenticationError> {
|
|
Ok(Localized::new(
|
|
Url::parse(uri).map_err(|_| AuthenticationError::OidcMetadataInvalid)?,
|
|
[],
|
|
))
|
|
})
|
|
.transpose()
|
|
}
|
|
}
|