Add function to report devices activity
This commit is contained in:
		@@ -0,0 +1,22 @@
 | 
			
		||||
use crate::logs::logs_manager;
 | 
			
		||||
use crate::logs::severity::LogSeverity;
 | 
			
		||||
use crate::server::custom_error::HttpResult;
 | 
			
		||||
use crate::server::devices_api::jwt_parser::JWTRequest;
 | 
			
		||||
use crate::server::WebEnergyActor;
 | 
			
		||||
use actix_web::{web, HttpResponse};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, serde::Deserialize)]
 | 
			
		||||
pub struct LogRequest {
 | 
			
		||||
    severity: LogSeverity,
 | 
			
		||||
    message: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Report log message from device
 | 
			
		||||
pub async fn report_log(body: web::Json<JWTRequest>, actor: WebEnergyActor) -> HttpResult {
 | 
			
		||||
    let (device, request) = body.parse_jwt::<LogRequest>(actor).await?;
 | 
			
		||||
 | 
			
		||||
    log::info!("Save log message from device: {request:#?}");
 | 
			
		||||
    logs_manager::save_log(Some(&device.id), request.severity, request.message)?;
 | 
			
		||||
 | 
			
		||||
    Ok(HttpResponse::Accepted().finish())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										96
									
								
								central_backend/src/server/devices_api/jwt_parser.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								central_backend/src/server/devices_api/jwt_parser.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
use crate::app_config::AppConfig;
 | 
			
		||||
use crate::crypto::pki;
 | 
			
		||||
use crate::devices::device::{Device, DeviceId};
 | 
			
		||||
use crate::energy::energy_actor;
 | 
			
		||||
use crate::server::WebEnergyActor;
 | 
			
		||||
use jsonwebtoken::{Algorithm, DecodingKey, Validation};
 | 
			
		||||
use openssl::x509::X509;
 | 
			
		||||
use serde::de::DeserializeOwned;
 | 
			
		||||
use std::collections::HashSet;
 | 
			
		||||
 | 
			
		||||
#[derive(thiserror::Error, Debug)]
 | 
			
		||||
pub enum JWTError {
 | 
			
		||||
    #[error("Failed to decode JWT header")]
 | 
			
		||||
    FailedDecodeJWT,
 | 
			
		||||
    #[error("Missing KID in JWT!")]
 | 
			
		||||
    MissingKidInJWT,
 | 
			
		||||
    #[error("Sent a JWT for a device which does not exists!")]
 | 
			
		||||
    DeviceDoesNotExists,
 | 
			
		||||
    #[error("Sent a JWT for a device which is not validated!")]
 | 
			
		||||
    DeviceNotValidated,
 | 
			
		||||
    #[error("Sent a JWT using a revoked certificate!")]
 | 
			
		||||
    RevokedCertificate,
 | 
			
		||||
    #[error("Failed to validate JWT!")]
 | 
			
		||||
    FailedValidateJWT,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize)]
 | 
			
		||||
pub struct JWTRequest {
 | 
			
