openauth-plugins 0.0.3

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

use http::StatusCode;
use openauth_core::api::ApiRequest;
use openauth_core::context::AuthContext;
use openauth_core::db::DbAdapter;
use openauth_core::error::OpenAuthError;
use openauth_core::plugin::{PluginAfterHookAction, PluginResponse};
use openauth_core::user::DbUserStore;
use openauth_core::verification::DbVerificationStore;
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 fn send_verification_after_sign_up<'a>(
    context: &'a AuthContext,
    request: &'a ApiRequest,
    response: PluginResponse,
    adapter: Arc<dyn DbAdapter>,
    options: Arc<EmailOtpOptions>,
) -> openauth_core::plugin::PluginAfterHookFuture<'a> {
    Box::pin(async move {
        if !response.status().is_success() {
            return Ok(PluginAfterHookAction::Continue(response));
        }
        send_email_verification_otp(context, request, adapter.as_ref(), &options).await?;
        Ok(PluginAfterHookAction::Continue(response))
    })
}

pub fn override_send_verification_email<'a>(
    context: &'a AuthContext,
    request: &'a ApiRequest,
    response: PluginResponse,
    adapter: Arc<dyn DbAdapter>,
    options: Arc<EmailOtpOptions>,
) -> openauth_core::plugin::PluginAfterHookFuture<'a> {
    Box::pin(async move {
        let response = if send_email_verification_otp(context, request, adapter.as_ref(), &options)
            .await?
            .is_some()
        {
            success_response()
        } else {
            response
        };
        Ok(PluginAfterHookAction::Continue(response))
    })
}

async fn send_email_verification_otp(
    context: &AuthContext,
    request: &ApiRequest,
    adapter: &dyn DbAdapter,
    options: &EmailOtpOptions,
) -> Result<Option<()>, OpenAuthError> {
    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(
        adapter,
        options,
        &context.secret_config,
        &email,
        EmailOtpType::EmailVerification,
        &identifier,
    )
    .await?;
    if DbUserStore::new(adapter)
        .find_user_by_email(&email)
        .await?
        .is_none()
    {
        DbVerificationStore::new(adapter)
            .delete_verification(&identifier)
            .await?;
        return Ok(Some(()));
    }
    send_email(
        options,
        &email,
        generated,
        EmailOtpType::EmailVerification,
        Some(request),
    )?;
    Ok(Some(()))
}

fn parse_email_body(request: &ApiRequest) -> Result<Option<EmailBody>, OpenAuthError> {
    if request.body().is_empty() {
        return Ok(None);
    }
    serde_json::from_slice(request.body())
        .map(Some)
        .map_err(|error| OpenAuthError::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()))
}