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()
44            .duration_since(UNIX_EPOCH)
45            .ok()?
46            .as_secs();
47        if now > expires_at {
48            return None; // Token expired
49        }
50    }
51
52    config.syncable_auth.access_token
53}
54
55/// Get the authenticated user's email
56pub fn get_user_email() -> Option<String> {
57    let config = load_config(None).ok()?;
58    config.syncable_auth.user_email
59}
60
61/// Check if the user is currently authenticated with a valid token
62pub fn is_authenticated() -> bool {
63    get_access_token().is_some()
64}
65
66/// Get authentication status including expiry info
67pub fn get_auth_status() -> AuthStatus {
68    let config = match load_config(None) {
69        Ok(c) => c,
70        Err(_) => return AuthStatus::NotAuthenticated,
71    };
72
73    match &config.syncable_auth.access_token {
74        None => AuthStatus::NotAuthenticated,
75        Some(_) => {
76            if let Some(expires_at) = config.syncable_auth.expires_at {
77                let now = SystemTime::now()
78                    .duration_since(UNIX_EPOCH)
79                    .map(|d| d.as_secs())
80                    .unwrap_or(0);
81
82                if now > expires_at {
83                    return AuthStatus::Expired;
84                }
85
86                AuthStatus::Authenticated {
87                    email: config.syncable_auth.user_email.clone(),
88                    expires_at: Some(expires_at),
89                }
90            } else {
91                AuthStatus::Authenticated {
92                    email: config.syncable_auth.user_email.clone(),
93                    expires_at: None,
94                }
95            }
96        }
97    }
98}
99
100/// Clear stored credentials (logout)
101pub fn clear_credentials() -> Result<()> {
102    let mut config = load_config(None).unwrap_or_default();
103    config.syncable_auth = SyncableAuth::default();
104    save_global_config(&config)?;
105    Ok(())
106}
107
108/// Authentication status enum
109#[derive(Debug)]
110pub enum AuthStatus {
111    NotAuthenticated,
112    Expired,
113    Authenticated {
114        email: Option<String>,
115        expires_at: Option<u64>,
116    },
117}