Skip to main content

better_auth_api/plugins/password_management/
callbacks.rs

1use super::{PasswordManagementConfig, PasswordManagementPlugin};
2use crate::plugins::endpoint_context::{EndpointContext, WithCallbacks};
3use better_auth_core::{
4    AuthContext, AuthError, AuthPlugin, AuthResult, AuthSchema, background::BackgroundFuture,
5    wire::UserView,
6};
7use std::sync::Arc;
8
9/// Password reset notification after the reset token has been persisted.
10#[derive(Clone)]
11pub struct PasswordResetEmail {
12    /// User snapshot supplied by the reset endpoint.
13    pub user: UserView,
14    /// Reset link, including the callback URL.
15    pub url: String,
16    /// Raw reset token.
17    pub token: String,
18}
19
20type Sender<S> = dyn Fn(&PasswordResetEmail, &EndpointContext<'_, S>) -> AuthResult<Option<BackgroundFuture>>
21    + Send
22    + Sync;
23
24/// Password reset delivery with the complete endpoint context.
25pub struct PasswordManagementCallbacks<S: AuthSchema> {
26    sender: Arc<Sender<S>>,
27}
28impl<S: AuthSchema> PasswordManagementCallbacks<S> {
29    /// Start delivery before scheduling. Return `None` for synchronous completion.
30    /// Use `EndpointContext::to_owned` when asynchronous delivery needs its context.
31    pub fn send_reset_password(
32        callback: impl Fn(
33            &PasswordResetEmail,
34            &EndpointContext<'_, S>,
35        ) -> AuthResult<Option<BackgroundFuture>>
36        + Send
37        + Sync
38        + 'static,
39    ) -> Self {
40        Self {
41            sender: Arc::new(callback),
42        }
43    }
44}
45impl PasswordManagementPlugin {
46    /// Attach typed delivery after configuring password management options.
47    pub fn callbacks<S: AuthSchema>(
48        self,
49        callbacks: PasswordManagementCallbacks<S>,
50    ) -> impl AuthPlugin<S> {
51        WithCallbacks {
52            plugin: self,
53            callbacks: Arc::new(callbacks),
54        }
55    }
56}
57
58pub(super) fn require_sender<S: AuthSchema>(
59    config: &PasswordManagementConfig,
60    ctx: &AuthContext<S>,
61) -> AuthResult<()> {
62    if config.send_reset_password.is_none()
63        && ctx
64            .extensions
65            .get::<Arc<PasswordManagementCallbacks<S>>>()
66            .is_none()
67    {
68        return Err(AuthError::bad_request("Reset password isn't enabled"));
69    }
70    Ok(())
71}
72
73pub(super) fn delivery<S: AuthSchema>(
74    config: &PasswordManagementConfig,
75    email: PasswordResetEmail,
76    endpoint: &EndpointContext<'_, S>,
77) -> AuthResult<Option<BackgroundFuture>> {
78    if let Some(callbacks) = endpoint
79        .auth
80        .extensions
81        .get::<Arc<PasswordManagementCallbacks<S>>>()
82    {
83        return (callbacks.sender)(&email, endpoint);
84    }
85    let sender = config
86        .send_reset_password
87        .clone()
88        .ok_or_else(|| AuthError::bad_request("Reset password isn't enabled"))?;
89    let user = serde_json::to_value(email.user)?;
90    let request = endpoint.request.cloned();
91    Ok(Some(Box::pin(async move {
92        sender
93            .send_with_request(&user, &email.url, &email.token, request.as_ref())
94            .await
95    })))
96}