Start to build sync route
This commit is contained in:
		
							
								
								
									
										65
									
								
								central_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										65
									
								
								central_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -510,6 +510,12 @@ version = "0.20.0"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
 | 
					checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "base64"
 | 
				
			||||||
 | 
					version = "0.21.7"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "base64"
 | 
					name = "base64"
 | 
				
			||||||
version = "0.22.1"
 | 
					version = "0.22.1"
 | 
				
			||||||
@@ -612,6 +618,7 @@ dependencies = [
 | 
				
			|||||||
 "foreign-types-shared",
 | 
					 "foreign-types-shared",
 | 
				
			||||||
 "futures",
 | 
					 "futures",
 | 
				
			||||||
 "futures-util",
 | 
					 "futures-util",
 | 
				
			||||||
 | 
					 "jsonwebtoken",
 | 
				
			||||||
 "lazy-regex",
 | 
					 "lazy-regex",
 | 
				
			||||||
 "lazy_static",
 | 
					 "lazy_static",
 | 
				
			||||||
 "libc",
 | 
					 "libc",
 | 
				
			||||||
@@ -1033,8 +1040,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			|||||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
 | 
					checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "cfg-if",
 | 
					 "cfg-if",
 | 
				
			||||||
 | 
					 "js-sys",
 | 
				
			||||||
 "libc",
 | 
					 "libc",
 | 
				
			||||||
 "wasi",
 | 
					 "wasi",
 | 
				
			||||||
 | 
					 "wasm-bindgen",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@@ -1357,6 +1366,21 @@ dependencies = [
 | 
				
			|||||||
 "wasm-bindgen",
 | 
					 "wasm-bindgen",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "jsonwebtoken"
 | 
				
			||||||
 | 
					version = "9.3.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "base64 0.21.7",
 | 
				
			||||||
 | 
					 "js-sys",
 | 
				
			||||||
 | 
					 "pem",
 | 
				
			||||||
 | 
					 "ring",
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 | 
					 "serde_json",
 | 
				
			||||||
 | 
					 "simple_asn1",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "language-tags"
 | 
					name = "language-tags"
 | 
				
			||||||
version = "0.3.2"
 | 
					version = "0.3.2"
 | 
				
			||||||
@@ -1498,12 +1522,31 @@ dependencies = [
 | 
				
			|||||||
 "tempfile",
 | 
					 "tempfile",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "num-bigint"
 | 
				
			||||||
 | 
					version = "0.4.6"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "num-integer",
 | 
				
			||||||
 | 
					 "num-traits",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "num-conv"
 | 
					name = "num-conv"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
 | 
					checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "num-integer"
 | 
				
			||||||
 | 
					version = "0.1.46"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "num-traits",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "num-traits"
 | 
					name = "num-traits"
 | 
				
			||||||
version = "0.2.19"
 | 
					version = "0.2.19"
 | 
				
			||||||
@@ -1607,6 +1650,16 @@ version = "1.0.15"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
 | 
					checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "pem"
 | 
				
			||||||
 | 
					version = "3.0.4"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "base64 0.22.1",
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "percent-encoding"
 | 
					name = "percent-encoding"
 | 
				
			||||||
version = "2.3.1"
 | 
					version = "2.3.1"
 | 
				
			||||||
@@ -2067,6 +2120,18 @@ dependencies = [
 | 
				
			|||||||
 "libc",
 | 
					 "libc",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "simple_asn1"
 | 
				
			||||||
 | 
					version = "0.6.2"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "num-bigint",
 | 
				
			||||||
 | 
					 "num-traits",
 | 
				
			||||||
 | 
					 "thiserror",
 | 
				
			||||||
 | 
					 "time",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "slab"
 | 
					name = "slab"
 | 
				
			||||||
version = "0.4.9"
 | 
					version = "0.4.9"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,4 +33,5 @@ lazy-regex = "3.2.0"
 | 
				
			|||||||
tokio = { version = "1.39.2", features = ["full"] }
 | 
					tokio = { version = "1.39.2", features = ["full"] }
 | 
				
			||||||
tokio_schedule = "0.3.2"
 | 
					tokio_schedule = "0.3.2"
 | 
				
			||||||
mime_guess = "2.0.5"
 | 
					mime_guess = "2.0.5"
 | 
				
			||||||
rust-embed = "8.5.0"
 | 
					rust-embed = "8.5.0"
 | 
				
			||||||
 | 
					jsonwebtoken = { version = "9.3.0", features = ["use_pem"] }
 | 
				
			||||||
@@ -76,6 +76,17 @@ impl CertData {
 | 
				
			|||||||
            crl: None,
 | 
					            crl: None,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Check if a certificate is revoked
 | 
				
			||||||
 | 
					    pub fn is_revoked(&self, cert: &X509) -> anyhow::Result<bool> {
 | 
				
			||||||
 | 
					        let crl = X509Crl::from_pem(&std::fs::read(
 | 
				
			||||||
 | 
					            self.crl.as_ref().ok_or(PKIError::MissingCRL)?,
 | 
				
			||||||
 | 
					        )?)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let res = crl.get_by_cert(cert);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(matches!(res, CrlStatus::Revoked(_)))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Generate private key
 | 
					/// Generate private key
 | 
				
			||||||
@@ -480,21 +491,10 @@ pub fn gen_certificate_for_device(csr: &X509Req) -> anyhow::Result<String> {
 | 
				
			|||||||
    Ok(String::from_utf8(cert)?)
 | 
					    Ok(String::from_utf8(cert)?)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Check if a certificate is revoked
 | 
					 | 
				
			||||||
fn is_revoked(cert: &X509, ca: &CertData) -> anyhow::Result<bool> {
 | 
					 | 
				
			||||||
    let crl = X509Crl::from_pem(&std::fs::read(
 | 
					 | 
				
			||||||
        ca.crl.as_ref().ok_or(PKIError::MissingCRL)?,
 | 
					 | 
				
			||||||
    )?)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let res = crl.get_by_cert(cert);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(matches!(res, CrlStatus::Revoked(_)))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Revoke a certificate
 | 
					/// Revoke a certificate
 | 
				
			||||||
pub fn revoke(cert: &X509, ca: &CertData) -> anyhow::Result<()> {
 | 
					pub fn revoke(cert: &X509, ca: &CertData) -> anyhow::Result<()> {
 | 
				
			||||||
    // Check if certificate is already revoked
 | 
					    // Check if certificate is already revoked
 | 
				
			||||||
    if is_revoked(cert, ca)? {
 | 
					    if ca.is_revoked(cert)? {
 | 
				
			||||||
        // No op
 | 
					        // No op
 | 
				
			||||||
        return Ok(());
 | 
					        return Ok(());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -238,3 +238,28 @@ impl Handler<DeleteDeviceRelay> for EnergyActor {
 | 
				
			|||||||
        self.devices.relay_delete(msg.0)
 | 
					        self.devices.relay_delete(msg.0)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Serialize)]
 | 
				
			||||||
 | 
					pub struct RelaySyncStatus {
 | 
				
			||||||
 | 
					    enabled: bool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Synchronize a device
 | 
				
			||||||
 | 
					#[derive(Message)]
 | 
				
			||||||
 | 
					#[rtype(result = "anyhow::Result<Vec<RelaySyncStatus>>")]
 | 
				
			||||||
 | 
					pub struct SynchronizeDevice(pub DeviceId, pub DeviceInfo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Handler<SynchronizeDevice> for EnergyActor {
 | 
				
			||||||
 | 
					    type Result = anyhow::Result<Vec<RelaySyncStatus>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn handle(&mut self, msg: SynchronizeDevice, _ctx: &mut Context<Self>) -> Self::Result {
 | 
				
			||||||
 | 
					        // TODO : implement real code
 | 
				
			||||||
 | 
					        let mut v = vec![];
 | 
				
			||||||
 | 
					        for i in 0..msg.1.max_relays {
 | 
				
			||||||
 | 
					            v.push(RelaySyncStatus {
 | 
				
			||||||
 | 
					                enabled: i % 2 == 0,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(v)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,14 @@
 | 
				
			|||||||
use crate::app_config::AppConfig;
 | 
					use crate::app_config::AppConfig;
 | 
				
			||||||
 | 
					use crate::crypto::pki;
 | 
				
			||||||
use crate::devices::device::{DeviceId, DeviceInfo};
 | 
					use crate::devices::device::{DeviceId, DeviceInfo};
 | 
				
			||||||
use crate::energy::energy_actor;
 | 
					use crate::energy::energy_actor;
 | 
				
			||||||
use crate::server::custom_error::HttpResult;
 | 
					use crate::server::custom_error::HttpResult;
 | 
				
			||||||
use crate::server::WebEnergyActor;
 | 
					use crate::server::WebEnergyActor;
 | 
				
			||||||
use actix_web::{web, HttpResponse};
 | 
					use actix_web::{web, HttpResponse};
 | 
				
			||||||
 | 
					use jsonwebtoken::{Algorithm, DecodingKey, Validation};
 | 
				
			||||||
use openssl::nid::Nid;
 | 
					use openssl::nid::Nid;
 | 
				
			||||||
use openssl::x509::X509Req;
 | 
					use openssl::x509::{X509Req, X509};
 | 
				
			||||||
 | 
					use std::collections::HashSet;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Deserialize)]
 | 
					#[derive(Debug, serde::Deserialize)]
 | 
				
			||||||
pub struct EnrollRequest {
 | 
					pub struct EnrollRequest {
 | 
				
			||||||
@@ -124,3 +127,81 @@ pub async fn get_certificate(query: web::Query<ReqWithDevID>, actor: WebEnergyAc
 | 
				
			|||||||
        .content_type("application/x-pem-file")
 | 
					        .content_type("application/x-pem-file")
 | 
				
			||||||
        .body(cert))
 | 
					        .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",
 | 
					                "/devices_api/mgmt/get_certificate",
 | 
				
			||||||
                web::get().to(mgmt_controller::get_certificate),
 | 
					                web::get().to(mgmt_controller::get_certificate),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/devices_api/mgmt/sync",
 | 
				
			||||||
 | 
					                web::post().to(mgmt_controller::sync_device),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            // Web app
 | 
					            // Web app
 | 
				
			||||||
            .route("/", web::get().to(web_app_controller::root_index))
 | 
					            .route("/", web::get().to(web_app_controller::root_index))
 | 
				
			||||||
            .route(
 | 
					            .route(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -91,3 +91,4 @@ def sync_device(dev_id: str, privkey):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    print(encoded)
 | 
					    print(encoded)
 | 
				
			||||||
    print(res)
 | 
					    print(res)
 | 
				
			||||||
 | 
					    print(res.text)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user