better-auth-api 1.0.0-alpha.5

Plugin implementations for better-auth
#![expect(
    clippy::panic_in_result_fn,
    reason = "Assertions verify record admission, getter order, and original error identity."
)]

use super::{native_object, native_record};
use better_auth_core::{
    AuthError, AuthResult, FieldDate, FieldFunction, FieldMap, FieldObject, FieldValue,
    field_value::{FieldObjectSource, FieldPropertyDescriptor},
};
use std::sync::{Arc, Mutex};

type Events = Arc<Mutex<Vec<String>>>;

struct Observed {
    label: &'static str,
    fields: FieldMap,
    inherited: FieldMap,
    hidden: Vec<&'static str>,
    failure: Option<&'static str>,
    sentinel: FieldValue,
    events: Events,
}

impl Observed {
    fn new(label: &'static str, fields: FieldMap, events: &Events) -> Self {
        Self {
            label,
            fields,
            inherited: FieldMap::new(),
            hidden: Vec::new(),
            failure: None,
            sentinel: FieldMap::new().into(),
            events: events.clone(),
        }
    }

    fn object(self) -> FieldObject {
        FieldObject::from_source(Arc::new(self))
    }

    fn record(&self, operation: &str) -> AuthResult<()> {
        self.events
            .lock()
            .map_err(|_| AuthError::internal("Record events lock poisoned"))?
            .push(format!("{}:{operation}", self.label));
        if self.failure == Some(operation) {
            return Err(AuthError::from_thrown(self.sentinel.clone()));
        }
        Ok(())
    }
}

impl FieldObjectSource for Observed {
    fn own_keys(&self) -> AuthResult<Vec<String>> {
        self.record("keys")?;
        Ok(self.fields.keys().cloned().collect())
    }

    fn own_property_descriptor(&self, name: &str) -> AuthResult<Option<FieldPropertyDescriptor>> {
        self.record(&format!("descriptor:{name}"))?;
        Ok(self
            .fields
            .contains_key(name)
            .then_some(FieldPropertyDescriptor {
                enumerable: !self.hidden.contains(&name),
            }))
    }

    fn has_property(&self, name: &str) -> AuthResult<bool> {
        self.record(&format!("has:{name}"))?;
        Ok(self.fields.contains_key(name) || self.inherited.contains_key(name))
    }

    fn get(&self, name: &str) -> AuthResult<Option<FieldValue>> {
        self.record(&format!("get:{name}"))?;
        Ok(self
            .fields
            .get(name)
            .or_else(|| self.inherited.get(name))
            .cloned())
    }

    fn set(&self, _: String, _: FieldValue) -> AuthResult<Option<FieldValue>> {
        Err(AuthError::type_error("The record input is read-only"))
    }

    fn delete(&self, _: &str) -> AuthResult<Option<FieldValue>> {
        Err(AuthError::type_error("The record input is read-only"))
    }

    fn snapshot_fields(&self) -> AuthResult<FieldMap> {
        Err(AuthError::internal(
            "Record validation must not snapshot the input",
        ))
    }

    fn identity(&self) -> usize {
        self as *const Self as usize
    }
}

fn events(trace: &Events) -> AuthResult<Vec<String>> {
    Ok(trace
        .lock()
        .map_err(|_| AuthError::internal("Record events lock poisoned"))?
        .clone())
}

fn constructor(properties: FieldObject) -> FieldValue {
    FieldFunction::contextual_with_properties(
        |_, _| {
            Err(AuthError::internal(
                "Record validation must not call constructors",
            ))
        },
        properties,
    )
    .into()
}

#[test]
fn skips_proto_before_descriptors_and_preserves_native_leaf_identity() -> AuthResult<()> {
    let child: FieldValue = FieldMap::from([("value".into(), 1.into())]).into();
    for forbidden in ["descriptor:__proto__", "get:__proto__", "get:hidden"] {
        let trace = Events::default();
        let mut source = Observed::new(
            "record",
            FieldMap::from([
                ("__proto__".into(), FieldValue::Null),
                ("child".into(), child.clone()),
                ("missing".into(), FieldValue::Undefined),
                ("hidden".into(), "unread".into()),
            ]),
            &trace,
        );
        source.hidden.push("hidden");
        source.failure = Some(forbidden);
        let output = native_record(&FieldValue::Object(source.object()))?
            .ok_or_else(|| AuthError::internal("Expected record output"))?;
        assert_eq!(
            output.keys().map(String::as_str).collect::<Vec<_>>(),
            ["child", "missing"]
        );
        assert!(
            output
                .get("child")
                .is_some_and(|value| value.strict_equals(&child))
        );
        assert_eq!(output.get("missing"), Some(&FieldValue::Undefined));
        assert_eq!(
            events(&trace)?,
            [
                "record:get:constructor",
                "record:keys",
                "record:descriptor:child",
                "record:get:child",
                "record:descriptor:missing",
                "record:get:missing",
                "record:descriptor:hidden",
            ]
        );
    }
    Ok(())
}

