zino_auth/
client_credentials.rs1use 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#[derive(Debug)]
15pub struct ClientCredentials<S: ?Sized> {
16 client_id: SharedString,
18 client_key: SharedString,
20 client_secret: SharedString,
22 access_token: RwLock<String>,
24 expires_at: RwLock<DateTime>,
26 phantom: PhantomData<S>,
28}
29
30impl<S: ?Sized> ClientCredentials<S> {
31 #[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 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 #[inline]
65 pub fn set_client_key(&mut self, client_key: impl Into<SharedString>) {
66 self.client_key = client_key.into();
67 }
68
69 #[inline]
71 pub fn set_access_token(&self, access_token: impl ToString) {
72 *self.access_token.write() = access_token.to_string();
73 }
74
75 #[inline]
77 pub fn set_expires(&self, expires_in: Duration) {
78 *self.expires_at.write() = DateTime::now() + expires_in
79 }
80
81 #[inline]
83 pub fn client_id(&self) -> &str {
84 self.client_id.as_ref()
85 }
86
87 #[inline]
89 pub fn client_key(&self) -> &str {
90 self.client_key.as_ref()
91 }
92
93 #[inline]
95 pub fn client_secret(&self) -> &str {
96 self.client_secret.as_ref()
97 }
98
99 #[inline]
101 pub fn access_token(&self) -> String {
102 self.access_token.read().clone()
103 }
104
105 #[inline]
107 pub fn expires_at(&self) -> DateTime {
108 *self.expires_at.read()
109 }
110
111 #[inline]
113 pub fn expires_in(&self) -> Duration {
114 self.expires_at().span_after_now().unwrap_or_default()
115 }
116
117 #[inline]
119 pub fn is_expired(&self) -> bool {
120 self.expires_at() <= DateTime::now()
121 }
122
123 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 #[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}