pidge_client/auth/
store.rs1use crate::auth::tokens::TokenSet;
7use crate::error::ClientError;
8
9const SERVICE_NAME: &str = "pidge";
10
11pub struct KeychainStore;
12
13impl KeychainStore {
14 fn entry(email: &str) -> Result<keyring::Entry, ClientError> {
15 keyring::Entry::new(SERVICE_NAME, email).map_err(ClientError::Keychain)
16 }
17
18 pub fn load(email: &str) -> Result<Option<TokenSet>, ClientError> {
20 let entry = Self::entry(email)?;
21 match entry.get_password() {
22 Ok(blob) => {
23 let tokens: TokenSet = serde_json::from_str(&blob)?;
24 Ok(Some(tokens))
25 }
26 Err(keyring::Error::NoEntry) => Ok(None),
27 Err(e) => Err(ClientError::Keychain(e)),
28 }
29 }
30
31 pub fn save(email: &str, tokens: &TokenSet) -> Result<(), ClientError> {
33 let blob = serde_json::to_string(tokens)?;
34 let entry = Self::entry(email)?;
35 entry.set_password(&blob).map_err(ClientError::Keychain)
36 }
37
38 pub fn delete(email: &str) -> Result<(), ClientError> {
40 let entry = Self::entry(email)?;
41 match entry.delete_credential() {
42 Ok(()) => Ok(()),
43 Err(keyring::Error::NoEntry) => Ok(()),
44 Err(e) => Err(ClientError::Keychain(e)),
45 }
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
56 use chrono::{Duration, Utc};
57
58 #[test]
59 fn token_blob_serializes_and_deserializes() {
60 let t = TokenSet {
61 access_token: "abc".into(),
62 refresh_token: "xyz".into(),
63 expires_at: Utc::now() + Duration::seconds(3600),
64 };
65 let blob = serde_json::to_string(&t).unwrap();
66 let t2: TokenSet = serde_json::from_str(&blob).unwrap();
67 assert_eq!(t, t2);
68 }
69}