asknothingx2_util/oauth/
new_types.rs

1use std::{
2    fmt::{Debug, Display, Formatter, Result as FmtResult},
3    hash::{Hash, Hasher},
4    ops::Deref,
5    str::FromStr,
6};
7
8use serde::{Deserialize, Serialize};
9use sha2::{Digest, Sha256};
10use subtle::ConstantTimeEq;
11use url::Url;
12use zeroize::{Zeroize, ZeroizeOnDrop};
13
14macro_rules! regular_type {
15    (
16    $(#[$attr:meta])*
17    $name:ident
18    ) => (
19        #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
20        #[serde(transparent)]
21        $(#[$attr])*
22        pub struct  $name(String);
23
24        impl $name {
25            pub fn as_str(&self) -> &str {
26                &self.0
27            }
28        }
29
30        impl Display for $name {
31            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
32                f.write_str(self.as_str())
33            }
34        }
35
36        impl From<String> for $name {
37            fn from(value: String) -> Self {
38                Self(value.into())
39            }
40        }
41
42        impl From<&str> for $name {
43            fn from(value: &str) -> Self {
44                Self(value.into())
45            }
46        }
47
48        impl AsRef<str> for $name {
49            fn as_ref(&self) -> &str {
50                self.as_str()
51            }
52        }
53
54        impl Deref for $name {
55            type Target = str;
56
57            fn deref(&self) -> &str {
58                self.as_str()
59            }
60        }
61
62
63    )
64}
65
66macro_rules! secret_type {
67    (
68    $(#[$attr:meta])*
69    $name:ident
70    ) => (
71        #[derive(Clone, Serialize, Deserialize)]
72        #[derive(Zeroize, ZeroizeOnDrop)]
73        #[serde(transparent)]
74        $(#[$attr])*
75        pub struct  $name(String);
76
77        impl $name {
78            pub fn secret(&self) -> &str {
79                &self.0
80            }
81
82            pub fn into_secret(mut self) -> String {
83                std::mem::take(&mut self.0)
84            }
85
86            pub fn clear(&mut self) {
87                self.zeroize();
88            }
89        }
90
91        impl Display for $name {
92            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
93                f.write_str("[REDACTED]")
94
95            }
96        }
97
98        impl Debug for $name {
99            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
100                f.debug_tuple(stringify!($name))
101                    .field(&"[REDACTED]")
102                    .finish()
103            }
104        }
105
106        impl From<String> for $name {
107            fn from(value: String) -> Self {
108                Self(value)
109            }
110        }
111
112        impl From<&str> for $name {
113            fn from(value: &str) -> Self {
114                Self(value.into())
115            }
116        }
117
118        impl AsRef<str> for $name {
119            fn as_ref(&self) -> &str {
120                &self.0
121            }
122        }
123
124        impl Deref for $name {
125            type Target = str;
126
127            fn deref(&self) -> &str {
128                &self.0
129            }
130        }
131
132        impl PartialEq for $name {
133            fn eq(&self, other: &Self) -> bool {
134                self.0.as_bytes().ct_eq(other.0.as_bytes()).into()
135            }
136        }
137
138        impl Eq for $name {}
139
140        impl Hash for $name {
141            fn hash<H: Hasher>(&self, state: &mut H) {
142                Sha256::digest(self.secret()).hash(state);
143            }
144        }
145
146    )
147}
148
149macro_rules! url_type {
150    (
151        $(#[$attr:meta])*
152        $name:ident
153    ) => (
154        #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
155        #[serde(transparent)]
156        $(#[$attr])*
157        pub struct  $name(String);
158
159        impl $name {
160            pub fn as_str(&self) -> &str {
161                &self.0
162            }
163
164            pub fn to_url(&self) -> Url {
165                Url::parse(&self.0).unwrap()
166            }
167        }
168
169        impl Display for $name {
170            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
171                f.write_str(self.as_str())
172            }
173        }
174
175        impl FromStr for $name {
176            type Err = url::ParseError;
177            fn from_str(s: &str) -> Result<Self, Self::Err> {
178                url::Url::parse(s)?;
179                Ok(Self(s.to_string()))
180            }
181        }
182
183        impl AsRef<str> for $name {
184            fn as_ref(&self) -> &str {
185                self.as_str()
186            }
187        }
188
189        impl Deref for $name {
190            type Target = str;
191
192            fn deref(&self) -> &str {
193                self.as_str()
194            }
195        }
196    )
197}
198
199regular_type!(ClientId);
200regular_type!(Scope);
201regular_type!(CsrfToken);
202
203secret_type!(ClientSecret);
204secret_type!(AuthorizationCode);
205secret_type!(RefreshToken);
206secret_type!(AccessToken);
207
208url_type!(AuthUrl);
209url_type!(TokenUrl);
210url_type!(RedirectUrl);
211url_type!(RevocationUrl);
212url_type!(ValidateUrl);
213
214#[cfg(test)]
215mod tests {
216    use std::str::FromStr;
217
218    use crate::oauth::{AccessToken, AuthUrl, ClientId, ClientSecret, RedirectUrl};
219
220    #[test]
221    fn regular_type() {
222        let client_id = ClientId::from("client_id");
223
224        let json = serde_json::to_string(&client_id).unwrap();
225        assert_eq!(json, "\"client_id\"");
226
227        let deserialized: ClientId = serde_json::from_str(&json).unwrap();
228        assert_eq!(client_id, deserialized);
229
230        let client_id2 = ClientId::from("client_id");
231        let client_id3 = ClientId::from("client_id3");
232        assert_eq!(client_id, client_id2);
233        assert_ne!(client_id, client_id3);
234
235        let as_str = client_id.as_str();
236        assert_eq!(as_str, "client_id");
237
238        let display = client_id.to_string();
239        assert_eq!(display, "client_id");
240
241        assert_eq!(&*client_id, "client_id");
242        assert_eq!(client_id.len(), 9);
243    }
244
245    #[test]
246    fn secret_type() {
247        let client_secret = ClientSecret::from("client_secret");
248
249        let json = serde_json::to_string(&client_secret).unwrap();
250        assert_eq!(json, "\"client_secret\"");
251
252        let deserialized: ClientSecret = serde_json::from_str(&json).unwrap();
253        assert_eq!(client_secret, deserialized);
254
255        let client_secret2 = ClientSecret::from("client_secret");
256        let client_secret3 = ClientSecret::from("client_secret3");
257        assert_eq!(client_secret, client_secret2);
258        assert_ne!(client_secret, client_secret3);
259
260        let secret = client_secret.secret();
261        assert_eq!(secret, "client_secret");
262
263        let display = client_secret.to_string();
264        assert_eq!(display, "[REDACTED]");
265
266        assert_eq!(&*client_secret, "client_secret");
267        assert_eq!(client_secret.len(), 13);
268
269        let mut token = AccessToken::from("access_token");
270
271        token.clear();
272
273        assert_eq!(token.secret(), "");
274    }
275
276    #[test]
277    fn url_type() {
278        let auth_url = AuthUrl::from_str("https://id.twitch.tv/oauth2/authorize").unwrap();
279
280        let json = serde_json::to_string(&auth_url).unwrap();
281        assert_eq!(json, "\"https://id.twitch.tv/oauth2/authorize\"");
282
283        let deserialized: AuthUrl = serde_json::from_str(&json).unwrap();
284        assert_eq!(auth_url, deserialized);
285
286        let auth_url2 = AuthUrl::from_str("https://id.twitch.tv/oauth2/authorize").unwrap();
287        assert_eq!(auth_url, auth_url2);
288
289        let as_str = auth_url.as_str();
290        assert_eq!(as_str, "https://id.twitch.tv/oauth2/authorize");
291
292        let display = auth_url.to_string();
293        assert_eq!(display, "https://id.twitch.tv/oauth2/authorize");
294
295        assert_eq!(&*auth_url, "https://id.twitch.tv/oauth2/authorize");
296
297        let valid = RedirectUrl::from_str("not_a_ual");
298        assert!(valid.is_err());
299    }
300}