better-auth-api 1.0.0-alpha.5

Plugin implementations for better-auth
use super::*;
use chrono::TimeZone as _;

#[tokio::test]
async fn keyring_reads_live_configuration_after_the_bound_adapter_returns() -> AuthResult<()> {
    let trace = Arc::new(Trace::default());
    let jwks = ObservedFields::new(
        FieldMap::from([(
            "keyPairConfig".into(),
            FieldMap::from([("alg".into(), "EdDSA".into())]).into(),
        )]),
        trace.clone(),
        None,
    );
    let legacy: FieldValue = FieldMap::from([
        ("id".into(), "legacy".into()),
        ("alg".into(), FieldValue::Null),
        (
            "createdAt".into(),
            FieldDate::from_milliseconds(20.0).into(),
        ),
    ])
    .into();
    let older: FieldValue = FieldMap::from([
        ("id".into(), "older".into()),
        ("alg".into(), "ES256".into()),
        (
            "createdAt".into(),
            FieldDate::from_milliseconds(10.0).into(),
        ),
    ])
    .into();
    let expected = legacy.clone();
    let current = jwks.value();
    let events = trace.clone();
    let callbacks = JwtCallbacks::<S>::default().get_jwks_native(move |endpoint| {
        assert_eq!(endpoint.auth.config.base_url.as_static(), Some(BASE));
        events.record("getJwks");
        let result = current.set_model_property(
            "keyPairConfig",
            FieldMap::from([("alg".into(), "ES256".into())]).into(),
        );
        let keys = vec![older.clone(), legacy.clone()];
        Box::pin(async move {
            result?;
            Ok(keys.into())
        })
    });
    let options = overrides::NativeOptions(
        FieldMap::from([
            ("jwks".into(), jwks.value()),
            ("adapter".into(), callbacks.into_native()),
        ])
        .into(),
    );
    let plugin = JwtPlugin::new();
    let context = context(&plugin, JwtCallbacks::default()).await?;
    let endpoint = EndpointContext::native(None, None, FieldValue::Null, &context);
    let keyring = adapter::Keyring::new(adapter::store(&endpoint), &options, &endpoint);
    assert!(
        keyring
            .latest(Some("ES256".into()))
            .await?
            .strict_equals(&expected)
    );
    assert_eq!(
        *trace.events.lock().unwrap(),
        ["getJwks", "get:keyPairConfig"]
    );
    Ok(())
}

#[tokio::test]
async fn custom_sign_does_not_read_unused_jwks_or_serialize_nested_options() -> AuthResult<()> {
    for to_json in [false, true] {
        let trace = Arc::new(Trace::default());
        let failure: FieldValue = FieldMap::from([("unused".into(), true.into())]).into();
        let returned: FieldValue = FieldMap::from([("signed".into(), true.into())]).into();
        let token = returned.clone();
        let jwt: FieldValue = FieldMap::from([(
            "sign".into(),
            FieldFunction::contextual(move |_, _| Ok(token.clone())).into(),
        )])
        .into();
        let original = failure.clone();
        let jwks: FieldValue = FieldMap::from([(
            "toJSON".into(),
            FieldFunction::contextual(move |_, _| Err(AuthError::from_thrown(original.clone())))
                .into(),
        )])
        .into();
        let options = ObservedFields::new(
            FieldMap::from([("jwt".into(), jwt), ("jwks".into(), jwks)]),
            trace.clone(),
            (!to_json).then_some(("jwks", failure)),
        );
        let plugin = JwtPlugin::new();
        let context = context(&plugin, JwtCallbacks::default()).await?;
        let endpoint = EndpointContext::native(None, None, FieldValue::Null, &context);
        assert!(
            plugin
                .sign_native_options(
                    claims(),
                    None,
                    &overrides::NativeOptions(options.value()),
                    &endpoint
                )
                .await?
                .strict_equals(&returned)
        );
        assert_eq!(*trace.events.lock().unwrap(), vec!["get:jwt"; 5]);
        assert!(context.database.list_jwk_records().await?.is_empty());
    }
    Ok(())
}

