asknothingx2_util/oauth/
new_types.rs1use 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}