rustauth-plugins 0.2.0

Official RustAuth plugin modules.
Documentation
use std::sync::{Arc, Mutex};

use http::StatusCode;
use rustauth_core::db::{DbValue, MemoryAdapter};

use super::{nonce, options, record_by_string, response_json, router, WALLET};

#[tokio::test]
async fn nonce_endpoint_returns_nonce_and_stores_chain_scoped_verification(
) -> Result<(), Box<dyn std::error::Error>> {
    let adapter = Arc::new(MemoryAdapter::new());
    let router = router(adapter.clone(), options())?;

    let response = nonce(&router, WALLET, Some(137)).await?;

    assert_eq!(response.status(), StatusCode::OK);
    assert_eq!(response_json(&response)?["nonce"], "A1b2C3d4E5f6G7h8J");
    let stored = record_by_string(
        &adapter,
        "verification",
        "identifier",
        "siwe:0x000000000000000000000000000000000000dEaD:137",
    )
    .await?
    .ok_or("verification missing")?;
    assert_eq!(
        stored.get("value"),
        Some(&DbValue::String("A1b2C3d4E5f6G7h8J".to_owned()))
    );
    Ok(())
}

#[tokio::test]
async fn nonce_endpoint_defaults_chain_id_to_one() -> Result<(), Box<dyn std::error::Error>> {
    let adapter = Arc::new(MemoryAdapter::new());
    let router = router(adapter.clone(), options())?;

    let response = nonce(&router, WALLET, None).await?;

    assert_eq!(response.status(), StatusCode::OK);
    assert!(record_by_string(
        &adapter,
        "verification",
        "identifier",
        "siwe:0x000000000000000000000000000000000000dEaD:1",
    )
    .await?
    .is_some());
    Ok(())
}

#[tokio::test]
async fn nonce_endpoint_rejects_invalid_wallet_address() -> Result<(), Box<dyn std::error::Error>> {
    let adapter = Arc::new(MemoryAdapter::new());
    let router = router(adapter, options())?;

    let response = nonce(&router, "invalid", None).await?;

    assert_eq!(response.status(), StatusCode::BAD_REQUEST);
    Ok(())
}

#[tokio::test]
async fn verify_message_receives_checksum_chain_signature_and_cacao(
) -> Result<(), Box<dyn std::error::Error>> {
    let adapter = Arc::new(MemoryAdapter::new());
    let seen = Arc::new(Mutex::new(None));
    let seen_for_callback = Arc::clone(&seen);
    let opts = rustauth_plugins::siwe::SiweOptions::new(
        "example.com",
        || async { Ok("nonce-for-cacao".to_owned()) },
        move |args: rustauth_plugins::siwe::SiweVerifyMessageArgs| {
            let seen_for_callback = Arc::clone(&seen_for_callback);
            async move {
                seen_for_callback
                    .lock()
                    .map_err(|_| {
                        rustauth_core::error::RustAuthError::Api("lock poisoned".to_owned())
                    })?
                    .replace(args);
                Ok(true)
            }
        },
    );
    let router = router(adapter, opts)?;

    nonce(&router, &WALLET.to_lowercase(), Some(137)).await?;
    super::verify(
        &router,
        &WALLET.to_lowercase(),
        Some(137),
        "message",
        "signature",
        None,
    )
    .await?;

    let args = seen
        .lock()
        .map_err(|_| "lock poisoned")?
        .clone()
        .ok_or("verify args missing")?;
    assert_eq!(args.address, WALLET);
    assert_eq!(args.chain_id, 137);
    assert_eq!(args.signature, "signature");
    assert_eq!(args.cacao.p.domain, "example.com");
    assert_eq!(args.cacao.p.nonce, "nonce-for-cacao");
    Ok(())
}