better-auth-api 1.0.0-alpha.5

Plugin implementations for better-auth
#![allow(
    clippy::expect_used,
    reason = "Fixture setup and response assertions require successful fallible construction."
)]
use super::*;
use better_auth_core::{
    AuthInitContext, AuthSchema,
    store::{EphemeralStore, MemoryCacheAdapter, SecondaryStorage, schema::EntityRole},
    user_fields::{UserConfig, UserFieldConfig},
};
use serde_json::{Value, json};

async fn check_permissions<S: AuthSchema>(
    ctx: AuthContext<S>,
    mode: &str,
    declaration: bool,
    sqlite: bool,
) -> AuthResult<()> {
    let permissions = json!({"machine": ["read"]});
    let cache = Arc::new(MemoryCacheAdapter::new());
    let plugin = ApiKeyPlugin::builder()
        .storage(if mode == "database" {
            ApiKeyStorage::Database
        } else {
            ApiKeyStorage::SecondaryStorage
        })
        .fallback_to_database(mode == "fallback")
        .custom_storage(cache.clone())
        .disable_key_hashing(true)
        .store_starting_characters(false)
        .build();
    let mut model_fields = better_auth_core::plugin_runtime::ModelFields::default();
    let mut init = AuthInitContext::new(ctx.config.clone(), ctx.database.clone())
        .expect("fixture context initializes");
    better_auth_core::AuthPlugin::<S>::schema(&plugin, &mut model_fields)?;
    plugin.on_init(&mut init).await?;
    if declaration {
        model_fields.register_model_fields(
            EntityRole::ApiKey,
            UserConfig {
                additional_fields: Some(
                    [(
                        "permissions".into(),
                        UserFieldConfig {
                            required: Some(false),
                            default_value: Some(permissions.to_string().into()),
                            ..Default::default()
                        },
                    )]
                    .into(),
                ),
            },
        )?;
    }
    let store = ctx
        .database
        .with_runtime(ctx.config.clone(), Vec::new(), model_fields)?;
    let ctx = AuthContext::new(ctx.config, store).expect("fixture context initializes");
    let created = plugin
        .create_key(
            &ctx,
            &CreateKeyRequest {
                user_id: Some("permissions-owner".into()),
                ..Default::default()
            },
        )
        .await?;
    let applied_default = declaration && mode != "secondary";
    let expected = json!({
        "id": created.api_key.id, "key": created.key, "configId": "default",
        "name": null, "prefix": null, "start": null, "enabled": true, "expiresAt": null,
        "referenceId": "permissions-owner", "lastRefillAt": null, "lastRequest": null,
        "metadata": null, "rateLimitMax": 10, "rateLimitTimeWindow": 86_400_000,
        "remaining": null, "refillAmount": null, "refillInterval": null, "rateLimitEnabled": true,
        "requestCount": 0, "createdAt": created.api_key.created_at, "updatedAt": created.api_key.updated_at,
        "permissions": if applied_default { permissions.clone() } else { Value::Null },
    });
    assert_eq!(
        serde_json::to_value(&created)?,
        expected,
        "{mode}/{declaration}"
    );
    let cached = cache
        .get(&format!("api-key:by-id:{}", created.api_key.id.typed()?))
        .await?;
    if mode == "database" {
        assert!(cached.is_none());
    } else {
        let mut stored = expected;
        if applied_default {
            stored["permissions"] = permissions.to_string().into();
        } else if mode == "secondary" || !sqlite {
            let _ = stored.as_object_mut().unwrap().remove("permissions");
        }
        assert_eq!(
            serde_json::from_str::<Value>(cached.unwrap().as_str().unwrap())?,
            stored,
            "{mode}/{declaration}"
        );
        let index = cache.get("api-key:by-ref:permissions-owner").await?;
        if mode == "fallback" {
            assert!(index.is_none());
        } else {
            assert_eq!(
                serde_json::from_str::<Value>(index.unwrap().as_str().unwrap())?,
                json!([created.api_key.id])
            );
        }
    }
    assert_eq!(
        ctx.database
            .count_api_keys_by_reference("permissions-owner")
            .await?,
        u64::from(mode != "secondary")
    );
    Ok(())
}

#[tokio::test]
async fn memory_omitted_permissions_preserve_defaults_and_complete_cache_records() -> AuthResult<()>
{
    for mode in ["database", "secondary", "fallback"] {
        for declaration in [false, true] {
            let config = Arc::new(crate::plugins::test_helpers::create_test_config());
            let ctx = AuthContext::new(config.clone(), Arc::new(EphemeralStore::new(config)))
                .expect("fixture context initializes");
            check_permissions(ctx, mode, declaration, false).await?;
        }
    }
    Ok(())
}

#[tokio::test]
async fn sqlite_omitted_permissions_preserve_defaults_and_complete_cache_records() -> AuthResult<()>
{
    for mode in ["database", "secondary", "fallback"] {
        for declaration in [false, true] {
            check_permissions(
                crate::plugins::test_helpers::create_test_context().await,
                mode,
                declaration,
                true,
            )
            .await?;
        }
    }
    Ok(())
}