#[test]
fn constructor_checks_own_prototype_property_without_reading_its_getter() -> AuthResult<()> {
    for own in [false, true] {
        let trace = Events::default();
        let mut prototype = Observed::new("prototype", FieldMap::new(), &trace);
        prototype.failure = Some("get:isPrototypeOf");
        prototype.hidden.push("isPrototypeOf");
        let fields = if own {
            &mut prototype.fields
        } else {
            &mut prototype.inherited
        };
        let _ = fields.insert("isPrototypeOf".into(), FieldValue::Undefined);
        let constructor = constructor(
            Observed::new(
                "constructor",
                FieldMap::from([("prototype".into(), FieldValue::Object(prototype.object()))]),
                &trace,
            )
            .object(),
        );
        let mut record = Observed::new(
            "record",
            FieldMap::from([("value".into(), 1.into())]),
            &trace,
        );
        let _ = record.inherited.insert("constructor".into(), constructor);
        let output = native_record(&FieldValue::Object(record.object()))?;
        assert_eq!(
            output,
            own.then(|| FieldMap::from([("value".into(), 1.into())]))
        );
        let mut expected = vec![
            "record:get:constructor",
            "constructor:get:prototype",
            "prototype:descriptor:isPrototypeOf",
        ];
        if own {
            expected.extend(["record:keys", "record:descriptor:value", "record:get:value"]);
        }
        assert_eq!(events(&trace)?, expected);
    }
    Ok(())
}

#[test]
fn getter_descriptor_and_enumeration_errors_keep_identity_and_stop_in_order() -> AuthResult<()> {
    let expected = [
        "record:get:constructor",
        "constructor:get:prototype",
        "prototype:descriptor:isPrototypeOf",
        "record:keys",
        "record:descriptor:value",
        "record:get:value",
    ];
    for (index, (label, operation)) in [
        ("record", "get:constructor"),
        ("constructor", "get:prototype"),
        ("prototype", "descriptor:isPrototypeOf"),
        ("record", "keys"),
        ("record", "descriptor:value"),
        ("record", "get:value"),
    ]
    .into_iter()
    .enumerate()
    {
        let trace = Events::default();
        let sentinel: FieldValue = FieldMap::from([("failure".into(), operation.into())]).into();
        let observed = |name, fields| {
            let mut source = Observed::new(name, fields, &trace);
            source.failure = (name == label).then_some(operation);
            source.sentinel = sentinel.clone();
            source
        };
        let prototype = observed(
            "prototype",
            FieldMap::from([("isPrototypeOf".into(), true.into())]),
        );
        let constructor = constructor(
            observed(
                "constructor",
                FieldMap::from([("prototype".into(), FieldValue::Object(prototype.object()))]),
            )
            .object(),
        );
        let mut record = observed("record", FieldMap::from([("value".into(), 1.into())]));
        let _ = record.inherited.insert("constructor".into(), constructor);
        let error = native_record(&FieldValue::Object(record.object()))
            .err()
            .ok_or_else(|| AuthError::internal("Expected the original record failure"))?;
        assert!(
            error
                .raw_value()
                .is_some_and(|value| value.strict_equals(&sentinel))
        );
        assert_eq!(
            events(&trace)?,
            expected.iter().take(index + 1).copied().collect::<Vec<_>>()
        );
    }
    Ok(())
}

#[test]
fn rejects_nonrecord_values_and_nonobject_function_prototypes() -> AuthResult<()> {
    let invalid = [
        FieldValue::Undefined,
        FieldValue::Null,
        false.into(),
        4.into(),
        "text".into(),
        Vec::<FieldValue>::new().into(),
        constructor(FieldMap::new().into()),
    ];
    for value in invalid {
        assert!(!native_object(&value));
        assert!(native_record(&value)?.is_none());
        let record: FieldValue = FieldMap::from([(
            "constructor".into(),
            constructor(FieldMap::from([("prototype".into(), value)]).into()),
        )])
        .into();
        assert!(native_record(&record)?.is_none());
    }
    Ok(())
}

#[test]
fn modified_date_constructor_distinguishes_absence_from_explicit_undefined() -> AuthResult<()> {
    for constructor in [
        None,
        Some(FieldValue::Undefined),
        Some(FieldValue::Null),
        Some(0.into()),
    ] {
        let trace = Events::default();
        let mut fields = FieldMap::from([("value".into(), 1.into())]);
        if let Some(value) = &constructor {
            let _ = fields.insert("constructor".into(), value.clone());
        }
        let value: FieldValue = FieldDate::from_milliseconds_with_properties(
            0.0,
            Observed::new("date", fields, &trace).object(),
        )
        .into();
        assert!(native_object(&value));
        let output = native_record(&value)?;
        assert_eq!(output.is_some(), constructor.is_some());
        let mut expected = vec!["date:get:constructor"];
        if constructor.as_ref().is_none_or(FieldValue::is_undefined) {
            expected.push("date:has:constructor");
        }
        if constructor.is_some() {
            expected.extend([
                "date:keys",
                "date:descriptor:value",
                "date:get:value",
                "date:descriptor:constructor",
                "date:get:constructor",
            ]);
        }
        assert_eq!(events(&trace)?, expected);
    }
    assert!(native_record(&FieldDate::invalid().into())?.is_none());
    Ok(())
}