runique 3.0.2

A Django-inspired web framework for Rust with ORM, templates, and comprehensive security middleware
Documentation
//! Hidden field `HiddenField` for non-displayed data (tokens, internal IDs).
use crate::forms::base::{CommonFieldConfig, FieldConfig, FormField};
use crate::utils::aliases::ATera;
use crate::utils::trad::{t, tf};
use async_trait::async_trait;
use serde::Serialize;
use subtle::ConstantTimeEq;

/// Hidden input field. Used internally for CSRF tokens; also available for opaque data
/// that should be submitted with the form but not displayed to the user.
#[derive(Clone, Serialize, Debug)]
pub struct HiddenField {
    pub base: FieldConfig,
    /// Expected session token (for CSRF validation)
    pub expected_value: Option<String>,
}

impl HiddenField {
    /// Specific constructor for a CSRF hidden field
    pub fn new_csrf() -> Self {
        Self {
            base: FieldConfig::new("csrf_token", "hidden", "csrf.html"),
            expected_value: None,
        }
    }

    /// Generic constructor for a hidden field
    pub fn new(name: &str) -> Self {
        Self {
            base: FieldConfig::new(name, "hidden", "base_hidden.html"),
            expected_value: None,
        }
    }

    /// Sets the expected value for validation (session token)
    pub fn set_expected_value(&mut self, expected: &str) {
        self.expected_value = Some(expected.to_string());
    }

    /// Overrides the auto-generated label.
    pub fn label(mut self, label: &str) -> Self {
        self.base.label = label.to_string();
        self
    }
}

impl CommonFieldConfig for HiddenField {
    fn get_field_config(&self) -> &FieldConfig {
        &self.base
    }

    fn get_field_config_mut(&mut self) -> &mut FieldConfig {
        &mut self.base
    }
}

/// HoneypotField - invisible anti-bot trap field
#[derive(Clone, Serialize, Debug)]
pub struct HoneypotField {
    pub base: FieldConfig,
}

impl HoneypotField {
    /// Creates a honeypot field with the given name. Always validates as
    /// `true` — the anti-bot middleware, not the field itself, checks
    /// whether it was filled in and rejects the submission.
    pub fn new(name: &str) -> Self {
        Self {
            base: FieldConfig::new(name, "text", "base_honeypot.html"),
        }
    }
}

impl CommonFieldConfig for HoneypotField {
    fn get_field_config(&self) -> &FieldConfig {
        &self.base
    }

    fn get_field_config_mut(&mut self) -> &mut FieldConfig {
        &mut self.base
    }
}

#[async_trait]
impl FormField for HoneypotField {
    async fn validate(&mut self) -> bool {
        true
    }

    fn render(&self, tera: &ATera) -> Result<String, String> {
        let context = self.base_context();
        tera.render(&self.base.template_name, &context)
            .map_err(|e| {
                tf(
                    "forms.finalize_error",
                    &[&self.base.template_name, &e.to_string()],
                )
                .to_string()
            })
    }
}

#[async_trait]
impl FormField for HiddenField {
    async fn validate(&mut self) -> bool {
        // For a CSRF field, check that the value matches the expected one
        if self.base.name == "csrf_token"
            && let Some(expected) = &self.expected_value
        {
            if self.base.value.trim().is_empty() {
                self.set_error(t("csrf.missing").to_string());
                return false;
            }

            // ct_eq: constant-time comparison — prevents an attacker
            // guessing the token byte by byte via response time
            if !bool::from(self.base.value.as_bytes().ct_eq(expected.as_bytes())) {
                self.set_error(t("csrf.invalid").to_string());
                return false;
            }
        }

        self.clear_error();
        true
    }

    fn render(&self, tera: &ATera) -> Result<String, String> {
        let mut context = self.base_context();
        context.insert("input_type", &self.base.type_field);

        tera.render(&self.base.template_name, &context)
            .map_err(|e| {
                tf(
                    "forms.finalize_error",
                    &[&self.base.template_name, &e.to_string()],
                )
                .to_string()
            })
    }
}