Skip to main content

openauth_plugins/magic_link/
token.rs

1use 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}