rhailib/src/macros/docs/ARCHITECTURE.md
2025-06-27 12:09:50 +03:00

8.5 KiB

Architecture of the macros Crate

The macros crate provides authorization mechanisms and procedural macros for Rhai functions that interact with databases. It implements a comprehensive security layer that ensures proper access control for all database operations within the Rhai scripting environment.

Core Architecture

The crate follows a macro-driven approach to authorization, providing declarative macros that generate secure database access functions:

graph TD
    A[macros Crate] --> B[Authorization Functions]
    A --> C[Utility Functions]
    A --> D[Registration Macros]
    
    B --> B1[Context Extraction]
    B --> B2[Access Control Checks]
    B --> B3[Circle Membership Validation]
    
    C --> C1[ID Conversion]
    C --> C2[Error Handling]
    
    D --> D1[register_authorized_get_by_id_fn!]
    D --> D2[register_authorized_create_by_id_fn!]
    D --> D3[register_authorized_delete_by_id_fn!]
    D --> D4[register_authorized_list_fn!]
    
    D1 --> E[Generated Rhai Functions]
    D2 --> E
    D3 --> E
    D4 --> E
    
    E --> F[Database Operations]
    F --> G[Secure Data Access]

Security Model

Authentication Context

All operations require authentication context passed through Rhai's NativeCallContext:

  • CALLER_ID: Identifies the requesting user
  • CONTEXT_ID: Identifies the target context (the circle)
  • DB_PATH: Specifies the database location

Authorization Levels

  1. Owner Access: Direct access when CALLER_ID == CONTEXT_ID
  2. Circle Member Access: Verified through is_circle_member() function
  3. Resource-Specific Access: Granular permissions via can_access_resource()

Access Control Flow

sequenceDiagram
    participant Script as Rhai Script
    participant Macro as Generated Function
    participant Auth as Authorization Layer
    participant DB as Database
    
    Script->>Macro: Call authorized function
    Macro->>Auth: Extract caller context
    Auth->>Auth: Validate CALLER_ID
    Auth->>Auth: Check circle membership
    Auth->>Auth: Verify resource access
    Auth->>DB: Execute database operation
    DB->>Macro: Return result
    Macro->>Script: Return authorized data

Core Components

1. Utility Functions

ID Conversion (id_from_i64_to_u32)

pub fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>>

Safely converts Rhai's i64 integers to Rust's u32 IDs with proper error handling.

2. Authorization Macros

Get By ID (register_authorized_get_by_id_fn!)

Generates functions for retrieving single resources by ID with authorization checks.

Features:

  • ID validation and conversion
  • Caller authentication
  • Resource-specific access control
  • Database error handling
  • Not found error handling

Create Resource (register_authorized_create_by_id_fn!)

Generates functions for creating new resources with authorization.

Features:

  • Circle membership validation
  • Object persistence
  • Creation authorization
  • Database transaction handling

Delete By ID (register_authorized_delete_by_id_fn!)

Generates functions for deleting resources by ID with authorization.

Features:

  • Deletion authorization
  • Circle membership validation
  • Cascade deletion handling
  • Audit trail support

List Resources (register_authorized_list_fn!)

Generates functions for listing resources with filtering based on access rights.

Features:

  • Bulk authorization checking
  • Result filtering
  • Collection wrapping
  • Performance optimization

Generated Function Architecture

Function Signature Pattern

All generated functions follow a consistent pattern:

move |context: rhai::NativeCallContext, /* parameters */| -> Result<ReturnType, Box<EvalAltResult>>

Context Extraction Pattern

let tag_map = context
    .tag()
    .and_then(|tag| tag.read_lock::<rhai::Map>())
    .ok_or_else(|| /* error */)?;

let caller_pk = tag_map.get("CALLER_ID")?.into_string()?;
let context_id = tag_map.get("CONTEXT_ID")?.into_string()?;
let db_path = tag_map.get("DB_PATH")?.into_string()?;

