librnxengine 1.1.0

implement robust software licensing, activation, and validation systems.
Documentation
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

/// Request to activate a license on a specific device.
///
/// This struct is sent from the client application to the license server
/// to request activation of a license. It includes hardware fingerprinting
/// and device metadata for license binding and tracking purposes.
///
/// # Fields
/// - `license_id`: Unique identifier of the license to activate
/// - `hardware_fingerprint`: SHA-256 hash of hardware identifiers for device binding
/// - `machine_name`: Human-readable name of the device/machine
/// - `ip_address`: Optional client IP address for geolocation and security
/// - `user_agent`: Optional client software/version for compatibility tracking
/// - `timestamp`: When the activation request was created (UTC)
/// - `nonce`: Unique one-time value to prevent replay attacks
///
/// # Security Considerations
/// - The `hardware_fingerprint` should be generated client-side to avoid transmitting raw hardware data
/// - `nonce` prevents replay attacks by ensuring each request is unique
/// - Timestamp allows detection of delayed or manipulated requests
///
/// # Example JSON
/// ```json
/// {
///   "license_id": "123e4567-e89b-12d3-a456-426614174000",
///   "hardware_fingerprint": "a1b2c3d4e5f67890...",
///   "machine_name": "Johns-MacBook-Pro",
///   "ip_address": "192.168.1.100",
///   "user_agent": "MyApp/2.0.0 (macOS 14.0)",
///   "timestamp": "2024-01-01T12:00:00Z",
///   "nonce": "550e8400-e29b-41d4-a716-446655440000"
/// }
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActivationRequest {
    pub license_id: Uuid,
    pub hardware_fingerprint: String,
    pub machine_name: String,
    pub ip_address: Option<String>,
    pub user_agent: Option<String>,
    pub timestamp: DateTime<Utc>,
    pub nonce: String,
}

/// Server response to a successful license activation.
///
/// Returned by the license server when activation is approved.
/// Contains tokens for future validation and metadata about the activation.
///
/// # Fields
/// - `activation_id`: Unique identifier for this specific activation instance
/// - `license_id`: The license that was activated (matches request)
/// - `activated_at`: Server timestamp when activation was recorded
/// - `expires_at`: When this activation expires (may differ from license expiration)
/// - `activation_token`: JWT or similar token for subsequent validations
/// - `refresh_token`: Optional token for refreshing the activation token without re-activation
/// - `hardware_bound`: Whether this activation is tied to specific hardware
/// - `max_activations`: Maximum concurrent activations allowed for this license
/// - `current_activations`: Current number of active activations for this license
///
/// # Token Security
/// - `activation_token`: Short-lived token for frequent validations
/// - `refresh_token`: Long-lived token stored securely, used to obtain new activation tokens
/// - Both tokens should be transmitted and stored securely (HTTPS, secure storage)
///
/// # Example JSON
/// ```json
/// {
///   "activation_id": "223e4567-e89b-12d3-a456-426614174001",
///   "license_id": "123e4567-e89b-12d3-a456-426614174000",
///   "activated_at": "2024-01-01T12:00:05Z",
///   "expires_at": "2024-12-31T23:59:59Z",
///   "activation_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
///   "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
///   "hardware_bound": true,
///   "max_activations": 5,
///   "current_activations": 1
/// }
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActivationResponse {
    pub activation_id: Uuid,
    pub license_id: Uuid,
    pub activated_at: DateTime<Utc>,
    pub expires_at: DateTime<Utc>,
    pub activation_token: String,
    pub refresh_token: Option<String>,
    pub hardware_bound: bool,
    pub max_activations: u32,
    pub current_activations: u32,
}

/// Database record tracking a license activation.
///
/// This struct represents the server-side persistent record of an activation.
/// Used for auditing, management, and revocation purposes.
///
/// # Fields
/// - `id`: Unique identifier of this activation record
/// - `license_id`: Reference to the activated license
/// - `hardware_fingerprint`: Hashed hardware identifiers for this activation
/// - `machine_name`: Name of the machine where activated
/// - `activated_at`: When the activation was created
/// - `last_checkin`: Last time this activation contacted the server (for heartbeat)
/// - `is_active`: Whether this activation is currently considered active
/// - `ip_address`: IP address from which activation was requested
///
/// # Database Considerations
/// - Used in SQL/NoSQL databases for activation tracking
/// - `last_checkin` allows cleanup of stale activations
/// - `is_active` enables soft deactivation without deleting history
/// - Indexes should be created on `license_id` and `hardware_fingerprint`
///
/// # Example Database Record
/// ```sql
/// INSERT INTO activations (
///   id, license_id, hardware_fingerprint, machine_name,
///   activated_at, last_checkin, is_active, ip_address
/// ) VALUES (
///   '223e4567-e89b-12d3-a456-426614174001',
///   '123e4567-e89b-12d3-a456-426614174000',
///   'a1b2c3d4e5f67890...',
///   'Johns-MacBook-Pro',
///   '2024-01-01T12:00:05Z',
///   '2024-01-02T12:00:00Z',
///   true,
///   '192.168.1.100'
/// );
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActivationRecord {
    pub id: Uuid,
    pub license_id: Uuid,
    pub hardware_fingerprint: String,
    pub machine_name: String,
    pub activated_at: DateTime<Utc>,
    pub last_checkin: DateTime<Utc>,
    pub is_active: bool,
    pub ip_address: Option<String>,
}

