openauth_plugins/magic_link/
token.rs1use std::sync::Arc;
2
3use base64::engine::general_purpose::URL_SAFE_NO_PAD;
4use base64::Engine;
5use openauth_core::error::OpenAuthError;
6use rand::rngs::OsRng;
7use rand::RngCore;
8use sha2::{Digest, Sha256};
9
10use super::options::MagicLinkFuture;
11
12pub type CustomTokenHasher =
13 Arc<dyn for<'a> Fn(&'a str) -> MagicLinkFuture<'a, String> + Send + Sync>;
14
15#[derive(Clone)]
16pub enum TokenStorage {
17 Plain,
18 Hashed,
19 CustomHasher(CustomTokenHasher),
20}
21
22impl TokenStorage {
23 pub fn custom<F>(hash: F) -> Self
24 where
25 F: for<'a> Fn(&'a str) -> MagicLinkFuture<'a, String> + Send + Sync + 'static,
26 {
27 Self::CustomHasher(Arc::new(hash))
28 }
29
30 pub(crate) async fn identifier(&self, token: &str) -> Result<String, OpenAuthError> {
31 match self {
32 Self::Plain => Ok(token.to_owned()),
33 Self::Hashed => Ok(default_key_hasher(token)),
34 Self::CustomHasher(hash) => hash(token).await,
35 }
36 }
37}
38
39pub fn generate_magic_link_token() -> String {
40 const LETTERS: &[u8; 52] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
41 const ACCEPT_LIMIT: u8 = 52 * 4;
42 let mut output = String::with_capacity(32);
43 while output.len() < 32 {
44 let mut random = [0_u8; 32];
45 OsRng.fill_bytes(&mut random);
46 for byte in random {
47 if byte >= ACCEPT_LIMIT {
48 continue;
49 }
50 output.push(char::from(LETTERS[usize::from(byte % 52)]));
51 if output.len() == 32 {
52 break;
53 }
54 }
55 }
56 output
57}
58
59pub fn default_key_hasher(token: &str) -> String {
60 URL_SAFE_NO_PAD.encode(Sha256::digest(token.as_bytes()))
61}