		||||
    pub payload: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl JWTRequest {
 | 
			
		||||
    pub async fn parse_jwt<E: DeserializeOwned>(
 | 
			
		||||
        &self,
 | 
			
		||||
        actor: WebEnergyActor,
 | 
			
		||||
    ) -> anyhow::Result<(Device, E)> {
 | 
			
		||||
        // First, we need to extract device kid from query
 | 
			
		||||
        let Ok(jwt_header) = jsonwebtoken::decode_header(&self.payload) else {
 | 
			
		||||
            log::error!("Failed to decode JWT header!");
 | 
			
		||||
            return Err(JWTError::FailedDecodeJWT.into());
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let Some(kid) = jwt_header.kid else {
 | 
			
		||||
            log::error!("Missing KID in JWT!");
 | 
			
		||||
            return Err(JWTError::MissingKidInJWT.into());
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Fetch device information
 | 
			
		||||
        let Some(device) = actor
 | 
			
		||||
            .send(energy_actor::GetSingleDevice(DeviceId(kid)))
 | 
			
		||||
            .await?
 | 
			
		||||
        else {
 | 
			
		||||
            log::error!("Sent a JWT for a device which does not exists!");
 | 
			
		||||
            return Err(JWTError::DeviceDoesNotExists.into());
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if !device.validated {
 | 
			
		||||
            log::error!("Sent a JWT for a device which is not validated!");
 | 
			
		||||
            return Err(JWTError::DeviceNotValidated.into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check certificate revocation status
 | 
			
		||||
        let cert_bytes = std::fs::read(AppConfig::get().device_cert_path(&device.id))?;
 | 
			
		||||
        let certificate = X509::from_pem(&cert_bytes)?;
 | 
			
		||||
 | 
			
		||||
        if pki::CertData::load_devices_ca()?.is_revoked(&certificate)? {
 | 
			
		||||
            log::error!("Sent a JWT using a revoked certificate!");
 | 
			
		||||
            return Err(JWTError::RevokedCertificate.into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let (key, alg) = match DecodingKey::from_ec_pem(&cert_bytes) {
 | 
			
		||||
            Ok(key) => (key, Algorithm::ES256),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::warn!("Failed to decode certificate as EC certificate {e}, trying RSA...");
 | 
			
		||||
                (
 | 
			
		||||
                    DecodingKey::from_rsa_pem(&cert_bytes)
 | 
			
		||||
                        .expect("Failed to decode RSA certificate"),
 | 
			
		||||
                    Algorithm::RS256,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        let mut validation = Validation::new(alg);
 | 
			
		||||
        validation.validate_exp = false;
 | 
			
		||||
        validation.required_spec_claims = HashSet::default();
 | 
			
		||||
 | 
			
		||||
        let c = match jsonwebtoken::decode::<E>(&self.payload, &key, &validation) {
 | 
			
		||||
            Ok(c) => c,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::error!("Failed to validate JWT! {e}");
 | 
			
		||||
                return Err(JWTError::FailedValidateJWT.into());
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Ok((device, c.claims))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,15 +1,13 @@
 | 
			
		||||
use crate::app_config::AppConfig;
 | 
			
		||||
use crate::crypto::pki;
 | 
			
		||||
use crate::devices::device::{DeviceId, DeviceInfo};
 | 
			
		||||
use crate::energy::energy_actor;
 | 
			
		||||
use crate::energy::energy_actor::RelaySyncStatus;
 | 
			
		||||
use crate::server::custom_error::HttpResult;
 | 
			
		||||
use crate::server::devices_api::jwt_parser::JWTRequest;
 | 
			
		||||
use crate::server::WebEnergyActor;
 | 
			
		||||
use actix_web::{web, HttpResponse};
 | 
			
		||||
use jsonwebtoken::{Algorithm, DecodingKey, Validation};
 | 
			
		||||
use openssl::nid::Nid;
 | 
			
		||||
use openssl::x509::{X509Req, X509};
 | 
			
		||||
use std::collections::HashSet;
 | 
			
		||||
use openssl::x509::X509Req;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, serde::Deserialize)]
 | 
			
		||||
pub struct EnrollRequest {
 | 
			
		||||
@@ -129,11 +127,6 @@ pub async fn get_certificate(query: web::Query<ReqWithDevID>, actor: WebEnergyAc
 | 
			
		||||
        .body(cert))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize)]
 | 
			
		||||
pub struct SyncRequest {
 | 
			
		||||
    payload: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
			
		||||
struct Claims {
 | 
			
		||||
    info: DeviceInfo,
 | 
			
		||||
@@ -145,68 +138,11 @@ struct SyncResult {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Synchronize device
 | 
			
		||||
pub async fn sync_device(body: web::Json<SyncRequest>, actor: WebEnergyActor) -> HttpResult {
 | 
			
		||||
    // First, we need to extract device kid from query
 | 
			
		||||
    let Ok(jwt_header) = jsonwebtoken::decode_header(&body.payload) else {
 | 
			
		||||
        log::error!("Failed to decode JWT header!");
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json("Failed to decode JWT header!"));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let Some(kid) = jwt_header.kid else {
 | 
			
		||||
        log::error!("Missing KID in JWT!");
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json("Missing KID in JWT!"));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Fetch device information
 | 
			
		||||
    let Some(device) = actor
 | 
			
		||||
        .send(energy_actor::GetSingleDevice(DeviceId(kid)))
 | 
			
		||||
        .await?
 | 
			
		||||
    else {
 | 
			
		||||
        log::error!("Sent a JWT for a device which does not exists!");
 | 
			
		||||
        return Ok(HttpResponse::NotFound().json("Sent a JWT for a device which does not exists!"));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if !device.validated {
 | 
			
		||||
        log::error!("Sent a JWT for a device which is not validated!");
 | 
			
		||||
        return Ok(HttpResponse::PreconditionFailed()
 | 
			
		||||
            .json("Sent a JWT for a device which is not validated!"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check certificate revocation status
 | 
			
		||||
    let cert_bytes = std::fs::read(AppConfig::get().device_cert_path(&device.id))?;
 | 
			
		||||
    let certificate = X509::from_pem(&cert_bytes)?;
 | 
			
		||||
 | 
			
		||||
    if pki::CertData::load_devices_ca()?.is_revoked(&certificate)? {
 | 
			
		||||
        log::error!("Sent a JWT using a revoked certificate!");
 | 
			
		||||
        return Ok(
 | 
			
		||||
            HttpResponse::PreconditionFailed().json("Sent a JWT using a revoked certificate!")
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let (key, alg) = match DecodingKey::from_ec_pem(&cert_bytes) {
 | 
			
		||||
        Ok(key) => (key, Algorithm::ES256),
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            log::warn!("Failed to decode certificate as EC certificate {e}, trying RSA...");
 | 
			
		||||
            (
 | 
			
		||||
                DecodingKey::from_rsa_pem(&cert_bytes).expect("Failed to decode RSA certificate"),
 | 
			
		||||
                Algorithm::RS256,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    let mut validation = Validation::new(alg);
 | 
			
		||||
    validation.validate_exp = false;
 | 
			
		||||
    validation.required_spec_claims = HashSet::default();
 | 
			
		||||
 | 
			
		||||
    let c = match jsonwebtoken::decode::<Claims>(&body.payload, &key, &validation) {
 | 
			
		||||
        Ok(c) => c,
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            log::error!("Failed to validate JWT! {e}");
 | 
			
		||||
            return Ok(HttpResponse::PreconditionFailed().json("Failed to validate JWT!"));
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
pub async fn sync_device(body: web::Json<JWTRequest>, actor: WebEnergyActor) -> HttpResult {
 | 
			
		||||
    let (device, claims) = body.0.parse_jwt::<Claims>(actor.clone()).await?;
 | 
			
		||||
 | 
			
		||||
    let relays = actor
 | 
			
		||||
        .send(energy_actor::SynchronizeDevice(device.id, c.claims.info))
 | 
			
		||||
        .send(energy_actor::SynchronizeDevice(device.id, claims.info))
 | 
			
		||||
        .await??;
 | 
			
		||||
 | 
			
		||||
    Ok(HttpResponse::Ok().json(SyncResult { relays }))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,4 @@
 | 
			
		||||
pub mod device_logging_controller;
 | 
			
		||||
pub mod jwt_parser;
 | 
			
		||||
pub mod mgmt_controller;
 | 
			
		||||
pub mod utils_controller;
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ use crate::constants;
 | 
			
		||||
use crate::crypto::pki;
 | 
			
		||||
use crate::energy::energy_actor::EnergyActorAddr;
 | 
			
		||||
use crate::server::auth_middleware::AuthChecker;
 | 
			
		||||
use crate::server::devices_api::{mgmt_controller, utils_controller};
 | 
			
		||||
use crate::server::devices_api::{device_logging_controller, mgmt_controller, utils_controller};
 | 
			
		||||
use crate::server::unsecure_server::*;
 | 
			
		||||
use crate::server::web_api::*;
 | 
			
		||||
use crate::server::web_app_controller;
 | 
			
		||||
@@ -226,6 +226,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
 | 
			
		||||
                "/devices_api/mgmt/sync",
 | 
			
		||||
                web::post().to(mgmt_controller::sync_device),
 | 
			
		||||
            )
 | 
			
		||||
            .route(
 | 
			
		||||
                "/devices_api/logging/record",
 | 
			
		||||
                web::post().to(device_logging_controller::report_log),
 | 
			
		||||
            )
 | 
			
		||||
            // Web app
 | 
			
		||||
            .route("/", web::get().to(web_app_controller::root_index))
 | 
			
		||||
            .route(
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user