lib_client_google_auth/
token.rs

1use async_trait::async_trait;
2use chrono::{DateTime, Duration, Utc};
3use serde::{Deserialize, Serialize};
4
5/// Access token with expiration.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Token {
8    /// The access token string.
9    pub access_token: String,
10
11    /// Token type (usually "Bearer").
12    pub token_type: String,
13
14    /// Expiration time.
15    pub expires_at: DateTime<Utc>,
16
17    /// Refresh token (for OAuth2).
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub refresh_token: Option<String>,
20
21    /// Scopes granted.
22    #[serde(default)]
23    pub scopes: Vec<String>,
24}
25
26impl Token {
27    /// Create a new token.
28    pub fn new(access_token: String, expires_in_secs: i64) -> Self {
29        Self {
30            access_token,
31            token_type: "Bearer".to_string(),
32            expires_at: Utc::now() + Duration::seconds(expires_in_secs),
33            refresh_token: None,
34            scopes: Vec::new(),
35        }
36    }
37
38    /// Check if the token is expired (with 60s buffer).
39    pub fn is_expired(&self) -> bool {
40        Utc::now() + Duration::seconds(60) >= self.expires_at
41    }
42
43    /// Set refresh token.
44    pub fn with_refresh_token(mut self, token: String) -> Self {
45        self.refresh_token = Some(token);
46        self
47    }
48
49    /// Set scopes.
50    pub fn with_scopes(mut self, scopes: Vec<String>) -> Self {
51        self.scopes = scopes;
52        self
53    }
54}
55
56/// Token response from Google OAuth2.
57#[derive(Debug, Deserialize)]
58pub struct TokenResponse {
59    pub access_token: String,
60    pub token_type: String,
61    pub expires_in: i64,
62    pub refresh_token: Option<String>,
63    pub scope: Option<String>,
64}
65
66impl From<TokenResponse> for Token {
67    fn from(resp: TokenResponse) -> Self {
68        let scopes = resp
69            .scope
70            .map(|s| s.split_whitespace().map(String::from).collect())
71            .unwrap_or_default();
72
73        Token::new(resp.access_token, resp.expires_in)
74            .with_scopes(scopes)
75            .with_refresh_token(resp.refresh_token.unwrap_or_default())
76    }
77}
78
79/// Trait for persistent token storage.
80#[async_trait]
81pub trait TokenStore: Send + Sync {
82    /// Load a stored token for the given key.
83    async fn load(&self, key: &str) -> crate::Result<Option<Token>>;
84
85    /// Store a token with the given key.
86    async fn store(&self, key: &str, token: &Token) -> crate::Result<()>;
87
88    /// Delete a stored token.
89    async fn delete(&self, key: &str) -> crate::Result<()>;
90}
91
92/// In-memory token store (tokens lost on restart).
93#[derive(Debug, Default)]
94pub struct MemoryTokenStore {
95    tokens: std::sync::RwLock<std::collections::HashMap<String, Token>>,
96}
97
98impl MemoryTokenStore {
99    pub fn new() -> Self {
100        Self::default()
101    }
102}
103
104#[async_trait]
105impl TokenStore for MemoryTokenStore {
106    async fn load(&self, key: &str) -> crate::Result<Option<Token>> {
107        Ok(self.tokens.read().unwrap().get(key).cloned())
108    }
109
110    async fn store(&self, key: &str, token: &Token) -> crate::Result<()> {
111        self.tokens
112            .write()
113            .unwrap()
114            .insert(key.to_string(), token.clone());
115        Ok(())
116    }
117
118    async fn delete(&self, key: &str) -> crate::Result<()> {
119        self.tokens.write().unwrap().remove(key);
120        Ok(())
121    }
122}