diff --git a/central_backend/src/devices/devices_list.rs b/central_backend/src/devices/devices_list.rs index d0e2fe0..c884206 100644 --- a/central_backend/src/devices/devices_list.rs +++ b/central_backend/src/devices/devices_list.rs @@ -1,4 +1,5 @@ use crate::app_config::AppConfig; +use crate::crypto::pki; use crate::devices::device::{Device, DeviceId, DeviceInfo}; use crate::utils::time_utils::time_secs; use openssl::x509::X509Req; @@ -10,6 +11,10 @@ pub enum DevicesListError { EnrollFailedDeviceAlreadyExists, #[error("Persist device config failed: the configuration of the device was not found!")] PersistFailedDeviceNotFound, + #[error("Validated device failed: the device does not exists!")] + ValidateDeviceFailedDeviceNotFound, + #[error("Validated device failed: the device is already validated!")] + ValidateDeviceFailedDeviceAlreadyValidated, } pub struct DevicesList(HashMap); @@ -96,6 +101,29 @@ impl DevicesList { self.0.clone().into_values().collect() } + /// Validate a device + pub fn validate(&mut self, id: &DeviceId) -> anyhow::Result<()> { + let dev = self + .0 + .get_mut(id) + .ok_or(DevicesListError::ValidateDeviceFailedDeviceNotFound)?; + + if dev.validated { + return Err(DevicesListError::ValidateDeviceFailedDeviceAlreadyValidated.into()); + } + + // Issue certificate + let csr = X509Req::from_pem(&std::fs::read(AppConfig::get().device_csr_path(id))?)?; + let cert = pki::gen_certificate_for_device(&csr)?; + std::fs::write(AppConfig::get().device_cert_path(id), cert)?; + + // Mark device as validated + dev.validated = true; + self.persist_dev_config(id)?; + + Ok(()) + } + /// Delete a device pub fn delete(&mut self, id: &DeviceId) -> anyhow::Result<()> { let crt_path = AppConfig::get().device_cert_path(id); diff --git a/central_backend/src/energy/energy_actor.rs b/central_backend/src/energy/energy_actor.rs index eb39584..eb72d03 100644 --- a/central_backend/src/energy/energy_actor.rs +++ b/central_backend/src/energy/energy_actor.rs @@ -94,6 +94,21 @@ impl Handler for EnergyActor { } } +/// Validate a device +#[derive(Message)] +#[rtype(result = "anyhow::Result<()>")] +pub struct ValidateDevice(pub DeviceId); + +impl Handler for EnergyActor { + type Result = anyhow::Result<()>; + + fn handle(&mut self, msg: ValidateDevice, _ctx: &mut Context) -> Self::Result { + log::info!("Requested to validate device {:?}...", &msg.0); + self.devices.validate(&msg.0)?; + Ok(()) + } +} + /// Delete a device #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] diff --git a/central_backend/src/server/servers.rs b/central_backend/src/server/servers.rs index 977d824..ab2021c 100644 --- a/central_backend/src/server/servers.rs +++ b/central_backend/src/server/servers.rs @@ -139,6 +139,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()> "/web_api/devices/list_validated", web::get().to(devices_controller::list_validated), ) + .route( + "/web_api/device/{id}/validate", + web::post().to(devices_controller::validate_device), + ) .route( "/web_api/device/{id}", web::delete().to(devices_controller::delete_device), @@ -152,6 +156,7 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()> "/devices_api/mgmt/enroll", web::post().to(mgmt_controller::enroll), ) + // TODO : check device status }) .bind_openssl(&AppConfig::get().listen_address, builder)? .run() diff --git a/central_backend/src/server/web_api/devices_controller.rs b/central_backend/src/server/web_api/devices_controller.rs index 482b344..1ab0db2 100644 --- a/central_backend/src/server/web_api/devices_controller.rs +++ b/central_backend/src/server/web_api/devices_controller.rs @@ -33,6 +33,15 @@ pub struct DeviceInPath { id: DeviceId, } +/// Validate a device +pub async fn validate_device(actor: WebEnergyActor, id: web::Path) -> HttpResult { + actor + .send(energy_actor::ValidateDevice(id.id.clone())) + .await??; + + Ok(HttpResponse::Accepted().finish()) +} + /// Delete a device pub async fn delete_device(actor: WebEnergyActor, id: web::Path) -> HttpResult { actor diff --git a/central_frontend/src/api/DeviceApi.ts b/central_frontend/src/api/DeviceApi.ts index a9f5a57..84d32c9 100644 --- a/central_frontend/src/api/DeviceApi.ts +++ b/central_frontend/src/api/DeviceApi.ts @@ -49,6 +49,15 @@ export class DeviceApi { }) ).data; } + /** + * Validate a device + */ + static async Validate(d: Device): Promise { + await APIClient.exec({ + uri: `/device/${encodeURIComponent(d.id)}/validate`, + method: "POST", + }); + } /** * Delete a device diff --git a/central_frontend/src/routes/PendingDevicesRoute.tsx b/central_frontend/src/routes/PendingDevicesRoute.tsx index 992e0ee..7000810 100644 --- a/central_frontend/src/routes/PendingDevicesRoute.tsx +++ b/central_frontend/src/routes/PendingDevicesRoute.tsx @@ -8,6 +8,7 @@ import { TableContainer, TableHead, TableRow, + Tooltip, } from "@mui/material"; import React from "react"; import { Device, DeviceApi } from "../api/DeviceApi"; @@ -18,6 +19,7 @@ import { useSnackbar } from "../hooks/context_providers/SnackbarProvider"; import { AsyncWidget } from "../widgets/AsyncWidget"; import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer"; import { TimeWidget } from "../widgets/TimeWidget"; +import CheckIcon from "@mui/icons-material/Check"; export function PendingDevicesRoute(): React.ReactElement { const loadKey = React.useRef(1); @@ -57,6 +59,21 @@ function PendingDevicesList(p: { const snackbar = useSnackbar(); const loadingMessage = useLoadingMessage(); + const validateDevice = async (d: Device) => { + try { + loadingMessage.show("Validating device..."); + await DeviceApi.Validate(d); + + snackbar("The device has been successfully validated!"); + p.onReload(); + } catch (e) { + console.error(`Failed to validate device! ${e})`); + alert("Failed to validate device!"); + } finally { + loadingMessage.hide(); + } + }; + const deleteDevice = async (d: Device) => { try { if ( @@ -109,9 +126,16 @@ function PendingDevicesList(p: { - deleteDevice(dev)}> - - + + validateDevice(dev)}> + + + + + deleteDevice(dev)}> + + + ))}