use async_trait::async_trait;
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Token {
pub access_token: String,
pub token_type: String,
pub expires_at: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub refresh_token: Option<String>,
#[serde(default)]
pub scopes: Vec<String>,
}
impl Token {
pub fn new(access_token: String, expires_in_secs: i64) -> Self {
Self {
access_token,
token_type: "Bearer".to_string(),
expires_at: Utc::now() + Duration::seconds(expires_in_secs),
refresh_token: None,
scopes: Vec::new(),
}
}
pub fn is_expired(&self) -> bool {
Utc::now() + Duration::seconds(60) >= self.expires_at
}
pub fn with_refresh_token(mut self, token: String) -> Self {
self.refresh_token = Some(token);
self
}
pub fn with_scopes(mut self, scopes: Vec<String>) -> Self {
self.scopes = scopes;
self
}
}
#[derive(Debug, Deserialize)]
pub struct TokenResponse {
pub access_token: String,
pub token_type: String,
pub expires_in: i64,
pub refresh_token: Option<String>,
pub scope: Option<String>,
}
impl From<TokenResponse> for Token {
fn from(resp: TokenResponse) -> Self {
let scopes = resp
.scope
.map(|s| s.split_whitespace().map(String::from).collect())
.unwrap_or_default();
Token::new(resp.access_token, resp.expires_in)
.with_scopes(scopes)
.with_refresh_token(resp.refresh_token.unwrap_or_default())
}
}
#[async_trait]
pub trait TokenStore: Send + Sync {
async fn load(&self, key: &str) -> crate::Result<Option<Token>>;
async fn store(&self, key: &str, token: &Token) -> crate::Result<()>;
async fn delete(&self, key: &str) -> crate::Result<()>;
}
#[derive(Debug, Default)]
pub struct MemoryTokenStore {
tokens: std::sync::RwLock<std::collections::HashMap<String, Token>>,
}
impl MemoryTokenStore {
pub fn new() -> Self {
Self::default()
}
}
#[async_trait]
impl TokenStore for MemoryTokenStore {
async fn load(&self, key: &str) -> crate::Result<Option<Token>> {
Ok(self.tokens.read().unwrap().get(key).cloned())
}
async fn store(&self, key: &str, token: &Token) -> crate::Result<()> {
self.tokens
.write()
.unwrap()
.insert(key.to_string(), token.clone());
Ok(())
}
async fn delete(&self, key: &str) -> crate::Result<()> {
self.tokens.write().unwrap().remove(key);
Ok(())
}
}