arcly-http-identity 0.9.0

CIAM building-block engine for arcly-http: identity store traits, password hashing, MFA (TOTP), passwordless & account lifecycle, risk signals, and an OIDC provider — storage-agnostic, zero-lock, all I/O behind traits.
Documentation
//! Delivery of identity messages (verification links, reset links, OTP codes,
//! magic links). The app supplies the transport — the crate links no mail/SMS
//! SDK, matching the `OAuth2Provider` / `SecretSource` pattern.

use async_trait::async_trait;

use crate::error::Result;

/// A message the engine wants delivered to a user out-of-band.
#[derive(Debug, Clone)]
pub struct Notification {
    /// Where to deliver — an email address or phone number.
    pub to: String,
    /// Delivery channel.
    pub channel: Channel,
    /// What this message is for (drives template selection app-side).
    pub kind: NotificationKind,
    /// The token / code / link the user must act on.
    pub payload: String,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Channel {
    Email,
    Sms,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NotificationKind {
    EmailVerification,
    PhoneVerification,
    PasswordReset,
    MagicLink,
    LoginOtp,
}

/// Transport for out-of-band identity messages. Implement with SES / Twilio /
/// SMTP / a queue. Returning `Ok(())` means "accepted for delivery".
#[async_trait]
pub trait Notifier: Send + Sync + 'static {
    async fn send(&self, message: Notification) -> Result<()>;
}

/// A `Notifier` that logs at `info` and drops the message — for local dev and
/// tests. **Never** use in production: it would silently swallow reset links.
pub struct LogNotifier;

#[async_trait]
impl Notifier for LogNotifier {
    async fn send(&self, message: Notification) -> Result<()> {
        tracing::info!(
            to = %message.to,
            channel = ?message.channel,
            kind = ?message.kind,
            payload = %message.payload,
            "identity notification (LogNotifier — not actually delivered)"
        );
        Ok(())
    }
}