basic totp support

This commit is contained in:
Bilal Elmoussaoui 2020-12-06 15:37:03 +01:00
parent 02f6cad04a
commit aa5cfe50f8
5 changed files with 104 additions and 52 deletions

View file

@ -26,6 +26,19 @@
margin-bottom: 8px;
margin-left: 2px;
}
.totp-progress,
.totp-progress trough progress,
.totp-progress trough {
border-top-right-radius: 0;
border-top-left-radius: 0;
border-bottom-right-radius:8px;
border-bottom-left-radius:8px;
}
.content row.account-row:last-child {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.account-row {
padding: 12px;

View file

@ -24,6 +24,14 @@
</style>
</object>
</child>
<child>
<object class="GtkProgressBar" id="progress">
<property name="pulse-step">0.0001</property>
<style>
<class name="totp-progress" />
</style>
</object>
</child>
</object>
</child>
<style>

View file

@ -1,3 +1,4 @@
use super::algorithm::Algorithm;
use super::provider::{DiProvider, Provider};
use crate::helpers::Keyring;
use crate::models::database;
@ -34,7 +35,7 @@ pub struct AccountPriv {
pub otp: RefCell<String>,
pub name: RefCell<String>,
pub token_id: RefCell<String>,
pub provider_id: Cell<i32>,
pub provider: RefCell<Option<Provider>>,
}
static PROPERTIES: [subclass::Property; 5] = [
@ -70,14 +71,12 @@ static PROPERTIES: [subclass::Property; 5] = [
glib::ParamFlags::READWRITE,
)
}),
subclass::Property("provider-id", |name| {
glib::ParamSpec::int(
subclass::Property("provider", |name| {
glib::ParamSpec::object(
name,
"provider-id",
"Provider Id",
0,
1000,
0,
"provider",
"The account provider",
Provider::static_type(),
glib::ParamFlags::READWRITE,
)
}),
@ -102,7 +101,7 @@ impl ObjectSubclass for AccountPriv {
name: RefCell::new("".to_string()),
otp: RefCell::new("".to_string()),
token_id: RefCell::new("".to_string()),
provider_id: Cell::new(0),
provider: RefCell::new(None),
}
}
}
@ -140,12 +139,11 @@ impl ObjectImpl for AccountPriv {
.unwrap();
self.token_id.replace(token_id);
}
subclass::Property("provider-id", ..) => {
let provider_id = value
subclass::Property("provider", ..) => {
let provider = value
.get()
.expect("type conformity checked by `Object::set_property`")
.unwrap();
self.provider_id.replace(provider_id);
.expect("type conformity checked by `Object::set_property`");
self.provider.replace(provider);
}
_ => unimplemented!(),
}
@ -159,7 +157,7 @@ impl ObjectImpl for AccountPriv {
subclass::Property("name", ..) => self.name.borrow().to_value(),
subclass::Property("otp", ..) => self.otp.borrow().to_value(),
subclass::Property("token-id", ..) => self.token_id.borrow().to_value(),
subclass::Property("provider-id", ..) => self.provider_id.get().to_value(),
subclass::Property("provider", ..) => self.provider.borrow().to_value(),
_ => unimplemented!(),
}
}
@ -170,7 +168,7 @@ glib_wrapper! {
}
impl Account {
pub fn create(name: &str, token_id: &str, provider_id: i32) -> Result<Account> {
pub fn create(name: &str, token_id: &str, provider: &Provider) -> Result<Account> {
let db = database::connection();
let conn = db.get()?;
@ -178,7 +176,7 @@ impl Account {
.values(NewAccount {
name: name.to_string(),
token_id: token_id.to_string(),
provider_id,
provider_id: provider.id(),
})
.execute(&conn)?;
@ -186,7 +184,14 @@ impl Account {
.order(accounts::columns::id.desc())
.first::<DiAccount>(&conn)
.map_err(From::from)
.map(From::from)
.map(|account| {
Self::new(
account.id,
&account.name,
&account.token_id,
provider.clone(),
)
})
}
pub fn load(p: &Provider) -> Result<Vec<Self>> {
@ -197,7 +202,7 @@ impl Account {
let results = DiAccount::belonging_to(&dip)
.load::<DiAccount>(&conn)?
.into_iter()
.map(From::from)
.map(|account| Self::new(account.id, &account.name, &account.token_id, p.clone()))
.collect::<Vec<Account>>();
Ok(results)
}
@ -209,14 +214,14 @@ impl Account {
account1.name().cmp(&account2.name())
}
pub fn new(id: i32, name: &str, token_id: &str, provider_id: i32) -> Account {
pub fn new(id: i32, name: &str, token_id: &str, provider: Provider) -> Account {
let account = glib::Object::new(
Account::static_type(),
&[
("id", &id),
("name", &name),
("token-id", &token_id),
("provider-id", &provider_id),
("provider", &provider),
],
)
.expect("Failed to create account")
@ -228,8 +233,10 @@ impl Account {
fn init(&self) {
self.generate_otp();
// Only trigger time-based callback after duration if it's a TOTP
if self.provider().algorithm() == Algorithm::TOTP {
glib::source::timeout_add_seconds_local(
30,
self.provider().period() as u32,
clone!(@weak self as account => @default-return glib::Continue(false), move || {
account.generate_otp();
@ -237,19 +244,27 @@ impl Account {
}),
);
}
}
fn generate_otp(&self) {
let token = Keyring::token(&self.token_id()).unwrap().unwrap();
let provider = self.provider();
match provider.algorithm() {
Algorithm::TOTP => {
let totp = TOTP::new(token);
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let code = totp.generate(30, timestamp);
let code = totp.generate(provider.period() as u64, timestamp);
self.set_property("otp", &code.to_string()).unwrap();
}
Algorithm::HOTP => {}
Algorithm::Steam => {}
}
}
pub fn copy_otp(&self) {
let display = gdk::Display::get_default().unwrap();
@ -263,6 +278,11 @@ impl Account {
priv_.id.get()
}
pub fn provider(&self) -> Provider {
let provider = self.get_property("provider").unwrap();
provider.get::<Provider>().unwrap().unwrap()
}
pub fn name(&self) -> String {
let priv_ = AccountPriv::from_instance(self);
priv_.name.borrow().clone()
@ -295,14 +315,3 @@ impl Account {
Ok(())
}
}
impl From<DiAccount> for Account {
fn from(account: DiAccount) -> Self {
Self::new(
account.id,
&account.name,
&account.token_id,
account.provider_id,
)
}
}

View file

@ -203,7 +203,7 @@ impl AccountAddDialog {
let token = self_.token_entry.get().get_text().unwrap();
if let Ok(token_id) = Keyring::store(&username, &token) {
let account = Account::create(&username, &token_id, provider.id())?;
let account = Account::create(&username, &token_id, provider)?;
send!(
self_.global_sender.get().unwrap(),
Action::AccountCreated(account, provider.clone())

View file

@ -1,4 +1,4 @@
use crate::models::{Account, AccountSorter, Provider};
use crate::models::{Account, AccountSorter, Algorithm, Provider};
use crate::widgets::accounts::AccountRow;
use gio::prelude::*;
use gio::subclass::ObjectSubclass;
@ -28,6 +28,8 @@ mod imp {
pub name_label: TemplateChild<gtk::Label>,
#[template_child(id = "accounts_list")]
pub accounts_list: TemplateChild<gtk::ListBox>,
#[template_child(id = "progress")]
pub progress: TemplateChild<gtk::ProgressBar>,
}
impl ObjectSubclass for ProviderRow {
@ -43,6 +45,7 @@ mod imp {
Self {
name_label: TemplateChild::default(),
accounts_list: TemplateChild::default(),
progress: TemplateChild::default(),
provider: RefCell::new(None),
}
}
@ -108,6 +111,25 @@ impl ProviderRow {
fn setup_widgets(&self) {
let self_ = imp::ProviderRow::from_instance(self);
let progress_bar = self_.progress.get();
if self.provider().algorithm() == Algorithm::TOTP {
progress_bar.set_fraction(1_f64);
let max = self.provider().period() as f64;
glib::timeout_add_local(
std::time::Duration::from_millis(50),
clone!(@weak progress_bar => @default-return glib::Continue(false), move || {
let mut new_value = progress_bar.get_fraction() - (0.05/max);
if new_value <= 0.0 {
new_value = 1.0;
}
progress_bar.set_fraction(new_value);
glib::Continue(true)
}),
);
} else {
progress_bar.hide();
}
self.provider()
.bind_property("name", &self_.name_label.get(), "label")
.flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE)
@ -118,7 +140,7 @@ impl ProviderRow {
let provider = self.provider();
let create_callback = clone!(@weak self as provider_row, @weak sorter => move |account: &glib::Object| {
let create_callback = clone!(@weak self as provider_row, @weak sorter, @weak provider => move |account: &glib::Object| {
let account = account.clone().downcast::<Account>().unwrap();
let row = AccountRow::new(account.clone());
row.connect_local(