novel_api/common/utils/
keyring.rs1use keyring_core::Entry;
2use zeroize::Zeroize;
3
4use crate::Error;
5
6#[must_use]
8pub struct Keyring {
9 entry: Entry,
10}
11
12impl Keyring {
13 pub fn new<T, E>(app_name: T, username: E) -> Result<Self, Error>
15 where
16 T: AsRef<str>,
17 E: AsRef<str>,
18 {
19 #[cfg(target_os = "macos")]
20 {
21 use apple_native_keyring_store::keychain::Store;
22 keyring_core::set_default_store(Store::new()?);
23 }
24 #[cfg(target_os = "windows")]
25 {
26 use windows_native_keyring_store::Store;
27 keyring_core::set_default_store(Store::new()?);
28 }
29 #[cfg(target_os = "linux")]
30 {
31 use linux_keyutils_keyring_store::Store;
32 keyring_core::set_default_store(Store::new()?);
33 }
34
35 let service = format!("novel-rs-{}", app_name.as_ref());
36 let entry = Entry::new(&service, username.as_ref())?;
37
38 Ok(Self { entry })
39 }
40
41 pub fn get_password(&self) -> Result<String, Error> {
43 Ok(self.entry.get_password()?)
44 }
45
46 pub fn set_password(&self, mut password: String) -> Result<(), Error> {
48 self.entry.set_password(&password)?;
49 password.zeroize();
50 Ok(())
51 }
52
53 pub fn delete_password(&self) -> Result<(), Error> {
55 Ok(self.entry.delete_credential()?)
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use pretty_assertions::assert_eq;
62 use testresult::TestResult;
63
64 use super::*;
65
66 #[tokio::test]
67 async fn keyring() -> TestResult {
68 let keyring = Keyring::new("test-app", "user-name")?;
69 let password = "test-password".to_string();
70
71 keyring.set_password(password.clone())?;
72 assert_eq!(keyring.get_password()?, password);
73
74 keyring.delete_password()?;
75
76 Ok(())
77 }
78}