kirino 0.3.0

A customizable, zero-trust authentication framework.
Documentation
#[cfg(test)]
mod tests {

    use crate::{
        database::sql::InMemoryUserDatabase,
        rbac::{
            store::{memory::InMemoryAssignmentStore, registry::SimpleRole},
            subject::StringSubject,
            traits::AssignmentStore,
        },
        service::login::{build_default_engine, AuthService, KirinoPermission},
    };

    fn make_auth() -> AuthService<
        InMemoryUserDatabase,
        KirinoPermission,
        SimpleRole<KirinoPermission>,
        InMemoryAssignmentStore<StringSubject, KirinoPermission>,
    > {
        let db = InMemoryUserDatabase::new();
        let engine = build_default_engine();
        AuthService::new(db, "test-secret", 24, engine, "admin", "viewer")
    }

    #[tokio::test]
    async fn test_register_and_login() {
        let auth = make_auth();

        let user = auth
            .register("alice", "password123", Some("Alice"))
            .await
            .unwrap();
        assert_eq!(user.username, "alice");
        assert_eq!(user.display_name, Some("Alice".to_string()));

        let result = auth.login("alice", "password123").await.unwrap();
        assert_eq!(result.username, "alice");
        assert!(!result.token.is_empty());

        let claims = auth.verify_token(&result.token).await.unwrap();
        assert_eq!(claims.sub, "alice");
    }

    #[tokio::test]
    async fn test_first_user_is_admin() {
        let auth = make_auth();

        auth.register("admin", "password123", None).await.unwrap();
        assert!(
            auth.check_permission(
                &auth.login("admin", "password123").await.unwrap().user_id,
                &KirinoPermission::SystemWrite,
            )
            .await
        );
    }

    #[tokio::test]
    async fn test_second_user_is_viewer() {
        let auth = make_auth();

        auth.register("admin", "password123", None).await.unwrap();
        auth.register("viewer", "password123", None).await.unwrap();

        let viewer_id = auth.login("viewer", "password123").await.unwrap().user_id;
        assert!(
            auth.check_permission(&viewer_id, &KirinoPermission::AgentRead)
                .await
        );
        assert!(
            !auth
                .check_permission(&viewer_id, &KirinoPermission::SystemWrite)
                .await
        );
    }

    #[tokio::test]
    async fn test_wrong_password() {
        let auth = make_auth();

        auth.register("alice", "password123", None).await.unwrap();
        assert!(auth.login("alice", "wrong").await.is_err());
    }

    #[tokio::test]
    async fn test_change_password() {
        let auth = make_auth();

        let user = auth.register("alice", "old_password", None).await.unwrap();
        auth.change_password(&user.id.to_string(), "old_password", "new_password")
            .await
            .unwrap();

        assert!(auth.login("alice", "old_password").await.is_err());
        assert!(auth.login("alice", "new_password").await.is_ok());
    }

    #[tokio::test]
    async fn test_delete_user() {
        let auth = make_auth();

        let user = auth.register("alice", "password123", None).await.unwrap();
        assert!(auth.delete_user(&user.id.to_string()).await.unwrap());
        assert!(auth.login("alice", "password123").await.is_err());
    }

    #[tokio::test]
    async fn test_duplicate_username() {
        let auth = make_auth();

        auth.register("alice", "password123", None).await.unwrap();
        assert!(auth.register("alice", "password456", None).await.is_err());
    }

    #[tokio::test]
    async fn test_weak_password() {
        let auth = make_auth();

        assert!(auth.register("alice", "short", None).await.is_err());
        assert!(auth.register("", "password123", None).await.is_err());
    }

    #[tokio::test]
    async fn test_list_users() {
        let auth = make_auth();

        auth.register("alice", "password123", None).await.unwrap();
        auth.register("bob", "password123", None).await.unwrap();

        let users = auth.list_users().await.unwrap();
        assert_eq!(users.len(), 2);
    }

    #[tokio::test]
    async fn test_rbac_role_change() {
        let auth = make_auth();

        let user = auth
            .register("operator", "password123", None)
            .await
            .unwrap();

        let subject = StringSubject::new(user.id.to_string());
        let engine = auth.engine();
        engine
            .assignment_store()
            .revoke_role(&subject, "admin")
            .await
            .unwrap();
        engine
            .assignment_store()
            .assign_role(&subject, "operator")
            .await
            .unwrap();

        let uid = user.id.to_string();
        assert!(
            auth.check_permission(&uid, &KirinoPermission::AgentWrite)
                .await
        );
        assert!(
            !auth
                .check_permission(&uid, &KirinoPermission::SystemWrite)
                .await
        );
    }
}