Can save Matrix session after authentication
This commit is contained in:
1
matrixgw_backend/Cargo.lock
generated
1
matrixgw_backend/Cargo.lock
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
Reference in New Issue
Block a user