Database Connection Pattern

let db_path = format!("{}/{}", db_path, context_id);
let db = Arc::new(OurDB::new(db_path, false).expect("Failed to create DB"));

Authorization Strategies

1. Circle-Based Authorization

if context_id != caller_pk_str {
    let is_circle_member = heromodels::models::access::access::is_circle_member(
        db.clone(),
        &caller_pk_str,
    );
    if !is_circle_member {
        return Err(/* authorization error */);
    }
}

2. Resource-Specific Authorization

let has_access = heromodels::models::access::access::can_access_resource(
    db.clone(),
    &caller_pk_str,
    actual_id,
    resource_type_str,
);

if !has_access {
    return Err(/* access denied error */);
}

3. Bulk Authorization (for lists)

let authorized_items: Vec<ResourceType> = all_items
    .into_iter()
    .filter(|item| {
        let resource_id = item.id();
        heromodels::models::access::access::can_access_resource(
            db.clone(),
            &caller_pk_str,
            resource_id,
            resource_type_str,
        )
    })
    .collect();

Error Handling Architecture

Error Categories

  1. Context Errors: Missing or invalid authentication context
  2. Type Conversion Errors: Invalid ID formats or type mismatches
  3. Authorization Errors: Access denied or insufficient permissions
  4. Database Errors: Connection failures or query errors
  5. Not Found Errors: Requested resources don't exist

Error Propagation Pattern

.map_err(|e| {
    Box::new(EvalAltResult::ErrorRuntime(
        format!("Database error: {:?}", e).into(),
        context.position(),
    ))
})?

Performance Considerations

Database Connection Management

  • Connection Per Operation: Each function creates its own database connection
  • Path-Based Isolation: Database paths include circle identifiers for isolation
  • Connection Pooling: Relies on underlying database implementation

Authorization Caching

  • No Caching: Authorization checks are performed for each operation
  • Stateless Design: No session state maintained between calls
  • Fresh Validation: Ensures up-to-date permission checking

Bulk Operations Optimization

  • Filtered Iteration: List operations filter results after database fetch
  • Lazy Evaluation: Authorization checks only performed on accessed items
  • Memory Efficiency: Results collected into appropriate wrapper types

Integration Patterns

Macro Usage in DSL Modules

register_authorized_get_by_id_fn!(
    module: &mut module,
    rhai_fn_name: "get_company",
    resource_type_str: "Company",
    rhai_return_rust_type: heromodels::models::biz::company::Company
);

Context Setup in Engine

let mut context_map = rhai::Map::new();
context_map.insert("CALLER_ID".into(), caller_pk.into());
context_map.insert("CONTEXT_ID".into(), context_id.into());
context_map.insert("DB_PATH".into(), db_path.into());
engine.set_tag(context_map);

Security Considerations

Authentication Requirements

  • Mandatory Context: All operations require valid authentication context
  • Public Key Validation: Caller identity verified through cryptographic keys
  • Circle Membership: Hierarchical access control through circle membership

Authorization Granularity

  • Resource-Level: Individual resource access control
  • Operation-Level: Different permissions for read/write/delete operations
  • Circle-Level: Organization-based access boundaries

Audit and Logging

  • Operation Logging: All database operations include caller identification
  • Access Logging: Authorization decisions are logged for audit trails
  • Error Logging: Failed authorization attempts are recorded

Extensibility

Adding New Operations

  1. Create new macro following existing patterns
  2. Implement authorization logic specific to operation
  3. Add error handling for operation-specific failures
  4. Register with DSL modules using macro

Custom Authorization Logic

// Custom authorization can be added within macro implementations
if requires_special_permission {
    let has_special_access = check_special_permission(db.clone(), &caller_pk_str);
    if !has_special_access {
        return Err(/* custom error */);
    }
}

This architecture provides a robust, secure foundation for database operations within the Rhai scripting environment while maintaining flexibility for future extensions and customizations.