authenticator/backup/
mod.rs

1use anyhow::Result;
2
3use crate::{
4    models::{Account, Algorithm, Method, ProvidersModel, keyring},
5    utils::spawn_tokio_blocking,
6};
7
8pub enum Operation {
9    Backup,
10    Restore,
11}
12
13pub trait Restorable: Sized {
14    /// Indicates that the GUI might need to prompt for a password.
15    const ENCRYPTABLE: bool = false;
16
17    /// Indicates that the GUI needs to show a QR code scanner.
18    const SCANNABLE: bool = false;
19
20    // Used to define the `restore.$identifier` action
21    const IDENTIFIER: &'static str;
22
23    type Item: RestorableItem;
24
25    fn title() -> String;
26    fn subtitle() -> String;
27
28    /// Restore many items from a slice of data, optionally using a key to
29    /// unencrypt it.
30    ///
31    /// If `key` is `None`, then the implementation should assume that the slice
32    /// is unencrypted, and error if it only supports encrypted slices.
33    fn restore_from_data(from: &[u8], key: Option<&str>) -> Result<Vec<Self::Item>>;
34}
35
36pub trait RestorableItem {
37    fn account(&self) -> String;
38    fn issuer(&self) -> String;
39    fn secret(&self) -> String;
40    fn period(&self) -> Option<u32>;
41    fn method(&self) -> Method;
42    fn algorithm(&self) -> Algorithm;
43    fn digits(&self) -> Option<u32>;
44    fn counter(&self) -> Option<u32>;
45
46    fn restore(&self, provider: &ProvidersModel) -> Result<()> {
47        let owned_token = self.secret();
48        let token_exists =
49            spawn_tokio_blocking(async move { keyring::token_exists(&owned_token).await })?;
50        if !token_exists {
51            let provider = provider.find_or_create(
52                &self.issuer(),
53                self.period(),
54                self.method(),
55                None,
56                self.algorithm(),
57                self.digits(),
58                self.counter(),
59                None,
60                None,
61            )?;
62
63            let account =
64                Account::create(&self.account(), &self.secret(), self.counter(), &provider)?;
65            provider.add_account(&account);
66        } else {
67            tracing::info!(
68                "Account {}/{} already exists",
69                self.issuer(),
70                self.account()
71            );
72        }
73        Ok(())
74    }
75}
76
77pub trait Backupable: Sized {
78    /// Indicates that the GUI might need to prompt for a password.
79    const ENCRYPTABLE: bool = false;
80    // Used to define the `backup.$identifier` action
81    const IDENTIFIER: &'static str;
82
83    fn title() -> String;
84    fn subtitle() -> String;
85    // if no key is provided the backup code should save it as plain text
86    fn backup(provider: &ProvidersModel, key: Option<&str>) -> Result<Vec<u8>>;
87}
88
89mod aegis;
90mod andotp;
91mod bitwarden;
92mod freeotp;
93mod freeotp_json;
94mod google;
95mod legacy;
96mod raivootp;
97mod yandex;
98pub use self::{
99    aegis::Aegis, andotp::AndOTP, bitwarden::Bitwarden, freeotp::FreeOTP,
100    freeotp_json::FreeOTPJSON, google::Google, legacy::LegacyAuthenticator, raivootp::RaivoOTP,
101    yandex::Yandex,
102};