#[tokio::test]
async fn generated_keys_keep_rotation_truthiness_repeated_reads_and_failure_identity()
-> AuthResult<()> {
    for mode in [
        "zero",
        "nan",
        "string-zero",
        "object",
        "negative",
        "failure",
    ] {
        let trace = Arc::new(Trace::default());
        let failure: FieldValue = FieldMap::from([("rotation".into(), true.into())]).into();
        let original = failure.clone();
        let events = trace.clone();
        let interval = match mode {
            "zero" => 0.0.into(),
            "nan" => f64::NAN.into(),
            "string-zero" => "0".into(),
            "negative" => (-2.0).into(),
            _ => FieldMap::from([(
                "valueOf".into(),
                FieldFunction::contextual(move |_, _| {
                    events.record("rotation.valueOf");
                    if mode == "failure" {
                        return Err(AuthError::from_thrown(original.clone()));
                    }
                    Ok("2".into())
                })
                .into(),
            )])
            .into(),
        };
        let jwks = ObservedFields::new(
            FieldMap::from([
                (
                    "keyPairConfig".into(),
                    FieldMap::from([
                        ("alg".into(), "EdDSA".into()),
                        ("crv".into(), "Ed25519".into()),
                    ])
                    .into(),
                ),
                ("disablePrivateKeyEncryption".into(), true.into()),
                ("rotationInterval".into(), interval),
            ]),
            trace.clone(),
            None,
        );
        let captured = Arc::new(Mutex::new(None::<FieldValue>));
        let saved = captured.clone();
        let events = trace.clone();
        let returned: FieldValue = FieldMap::from([("created".into(), true.into())]).into();
        let token = returned.clone();
        let callbacks = JwtCallbacks::<S>::default().create_jwk_value(move |key, endpoint| {
            assert_eq!(endpoint.auth.config.base_url.as_static(), Some(BASE));
            events.record("createJwk");
            *saved.lock().unwrap() = Some(key);
            let token = token.clone();
            Box::pin(async move { Ok(token) })
        });
        let options = overrides::NativeOptions(
            FieldMap::from([
                ("jwks".into(), jwks.value()),
                ("adapter".into(), callbacks.into_native()),
            ])
            .into(),
        );
        let plugin = JwtPlugin::new();
        let clock_trace = trace.clone();
        let context = context(&plugin, JwtCallbacks::default())
            .await?
            .with_clock(move || {
                clock_trace.record("clock");
                Utc.timestamp_millis_opt(1000).single().unwrap()
            });
        let endpoint = EndpointContext::native(None, None, FieldValue::Null, &context);
        let result = plugin.create_key_native(&options, &endpoint).await;
        let mut expected = vec![
            "get:keyPairConfig",
            "get:disablePrivateKeyEncryption",
            "get:rotationInterval",
        ];
        if !matches!(mode, "zero" | "nan") {
            expected.push("clock");
            expected.push("get:rotationInterval");
        }
        if matches!(mode, "object" | "failure") {
            expected.push("rotation.valueOf");
        }
        if mode == "failure" {
            assert!(
                result
                    .unwrap_err()
                    .into_thrown_value()?
                    .strict_equals(&failure)
            );
            assert!(captured.lock().unwrap().is_none());
        } else {
            expected.push("createJwk");
            assert!(result?.strict_equals(&returned));
            let key = captured.lock().unwrap().clone().unwrap();
            let expiring = !matches!(mode, "zero" | "nan");
            let mut names = vec!["alg", "crv", "publicKey", "privateKey", "createdAt"];
            if expiring {
                names.push("expiresAt");
            }
            assert_eq!(
                key.enumerable_fields()?
                    .keys()
                    .map(String::as_str)
                    .collect::<Vec<_>>(),
                names
            );
            assert!(key.model_property("createdAt")?.as_date().is_some());
            if expiring {
                let shift = if mode == "negative" {
                    -2000.0
                } else if mode == "string-zero" {
                    0.0
                } else {
                    2000.0
                };
                let expiry = key
                    .model_property("expiresAt")?
                    .as_date()
                    .unwrap()
                    .milliseconds();
                assert_eq!(expiry, 1000.0 + shift);
            }
        }
        assert_eq!(*trace.events.lock().unwrap(), expected);
        assert!(context.database.list_jwk_records().await?.is_empty());
    }
    Ok(())
}

#[tokio::test]
async fn unbound_native_adapter_requires_the_shared_endpoint_context_boundary() -> AuthResult<()> {
    let plugin = JwtPlugin::new();
    let context = context(&plugin, JwtCallbacks::default()).await?;
    let calls = Arc::new(AtomicUsize::new(0));
    let count = calls.clone();
    let options: FieldValue = FieldMap::from([(
        "adapter".into(),
        FieldMap::from([(
            "getJwks".into(),
            FieldFunction::contextual(move |_, _| {
                let _ = count.fetch_add(1, Ordering::SeqCst);
                Ok(Vec::<FieldValue>::new().into())
            })
            .into(),
        )])
        .into(),
    )])
    .into();
    let error = JwtApi::from_context(&context)?
        .sign_value_with_overrides(claims(), options)
        .await
        .unwrap_err();
    assert_eq!(
        error.message_value()?,
        "Native JWT adapter callbacks require a bound EndpointContext callback".into()
    );
    assert_eq!(calls.load(Ordering::SeqCst), 0);
    assert!(context.database.list_jwk_records().await?.is_empty());
    Ok(())
}