Start to build sync route
This commit is contained in:
		@@ -1,11 +1,14 @@
 | 
			
		||||
use crate::app_config::AppConfig;
 | 
			
		||||
use crate::crypto::pki;
 | 
			
		||||
use crate::devices::device::{DeviceId, DeviceInfo};
 | 
			
		||||
use crate::energy::energy_actor;
 | 
			
		||||
use crate::server::custom_error::HttpResult;
 | 
			
		||||
use crate::server::WebEnergyActor;
 | 
			
		||||
use actix_web::{web, HttpResponse};
 | 
			
		||||
use jsonwebtoken::{Algorithm, DecodingKey, Validation};
 | 
			
		||||
use openssl::nid::Nid;
 | 
			
		||||
use openssl::x509::X509Req;
 | 
			
		||||
use openssl::x509::{X509Req, X509};
 | 
			
		||||
use std::collections::HashSet;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, serde::Deserialize)]
 | 
			
		||||
pub struct EnrollRequest {
 | 
			
		||||
@@ -124,3 +127,81 @@ pub async fn get_certificate(query: web::Query<ReqWithDevID>, actor: WebEnergyAc
 | 
			
		||||
        .content_type("application/x-pem-file")
 | 
			
		||||
        .body(cert))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize)]
 | 
			
		||||
pub struct SyncRequest {
 | 
			
		||||
    payload: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
			
		||||
struct Claims {
 | 
			
		||||
    info: DeviceInfo,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// 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!"));
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let res = actor
 | 
			
		||||
        .send(energy_actor::SynchronizeDevice(device.id, c.claims.info))
 | 
			
		||||
        .await??;
 | 
			
		||||
 | 
			
		||||
    Ok(HttpResponse::Ok().json(res))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -194,6 +194,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
 | 
			
		||||
                "/devices_api/mgmt/get_certificate",
 | 
			
		||||
                web::get().to(mgmt_controller::get_certificate),
 | 
			
		||||
            )
 | 
			
		||||
            .route(
 | 
			
		||||
                "/devices_api/mgmt/sync",
 | 
			
		||||
                web::post().to(mgmt_controller::sync_device),
 | 
			
		||||
            )
 | 
			
		||||
            // Web app
 | 
			
		||||
            .route("/", web::get().to(web_app_controller::root_index))
 | 
			
		||||
            .route(
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user