better-auth-api 1.0.0-alpha.5

Plugin implementations for better-auth
use better_auth_core::{AuthRequest, AuthResult};
use serde_json::Value;

use super::SignInRequest;
use crate::plugins::json_body::{invalid_type, type_name, validation_error};

fn parse_sign_in(req: &AuthRequest) -> AuthResult<(SignInRequest, Value)> {
    let body = req.input_body()?;
    let object = body
        .as_ref()
        .and_then(Value::as_object)
        .ok_or_else(|| validation_error(&invalid_type("body", "object", body.as_ref())))?;
    let mut issues = Vec::new();
    let mut output = serde_json::Map::new();
    for (field, expected, required) in [
        ("email", "string", true),
        ("password", "string", true),
        ("callbackURL", "string", false),
        ("rememberMe", "boolean", false),
    ] {
        let value = object.get(field);
        if (required || value.is_some()) && type_name(value) != expected {
            issues.push(invalid_type(&format!("body.{field}"), expected, value));
        } else if let Some(value) = value {
            let _ = output.insert(field.to_owned(), value.clone());
        }
    }
    if !issues.is_empty() {
        return Err(validation_error(&issues.join("; ")));
    }
    let _ = output
        .entry("rememberMe".to_owned())
        .or_insert(Value::Bool(true));
    let projection = Value::Object(output);
    Ok((serde_json::from_value(projection.clone())?, projection))
}

pub(super) fn sign_in_body(
    req: &AuthRequest,
) -> AuthResult<better_auth_core::endpoint_input::ValidatedBody> {
    let (typed, projection) = parse_sign_in(req)?;
    Ok(better_auth_core::endpoint_input::ValidatedBody::new(
        Some(projection),
        typed,
    ))
}

pub(super) fn sign_in(req: &AuthRequest) -> AuthResult<SignInRequest> {
    match req.validated_body::<SignInRequest>() {
        Some(body) => Ok(body.clone()),
        None => parse_sign_in(req).map(|(body, _)| body),
    }
}

pub(super) async fn form_csrf(
    req: &AuthRequest,
    ctx: &better_auth_core::AuthContext<impl better_auth_core::AuthSchema>,
) -> AuthResult<()> {
    if req.has_http_body_context() || req.url().is_some() {
        let policy = ctx
            .extensions
            .get::<better_auth_core::middleware::CsrfConfig>()
            .cloned()
            .unwrap_or_default();
        better_auth_core::middleware::CsrfMiddleware::from_context(policy, ctx)
            .validate_form_request(req)
            .await?;
    }
    Ok(())
}

fn parse_sign_up(req: &AuthRequest) -> AuthResult<(super::SignUpRequest, Value)> {
    let body = req.input_body()?;
    let object = body.as_ref().and_then(Value::as_object).ok_or_else(|| {
        validation_error(&format!(
            "{}; {}",
            invalid_type("body", "object", body.as_ref()),
            invalid_type("body", "record", body.as_ref())
        ))
    })?;
    let mut errors = Vec::new();
    for (field, expected, required) in [
        ("name", "string", true),
        ("email", "string", true),
        ("password", "string", true),
        ("image", "string", false),
        ("callbackURL", "string", false),
        ("rememberMe", "boolean", false),
    ] {
        let value = object.get(field);
        if (required || value.is_some()) && type_name(value) != expected {
            errors.push(invalid_type(&format!("body.{field}"), expected, value));
        } else if let Some(Value::String(value)) = value {
            if field == "email" && !crate::plugins::json_body::valid_email(value)? {
                errors.push("[body.email] Invalid email address".to_owned());
            }
            if field == "password" && value.is_empty() {
                errors.push(
                    "[body.password] Too small: expected string to have >=1 characters".to_owned(),
                );
            }
        }
    }
    if !errors.is_empty() {
        return Err(validation_error(&errors.join("; ")));
    }
    let projection = Value::Object(object.clone());
    Ok((serde_json::from_value(projection.clone())?, projection))
}

pub(super) fn sign_up_body(
    req: &AuthRequest,
) -> AuthResult<better_auth_core::endpoint_input::ValidatedBody> {
    let (typed, projection) = parse_sign_up(req)?;
    Ok(better_auth_core::endpoint_input::ValidatedBody::new(
        Some(projection),
        typed,
    ))
}

pub(super) fn sign_up(req: &AuthRequest) -> AuthResult<super::SignUpRequest> {
    match req.validated_body::<super::SignUpRequest>() {
        Some(body) => Ok(body.clone()),
        None => parse_sign_up(req).map(|(body, _)| body),
    }
}