Add a way to log out

This commit is contained in:
Elias Projahn 2021-02-07 00:43:59 +01:00
parent 67fad1329d
commit 5b066a05b6
6 changed files with 126 additions and 35 deletions

View file

@ -10,15 +10,15 @@ use std::rc::Rc;
/// A dialog for entering login credentials. /// A dialog for entering login credentials.
pub struct LoginDialog { pub struct LoginDialog {
handle: NavigationHandle<LoginData>, handle: NavigationHandle<Option<LoginData>>,
widget: gtk::Stack, widget: gtk::Stack,
info_bar: gtk::InfoBar, info_bar: gtk::InfoBar,
username_entry: gtk::Entry, username_entry: gtk::Entry,
password_entry: gtk::Entry, password_entry: gtk::Entry,
} }
impl Screen<(), LoginData> for LoginDialog { impl Screen<Option<LoginData>, Option<LoginData>> for LoginDialog {
fn new(_: (), handle: NavigationHandle<LoginData>) -> Rc<Self> { fn new(data: Option<LoginData>, handle: NavigationHandle<Option<LoginData>>) -> Rc<Self> {
// Create UI // Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/login_dialog.ui"); let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/login_dialog.ui");
@ -28,7 +28,16 @@ impl Screen<(), LoginData> for LoginDialog {
get_widget!(builder, gtk::Button, login_button); get_widget!(builder, gtk::Button, login_button);
get_widget!(builder, gtk::Entry, username_entry); get_widget!(builder, gtk::Entry, username_entry);
get_widget!(builder, gtk::Entry, password_entry); get_widget!(builder, gtk::Entry, password_entry);
get_widget!(builder, gtk::Box, register_box);
get_widget!(builder, gtk::Button, register_button); get_widget!(builder, gtk::Button, register_button);
get_widget!(builder, gtk::Box, logout_box);
get_widget!(builder, gtk::Button, logout_button);
if let Some(data) = data {
username_entry.set_text(&data.username);
register_box.hide();
logout_box.show();
}
let this = Rc::new(Self { let this = Rc::new(Self {
handle, handle,
@ -53,9 +62,9 @@ impl Screen<(), LoginData> for LoginDialog {
}; };
spawn!(@clone this, async move { spawn!(@clone this, async move {
this.handle.backend.set_login_data(data.clone()).await; this.handle.backend.set_login_data(Some(data.clone())).await;
if this.handle.backend.cl().login().await.unwrap() { if this.handle.backend.cl().login().await.unwrap() {
this.handle.pop(Some(data)); this.handle.pop(Some(Some(data)));
} else { } else {
this.widget.set_visible_child_name("content"); this.widget.set_visible_child_name("content");
this.info_bar.set_revealed(true); this.info_bar.set_revealed(true);
@ -66,11 +75,18 @@ impl Screen<(), LoginData> for LoginDialog {
register_button.connect_clicked(clone!(@weak this => move |_| { register_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move { spawn!(@clone this, async move {
if let Some(data) = push!(this.handle, RegisterDialog).await { if let Some(data) = push!(this.handle, RegisterDialog).await {
this.handle.pop(Some(data)); this.handle.pop(Some(Some(data)));
} }
}); });
})); }));
logout_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move {
this.handle.backend.set_login_data(None).await;
this.handle.pop(Some(None));
});
}));
this this
} }
} }

View file

@ -96,8 +96,12 @@ impl Preferences {
window.set_transient_for(&this.window); window.set_transient_for(&this.window);
spawn!(@clone this, async move { spawn!(@clone this, async move {
if let Some(data) = replace!(window.navigator, LoginDialog).await { if let Some(data) = replace!(window.navigator, LoginDialog, this.backend.get_login_data()).await {
if let Some(data) = data {
this.login_row.set_subtitle(Some(&data.username)); this.login_row.set_subtitle(Some(&data.username));
} else {
this.login_row.set_subtitle(Some(&gettext("Not logged in")));
}
} }
}); });
})); }));

View file

