ax_core 0.3.2

Core library implementing the functions of ax
Documentation
use crate::{
    api::rejections::{ApiError, UnauthorizedReason},
    certs::{AppLicenseType, Expiring, SignedAppLicense},
    crypto::PublicKey,
};
use ax_types::AppId;
use chrono::Utc;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Licensing {
    node: String,
    pub apps: BTreeMap<AppId, String>,
}

impl Licensing {
    pub fn new(node: String, apps: BTreeMap<AppId, String>) -> Self {
        Self { node, apps }
    }

    pub fn is_node_licensed(&self, ax_public_key: &PublicKey) -> Result<bool, ApiError> {
        if self.node == "development" {
            return Ok(false);
        }
        let license = self
            .node
            .parse::<SignedAppLicense>()
            .map_err(|_| ApiError::NodeUnauthorized {
                reason: UnauthorizedReason::MalformedLicense,
            })?;
        license
            .validate(ax_public_key)
            .map_err(|_| ApiError::NodeUnauthorized {
                reason: UnauthorizedReason::InvalidSignature,
            })?;
        match license.license.license_type {
            AppLicenseType::Expiring(Expiring { app_id, expires_at }) => {
                if app_id.as_str() != "com.actyx.node" {
                    Err(ApiError::NodeUnauthorized {
                        reason: UnauthorizedReason::WrongSubject,
                    })
                } else if expires_at < Utc::now() {
                    Err(ApiError::NodeUnauthorized {
                        reason: UnauthorizedReason::Expired,
                    })
                } else {
                    Ok(true)
                }
            }
        }
    }

    pub fn app_id_license(&self, app_id: &AppId) -> Option<&String> {
        self.apps.get(app_id)
    }
}

impl Default for Licensing {
    fn default() -> Self {
        Licensing {
            node: "development".into(),
            apps: BTreeMap::default(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::api::{licensing::Licensing, rejections::ApiError};
    use std::collections::BTreeMap;

    #[test]
    fn default() {
        let licensing = Licensing::default();
        assert_eq!(licensing.node, "development");
        assert!(licensing.apps.is_empty());
    }

    #[test]
    fn is_node_licensed() {
        let licensing = Licensing::default();
        let ax_key = PublicKey::ax_public_key();
        assert!(!licensing.is_node_licensed(&ax_key).unwrap());

        let licensing = Licensing {
            node: "licensed".into(),
            apps: BTreeMap::default(),
        };
        assert_eq!(
            licensing.is_node_licensed(&ax_key).unwrap_err(),
            ApiError::NodeUnauthorized {
                reason: UnauthorizedReason::MalformedLicense
            }
        );
    }
}