Skip to main content

openauth_plugins/magic_link/
options.rs

1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4
5use http::Request;
6use openauth_core::context::AuthContext;
7use openauth_core::error::OpenAuthError;
8use openauth_core::options::RateLimitRule;
9use serde_json::Value;
10
11use super::token::TokenStorage;
12
13pub type MagicLinkFuture<'a, T> =
14    Pin<Box<dyn Future<Output = Result<T, OpenAuthError>> + Send + 'a>>;
15
16#[derive(Debug, Clone, PartialEq)]
17pub struct MagicLinkEmail {
18    pub email: String,
19    pub url: String,
20    pub token: String,
21    pub metadata: Option<Value>,
22}
23
24#[derive(Clone, Copy)]
25pub struct MagicLinkSendContext<'a> {
26    pub context: &'a AuthContext,
27    pub request: &'a Request<Vec<u8>>,
28}
29
30pub type SendMagicLink = Arc<dyn Fn(MagicLinkEmail) -> MagicLinkFuture<'static, ()> + Send + Sync>;
31pub type SendMagicLinkWithContext = Arc<
32    dyn for<'a> Fn(MagicLinkEmail, MagicLinkSendContext<'a>) -> MagicLinkFuture<'a, ()>
33        + Send
34        + Sync,
35>;
36pub type GenerateToken = Arc<dyn for<'a> Fn(&'a str) -> MagicLinkFuture<'a, String> + Send + Sync>;
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct MagicLinkRateLimit {
40    pub window: u64,
41    pub max: u64,
42}
43
44impl Default for MagicLinkRateLimit {
45    fn default() -> Self {
46        Self { window: 60, max: 5 }
47    }
48}
49
50#[derive(Clone)]
51pub struct MagicLinkOptions {
52    pub(crate) expires_in: u64,
53    pub(crate) allowed_attempts: AllowedAttempts,
54    pub(crate) send_magic_link: SendMagicLinkWithContext,
55    pub(crate) disable_sign_up: bool,
56    pub(crate) rate_limit: MagicLinkRateLimit,
57    pub(crate) generate_token: Option<GenerateToken>,
58    pub(crate) store_token: TokenStorage,
59}
60
61impl MagicLinkOptions {
62    pub fn new<F>(send_magic_link: F) -> Self
63    where
64        F: Fn(MagicLinkEmail) -> MagicLinkFuture<'static, ()> + Send + Sync + 'static,
65    {
66        let send_magic_link: SendMagicLink = Arc::new(send_magic_link);
67        Self::new_with_context(move |email, _ctx| {
68            let send_magic_link = Arc::clone(&send_magic_link);
69            Box::pin(async move { send_magic_link(email).await })
70        })
71    }
72
73    pub fn new_with_context<F>(send_magic_link: F) -> Self
74    where
75        F: for<'a> Fn(MagicLinkEmail, MagicLinkSendContext<'a>) -> MagicLinkFuture<'a, ()>
76            + Send
77            + Sync
78            + 'static,
79    {
80        Self {
81            expires_in: 60 * 5,
82            allowed_attempts: AllowedAttempts::Limited(1),
83            send_magic_link: Arc::new(send_magic_link),
84            disable_sign_up: false,
85            rate_limit: MagicLinkRateLimit::default(),
86            generate_token: None,
87            store_token: TokenStorage::Plain,
88        }
89    }
90
91    #[must_use]
92    pub fn expires_in(mut self, seconds: u64) -> Self {
93        self.expires_in = seconds;
94        self
95    }
96
97    #[must_use]
98    pub fn allowed_attempts(mut self, attempts: u64) -> Self {
99        self.allowed_attempts = AllowedAttempts::Limited(attempts);
100        self
101    }
102
103    #[must_use]
104    pub fn unlimited_attempts(mut self) -> Self {
105        self.allowed_attempts = AllowedAttempts::Unlimited;
106        self
107    }
108
109    #[must_use]
110    pub fn disable_sign_up(mut self, disabled: bool) -> Self {
111        self.disable_sign_up = disabled;
112        self
113    }
114
115    #[must_use]
116    pub fn rate_limit(mut self, rate_limit: MagicLinkRateLimit) -> Self {
117        self.rate_limit = rate_limit;
118        self
119    }
120
121    #[must_use]
122    pub fn generate_token<F>(mut self, generate_token: F) -> Self
123    where
124        F: for<'a> Fn(&'a str) -> MagicLinkFuture<'a, String> + Send + Sync + 'static,
125    {
126        self.generate_token = Some(Arc::new(generate_token));
127        self
128    }
129
130    #[must_use]
131    pub fn store_token(mut self, store_token: TokenStorage) -> Self {
132        self.store_token = store_token;
133        self
134    }
135
136    pub(crate) fn rate_limit_rule(&self) -> RateLimitRule {
137        RateLimitRule {
138            window: self.rate_limit.window,
139            max: self.rate_limit.max,
140        }
141    }
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145pub(crate) enum AllowedAttempts {
146    Limited(u64),
147    Unlimited,
148}
149
150impl AllowedAttempts {
151    pub(crate) fn exceeded(self, attempt: u64) -> bool {
152        match self {
153            Self::Limited(limit) => attempt >= limit,
154            Self::Unlimited => false,
155        }
156    }
157}