use super::{PasswordManagementConfig, PasswordManagementPlugin};
use crate::plugins::endpoint_context::{EndpointContext, WithCallbacks};
use better_auth_core::{
AuthContext, AuthError, AuthPlugin, AuthResult, AuthSchema, background::BackgroundFuture,
wire::UserView,
};
use std::sync::Arc;
#[derive(Clone)]
pub struct PasswordResetEmail {
pub user: UserView,
pub url: String,
pub token: String,
}
type Sender<S> = dyn Fn(&PasswordResetEmail, &EndpointContext<'_, S>) -> AuthResult<Option<BackgroundFuture>>
+ Send
+ Sync;
pub struct PasswordManagementCallbacks<S: AuthSchema> {
sender: Arc<Sender<S>>,
}
impl<S: AuthSchema> PasswordManagementCallbacks<S> {
pub fn send_reset_password(
callback: impl Fn(
&PasswordResetEmail,
&EndpointContext<'_, S>,
) -> AuthResult<Option<BackgroundFuture>>
+ Send
+ Sync
+ 'static,
) -> Self {
Self {
sender: Arc::new(callback),
}
}
}
impl PasswordManagementPlugin {
pub fn callbacks<S: AuthSchema>(
self,
callbacks: PasswordManagementCallbacks<S>,
) -> impl AuthPlugin<S> {
WithCallbacks {
plugin: self,
callbacks: Arc::new(callbacks),
}
}
}
pub(super) fn require_sender<S: AuthSchema>(
config: &PasswordManagementConfig,
ctx: &AuthContext<S>,
) -> AuthResult<()> {
if config.send_reset_password.is_none()
&& ctx
.extensions
.get::<Arc<PasswordManagementCallbacks<S>>>()
.is_none()
{
return Err(AuthError::from_code(
"BAD_REQUEST",
"RESET_PASSWORD_DISABLED",
"Reset password isn't enabled",
));
}
Ok(())
}
pub(super) fn delivery<S: AuthSchema>(
config: &PasswordManagementConfig,
email: PasswordResetEmail,
endpoint: &EndpointContext<'_, S>,
) -> AuthResult<Option<BackgroundFuture>> {
if let Some(callbacks) = endpoint
.auth
.extensions
.get::<Arc<PasswordManagementCallbacks<S>>>()
{
return (callbacks.sender)(&email, endpoint);
}
let sender = config.send_reset_password.clone().ok_or_else(|| {
AuthError::from_code(
"BAD_REQUEST",
"RESET_PASSWORD_DISABLED",
"Reset password isn't enabled",
)
})?;
let user = serde_json::to_value(email.user)?;
let request = endpoint.request.cloned();
Ok(Some(BackgroundFuture::new(async move {
sender
.send_with_request(&user, &email.url, &email.token, request.as_ref())
.await
})))
}