zino_auth/
client_credentials.rsuse super::AuthorizationProvider;
use parking_lot::RwLock;
use std::{marker::PhantomData, time::Duration};
use toml::Table;
use zino_core::{
datetime::DateTime,
error::Error,
extension::{JsonObjectExt, TomlTableExt},
warn, Map, SharedString,
};
#[derive(Debug)]
pub struct ClientCredentials<S: ?Sized> {
client_id: SharedString,
client_key: SharedString,
client_secret: SharedString,
access_token: RwLock<String>,
expires_at: RwLock<DateTime>,
phantom: PhantomData<S>,
}
impl<S: ?Sized> ClientCredentials<S> {
#[inline]
pub fn new(client_id: impl Into<SharedString>, client_secret: impl Into<SharedString>) -> Self {
Self {
client_id: client_id.into(),
client_key: "".into(),
client_secret: client_secret.into(),
access_token: RwLock::new(String::new()),
expires_at: RwLock::new(DateTime::now()),
phantom: PhantomData,
}
}
pub fn try_from_config(config: &'static Table) -> Result<Self, Error> {
let client_id = config
.get_str("client-id")
.ok_or_else(|| warn!("the `client-id` field should be specified"))?;
let client_key = config.get_str("client-key").unwrap_or_default();
let client_secret = config
.get_str("client-secret")
.ok_or_else(|| warn!("the `client-secret` field should be specified"))?;
Ok(Self {
client_id: client_id.into(),
client_key: client_key.into(),
client_secret: client_secret.into(),
access_token: RwLock::new(String::new()),
expires_at: RwLock::new(DateTime::now()),
phantom: PhantomData,
})
}
#[inline]
pub fn set_client_key(&mut self, client_key: impl Into<SharedString>) {
self.client_key = client_key.into();
}
#[inline]
pub fn set_access_token(&self, access_token: impl ToString) {
*self.access_token.write() = access_token.to_string();
}
#[inline]
pub fn set_expires(&self, expires_in: Duration) {
*self.expires_at.write() = DateTime::now() + expires_in
}
#[inline]
pub fn client_id(&self) -> &str {
self.client_id.as_ref()
}
#[inline]
pub fn client_key(&self) -> &str {
self.client_key.as_ref()
}
#[inline]
pub fn client_secret(&self) -> &str {
self.client_secret.as_ref()
}
#[inline]
pub fn access_token(&self) -> String {
self.access_token.read().clone()
}
#[inline]
pub fn expires_at(&self) -> DateTime {
*self.expires_at.read()
}
#[inline]
pub fn expires_in(&self) -> Duration {
self.expires_at().span_after_now().unwrap_or_default()
}
#[inline]
pub fn is_expired(&self) -> bool {
self.expires_at() <= DateTime::now()
}
pub fn to_request_params(&self) -> Map {
let mut params = Map::new();
let client_id = self.client_id();
let client_key = self.client_key();
let client_secret = self.client_secret();
if !client_id.is_empty() {
params.upsert("client_id", client_id);
}
if !client_key.is_empty() {
params.upsert("client_key", client_key);
}
if !client_secret.is_empty() {
params.upsert("client_secret", client_secret);
}
params
}
}
impl<S: ?Sized + AuthorizationProvider> ClientCredentials<S> {
#[inline]
pub async fn request(&self) -> Result<String, Error> {
if self.is_expired() {
S::grant_client_credentials(self).await?;
}
Ok(self.access_token())
}
}