authenticator/backup/
yandex.rs1use std::str::FromStr;
2
3use anyhow::Result;
4use gettextrs::gettext;
5use serde::Deserialize;
6
7use super::Restorable;
8use crate::models::{Method, OTPUri};
9
10pub struct Yandex;
11
12impl Restorable for Yandex {
13 const ENCRYPTABLE: bool = false;
14 const SCANNABLE: bool = false;
15 const IDENTIFIER: &'static str = "yandex-txt";
16 type Item = OTPUri;
17
18 fn title() -> String {
19 gettext("Yandex ID")
20 }
21
22 fn subtitle() -> String {
23 gettext("From a plain-text file generated by Yandex ID")
24 }
25
26 fn restore_from_data(from: &[u8], _key: Option<&str>) -> Result<Vec<Self::Item>> {
27 #[derive(Deserialize)]
28 struct Entry {
29 #[serde(rename = "techInfo")]
30 tech_info: String,
31 #[serde(rename = "warning")]
32 _warning: Option<String>,
33 }
34
35 let raw: Vec<Entry> = serde_json::from_slice(from)?;
36 let mut items = Vec::with_capacity(raw.len());
37
38 for entry in raw {
39 let Ok(mut normalized) = OTPUri::from_str(&entry.tech_info) else {
40 continue;
41 };
42
43 if !matches!(normalized.method, Method::TOTP | Method::HOTP) {
45 continue;
46 }
47
48 match normalized.method {
49 Method::TOTP => {
50 normalized.digits = None;
51 normalized.counter = None;
52 }
53 Method::HOTP => {
54 normalized.counter.get_or_insert(0);
55 }
56 Method::Steam => {
57 continue;
58 }
59 }
60
61 items.push(normalized);
62 }
63
64 Ok(items)
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use crate::{
72 backup::RestorableItem,
73 models::{Algorithm, Method},
74 };
75
76 #[test]
77 fn parse_yandex_json_anonymous() {
78 let data = r#"[
79 {
80 "techInfo": "otpauth://yaotp/user?chars=alpha&secret=REDACTEDSECRET&creation_type=unknown"
81 },
82 {
83 "name": "user1@example.com",
84 "secret": "ABCDEFGHIJKLMNOP",
85 "techInfo": "otpauth://totp/Issuer1:user1@example.com?algorithm=SHA256&digits=8&secret=ABCDEFGHIJKLMNOP&issuer=Issuer1&period=45"
86 },
87 {
88 "name": "user2@example.com",
89 "secret": "QRSTUVWXYZ123456",
90 "techInfo": "otpauth://hotp/Issuer2:user2@example.com?algorithm=SHA512&digits=7&secret=QRSTUVWXYZ123456&issuer=Issuer2&counter=5"
91 }
92 ]"#;
93
94 let items = Yandex::restore_from_data(data.as_bytes(), None).unwrap();
96 assert_eq!(items.len(), 2);
97
98 let totp = items.iter().find(|i| i.method() == Method::TOTP).unwrap();
99 assert_eq!(totp.issuer(), "Issuer1");
100 assert_eq!(totp.account(), "user1@example.com");
101 assert_eq!(totp.secret(), "ABCDEFGHIJKLMNOP");
102 assert_eq!(totp.method(), Method::TOTP);
103 assert_eq!(totp.algorithm(), Algorithm::SHA256);
104 assert_eq!(totp.digits(), None);
105 assert_eq!(totp.period(), Some(45));
106
107 let hotp = items.iter().find(|i| i.method() == Method::HOTP).unwrap();
108 assert_eq!(hotp.issuer(), "Issuer2");
109 assert_eq!(hotp.account(), "user2@example.com");
110 assert_eq!(hotp.secret(), "QRSTUVWXYZ123456");
111 assert_eq!(hotp.method(), Method::HOTP);
112 assert_eq!(hotp.algorithm(), Algorithm::SHA512);
113 assert_eq!(hotp.digits(), Some(7));
114 assert_eq!(hotp.counter(), Some(5));
115 }
116}