rustauth-plugins 0.3.0

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

use http::StatusCode;
use rustauth_core::api::ApiRequest;
use rustauth_core::context::AuthContext;
use rustauth_core::error::RustAuthError;
use rustauth_core::plugin::{PluginAfterHookAction, PluginResponse};
use serde::Deserialize;

use super::helpers::{resolve_otp, send_email, validated_email};
use super::otp;
use super::types::{EmailOtpOptions, EmailOtpType};

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct EmailBody {
    email: String,
}

pub async fn send_verification_after_sign_up(
    context: &AuthContext,
    request: &ApiRequest,
    response: PluginResponse,
    options: Arc<EmailOtpOptions>,
) -> Result<PluginAfterHookAction, RustAuthError> {
    if !response.status().is_success() {
        return Ok(PluginAfterHookAction::Continue(response));
    }
    context.require_adapter()?;
    send_email_verification_otp(context, request, &options).await?;
    Ok(PluginAfterHookAction::Continue(response))
}

pub async fn override_send_verification_email(
    context: &AuthContext,
    request: &ApiRequest,
    response: PluginResponse,
    options: Arc<EmailOtpOptions>,
) -> Result<PluginAfterHookAction, RustAuthError> {
    context.require_adapter()?;
    let response = if send_email_verification_otp(context, request, &options)
        .await?
        .is_some()
    {
        success_response()
    } else {
        response
    };
    Ok(PluginAfterHookAction::Continue(response))
}

async fn send_email_verification_otp(
    context: &AuthContext,
    request: &ApiRequest,
    options: &EmailOtpOptions,
) -> Result<Option<()>, RustAuthError> {
    let Some(body) = parse_email_body(request)? else {
        return Ok(None);
    };
    let email = match validated_email(&body.email)? {
        Ok(email) => email,
        Err(_) => return Ok(None),
    };
    let identifier = otp::identifier(EmailOtpType::EmailVerification, &email);
    let generated = resolve_otp(
        context,
        options,
        &context.secret_config,
        &email,
        EmailOtpType::EmailVerification,
        &identifier,
    )
    .await?;
    if context.users()?.find_user_by_email(&email).await?.is_none() {
        context
            .verifications()?
            .delete_verification(&identifier)
            .await?;
        return Ok(Some(()));
    }
    send_email(
        context,
        options,
        &email,
        generated,
        EmailOtpType::EmailVerification,
        Some(request),
    )?;
    Ok(Some(()))
}

fn parse_email_body(request: &ApiRequest) -> Result<Option<EmailBody>, RustAuthError> {
    if request.body().is_empty() {
        return Ok(None);
    }
    serde_json::from_slice(request.body())
        .map(Some)
        .map_err(|error| RustAuthError::Api(format!("invalid request body: {error}")))
}

fn success_response() -> PluginResponse {
    http::Response::builder()
        .status(StatusCode::OK)
        .header(http::header::CONTENT_TYPE, "application/json")
        .body(br#"{"status":true}"#.to_vec())
        .unwrap_or_else(|_| http::Response::new(Vec::new()))
}