// Note: UUIDs are used throughout for identifiers because:
// - Globally unique across distributed systems
// - No sequential pattern (harder to guess)
// - Standardized format (RFC 4122)
// - Good database performance with proper indexing

// Note: All timestamps use UTC to avoid timezone confusion in distributed systems:
// - Consistent across servers in different regions
// - Easier for chronological sorting and comparison
// - Standard format for JSON serialization (RFC 3339)

// Note: The `Option<T>` type is used for optional fields to clearly distinguish
// between "not provided" and "empty value". This is important for:
// - Backward compatibility when adding new fields
// - Clear API semantics
// - Database schema evolution

// Note: Activation flow typically works as:
// 1. Client generates hardware fingerprint
// 2. Client sends `ActivationRequest` to server
// 3. Server validates license, checks limits, creates `ActivationRecord`
// 4. Server returns `ActivationResponse` with tokens
// 5. Client stores tokens securely for future validations
// 6. Periodic heartbeats update `last_checkin` timestamp

// Note: Security best practices for activations:
// 1. Validate hardware fingerprint format and length
// 2. Implement rate limiting on activation attempts
// 3. Log all activation attempts for audit trail
// 4. Use secure random UUID generation for IDs and nonces
// 5. Implement token expiration and rotation policies
// 6. Encrypt sensitive data at rest in database

// Note: The `nonce` field in `ActivationRequest` serves multiple purposes:
// - Prevents replay attacks (each request must be unique)
// - Can be used for idempotency (prevent duplicate processing)
// - Helps with request ordering and deduplication
// - Should be a cryptographically secure random value

// Example workflow using these structs:
// ```
// // Client side
// let request = ActivationRequest {
//     license_id: license.license_id(),
//     hardware_fingerprint: HardwareFingerprint::generate()?,
//     machine_name: hostname::get()?.to_string_lossy().to_string(),
//     ip_address: get_public_ip().await.ok(),
//     user_agent: Some(format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))),
//     timestamp: Utc::now(),
//     nonce: Uuid::new_v4().to_string(),
// };
//
// // Send to server
// let response: ActivationResponse = client.post("/activate")
//     .json(&request)
//     .send()
//     .await?
//     .json()
//     .await?;
//
// // Store tokens securely
// save_token("activation_token", &response.activation_token);
// if let Some(refresh) = &response.refresh_token {
//     save_token("refresh_token", refresh);
// }
//
// // Server side (simplified)
// fn handle_activation(request: ActivationRequest) -> Result<ActivationResponse, Error> {
//     // Validate license exists and is valid
//     let license = licenses.get(&request.license_id)?;
//
//     // Check activation limits
//     let current = count_activations(&request.license_id)?;
//     if current >= license.max_activations {
//         return Err(Error::ActivationLimitExceeded);
//     }
//
//     // Create activation record
//     let record = ActivationRecord {
//         id: Uuid::new_v4(),
//         license_id: request.license_id,
//         hardware_fingerprint: request.hardware_fingerprint.clone(),
//         machine_name: request.machine_name.clone(),
//         activated_at: Utc::now(),
//         last_checkin: Utc::now(),
//         is_active: true,
//         ip_address: request.ip_address.clone(),
//     };
//
//     // Save to database
//     save_activation_record(&record)?;
//
//     // Generate tokens
//     let activation_token = generate_jwt(&record.id, Duration::hours(24))?;
//     let refresh_token = generate_jwt(&record.id, Duration::days(30))?;
//
//     // Return response
//     Ok(ActivationResponse {
//         activation_id: record.id,
//         license_id: record.license_id,
//         activated_at: record.activated_at,
//         expires_at: license.expires_at,
//         activation_token,
//         refresh_token: Some(refresh_token),
//         hardware_bound: true,
//         max_activations: license.max_activations,
//         current_activations: current + 1,
//     })
// }
// ```