1use digest::{Digest, FixedOutputReset};
2use rand::{
3 rngs::{OsRng, StdRng, ThreadRng},
4 RngCore, SeedableRng,
5};
6use std::fmt;
7use std::{error::Error, marker::PhantomData};
8
9#[cfg(feature = "sha2")]
10use sha2::{Sha224, Sha256, Sha384, Sha512, Sha512_224, Sha512_256};
11
12use crate::controller::PrefixedApiKeyController;
13
14#[derive(Debug, Clone)]
15pub enum BuilderError {
16 MissingPrefix,
17 MissingRng,
18 MissingDigest,
19 MissingShortTokenLength,
20 MissingLongTokenLength,
21}
22
23impl fmt::Display for BuilderError {
24 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25 match self {
26 BuilderError::MissingPrefix => write!(f, "expected prefix to be set, but wasn't"),
27 BuilderError::MissingRng => write!(f, "expected rng to be set, but wasn't"),
28 BuilderError::MissingDigest => write!(f, "expected digest to be set, but wasn't"),
29 BuilderError::MissingShortTokenLength => {
30 write!(f, "expected short_token_length to be set, but wasn't")
31 }
32 BuilderError::MissingLongTokenLength => {
33 write!(f, "expected long_token_length to be set, but wasn't")
34 }
35 }
36 }
37}
38
39impl Error for BuilderError {}
40
41pub struct ControllerBuilder<R: RngCore + Clone, D: Digest + FixedOutputReset> {
42 prefix: Option<String>,
43 rng: Option<R>,
44 digest: PhantomData<D>,
45 short_token_prefix: Option<String>,
46 short_token_length: Option<usize>,
47 long_token_length: Option<usize>,
48}
49
50impl<R: RngCore + Clone, D: Digest + FixedOutputReset> ControllerBuilder<R, D> {
51 pub fn new() -> ControllerBuilder<R, D> {
52 ControllerBuilder {
53 prefix: None,
54 rng: None,
55 digest: PhantomData,
56 short_token_prefix: None,
57 short_token_length: None,
58 long_token_length: None,
59 }
60 }
61
62 pub fn finalize(self) -> Result<PrefixedApiKeyController<R, D>, BuilderError> {
65 if self.prefix.is_none() {
66 return Err(BuilderError::MissingPrefix);
67 }
68
69 if self.rng.is_none() {
70 return Err(BuilderError::MissingRng);
71 }
72
73 if self.short_token_length.is_none() {
74 return Err(BuilderError::MissingShortTokenLength);
75 }
76
77 if self.long_token_length.is_none() {
78 return Err(BuilderError::MissingLongTokenLength);
79 }
80
81 Ok(PrefixedApiKeyController::new(
82 self.prefix.unwrap(),
83 self.rng.unwrap(),
84 self.short_token_prefix,
85 self.short_token_length.unwrap(),
86 self.long_token_length.unwrap(),
87 ))
88 }
89
90 pub fn default_lengths(self) -> Self {
93 self.short_token_length(8).long_token_length(24)
94 }
95
96 pub fn prefix(mut self, prefix: String) -> Self {
98 self.prefix = Some(prefix);
99 self
100 }
101
102 pub fn rng(mut self, rng: R) -> Self {
105 self.rng = Some(rng);
106 self
107 }
108
109 pub fn short_token_prefix(mut self, short_token_prefix: Option<String>) -> Self {
115 self.short_token_prefix = short_token_prefix;
116 self
117 }
118
119 pub fn short_token_length(mut self, short_token_length: usize) -> Self {
121 self.short_token_length = Some(short_token_length);
122 self
123 }
124
125 pub fn long_token_length(mut self, long_token_length: usize) -> Self {
127 self.long_token_length = Some(long_token_length);
128 self
129 }
130}
131
132impl<D: Digest + FixedOutputReset + Clone> ControllerBuilder<OsRng, D> {
133 pub fn rng_osrng(self) -> Self {
144 self.rng(OsRng)
145 }
146}
147
148impl<D: Digest + FixedOutputReset + Clone> ControllerBuilder<ThreadRng, D> {
149 pub fn rng_threadrng(self) -> Self {
161 self.rng(ThreadRng::default())
162 }
163}
164
165impl<D: Digest + FixedOutputReset + Clone> ControllerBuilder<StdRng, D> {
166 pub fn rng_stdrng(self) -> Self {
178 self.rng(StdRng::from_entropy())
179 }
180}
181
182#[cfg(feature = "sha2")]
183impl ControllerBuilder<OsRng, Sha256> {
184 pub fn seam_defaults(self) -> Self {
188 self.digest_sha256().rng_osrng().default_lengths()
189 }
190}
191
192#[cfg(feature = "sha2")]
193impl<R: RngCore + Clone> ControllerBuilder<R, Sha224> {
194 pub fn digest_sha224(self) -> Self {
198 self
199 }
200}
201
202#[cfg(feature = "sha2")]
203impl<R: RngCore + Clone> ControllerBuilder<R, Sha256> {
204 pub fn digest_sha256(self) -> Self {
208 self
209 }
210}
211
212#[cfg(feature = "sha2")]
213impl<R: RngCore + Clone> ControllerBuilder<R, Sha384> {
214 pub fn digest_sha384(self) -> Self {
218 self
219 }
220}
221
222#[cfg(feature = "sha2")]
223impl<R: RngCore + Clone> ControllerBuilder<R, Sha512> {
224 pub fn digest_sha512(self) -> Self {
228 self
229 }
230}
231
232#[cfg(feature = "sha2")]
233impl<R: RngCore + Clone> ControllerBuilder<R, Sha512_224> {
234 pub fn digest_sha512_224(self) -> Self {
238 self
239 }
240}
241
242#[cfg(feature = "sha2")]
243impl<R: RngCore + Clone> ControllerBuilder<R, Sha512_256> {
244 pub fn digest_sha512_256(self) -> Self {
248 self
249 }
250}
251
252impl<R: RngCore + Clone, D: Digest + FixedOutputReset + Clone> Default for ControllerBuilder<R, D> {
253 fn default() -> Self {
254 Self::new()
255 }
256}
257
258#[cfg(test)]
259mod controller_builder_tests {
260 use rand::rngs::OsRng;
261 use sha2::Sha256;
262
263 use super::ControllerBuilder;
264
265 #[test]
266 fn errors_when_no_values_set() {
267 let controller_result = ControllerBuilder::<OsRng, Sha256>::new().finalize();
268 assert!(controller_result.is_err())
269 }
270
271 #[test]
272 fn ok_with_all_values_provided() {
273 let controller_result = ControllerBuilder::<_, Sha256>::new()
274 .prefix("mycompany".to_owned())
275 .rng(OsRng)
276 .short_token_prefix(None)
277 .short_token_length(4)
278 .long_token_length(500)
279 .finalize();
280 assert!(controller_result.is_ok())
281 }
282
283 #[test]
284 fn ok_with_default_short_token_prefix() {
285 let controller_result = ControllerBuilder::<_, Sha256>::new()
287 .prefix("mycompany".to_owned())
288 .rng(OsRng)
289 .short_token_length(4)
290 .long_token_length(500)
291 .finalize();
292 assert!(controller_result.is_ok())
293 }
294
295 #[test]
296 fn ok_with_default_lengths() {
297 let controller_result = ControllerBuilder::<_, Sha256>::new()
298 .prefix("mycompany".to_owned())
299 .rng(OsRng)
300 .short_token_prefix(None)
301 .default_lengths()
302 .finalize();
303 assert!(controller_result.is_ok())
304 }
305
306 #[test]
307 fn ok_with_rng_osrng() {
308 let controller_result = ControllerBuilder::<_, Sha256>::new()
309 .prefix("mycompany".to_owned())
310 .rng_osrng()
311 .short_token_prefix(None)
312 .default_lengths()
313 .finalize();
314 assert!(controller_result.is_ok())
315 }
316
317 #[test]
318 fn ok_with_rng_threadrng() {
319 let controller_result = ControllerBuilder::<_, Sha256>::new()
320 .prefix("mycompany".to_owned())
321 .rng_threadrng()
322 .short_token_prefix(None)
323 .default_lengths()
324 .finalize();
325 assert!(controller_result.is_ok())
326 }
327
328 #[test]
329 fn ok_with_rng_stdrng() {
330 let controller_result = ControllerBuilder::<_, Sha256>::new()
331 .prefix("mycompany".to_owned())
332 .rng_stdrng()
333 .short_token_prefix(None)
334 .default_lengths()
335 .finalize();
336 assert!(controller_result.is_ok())
337 }
338}
339
340#[cfg(feature = "sha2")]
341#[cfg(test)]
342mod controller_builder_sha2_tests {
343 use digest::{Digest, FixedOutputReset};
344
345 use crate::{
346 rand::rngs::OsRng, rand::rngs::StdRng, rand::rngs::ThreadRng, rand::RngCore,
347 rand::SeedableRng, BuilderError, PakControllerOsSha224, PakControllerOsSha256,
348 PakControllerOsSha384, PakControllerOsSha512, PakControllerOsSha512_224,
349 PakControllerOsSha512_256, PakControllerStdSha256, PakControllerThreadSha256,
350 };
351
352 use super::{ControllerBuilder, PrefixedApiKeyController};
353
354 fn controller_generates_matching_hash<R, D>(controller: PrefixedApiKeyController<R, D>) -> bool
355 where
356 R: RngCore + Clone,
357 D: Digest + FixedOutputReset,
358 {
359 let (pak, hash) = controller.generate_key_and_hash();
360 controller.check_hash(&pak, &hash)
361 }
362
363 #[test]
364 fn ok_with_digest_sha224() {
365 let controller_result: Result<PakControllerOsSha224, BuilderError> =
366 ControllerBuilder::new()
367 .prefix("mycompany".to_owned())
368 .rng(OsRng)
369 .digest_sha224()
370 .short_token_prefix(None)
371 .default_lengths()
372 .finalize();
373 assert!(controller_result.is_ok());
374 assert!(controller_generates_matching_hash(
375 controller_result.unwrap()
376 ));
377 }
378
379 #[test]
380 fn ok_with_digest_sha256() {
381 let controller_result: Result<PakControllerOsSha256, BuilderError> =
382 ControllerBuilder::new()
383 .prefix("mycompany".to_owned())
384 .rng(OsRng)
385 .digest_sha256()
386 .short_token_prefix(None)
387 .default_lengths()
388 .finalize();
389 assert!(controller_result.is_ok());
390 assert!(controller_generates_matching_hash(
391 controller_result.unwrap()
392 ));
393 }
394
395 #[test]
396 fn ok_with_digest_sha384() {
397 let controller_result: Result<PakControllerOsSha384, BuilderError> =
398 ControllerBuilder::new()
399 .prefix("mycompany".to_owned())
400 .rng(OsRng)
401 .digest_sha384()
402 .short_token_prefix(None)
403 .default_lengths()
404 .finalize();
405 assert!(controller_result.is_ok());
406 assert!(controller_generates_matching_hash(
407 controller_result.unwrap()
408 ));
409 }
410
411 #[test]
412 fn ok_with_digest_sha512() {
413 let controller_result: Result<PakControllerOsSha512, BuilderError> =
414 ControllerBuilder::new()
415 .prefix("mycompany".to_owned())
416 .rng(OsRng)
417 .digest_sha512()
418 .short_token_prefix(None)
419 .default_lengths()
420 .finalize();
421 assert!(controller_result.is_ok());
422 assert!(controller_generates_matching_hash(
423 controller_result.unwrap()
424 ));
425 }
426
427 #[test]
428 fn ok_with_digest_sha512_224() {
429 let controller_result: Result<PakControllerOsSha512_224, BuilderError> =
430 ControllerBuilder::new()
431 .prefix("mycompany".to_owned())
432 .rng(OsRng)
433 .digest_sha512_224()
434 .short_token_prefix(None)
435 .default_lengths()
436 .finalize();
437 assert!(controller_result.is_ok());
438 assert!(controller_generates_matching_hash(
439 controller_result.unwrap()
440 ));
441 }
442
443 #[test]
444 fn ok_with_digest_sha512_256() {
445 let controller_result: Result<PakControllerOsSha512_256, BuilderError> =
446 ControllerBuilder::new()
447 .prefix("mycompany".to_owned())
448 .rng(OsRng)
449 .digest_sha512_256()
450 .short_token_prefix(None)
451 .default_lengths()
452 .finalize();
453 assert!(controller_result.is_ok());
454 assert!(controller_generates_matching_hash(
455 controller_result.unwrap()
456 ));
457 }
458
459 #[test]
460 fn ok_with_rng_std() {
461 let controller_result: Result<PakControllerStdSha256, BuilderError> =
462 ControllerBuilder::new()
463 .prefix("mycompany".to_owned())
464 .rng_stdrng()
465 .digest_sha256()
466 .short_token_prefix(None)
467 .default_lengths()
468 .finalize();
469 assert!(controller_result.is_ok());
470 assert!(controller_generates_matching_hash(
471 controller_result.unwrap()
472 ));
473 }
474
475 #[test]
476 fn ok_with_rng_thread() {
477 let controller_result: Result<PakControllerThreadSha256, BuilderError> =
478 ControllerBuilder::new()
479 .prefix("mycompany".to_owned())
480 .rng_threadrng()
481 .digest_sha256()
482 .short_token_prefix(None)
483 .default_lengths()
484 .finalize();
485 assert!(controller_result.is_ok());
486 assert!(controller_generates_matching_hash(
487 controller_result.unwrap()
488 ));
489 }
490
491 #[test]
492 fn ok_with_seam_deafults() {
493 let controller_result: Result<PakControllerOsSha256, BuilderError> =
494 ControllerBuilder::new()
495 .prefix("mycompany".to_owned())
496 .seam_defaults()
497 .finalize();
498 assert!(controller_result.is_ok());
499 assert!(controller_generates_matching_hash(
500 controller_result.unwrap()
501 ));
502 }
503}