@ -98,7 +98,7 @@ impl Backend {
} }
match Self::load_login_data().await { match Self::load_login_data().await {
Ok(Some(data)) => self.client.set_login_data(data), Ok(Some(data)) => self.client.set_login_data(Some(data)),
Err(err) => warn!("The login data could not be loaded from SecretService. It will not \ Err(err) => warn!("The login data could not be loaded from SecretService. It will not \
be available. Error message: {}", err), be available. Error message: {}", err),
_ => (), _ => (),
@ -129,12 +129,20 @@ impl Backend {
} }
/// Set the user credentials to use. /// Set the user credentials to use.
pub async fn set_login_data(&self, data: LoginData) { pub async fn set_login_data(&self, data: Option<LoginData>) {
if let Some(data) = &data {
if let Err(err) = Self::store_login_data(data.clone()).await { if let Err(err) = Self::store_login_data(data.clone()).await {
warn!("An error happened while trying to store the login data using SecretService. \ warn!("An error happened while trying to store the login data using SecretService. \
This means, that they will not be available at the next startup most likely. \ This means, that they will not be available at the next startup most likely. \
Error message: {}", err); Error message: {}", err);
} }
} else {
if let Err(err) = Self::delete_secrets().await {
warn!("An error happened while trying to delete the login data from SecretService. \
This may result in the login data being reloaded at the next startup. Error \
message: {}", err);
}
}
self.client.set_login_data(data); self.client.set_login_data(data);
} }

View file

@ -20,6 +20,13 @@ impl Backend {
receiver.await? receiver.await?
} }
/// Delete all stored secrets.
pub(super) async fn delete_secrets() -> Result<()> {
let (sender, receiver) = oneshot::channel();
thread::spawn(move || sender.send(Self::delete_secrets_priv()).unwrap());
receiver.await?
}
/// Get the login credentials from secret storage. /// Get the login credentials from secret storage.
fn load_login_data_priv() -> Result<Option<LoginData>> { fn load_login_data_priv() -> Result<Option<LoginData>> {
let ss = SecretService::new(EncryptionType::Dh)?; let ss = SecretService::new(EncryptionType::Dh)?;
@ -52,7 +59,7 @@ impl Backend {
let collection = Self::get_collection(&ss)?; let collection = Self::get_collection(&ss)?;
let key = "musicus-login-data"; let key = "musicus-login-data";
Self::delete_secrets(&collection, key)?; Self::delete_secrets_for_key(&collection, key)?;
let mut attributes = HashMap::new(); let mut attributes = HashMap::new();
attributes.insert("username", data.username.as_str()); attributes.insert("username", data.username.as_str());
@ -61,8 +68,19 @@ impl Backend {
Ok(()) Ok(())
} }
/// Delete all stored secrets.
fn delete_secrets_priv() -> Result<()> {
let ss = SecretService::new(EncryptionType::Dh)?;
let collection = Self::get_collection(&ss)?;
let key = "musicus-login-data";
Self::delete_secrets_for_key(&collection, key)?;
Ok(())
}
/// Delete all stored secrets for the provided key. /// Delete all stored secrets for the provided key.
fn delete_secrets(collection: &Collection, key: &str) -> Result<()> { fn delete_secrets_for_key(collection: &Collection, key: &str) -> Result<()> {
let items = collection.get_all_items()?; let items = collection.get_all_items()?;
for item in items { for item in items {

View file

@ -65,8 +65,8 @@ impl Client {
} }
/// Set the user credentials to use. /// Set the user credentials to use.
pub fn set_login_data(&self, data: LoginData) { pub fn set_login_data(&self, data: Option<LoginData>) {
self.login_data.replace(Some(data)); self.login_data.replace(data);
self.token.replace(None); self.token.replace(None);
} }

View file

@ -99,6 +99,10 @@
</child> </child>
</object> </object>
</child> </child>
<child>
<object class="GtkBox" id="register_box">
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child> <child>
<object class="GtkLabel"> <object class="GtkLabel">
<property name="halign">start</property> <property name="halign">start</property>
@ -133,6 +137,47 @@
</child> </child>
</object> </object>
</child> </child>
<child>
<object class="GtkBox" id="logout_box">
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="visible">false</property>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">Logout</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</child>
<child>
<object class="GtkFrame">
<property name="valign">start</property>
<child>
<object class="GtkListBox">
<property name="selection-mode">none</property>
<child>
<object class="AdwActionRow">
<property name="activatable">True</property>
<property name="title" translatable="yes">Don't use an account</property>
<property name="activatable-widget">logout_button</property>
<child>
<object class="GtkButton" id="logout_button">
<property name="label" translatable="yes">Logout</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object> </object>
</child> </child>
</object> </object>