1use std::marker::PhantomData;
2
3use constant_time_eq::constant_time_eq;
4use digest::{Digest, FixedOutputReset};
5use rand::RngCore;
6
7use crate::controller_builder::ControllerBuilder;
8use crate::prefixed_api_key::PrefixedApiKey;
9
10#[derive(Clone, Debug)]
11pub struct PrefixedApiKeyController<R: RngCore + Clone, D: Digest + FixedOutputReset> {
12 prefix: String,
13 rng: R,
14 digest: PhantomData<D>,
15 short_token_prefix: Option<String>,
16 short_token_length: usize,
17 long_token_length: usize,
18}
19
20impl<R: RngCore + Clone, D: Digest + FixedOutputReset> PrefixedApiKeyController<R, D> {
21 pub fn new(
22 prefix: String,
23 rng: R,
24 short_token_prefix: Option<String>,
25 short_token_length: usize,
26 long_token_length: usize,
27 ) -> PrefixedApiKeyController<R, D> {
28 PrefixedApiKeyController {
29 prefix,
30 rng,
31 digest: PhantomData,
32 short_token_prefix,
33 short_token_length,
34 long_token_length,
35 }
36 }
37
38 pub fn configure() -> ControllerBuilder<R, D> {
41 ControllerBuilder::new()
42 }
43
44 fn get_random_bytes(&self, length: usize) -> Vec<u8> {
48 let mut random_bytes = vec![0u8; length];
49 let mut rng = self.rng.clone();
50 rng.fill_bytes(&mut random_bytes);
51 random_bytes
52 }
53
54 fn try_get_random_bytes(&self, length: usize) -> Result<Vec<u8>, crate::rand::Error> {
56 let mut random_bytes = vec![0u8; length];
57 let mut rng = self.rng.clone();
58 match rng.try_fill_bytes(&mut random_bytes) {
59 Ok(_) => Ok(random_bytes),
60 Err(err) => Err(err),
61 }
62 }
63
64 fn get_random_token(&self, length: usize) -> String {
70 let bytes = self.get_random_bytes(length);
71 bs58::encode(bytes).into_string()
72 }
73
74 fn try_get_random_token(&self, length: usize) -> Result<String, crate::rand::Error> {
78 match self.try_get_random_bytes(length) {
79 Ok(bytes) => Ok(bs58::encode(bytes).into_string()),
80 Err(err) => Err(err),
81 }
82 }
83
84 pub fn generate_key(&self) -> PrefixedApiKey {
91 let mut short_token = self.get_random_token(self.short_token_length);
93
94 if self.short_token_prefix.is_some() {
97 let prefix_string = self.short_token_prefix.as_ref().unwrap().to_owned();
98 short_token = (prefix_string + &short_token)
99 .chars()
100 .take(self.short_token_length)
101 .collect()
102 }
103
104 let long_token = self.get_random_token(self.long_token_length);
106
107 PrefixedApiKey::new(self.prefix.to_owned(), short_token, long_token)
109 }
110
111 pub fn try_generate_key(&self) -> Result<PrefixedApiKey, crate::rand::Error> {
116 let mut short_token = self.try_get_random_token(self.short_token_length)?;
118
119 if self.short_token_prefix.is_some() {
122 let prefix_string = self.short_token_prefix.as_ref().unwrap().to_owned();
123 short_token = (prefix_string + &short_token)
124 .chars()
125 .take(self.short_token_length)
126 .collect()
127 }
128
129 let long_token = self.try_get_random_token(self.long_token_length)?;
131
132 let pak = PrefixedApiKey::new(self.prefix.to_owned(), short_token, long_token);
134 Ok(pak)
135 }
136
137 pub fn generate_key_and_hash(&self) -> (PrefixedApiKey, String) {
142 let pak = self.generate_key();
143 let hash = self.long_token_hashed(&pak);
144 (pak, hash)
145 }
146
147 pub fn try_generate_key_and_hash(
150 &self,
151 ) -> Result<(PrefixedApiKey, String), crate::rand::Error> {
152 match self.try_generate_key() {
153 Ok(pak) => {
154 let hash = self.long_token_hashed(&pak);
155 Ok((pak, hash))
156 }
157 Err(err) => Err(err),
158 }
159 }
160
161 pub fn long_token_hashed(&self, pak: &PrefixedApiKey) -> String {
166 let mut digest = D::new();
167 pak.long_token_hashed(&mut digest)
168 }
169
170 pub fn check_hash(&self, pak: &PrefixedApiKey, hash: &str) -> bool {
175 let pak_hash = self.long_token_hashed(pak);
176 constant_time_eq(pak_hash.as_bytes(), hash.as_bytes())
177 }
178}
179
180#[cfg(test)]
181mod controller_tests {
182 use rand::rngs::OsRng;
183 use sha2::Sha256;
184
185 use crate::controller::PrefixedApiKeyController;
186 use crate::PrefixedApiKey;
187
188 #[test]
189 fn configuration_works() {
190 let controller = PrefixedApiKeyController::<_, Sha256>::configure()
191 .default_lengths()
192 .prefix("mycompany".to_owned())
193 .rng(OsRng)
194 .finalize();
195 assert!(controller.is_ok())
196 }
197
198 #[test]
199 fn generator() {
200 let generator =
201 PrefixedApiKeyController::<_, Sha256>::new("mycompany".to_owned(), OsRng, None, 8, 24);
202 let token_string = generator.generate_key().to_string();
203 let pak_result = PrefixedApiKey::from_string(&token_string);
204 assert_eq!(pak_result.is_ok(), true);
205 let pak_string = pak_result.unwrap().to_string();
206 assert_eq!(token_string, pak_string);
207 }
208
209 #[test]
210 fn try_generator() {
211 let generator =
212 PrefixedApiKeyController::<_, Sha256>::new("mycompany".to_owned(), OsRng, None, 8, 24);
213 let token_res = generator.try_generate_key();
214 assert!(token_res.is_ok());
215 let token_string = token_res.unwrap().to_string();
216 let pak_result = PrefixedApiKey::from_string(&token_string);
217 assert_eq!(pak_result.is_ok(), true);
218 let pak_string = pak_result.unwrap().to_string();
219 assert_eq!(token_string, pak_string);
220 }
221
222 #[test]
223 fn generator_short_token_prefix() {
224 let short_length = 8;
225 let short_prefix = "a".repeat(short_length);
226 let generator = PrefixedApiKeyController::<_, Sha256>::new(
227 "mycompany".to_owned(),
228 OsRng,
229 Some(short_prefix.clone()),
230 short_length,
231 24,
232 );
233 let pak_short_token = generator.generate_key().short_token().to_owned();
234 assert_eq!(pak_short_token, short_prefix);
235 }
236
237 #[test]
238 fn generate_key_and_hash() {
239 let generator =
240 PrefixedApiKeyController::<_, Sha256>::new("mycompany".to_owned(), OsRng, None, 8, 24);
241 let (pak, hash) = generator.generate_key_and_hash();
242 assert!(generator.check_hash(&pak, &hash))
243 }
244
245 #[test]
246 fn check_long_token_via_generator() {
247 let pak_string = "mycompany_CEUsS4psCmc_BddpcwWyCT3EkDjHSSTRaSK1dxtuQgbjb";
248 let hash = "0f01ab6e0833f280b73b2b618c16102d91c0b7c585d42a080d6e6603239a8bee";
249
250 let pak: PrefixedApiKey = pak_string.try_into().unwrap();
251
252 let generator =
253 PrefixedApiKeyController::<_, Sha256>::new("mycompany".to_owned(), OsRng, None, 8, 24);
254
255 assert_eq!(generator.long_token_hashed(&pak), hash);
256 }
257
258 #[test]
259 fn generator_digest_resets_after_hashing() {
260 let pak1_string = "mycompany_CEUsS4psCmc_BddpcwWyCT3EkDjHSSTRaSK1dxtuQgbjb";
261 let pak1_hash = "0f01ab6e0833f280b73b2b618c16102d91c0b7c585d42a080d6e6603239a8bee";
262 let pak1: PrefixedApiKey = pak1_string.try_into().unwrap();
263
264 let pak2_string = "mycompany_CEUsS4psCmc_BddpcwWyCT3EkDjHSSTRaSK1dxtuQgbjb";
265 let pak2_hash = "0f01ab6e0833f280b73b2b618c16102d91c0b7c585d42a080d6e6603239a8bee";
266 let pak2: PrefixedApiKey = pak2_string.try_into().unwrap();
267
268 let generator =
269 PrefixedApiKeyController::<_, Sha256>::new("mycompany".to_owned(), OsRng, None, 8, 24);
270
271 assert_eq!(generator.long_token_hashed(&pak1), pak1_hash);
272 assert_eq!(generator.long_token_hashed(&pak2), pak2_hash);
273 }
274
275 #[test]
276 fn generator_matches_hash() {
277 let pak_string = "mycompany_CEUsS4psCmc_BddpcwWyCT3EkDjHSSTRaSK1dxtuQgbjb";
278 let pak_hash = "0f01ab6e0833f280b73b2b618c16102d91c0b7c585d42a080d6e6603239a8bee";
279 let pak: PrefixedApiKey = pak_string.try_into().unwrap();
280
281 let generator =
282 PrefixedApiKeyController::<_, Sha256>::new("mycompany".to_owned(), OsRng, None, 8, 24);
283
284 assert!(generator.check_hash(&pak, pak_hash));
285 }
286}