WIP: Migrate to matrix-rust-sdk #111

Draft
pierre wants to merge 21 commits from migrate-to-matrix-sdk into master
6 changed files with 124 additions and 18 deletions
Showing only changes of commit 1438e2de0e - Show all commits

View File

@@ -3020,6 +3020,7 @@ dependencies = [
"ractor", "ractor",
"rand 0.9.2", "rand 0.9.2",
"serde", "serde",
"serde_json",
"sha2", "sha2",
"thiserror 2.0.17", "thiserror 2.0.17",
"tokio", "tokio",

View File

@@ -30,4 +30,5 @@ hex = "0.4.3"
mailchecker = "6.0.19" mailchecker = "6.0.19"
matrix-sdk = "0.14.0" matrix-sdk = "0.14.0"
url = "2.5.7" url = "2.5.7"
ractor = "0.15.9" ractor = "0.15.9"
serde_json = "1.0.145"

View File

@@ -220,6 +220,11 @@ impl AppConfig {
pub fn user_matrix_passphrase_path(&self, mail: &UserEmail) -> PathBuf { pub fn user_matrix_passphrase_path(&self, mail: &UserEmail) -> PathBuf {
self.user_directory(mail).join("matrix-db-passphrase") self.user_directory(mail).join("matrix-db-passphrase")
} }
/// Get user Matrix session file path
pub fn user_matrix_session_file_path(&self, mail: &UserEmail) -> PathBuf {
self.user_directory(mail).join("matrix-session.json")
}
} }
#[derive(Debug, Clone, serde::Serialize)] #[derive(Debug, Clone, serde::Serialize)]

View File

