Actually make state store presistent
This sets a passphrase and path for the state store created by the sdk. The additional information is stored in via the SecretService. This sets opt-level for deps to 3, because otherwise the statestore would be really slow because of the passphrase.
This commit is contained in:
parent
fc02302620
commit
601ddee7c3
|
@ -684,6 +684,7 @@ dependencies = [
|
|||
"log",
|
||||
"matrix-sdk",
|
||||
"once_cell",
|
||||
"rand 0.8.3",
|
||||
"secret-service",
|
||||
"serde_json",
|
||||
"sourceview5",
|
||||
|
|
|
@ -4,6 +4,9 @@ version = "0.1.0"
|
|||
authors = ["Daniel García Moreno <danigm@wadobo.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
tracing-subscriber = "0.2"
|
||||
|
@ -18,6 +21,7 @@ html2pango = "0.4"
|
|||
chrono = "0.4"
|
||||
futures = "0.3"
|
||||
comrak = "0.10"
|
||||
rand = "0.8"
|
||||
|
||||
[dependencies.sourceview]
|
||||
branch = "main"
|
||||
|
|
12
src/login.rs
12
src/login.rs
|
@ -121,16 +121,20 @@ impl Login {
|
|||
|
||||
self.freeze();
|
||||
|
||||
let session = Session::new(homeserver);
|
||||
let session = Session::new();
|
||||
self.setup_session(&session);
|
||||
session.login_with_password(username, password);
|
||||
session.login_with_password(
|
||||
url::Url::parse(homeserver.as_str()).unwrap(),
|
||||
username,
|
||||
password,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn restore_sessions(&self) -> Result<(), secret_service::Error> {
|
||||
let sessions = secret::restore_sessions()?;
|
||||
|
||||
for (homeserver, stored_session) in sessions {
|
||||
let session = Session::new(homeserver.to_string());
|
||||
for stored_session in sessions {
|
||||
let session = Session::new();
|
||||
self.setup_session(&session);
|
||||
session.login_with_previous_session(stored_session);
|
||||
}
|
||||
|
|
105
src/secret.rs
105
src/secret.rs
|
@ -1,38 +1,74 @@
|
|||
use matrix_sdk::identifiers::{DeviceIdBox, UserId};
|
||||
use secret_service::EncryptionType;
|
||||
use secret_service::SecretService;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::path::PathBuf;
|
||||
use url::Url;
|
||||
|
||||
pub struct StoredSession {
|
||||
pub homeserver: Url,
|
||||
pub path: PathBuf,
|
||||
pub passphrase: String,
|
||||
pub user_id: UserId,
|
||||
pub access_token: String,
|
||||
pub device_id: DeviceIdBox,
|
||||
}
|
||||
|
||||
/// Retrives all sessions stored to the `SecretService`
|
||||
pub fn restore_sessions() -> Result<Vec<(String, matrix_sdk::Session)>, secret_service::Error> {
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub fn restore_sessions() -> Result<Vec<StoredSession>, secret_service::Error> {
|
||||
let ss = SecretService::new(EncryptionType::Dh)?;
|
||||
let collection = ss.get_default_collection()?;
|
||||
|
||||
// Sessions that contain or produce errors are ignored.
|
||||
// TODO: Return error for corrupt sessions
|
||||
|
||||
let res = collection
|
||||
.get_all_items()?
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
let attr = item.get_attributes().ok()?;
|
||||
if let (Some(homeserver), Some(access_token), Some(user_id), Some(device_id)) = (
|
||||
attr.get("homeserver"),
|
||||
String::from_utf8(item.get_secret().ok()?).ok(),
|
||||
attr.get("user-id")
|
||||
.and_then(|s| s.to_string().try_into().ok()),
|
||||
attr.get("device-id")
|
||||
.and_then(|s| Some(s.to_string().into())),
|
||||
) {
|
||||
let session = matrix_sdk::Session {
|
||||
access_token,
|
||||
user_id,
|
||||
device_id,
|
||||
};
|
||||
Some((homeserver.to_string(), session))
|
||||
} else {
|
||||
None
|
||||
.fold(HashMap::new(), |mut acc, item| {
|
||||
let finder = move || -> Option<((String, String, String), (String, Option<String>))> {
|
||||
let attr = item.get_attributes().ok()?;
|
||||
|
||||
let homeserver = attr.get("homeserver")?.to_string();
|
||||
let user_id = attr.get("user-id")?.to_string();
|
||||
let device_id = attr.get("device-id")?.to_string();
|
||||
let secret = String::from_utf8(item.get_secret().ok()?).ok()?;
|
||||
let path = attr.get("path").map(|s| s.to_string());
|
||||
Some(((homeserver, user_id, device_id), (secret, path)))
|
||||
};
|
||||
|
||||
if let Some((key, value)) = finder() {
|
||||
acc.entry(key).or_insert(vec![]).push(value);
|
||||
}
|
||||
|
||||
acc
|
||||
})
|
||||
.into_iter()
|
||||
.filter_map(|((homeserver, user_id, device_id), mut items)| {
|
||||
if items.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
let (access_token, passphrase, path) = match items.pop()? {
|
||||
(secret, Some(path)) => (items.pop()?.0, secret, PathBuf::from(path)),
|
||||
(secret, None) => {
|
||||
let item = items.pop()?;
|
||||
(secret, item.0, PathBuf::from(item.1?))
|
||||
}
|
||||
};
|
||||
|
||||
let homeserver = Url::parse(&homeserver).ok()?;
|
||||
let user_id = UserId::try_from(user_id).ok()?;
|
||||
let device_id = DeviceIdBox::try_from(device_id).ok()?;
|
||||
|
||||
Some(StoredSession {
|
||||
homeserver,
|
||||
path,
|
||||
passphrase,
|
||||
user_id,
|
||||
access_token,
|
||||
device_id,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
@ -41,16 +77,14 @@ pub fn restore_sessions() -> Result<Vec<(String, matrix_sdk::Session)>, secret_s
|
|||
|
||||
/// Writes a sessions to the `SecretService`, overwriting any previously stored session with the
|
||||
/// same `homeserver`, `username` and `device-id`.
|
||||
pub fn store_session(
|
||||
homeserver: &str,
|
||||
session: matrix_sdk::Session,
|
||||
) -> Result<(), secret_service::Error> {
|
||||
pub fn store_session(session: StoredSession) -> Result<(), secret_service::Error> {
|
||||
let ss = SecretService::new(EncryptionType::Dh)?;
|
||||
let collection = ss.get_default_collection()?;
|
||||
|
||||
// Store the infromation for the login
|
||||
let attributes: HashMap<&str, &str> = [
|
||||
("user-id", session.user_id.as_str()),
|
||||
("homeserver", homeserver),
|
||||
("homeserver", session.homeserver.as_str()),
|
||||
("device-id", session.device_id.as_str()),
|
||||
]
|
||||
.iter()
|
||||
|
@ -65,5 +99,24 @@ pub fn store_session(
|
|||
"text/plain",
|
||||
)?;
|
||||
|
||||
// Store the infromation for the crypto store
|
||||
let attributes: HashMap<&str, &str> = [
|
||||
("path", session.path.to_str().unwrap()),
|
||||
("user-id", session.user_id.as_str()),
|
||||
("homeserver", session.homeserver.as_str()),
|
||||
("device-id", session.device_id.as_str()),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
collection.create_item(
|
||||
"Fractal (Encrypted local database)",
|
||||
attributes,
|
||||
session.passphrase.as_bytes(),
|
||||
true,
|
||||
"text/plain",
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -11,9 +11,11 @@ use self::user::User;
|
|||
|
||||
use crate::event_from_sync_event;
|
||||
use crate::secret;
|
||||
use crate::secret::StoredSession;
|
||||
use crate::utils::do_async;
|
||||
use crate::RUNTIME;
|
||||
|
||||
use crate::session::categories::Categories;
|
||||
use adw;
|
||||
use adw::subclass::prelude::BinImpl;
|
||||
use gtk::subclass::prelude::*;
|
||||
|
@ -27,13 +29,14 @@ use matrix_sdk::{
|
|||
deserialized_responses::SyncResponse,
|
||||
events::{AnyRoomEvent, AnySyncRoomEvent},
|
||||
identifiers::RoomId,
|
||||
uuid::Uuid,
|
||||
Client, ClientConfig, RequestConfig, SyncSettings,
|
||||
};
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use std::fs;
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
use crate::session::categories::Categories;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
use glib::subclass::{InitializingObject, Signal};
|
||||
|
@ -50,7 +53,6 @@ mod imp {
|
|||
pub sidebar: TemplateChild<Sidebar>,
|
||||
#[template_child]
|
||||
pub content: TemplateChild<Content>,
|
||||
pub homeserver: OnceCell<String>,
|
||||
/// Contains the error if something went wrong
|
||||
pub error: RefCell<Option<matrix_sdk::Error>>,
|
||||
pub client: OnceCell<Client>,
|
||||
|
@ -79,13 +81,6 @@ mod imp {
|
|||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![
|
||||
glib::ParamSpec::new_string(
|
||||
"homeserver",
|
||||
"Homeserver",
|
||||
"The matrix homeserver of this session",
|
||||
None,
|
||||
glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
|
||||
),
|
||||
glib::ParamSpec::new_object(
|
||||
"categories",
|
||||
"Categories",
|
||||
|
@ -114,10 +109,6 @@ mod imp {
|
|||
pspec: &glib::ParamSpec,
|
||||
) {
|
||||
match pspec.name() {
|
||||
"homeserver" => {
|
||||
let homeserver = value.get().unwrap();
|
||||
let _ = obj.set_homeserver(homeserver);
|
||||
}
|
||||
"selected-room" => {
|
||||
let selected_room = value.get().unwrap();
|
||||
obj.set_selected_room(selected_room);
|
||||
|
@ -128,7 +119,6 @@ mod imp {
|
|||
|
||||
fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
match pspec.name() {
|
||||
"homeserver" => self.homeserver.get().to_value(),
|
||||
"categories" => self.categories.to_value(),
|
||||
"selected-room" => obj.selected_room().to_value(),
|
||||
_ => unimplemented!(),
|
||||
|
@ -156,8 +146,8 @@ glib::wrapper! {
|
|||
}
|
||||
|
||||
impl Session {
|
||||
pub fn new(homeserver: String) -> Self {
|
||||
glib::Object::new(&[("homeserver", &homeserver)]).expect("Failed to create Session")
|
||||
pub fn new() -> Self {
|
||||
glib::Object::new(&[]).expect("Failed to create Session")
|
||||
}
|
||||
|
||||
pub fn selected_room(&self) -> Option<Room> {
|
||||
|
@ -184,42 +174,51 @@ impl Session {
|
|||
self.notify("selected-room");
|
||||
}
|
||||
|
||||
fn set_homeserver(&self, homeserver: String) {
|
||||
let priv_ = imp::Session::from_instance(self);
|
||||
|
||||
priv_.homeserver.set(homeserver.clone()).unwrap();
|
||||
|
||||
let config = ClientConfig::new().request_config(RequestConfig::new().retry_limit(2));
|
||||
let homeserver = match Url::parse(homeserver.as_str()) {
|
||||
Ok(homeserver) => homeserver,
|
||||
Err(_error) => {
|
||||
// TODO: hanlde parse error
|
||||
panic!();
|
||||
}
|
||||
};
|
||||
|
||||
let client = Client::new_with_config(homeserver, config).unwrap();
|
||||
priv_.client.set(client).unwrap();
|
||||
}
|
||||
|
||||
fn client(&self) -> Client {
|
||||
let priv_ = imp::Session::from_instance(self);
|
||||
priv_.client.get().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn login_with_password(&self, username: String, password: String) {
|
||||
let client = self.client();
|
||||
pub fn login_with_password(&self, homeserver: Url, username: String, password: String) {
|
||||
let mut path = glib::user_data_dir();
|
||||
path.push(
|
||||
&Uuid::new_v4()
|
||||
.to_hyphenated()
|
||||
.encode_lower(&mut Uuid::encode_buffer()),
|
||||
);
|
||||
|
||||
do_async(
|
||||
async move {
|
||||
client
|
||||
let passphrase: String = {
|
||||
let mut rng = thread_rng();
|
||||
(&mut rng)
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(30)
|
||||
.map(char::from)
|
||||
.collect()
|
||||
};
|
||||
let config = ClientConfig::new()
|
||||
.request_config(RequestConfig::new().retry_limit(2))
|
||||
.passphrase(passphrase.clone())
|
||||
.store_path(path.clone());
|
||||
|
||||
let client = Client::new_with_config(homeserver.clone(), config).unwrap();
|
||||
let response = client
|
||||
.login(&username, &password, None, Some("Fractal Next"))
|
||||
.await
|
||||
.map(|response| matrix_sdk::Session {
|
||||
access_token: response.access_token,
|
||||
user_id: response.user_id,
|
||||
device_id: response.device_id,
|
||||
})
|
||||
.await;
|
||||
match response {
|
||||
Ok(response) => Ok((
|
||||
client,
|
||||
StoredSession {
|
||||
homeserver: homeserver,
|
||||
path: path,
|
||||
passphrase: passphrase,
|
||||
access_token: response.access_token,
|
||||
user_id: response.user_id,
|
||||
device_id: response.device_id,
|
||||
},
|
||||
)),
|
||||
Err(error) => {
|
||||
// Remove the store created by Client::new()
|
||||
fs::remove_dir_all(path).unwrap();
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
},
|
||||
clone!(@weak self as obj => move |result| async move {
|
||||
obj.handle_login_result(result, true);
|
||||
|
@ -227,11 +226,24 @@ impl Session {
|
|||
);
|
||||
}
|
||||
|
||||
pub fn login_with_previous_session(&self, session: matrix_sdk::Session) {
|
||||
let client = self.client();
|
||||
|
||||
pub fn login_with_previous_session(&self, session: StoredSession) {
|
||||
do_async(
|
||||
async move { client.restore_login(session.clone()).await.map(|_| session) },
|
||||
async move {
|
||||
let config = ClientConfig::new()
|
||||
.request_config(RequestConfig::new().retry_limit(2))
|
||||
.passphrase(session.passphrase.clone())
|
||||
.store_path(session.path.clone());
|
||||
|
||||
let client = Client::new_with_config(session.homeserver.clone(), config).unwrap();
|
||||
client
|
||||
.restore_login(matrix_sdk::Session {
|
||||
user_id: session.user_id.clone(),
|
||||
device_id: session.device_id.clone(),
|
||||
access_token: session.access_token.clone(),
|
||||
})
|
||||
.await
|
||||
.map(|_| (client, session))
|
||||
},
|
||||
clone!(@weak self as obj => move |result| async move {
|
||||
obj.handle_login_result(result, false);
|
||||
}),
|
||||
|
@ -240,15 +252,17 @@ impl Session {
|
|||
|
||||
fn handle_login_result(
|
||||
&self,
|
||||
result: Result<matrix_sdk::Session, matrix_sdk::Error>,
|
||||
result: Result<(Client, StoredSession), matrix_sdk::Error>,
|
||||
store_session: bool,
|
||||
) {
|
||||
let priv_ = &imp::Session::from_instance(self);
|
||||
let priv_ = imp::Session::from_instance(self);
|
||||
match result {
|
||||
Ok(session) => {
|
||||
Ok((client, session)) => {
|
||||
priv_.client.set(client).unwrap();
|
||||
self.set_user(User::new(&session.user_id));
|
||||
if store_session {
|
||||
self.store_session(session).unwrap();
|
||||
// TODO: report secret service errors
|
||||
secret::store_session(session).unwrap();
|
||||
}
|
||||
self.load();
|
||||
self.sync();
|
||||
|
@ -261,8 +275,9 @@ impl Session {
|
|||
}
|
||||
|
||||
fn sync(&self) {
|
||||
let priv_ = imp::Session::from_instance(self);
|
||||
let sender = self.create_new_sync_response_sender();
|
||||
let client = self.client();
|
||||
let client = priv_.client.get().unwrap().clone();
|
||||
RUNTIME.spawn(async move {
|
||||
// TODO: only create the filter once and reuse it in the future
|
||||
let room_event_filter = assign!(RoomEventFilter::default(), {
|
||||
|
@ -344,12 +359,6 @@ impl Session {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
fn store_session(&self, session: matrix_sdk::Session) -> Result<(), secret_service::Error> {
|
||||
let priv_ = &imp::Session::from_instance(self);
|
||||
let homeserver = priv_.homeserver.get().unwrap();
|
||||
secret::store_session(homeserver, session)
|
||||
}
|
||||
|
||||
fn handle_sync_response(&self, response: SyncResponse) {
|
||||
let priv_ = imp::Session::from_instance(self);
|
||||
|
||||
|
|
Loading…
Reference in New Issue