zino_auth/
client_credentials.rs

1use super::AuthorizationProvider;
2use parking_lot::RwLock;
3use std::{marker::PhantomData, time::Duration};
4use toml::Table;
5use zino_core::{
6    Map, SharedString,
7    datetime::DateTime,
8    error::Error,
9    extension::{JsonObjectExt, TomlTableExt},
10    warn,
11};
12
13/// Credentials for the client authentication.
14#[derive(Debug)]
15pub struct ClientCredentials<S: ?Sized> {
16    /// Client ID.
17    client_id: SharedString,
18    /// Client key.
19    client_key: SharedString,
20    /// Client secret.
21    client_secret: SharedString,
22    /// Access token.
23    access_token: RwLock<String>,
24    /// Expires time.
25    expires_at: RwLock<DateTime>,
26    /// Phantom type of authorization server.
27    phantom: PhantomData<S>,
28}
29
30impl<S: ?Sized> ClientCredentials<S> {
31    /// Creates a new instance.
32    #[inline]
33    pub fn new(client_id: impl Into<SharedString>, client_secret: impl Into<SharedString>) -> Self {
34        Self {
35            client_id: client_id.into(),
36            client_key: "".into(),
37            client_secret: client_secret.into(),
38            access_token: RwLock::new(String::new()),
39            expires_at: RwLock::new(DateTime::now()),
40            phantom: PhantomData,
41        }
42    }
43
44    /// Attempts to create a new instance with the configuration.
45    pub fn try_from_config(config: &'static Table) -> Result<Self, Error> {
46        let client_id = config
47            .get_str("client-id")
48            .ok_or_else(|| warn!("field `client-id` should be specified"))?;
49        let client_key = config.get_str("client-key").unwrap_or_default();
50        let client_secret = config
51            .get_str("client-secret")
52            .ok_or_else(|| warn!("field `client-secret` should be specified"))?;
53        Ok(Self {
54            client_id: client_id.into(),
55            client_key: client_key.into(),
56            client_secret: client_secret.into(),
57            access_token: RwLock::new(String::new()),
58            expires_at: RwLock::new(DateTime::now()),
59            phantom: PhantomData,
60        })
61    }
62
63    /// Sets the client key.
64    #[inline]
65    pub fn set_client_key(&mut self, client_key: impl Into<SharedString>) {
66        self.client_key = client_key.into();
67    }
68
69    /// Sets the access token.
70    #[inline]
71    pub fn set_access_token(&self, access_token: impl ToString) {
72        *self.access_token.write() = access_token.to_string();
73    }
74
75    /// Sets the expires.
76    #[inline]
77    pub fn set_expires(&self, expires_in: Duration) {
78        *self.expires_at.write() = DateTime::now() + expires_in
79    }
80
81    /// Returns the client ID.
82    #[inline]
83    pub fn client_id(&self) -> &str {
84        self.client_id.as_ref()
85    }
86
87    /// Returns the client key.
88    #[inline]
89    pub fn client_key(&self) -> &str {
90        self.client_key.as_ref()
91    }
92
93    /// Returns the client secret.
94    #[inline]
95    pub fn client_secret(&self) -> &str {
96        self.client_secret.as_ref()
97    }
98
99    /// Returns the access token regardless of whether it has expired.
100    #[inline]
101    pub fn access_token(&self) -> String {
102        self.access_token.read().clone()
103    }
104
105    /// Returns the time the client credentials expire at.
106    #[inline]
107    pub fn expires_at(&self) -> DateTime {
108        *self.expires_at.read()
109    }
110
111    /// Returns the time when the client credentials will expire in.
112    #[inline]
113    pub fn expires_in(&self) -> Duration {
114        self.expires_at().span_after_now().unwrap_or_default()
115    }
116
117    /// Returns `true` if the access token for the client credentials has expired.
118    #[inline]
119    pub fn is_expired(&self) -> bool {
120        self.expires_at() <= DateTime::now()
121    }
122
123    /// Converts `self` to the request params.
124    pub fn to_request_params(&self) -> Map {
125        let mut params = Map::new();
126        let client_id = self.client_id();
127        let client_key = self.client_key();
128        let client_secret = self.client_secret();
129        if !client_id.is_empty() {
130            params.upsert("client_id", client_id);
131        }
132        if !client_key.is_empty() {
133            params.upsert("client_key", client_key);
134        }
135        if !client_secret.is_empty() {
136            params.upsert("client_secret", client_secret);
137        }
138        params
139    }
140}
141
142impl<S: ?Sized + AuthorizationProvider> ClientCredentials<S> {
143    /// Requests an access token for the client credentials.
144    #[inline]
145    pub async fn request(&self) -> Result<String, Error> {
146        if self.is_expired() {
147            S::grant_client_credentials(self).await?;
148        }
149        Ok(self.access_token())
150    }
151}