@@ -2,7 +2,6 @@ use crate::controllers::HttpResult;
use crate::extractors::matrix_client_extractor::MatrixClientExtractor; use crate::extractors::matrix_client_extractor::MatrixClientExtractor;
use crate::matrix_connection::matrix_client::FinishMatrixAuth; use crate::matrix_connection::matrix_client::FinishMatrixAuth;
use actix_web::HttpResponse; use actix_web::HttpResponse;
use anyhow::Context;
#[derive(serde::Serialize)] #[derive(serde::Serialize)]
struct StartAuthResponse { struct StartAuthResponse {
@@ -17,10 +16,15 @@ pub async fn start_auth(client: MatrixClientExtractor) -> HttpResult {
/// Finish user authentication on Matrix server /// Finish user authentication on Matrix server
pub async fn finish_auth(client: MatrixClientExtractor) -> HttpResult { pub async fn finish_auth(client: MatrixClientExtractor) -> HttpResult {
client match client
.client .client
.finish_login(client.auth.decode_json_body::<FinishMatrixAuth>()?) .finish_login(client.auth.decode_json_body::<FinishMatrixAuth>()?)
.await .await
.context("Failed to finalize Matrix authentication!")?; {
Ok(HttpResponse::Accepted().finish()) Ok(_) => Ok(HttpResponse::Accepted().finish()),
Err(e) => {
log::error!("Failed to finish Matrix authentication: {e}");
Err(e.into())
}
}
} }

View File

@@ -1,15 +1,29 @@
use crate::app_config::AppConfig; use crate::app_config::AppConfig;
use crate::users::UserEmail; use crate::users::UserEmail;
use crate::utils::rand_utils::rand_string; use crate::utils::rand_utils::rand_string;
use anyhow::Context;
use matrix_sdk::authentication::oauth::error::OAuthDiscoveryError; use matrix_sdk::authentication::oauth::error::OAuthDiscoveryError;
use matrix_sdk::authentication::oauth::{OAuthError, UrlOrQuery}; use matrix_sdk::authentication::oauth::{ClientId, OAuthError, UrlOrQuery, UserSession};
use matrix_sdk::ruma::serde::Raw; use matrix_sdk::ruma::serde::Raw;
use matrix_sdk::{Client, ClientBuildError}; use matrix_sdk::{Client, ClientBuildError};
use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
/// The full session to persist.
#[derive(Debug, Serialize, Deserialize, Clone)]
struct StoredSession {
/// The OAuth 2.0 user session.
user_session: UserSession,
/// The OAuth 2.0 client ID.
client_id: ClientId,
}
/// Matrix Gateway session errors /// Matrix Gateway session errors
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
enum MatrixClientError { enum MatrixClientError {
#[error("Failed to destroy previous client data! {0}")]
DestroyPreviousData(Box<MatrixClientError>),
#[error("Failed to create Matrix database storage directory! {0}")] #[error("Failed to create Matrix database storage directory! {0}")]
CreateMatrixDbDir(std::io::Error), CreateMatrixDbDir(std::io::Error),
#[error("Failed to create database passphrase! {0}")] #[error("Failed to create database passphrase! {0}")]
@@ -18,6 +32,8 @@ enum MatrixClientError {
ReadDbPassphrase(std::io::Error), ReadDbPassphrase(std::io::Error),
#[error("Failed to build Matrix client! {0}")] #[error("Failed to build Matrix client! {0}")]
BuildMatrixClient(ClientBuildError), BuildMatrixClient(ClientBuildError),
#[error("Failed to clear Matrix session file! {0}")]
ClearMatrixSessionFile(std::io::Error),
#[error("Failed to clear Matrix database storage directory! {0}")] #[error("Failed to clear Matrix database storage directory! {0}")]
ClearMatrixDbDir(std::io::Error), ClearMatrixDbDir(std::io::Error),
#[error("Failed to remove database passphrase! {0}")] #[error("Failed to remove database passphrase! {0}")]
@@ -30,6 +46,8 @@ enum MatrixClientError {
BuildAuthRequest(OAuthError), BuildAuthRequest(OAuthError),
#[error("Failed to finalize authentication! {0}")] #[error("Failed to finalize authentication! {0}")]
FinishLogin(matrix_sdk::Error), FinishLogin(matrix_sdk::Error),
#[error("Failed to write session file! {0}")]
WriteSessionFile(std::io::Error),
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
@@ -47,6 +65,15 @@ pub struct MatrixClient {
impl MatrixClient { impl MatrixClient {
/// Start to build Matrix client to initiate user authentication /// Start to build Matrix client to initiate user authentication
pub async fn build_client(email: &UserEmail) -> anyhow::Result<Self> { pub async fn build_client(email: &UserEmail) -> anyhow::Result<Self> {
// Check if we are restoring a previous state
let is_restoring = AppConfig::get()
.user_matrix_session_file_path(email)
.is_file();
if !is_restoring {
Self::destroy_data(email).map_err(MatrixClientError::DestroyPreviousData)?;
}
// Determine Matrix database path
let db_path = AppConfig::get().user_matrix_db_path(email); let db_path = AppConfig::get().user_matrix_db_path(email);
std::fs::create_dir_all(&db_path).map_err(MatrixClientError::CreateMatrixDbDir)?; std::fs::create_dir_all(&db_path).map_err(MatrixClientError::CreateMatrixDbDir)?;
@@ -69,34 +96,46 @@ impl MatrixClient {
.map_err(MatrixClientError::BuildMatrixClient)?; .map_err(MatrixClientError::BuildMatrixClient)?;
// Check metadata // Check metadata
let server_metadata = client let oauth = client.oauth();
.oauth() let server_metadata = oauth
.server_metadata() .server_metadata()
.await .await
.map_err(MatrixClientError::FetchServerMetadata)?; .map_err(MatrixClientError::FetchServerMetadata)?;
log::info!("OAuth2 server issuer: {:?}", server_metadata.issuer); log::info!("OAuth2 server issuer: {:?}", server_metadata.issuer);
// TODO : restore client ID to oauth if needed
// TODO : restore client if client already existed // TODO : restore client if client already existed
Ok(Self { let client = Self {
email: email.clone(), email: email.clone(),
client, client,
}) };
// Automatically save session when token gets refreshed
client.setup_background_session_save().await;
Ok(client)
} }
/// Destroy this Matrix client instance /// Destroy Matrix client related data
pub fn destroy(&self) -> anyhow::Result<()> { fn destroy_data(email: &UserEmail) -> anyhow::Result<(), Box<MatrixClientError>> {
let db_path = AppConfig::get().user_matrix_db_path(&self.email); let session_path = AppConfig::get().user_matrix_session_file_path(email);
if db_path.is_file() { if session_path.is_file() {
std::fs::remove_file(&session_path)
.map_err(MatrixClientError::ClearMatrixSessionFile)?;
}
let db_path = AppConfig::get().user_matrix_db_path(email);
if db_path.is_dir() {
std::fs::remove_dir_all(&db_path).map_err(MatrixClientError::ClearMatrixDbDir)?; std::fs::remove_dir_all(&db_path).map_err(MatrixClientError::ClearMatrixDbDir)?;
} }
let passphrase_path = AppConfig::get().user_matrix_passphrase_path(&self.email); let passphrase_path = AppConfig::get().user_matrix_passphrase_path(email);
if passphrase_path.is_file() { if passphrase_path.is_file() {
std::fs::remove_file(passphrase_path).map_err(MatrixClientError::ClearDbPassphrase)?; std::fs::remove_file(passphrase_path).map_err(MatrixClientError::ClearDbPassphrase)?;
} }
todo!() Ok(())
} }
/// Initiate OAuth authentication /// Initiate OAuth authentication
@@ -137,6 +176,60 @@ impl MatrixClient {
self.client.user_id().unwrap() self.client.user_id().unwrap()
); );
// Persist session tokens
self.save_stored_session().await?;
Ok(())
}
/// Automatically persist session onto disk
pub async fn setup_background_session_save(&self) {
let this = self.clone();
tokio::spawn(async move {
while let Ok(update) = this.client.subscribe_to_session_changes().recv().await {
match update {
matrix_sdk::SessionChange::UnknownToken { soft_logout } => {
log::warn!("Received an unknown token error; soft logout? {soft_logout:?}");
}
matrix_sdk::SessionChange::TokensRefreshed => {
// The tokens have been refreshed, persist them to disk.
if let Err(err) = this.save_stored_session().await {
log::error!("Unable to store a session in the background: {err}");
}
}
}
}
});
}
/// Update the session stored on the filesystem.
async fn save_stored_session(&self) -> anyhow::Result<()> {
log::debug!("Save the stored session for {:?}...", self.email);
let user_session: UserSession = self
.client
.oauth()
.user_session()
.context("A logged in client must have a session")?;
let stored_session = StoredSession {
user_session,
client_id: self
.client
.oauth()
.client_id()
.context("Client ID should be set at this point!")?
.clone(),
};
let serialized_session = serde_json::to_string(&stored_session)?;
std::fs::write(
AppConfig::get().user_matrix_session_file_path(&self.email),
serialized_session,
)
.map_err(MatrixClientError::WriteSessionFile)?;
log::debug!("Updating the stored session: done!");
Ok(()) Ok(())
} }
} }

View File

@@ -29,17 +29,19 @@ export function MatrixAuthCallback(): React.ReactElement {
count.current = code!; count.current = code!;
await MatrixLinkApi.FinishAuth(code!, state!); await MatrixLinkApi.FinishAuth(code!, state!);
info.reloadUserInfo();
snackbar("Successfully linked to Matrix account!"); snackbar("Successfully linked to Matrix account!");
navigate("/matrix_link"); navigate("/matrix_link");
} catch (e) { } catch (e) {
console.error(e); console.error(e);
setError(String(e)); setError(String(e));
} finally {
info.reloadUserInfo();
} }
}; };
load(); load();
}); }, [code, state]);
if (error) if (error)
return ( return (