syncable_cli/auth/
credentials.rs

1//! Credential storage and retrieval for Syncable authentication
2//!
3//! Stores authentication tokens in ~/.syncable.toml
4
5use crate::config::{load_config, save_global_config, types::SyncableAuth};
6use anyhow::Result;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9/// Save credentials to global config file
10pub fn save_credentials(
11    access_token: &str,
12    refresh_token: Option<&str>,
13    user_email: Option<&str>,
14    expires_in_secs: Option<u64>,
15) -> Result<()> {
16    let mut config = load_config(None).unwrap_or_default();
17
18    let expires_at = expires_in_secs.map(|secs| {
19        SystemTime::now()
20            .duration_since(UNIX_EPOCH)
21            .unwrap()
22            .as_secs()
23            + secs
24    });
25
26    config.syncable_auth = SyncableAuth {
27        access_token: Some(access_token.to_string()),
28        refresh_token: refresh_token.map(|s| s.to_string()),
29        expires_at,
30        user_email: user_email.map(|s| s.to_string()),
31    };
32
33    save_global_config(&config)?;
34    Ok(())
35}
36
37/// Get the current access token if valid
38pub fn get_access_token() -> Option<String> {
39    let config = load_config(None).ok()?;
40
41    // Check expiry
42    if let Some(expires_at) = config.syncable_auth.expires_at {
43        let now = SystemTime::now().duration_since(UNIX_EPOCH).ok()?.as_secs();
44        if now > expires_at {
45            return None; // Token expired
46        }
47    }
48
49    config.syncable_auth.access_token
50}
51
52/// Get the authenticated user's email
53pub fn get_user_email() -> Option<String> {
54    let config = load_config(None).ok()?;
55    config.syncable_auth.user_email
56}
57
58/// Check if the user is currently authenticated with a valid token
59pub fn is_authenticated() -> bool {
60    get_access_token().is_some()
61}
62
63/// Get authentication status including expiry info
64pub fn get_auth_status() -> AuthStatus {
65    let config = match load_config(None) {
66        Ok(c) => c,
67        Err(_) => return AuthStatus::NotAuthenticated,
68    };
69
70    match &config.syncable_auth.access_token {
71        None => AuthStatus::NotAuthenticated,
72        Some(_) => {
73            if let Some(expires_at) = config.syncable_auth.expires_at {
74                let now = SystemTime::now()
75                    .duration_since(UNIX_EPOCH)
76                    .map(|d| d.as_secs())
77                    .unwrap_or(0);
78
79                if now > expires_at {
80                    return AuthStatus::Expired;
81                }
82
83                AuthStatus::Authenticated {
84                    email: config.syncable_auth.user_email.clone(),
85                    expires_at: Some(expires_at),
86                }
87            } else {
88                AuthStatus::Authenticated {
89                    email: config.syncable_auth.user_email.clone(),
90                    expires_at: None,
91                }
92            }
93        }
94    }
95}
96
97/// Clear stored credentials (logout)
98pub fn clear_credentials() -> Result<()> {
99    let mut config = load_config(None).unwrap_or_default();
100    config.syncable_auth = SyncableAuth::default();
101    save_global_config(&config)?;
102    Ok(())
103}
104
105/// Authentication status enum
106#[derive(Debug)]
107pub enum AuthStatus {
108    NotAuthenticated,
109    Expired,
110    Authenticated {
111        email: Option<String>,
112        expires_at: Option<u64>,
113    },
114}