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.
pub struct LoginDialog {
handle: NavigationHandle<LoginData>,
handle: NavigationHandle<Option<LoginData>>,
widget: gtk::Stack,
info_bar: gtk::InfoBar,
username_entry: gtk::Entry,
password_entry: gtk::Entry,
}
impl Screen<(), LoginData> for LoginDialog {
fn new(_: (), handle: NavigationHandle<LoginData>) -> Rc<Self> {
impl Screen<Option<LoginData>, Option<LoginData>> for LoginDialog {
fn new(data: Option<LoginData>, handle: NavigationHandle<Option<LoginData>>) -> Rc<Self> {
// Create 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::Entry, username_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::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 {
handle,
@ -53,9 +62,9 @@ impl Screen<(), LoginData> for LoginDialog {
};
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() {
this.handle.pop(Some(data));
this.handle.pop(Some(Some(data)));
} else {
this.widget.set_visible_child_name("content");
this.info_bar.set_revealed(true);
@ -66,11 +75,18 @@ impl Screen<(), LoginData> for LoginDialog {
register_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move {
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
}
}

View file

@ -96,8 +96,12 @@ impl Preferences {
window.set_transient_for(&this.window);
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));
} 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 {
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 \
be available. Error message: {}", err),
_ => (),
@ -129,12 +129,20 @@ impl Backend {
}
/// 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 {
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. \
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);
}

View file

@ -20,6 +20,13 @@ impl Backend {
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.
fn load_login_data_priv() -> Result<Option<LoginData>> {
let ss = SecretService::new(EncryptionType::Dh)?;
@ -52,7 +59,7 @@ impl Backend {
let collection = Self::get_collection(&ss)?;
let key = "musicus-login-data";
Self::delete_secrets(&collection, key)?;
Self::delete_secrets_for_key(&collection, key)?;
let mut attributes = HashMap::new();
attributes.insert("username", data.username.as_str());
@ -61,8 +68,19 @@ impl Backend {
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.
fn delete_secrets(collection: &Collection, key: &str) -> Result<()> {
fn delete_secrets_for_key(collection: &Collection, key: &str) -> Result<()> {
let items = collection.get_all_items()?;
for item in items {

View file

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

View file

@ -99,6 +99,10 @@
</child>
</object>
</child>
<child>
<object class="GtkBox" id="register_box">
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
@ -133,6 +137,47 @@
</child>
</object>
</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>
</child>
</object>