rustauth-plugins 0.2.0

Official RustAuth plugin modules.
Documentation
use std::sync::Arc;

use rustauth_core::db::MemoryAdapter;
use rustauth_plugins::email_otp::EmailOtpOptions;

use super::common::*;

#[tokio::test]
async fn send_verification_on_sign_up_hook_sends_otp() {
    let adapter = Arc::new(MemoryAdapter::new());
    let sender = CaptureSender::default();
    let router = router(
        adapter.clone(),
        sender.clone(),
        EmailOtpOptions {
            send_verification_on_sign_up: true,
            ..EmailOtpOptions::default()
        },
    )
    .unwrap();

    let response = router
        .handle_async(
            json_request(
                "/sign-up/email",
                r#"{"name":"Ada","email":"ada@example.com","password":"valid-password"}"#,
                None,
            )
            .unwrap(),
        )
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::OK);
    assert_eq!(sender.count_after_dispatch(1).await, 1);
    assert!(
        verification_value(&adapter, "email-verification-otp-ada@example.com")
            .await
            .is_some()
    );
}

#[tokio::test]
async fn send_verification_otp_returns_before_slow_sender_finishes(
) -> Result<(), Box<dyn std::error::Error>> {
    use std::sync::atomic::{AtomicUsize, Ordering};
    use std::time::{Duration, Instant};

    struct SlowSender {
        started: Arc<AtomicUsize>,
    }

    impl rustauth_plugins::email_otp::SendEmailOtp for SlowSender {
        fn send_email_otp(
            &self,
            _payload: rustauth_plugins::email_otp::EmailOtpPayload,
            _request: Option<&http::Request<Vec<u8>>>,
        ) -> rustauth_core::outbound::OutboundSendFuture {
            let started = Arc::clone(&self.started);
            Box::pin(async move {
                started.fetch_add(1, Ordering::SeqCst);
                tokio::time::sleep(Duration::from_secs(2)).await;
                Ok(())
            }) as rustauth_core::outbound::OutboundSendFuture
        }
    }

    let started = Arc::new(AtomicUsize::new(0));
    let adapter = Arc::new(MemoryAdapter::new());
    create_user(&adapter, "ada@example.com", false).await;
    let options = EmailOtpOptions {
        sender: Some(Arc::new(SlowSender {
            started: Arc::clone(&started),
        })),
        ..EmailOtpOptions::default()
    };
    let router = router_with_async_outbound(adapter, CaptureSender::default(), options)?;

    let request_started = Instant::now();
    let response = router
        .handle_async(json_request(
            "/email-otp/send-verification-otp",
            r#"{"email":"ada@example.com","type":"email-verification"}"#,
            None,
        )?)
        .await?;
    let elapsed = request_started.elapsed();

    assert_eq!(response.status(), StatusCode::OK);
    assert!(
        elapsed < Duration::from_millis(100),
        "email-otp send took {elapsed:?}"
    );
    assert_eq!(started.load(Ordering::SeqCst), 0);

    for _ in 0..100 {
        if started.load(Ordering::SeqCst) == 1 {
            break;
        }
        tokio::time::sleep(Duration::from_millis(5)).await;
    }
    assert_eq!(started.load(Ordering::SeqCst), 1);
    Ok(())
}

#[tokio::test]
async fn override_default_send_verification_email_sends_otp() {
    let adapter = Arc::new(MemoryAdapter::new());
    create_user(&adapter, "ada@example.com", false).await;
    let sender = CaptureSender::default();
    let router = router(
        adapter.clone(),
        sender.clone(),
        EmailOtpOptions {
            override_default_email_verification: true,
            ..EmailOtpOptions::default()
        },
    )
    .unwrap();

    let response = router
        .handle_async(
            json_request(
                "/send-verification-email",
                r#"{"email":"ada@example.com"}"#,
                None,
            )
            .unwrap(),
        )
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::OK);
    assert_eq!(sender.count_after_dispatch(1).await, 1);
    assert!(
        verification_value(&adapter, "email-verification-otp-ada@example.com")
            .await
            .is_some()
    );
}