diff --git a/Cargo.toml b/Cargo.toml
index 18bfcd88..1e6535b1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,9 +17,12 @@ futures = {version = "0.3.5", features = ["thread-pool"]}
futures-executor = "0.3.5"
gdk = "0.12.1"
gio = "0.8.1"
+log = "0.4.8"
+log4rs = "0.12.0"
rusqlite = "0.23.1"
-serde_json = "1.0.55"
serde = { version = "1.0.111", features = ["derive"] }
+serde_json = "1.0.55"
+serde_yaml = "0.8.13"
totp-rs = "0.2.6"
[dependencies.gtk]
diff --git a/README.md b/README.md
index 49880e1f..87968309 100644
--- a/README.md
+++ b/README.md
@@ -41,7 +41,7 @@ Download from the [release](https://github.com/grumlimited/authenticator-rs/rele
make target/release/authenticator-rs
- $HOME/.cargo/bin/authenticator-rust
+ ./target/release/authenticator-rs
## Running (dev)
diff --git a/data/log4rs.yaml b/data/log4rs.yaml
new file mode 100644
index 00000000..7f3aff2d
--- /dev/null
+++ b/data/log4rs.yaml
@@ -0,0 +1,14 @@
+refresh_rate: 30 seconds
+
+appenders:
+ stdout:
+ kind: console
+
+root:
+ level: info
+ appenders:
+ - stdout
+
+loggers:
+ authenticator_rs:
+ level: debug
diff --git a/data/uk.co.grumlimited.authenticator-rs.xml b/data/uk.co.grumlimited.authenticator-rs.xml
index 41371b7b..8e3a37b5 100644
--- a/data/uk.co.grumlimited.authenticator-rs.xml
+++ b/data/uk.co.grumlimited.authenticator-rs.xml
@@ -3,5 +3,6 @@
data/uk.co.grumlimited.authenticator-rs.svg
data/style.css
+ data/log4rs.yaml
diff --git a/src/helpers/config_manager.rs b/src/helpers/config_manager.rs
index a61f88d6..e56ea0ed 100644
--- a/src/helpers/config_manager.rs
+++ b/src/helpers/config_manager.rs
@@ -20,13 +20,6 @@ pub enum LoadError {
}
impl ConfigManager {
- pub fn _log4rs() -> std::path::PathBuf {
- let mut path = ConfigManager::path();
- path.push("log4rs.yaml");
-
- path
- }
-
fn db_path() -> std::path::PathBuf {
let mut path = ConfigManager::path();
path.push("authenticator.db");
@@ -54,7 +47,9 @@ impl ConfigManager {
let conn = conn.lock().unwrap();
- let mut stmt = conn.prepare("SELECT id, name FROM groups").unwrap();
+ let mut stmt = conn
+ .prepare("SELECT id, name FROM groups ORDER BY LOWER(name)")
+ .unwrap();
stmt.query_map(params![], |row| {
let id: u32 = row.get(0)?;
@@ -75,27 +70,6 @@ impl ConfigManager {
.map_err(|e| LoadError::DbError(format!("{:?}", e)))
}
- fn _create_group(
- conn: Arc>,
- group_name: &str,
- ) -> Result {
- let conn = conn.lock().unwrap();
-
- conn.execute("INSERT INTO groups (name) VALUES (?1)", params![group_name])
- .map_err(|e| LoadError::DbError(format!("{:?}", e)))
- .unwrap();
-
- let mut stmt = conn.prepare("SELECT last_insert_rowid()").unwrap();
-
- stmt.query_row(NO_PARAMS, |row| row.get::(0))
- .map(|id| AccountGroup {
- id,
- name: group_name.to_owned(),
- ..Default::default()
- })
- .map_err(|e| LoadError::DbError(format!("{:?}", e)))
- }
-
pub fn update_group(
conn: Arc>,
group: &AccountGroup,
@@ -274,8 +248,9 @@ impl ConfigManager {
}
fn get_accounts(conn: &Connection, group_id: u32) -> Result, rusqlite::Error> {
- let mut stmt =
- conn.prepare("SELECT id, label, secret FROM accounts WHERE group_id = ?1")?;
+ let mut stmt = conn.prepare(
+ "SELECT id, label, secret FROM accounts WHERE group_id = ?1 ORDER BY LOWER(label)",
+ )?;
stmt.query_map(params![group_id], |row| {
let id: u32 = row.get(0)?;
@@ -294,10 +269,8 @@ impl ConfigManager {
mod tests {
use super::ConfigManager;
use rusqlite::Connection;
- use std::path::Path;
use crate::model::{Account, AccountGroup};
- use async_std::task;
use std::sync::{Arc, Mutex};
#[test]
@@ -306,7 +279,7 @@ mod tests {
let _ = {
let conn = conn.clone();
- ConfigManager::init_tables(conn);
+ ConfigManager::init_tables(conn).expect("boom!");
};
let mut group = AccountGroup::new(0, "new group", vec![]);
@@ -352,7 +325,7 @@ mod tests {
let _ = {
let conn = conn.clone();
- ConfigManager::init_tables(conn);
+ ConfigManager::init_tables(conn).expect("boom!");
};
let mut group = AccountGroup::new(0, "new group", vec![]);
@@ -382,12 +355,13 @@ mod tests {
let _ = {
let conn = conn.clone();
- ConfigManager::init_tables(conn);
+ ConfigManager::init_tables(conn).expect("boom!");
};
+ let mut group = AccountGroup::new(0, "existing_group2", vec![]);
let group = {
let conn = conn.clone();
- ConfigManager::_create_group(conn, "existing_group2").unwrap()
+ ConfigManager::save_group(conn, &mut group).unwrap()
};
let mut account = Account::new(0, group.id, "label", "secret");
@@ -409,21 +383,42 @@ mod tests {
}
#[test]
- fn get_or_create_group_with_new_group() {
+ fn save_group_ordering() {
let conn = Arc::new(Mutex::new(Connection::open_in_memory().unwrap()));
- let _ = {
+ {
let conn = conn.clone();
- ConfigManager::init_tables(conn);
+ ConfigManager::init_tables(conn).expect("boom!");
};
- let group = {
+ {
+ let conn = conn.clone();
+ let conn2 = conn.clone();
+ let conn3 = conn.clone();
+ let mut group = AccountGroup::new(0, "bbb", vec![]);
+ ConfigManager::save_group(conn, &mut group).unwrap();
+
+ let mut account1 = Account::new(0, group.id, "hhh", "secret3");
+ ConfigManager::save_account(conn2, &mut account1).expect("boom!");
+ let mut account2 = Account::new(0, group.id, "ccc", "secret3");
+ ConfigManager::save_account(conn3, &mut account2).expect("boom!");
+ };
+
+ {
let conn = conn.clone();
- ConfigManager::_create_group(conn, "existing_group2").unwrap()
+ let mut group = AccountGroup::new(0, "AAA", vec![]);
+ ConfigManager::save_group(conn, &mut group).expect("boom!");
};
- assert!(group.id > 0);
- assert_eq!("existing_group2", group.name);
+ let results = ConfigManager::load_account_groups(conn).unwrap();
+
+ //groups in order
+ assert_eq!("AAA", results.get(0).unwrap().name);
+ assert_eq!("bbb", results.get(1).unwrap().name);
+
+ //accounts in order
+ assert_eq!("ccc", results.get(1).unwrap().entries.get(0).unwrap().label);
+ assert_eq!("hhh", results.get(1).unwrap().entries.get(1).unwrap().label);
}
#[test]
@@ -432,7 +427,7 @@ mod tests {
let _ = {
let conn = conn.clone();
- ConfigManager::init_tables(conn);
+ ConfigManager::init_tables(conn).expect("boom!");
};
let mut account = Account::new(0, 0, "label", "secret");
diff --git a/src/main.rs b/src/main.rs
index 310df038..0657afee 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,6 +11,8 @@ use crate::model::AccountGroup;
use crate::ui::{AccountsWindow, AddGroupWindow, EditAccountWindow};
use gio::prelude::*;
use gtk::prelude::*;
+use log4rs::config::Config;
+use log4rs::file::{Deserializers, RawConfig};
use rusqlite::Connection;
use std::cell::RefCell;
use std::sync::{Arc, Mutex};
@@ -19,6 +21,8 @@ mod helpers;
mod model;
mod ui;
+use log::info;
+
const NAMESPACE: &str = "uk.co.grumlimited.authenticator-rs";
const NAMESPACE_PREFIX: &str = "/uk/co/grumlimited/authenticator-rs";
@@ -27,7 +31,7 @@ fn main() {
.expect("Initialization failed...");
let resource = {
- match gio::Resource::load(format!("/usr/share/{}/{}.gresource", NAMESPACE, NAMESPACE)) {
+ match gio::Resource::load(format!("data/{}.gresource", NAMESPACE)) {
Ok(resource) => resource,
Err(_) => gio::Resource::load(format!("data/{}.gresource", NAMESPACE)).unwrap(),
}
@@ -44,6 +48,8 @@ fn main() {
&provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
+
+ configure_logging();
});
application.connect_activate(|app| {
@@ -88,7 +94,36 @@ fn main() {
}
AccountsWindow::delete_buttons_actions(gui, connection);
+
+ info!("Authenticator RS initialised");
});
application.run(&[]);
}
+
+/**
+* Loads log4rs yaml config from gResource.
+* And in the most convoluted possible way, feeds it to Log4rs.
+*/
+fn configure_logging() {
+ let log4rs_yaml = gio::functions::resources_lookup_data(
+ format!("{}/{}", NAMESPACE_PREFIX, "log4rs.yaml").as_str(),
+ gio::ResourceLookupFlags::NONE,
+ )
+ .unwrap();
+ let log4rs_yaml = log4rs_yaml.to_vec();
+ let log4rs_yaml = String::from_utf8(log4rs_yaml).unwrap();
+
+ // log4rs-0.12.0/src/file.rs#592
+ let config = serde_yaml::from_str::(log4rs_yaml.as_str()).unwrap();
+ let (appenders, _) = config.appenders_lossy(&Deserializers::default());
+
+ // log4rs-0.12.0/src/priv_file.rs#deserialize(config: &RawConfig, deserializers: &Deserializers)#186
+ let config = Config::builder()
+ .appenders(appenders)
+ .loggers(config.loggers())
+ .build(config.root())
+ .unwrap();
+
+ log4rs::init_config(config).unwrap();
+}
diff --git a/src/main_window.rs b/src/main_window.rs
index 7f0bad63..ed84362b 100644
--- a/src/main_window.rs
+++ b/src/main_window.rs
@@ -23,7 +23,7 @@ pub struct MainWindow {
pub edit_account_window: ui::EditAccountWindow,
pub accounts_window: ui::AccountsWindow,
pub add_group: ui::AddGroupWindow,
- pool: ThreadPool,
+ pub pool: ThreadPool,
state: Rc>,
}
@@ -235,10 +235,18 @@ impl MainWindow {
});
}
+ {
+ let titlebar = gtk::HeaderBarBuilder::new()
+ .decoration_layout(":")
+ .title("About")
+ .build();
+
+ let popup = self.popup.clone();
+ popup.set_titlebar(Some(&titlebar));
+ }
{
let popup = self.popup.clone();
about_button.connect_clicked(move |_| {
- popup.set_title("About");
popover.set_visible(false);
popup.set_visible(true);
popup.show_all();
@@ -365,6 +373,10 @@ impl MainWindow {
.append(entry_id, group.name.as_str());
});
+ let first_entry = groups.get(0).map(|e| format!("{}", e.id));
+ let first_entry = first_entry.as_deref();
+ edit_account_window.input_group.set_active_id(first_entry);
+
edit_account_window.input_account_id.set_text("0");
edit_account_window.input_name.set_text("");
diff --git a/src/mainwindow.glade b/src/mainwindow.glade
index a5b43e51..0a4445e5 100644
--- a/src/mainwindow.glade
+++ b/src/mainwindow.glade
@@ -536,6 +536,8 @@
True
center-on-parent
True
+ True
+ True
False
main_window
main_window
diff --git a/src/model/account.rs b/src/model/account.rs
index 05e4266c..b5590f1c 100644
--- a/src/model/account.rs
+++ b/src/model/account.rs
@@ -5,6 +5,7 @@ use base32::Alphabet::RFC4648;
use gtk::prelude::*;
use gtk::{Align, Orientation};
+use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Account {
@@ -21,7 +22,10 @@ pub struct AccountWidgets {
pub grid: gtk::Grid,
pub edit_button: gtk::Button,
pub delete_button: gtk::Button,
+ pub copy_button: Arc>,
pub popover: gtk::PopoverMenu,
+ pub edit_copy_img: Arc>,
+ pub dialog_ok_img: Arc>,
totp_label: gtk::Label,
totp_secret: String,
}
@@ -68,12 +72,13 @@ impl Account {
.xalign(0.0)
.build();
- let image = gtk::ImageBuilder::new().icon_name("edit-copy").build();
+ let edit_copy_img = gtk::ImageBuilder::new().icon_name("edit-copy").build();
+ let dialog_ok_img = gtk::ImageBuilder::new().icon_name("dialog-ok").build();
let copy_button = gtk::ButtonBuilder::new()
.margin_start(5)
.margin_end(5)
- .image(&image)
+ .image(&edit_copy_img)
.always_show_image(true)
.build();
@@ -146,6 +151,9 @@ impl Account {
grid,
edit_button,
delete_button,
+ copy_button: Arc::new(Mutex::new(copy_button)),
+ edit_copy_img: Arc::new(Mutex::new(edit_copy_img)),
+ dialog_ok_img: Arc::new(Mutex::new(dialog_ok_img)),
popover: popover_clone,
totp_label: totp_label_clone2,
totp_secret: self.secret.clone(),
diff --git a/src/ui/accounts_window.rs b/src/ui/accounts_window.rs
index 5a5e5b7d..6cc536e0 100644
--- a/src/ui/accounts_window.rs
+++ b/src/ui/accounts_window.rs
@@ -148,6 +148,35 @@ impl AccountsWindow {
let gui = gui.clone();
+ {
+ let (tx, rx) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT);
+
+ let dialog_ok_img = account_widgets.dialog_ok_img.clone();
+ let edit_copy_img = account_widgets.edit_copy_img.clone();
+ let copy_button_1 = account_widgets.copy_button.clone();
+ let copy_button_2 = account_widgets.copy_button.clone();
+
+ let pool = gui.pool.clone();
+
+ rx.attach(None, move |_| {
+ let edit_copy_img = edit_copy_img.lock().unwrap();
+ let edit_copy_img = edit_copy_img.deref();
+ let copy_button = copy_button_2.lock().unwrap();
+ copy_button.set_image(Some(edit_copy_img));
+ glib::Continue(true)
+ });
+
+ let copy_button = copy_button_1.lock().unwrap();
+ copy_button.connect_clicked(move |b| {
+ let dialog_ok_img = dialog_ok_img.lock().unwrap();
+ let dialog_ok_img = dialog_ok_img.deref();
+ b.set_image(Some(dialog_ok_img));
+
+ let tx = tx.clone();
+ pool.spawn_ok(times_up(tx, 2000));
+ });
+ }
+
account_widgets.edit_button.connect_clicked(move |_| {
let groups = {
let connection = connection.clone();
@@ -238,6 +267,19 @@ impl AccountsWindow {
}
}
+use glib::Sender;
+use std::ops::Deref;
+use std::{thread, time};
+
+/**
+* Sleeps for some time then messages end of wait, so that copy button
+* gets its default image restored.
+*/
+async fn times_up(tx: Sender, wait_ms: u64) {
+ thread::sleep(time::Duration::from_millis(wait_ms));
+ tx.send(true).expect("Couldn't send data to channel");
+}
+
#[cfg(test)]
mod tests {
use super::*;