peach-workspace/peach-web/src/json_api.rs

540 lines
20 KiB
Rust

//! JSON API routes for PeachCloud.
//!
//! This module contains handlers which allow retrieval and modification of
//! device state via JSON.
//!
//! API ROUTES
//!
//! | Method | URL | Description |
//! | ------ | -------------------------------- | ----------------------------- |
//! | POST | /api/v1/device/reboot | Reboot device |
//! | POST | /api/v1/device/shutdown | Shutdown device |
//! | POST | /api/v1/network/activate_ap | |
//! | POST | /api/v1/network/activate_client | |
//! | GET | /api/v1/network/ip | |
//! | GET | /api/v1/network/rssi | |
//! | GET | /api/v1/network/ssid | |
//! | GET | /api/v1/network/state | |
//! | GET | /api/v1/network/status | |
//! | GET | /api/v1/network/wifi | Retrieve available networks |
//! | POST | /api/v1/network/wifi | Add WiFi AP credentials |
//! | POST | /api/v1/network/wifi/connect | Connect to WiFi access point |
//! | POST | /api/v1/network/wifi/disconnect | Disconnect WiFi access point |
//! | POST | /api/v1/network/wifi/forget | Forget / remove network |
//! | POST | /api/v1/network/wifi/modify | Modify network password |
//! | POST | /api/v1/network/wifi/usage | Update alert thresholds |
//! | POST | /api/v1/network/wifi/usage/reset | Reset stored data usage total |
//! | GET | /api/v1/ping | |
//! | GET | /api/v1/ping/network | Ping `peach-network` |
//! | GET | /api/v1/ping/oled | Ping `peach-oled` |
//! | GET | /api/v1/ping/stats | Ping `peach-stats` |
//! | POST | /api/v1/dns/configure | Modify dns configurations |
//! | POST | /api/v1/settings/change_password | Change password (logged in) |
//! | POST | /public/api/v1/reset_password | Change password (public) |
use log::{debug, warn};
use rocket::{get, post};
use rocket_contrib::json;
use rocket_contrib::json::{Json, JsonValue};
use serde::Serialize;
use peach_lib::dyndns_client::is_dns_updater_online;
use peach_lib::network_client;
use peach_lib::oled_client;
use peach_lib::stats_client;
use peach_lib::stats_client::Traffic;
use crate::common::{save_dns_configuration, save_password_form, save_reset_password_form};
use crate::device;
use crate::forms::{DnsForm, PasswordForm, ResetPasswordForm, Ssid, WiFi};
use crate::monitor;
use crate::monitor::Threshold;
#[derive(Serialize)]
pub struct JsonResponse {
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<JsonValue>,
#[serde(skip_serializing_if = "Option::is_none")]
pub msg: Option<String>,
}
// reboot the device
#[post("/api/v1/device/reboot")]
pub fn reboot_device() -> Json<JsonResponse> {
match device::reboot() {
Ok(_) => {
debug!("Going down for reboot...");
let status = "success".to_string();
let msg = "Going down for reboot.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
Err(_) => {
warn!("Reboot failed");
let status = "error".to_string();
let msg = "Failed to reboot the device.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
// shutdown the device
#[post("/api/v1/device/shutdown")]
pub fn shutdown_device() -> Json<JsonResponse> {
match device::shutdown() {
Ok(_) => {
debug!("Going down for shutdown...");
let status = "success".to_string();
let msg = "Going down for shutdown.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
Err(_) => {
warn!("Shutdown failed");
let status = "error".to_string();
let msg = "Failed to shutdown the device.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
#[post("/api/v1/network/activate_ap")]
pub fn activate_ap() -> Json<JsonResponse> {
// activate the wireless access point
debug!("Activating WiFi access point.");
match network_client::activate_ap() {
Ok(_) => {
let status = "success".to_string();
Json(build_json_response(status, None, None))
}
Err(_) => {
let status = "error".to_string();
let msg = "Failed to activate WiFi access point.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
#[post("/api/v1/network/activate_client")]
pub fn activate_client() -> Json<JsonResponse> {
// activate the wireless client
debug!("Activating WiFi client mode.");
match network_client::activate_client() {
Ok(_) => {
let status = "success".to_string();
Json(build_json_response(status, None, None))
}
Err(_) => {
let status = "error".to_string();
let msg = "Failed to activate WiFi client mode.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
#[get("/api/v1/network/ip")]
pub fn return_ip() -> Json<JsonResponse> {
// retrieve ip for wlan0 or set to x.x.x.x if not found
let wlan_ip = match network_client::ip("wlan0") {
Ok(ip) => ip,
Err(_) => "x.x.x.x".to_string(),
};
// retrieve ip for ap0 or set to x.x.x.x if not found
let ap_ip = match network_client::ip("ap0") {
Ok(ip) => ip,
Err(_) => "x.x.x.x".to_string(),
};
let data = json!({
"wlan0": wlan_ip,
"ap0": ap_ip
});
let status = "success".to_string();
Json(build_json_response(status, Some(data), None))
}
#[get("/api/v1/network/rssi")]
pub fn return_rssi() -> Json<JsonResponse> {
// retrieve rssi for connected network
match network_client::rssi("wlan0") {
Ok(rssi) => {
let status = "success".to_string();
let data = json!(rssi);
Json(build_json_response(status, Some(data), None))
}
Err(_) => {
let status = "success".to_string();
let msg = "Not currently connected to an access point.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
#[get("/api/v1/network/ssid")]
pub fn return_ssid() -> Json<JsonResponse> {
// retrieve ssid for connected network
match network_client::ssid("wlan0") {
Ok(network) => {
let status = "success".to_string();
let data = json!(network);
Json(build_json_response(status, Some(data), None))
}
Err(_) => {
let status = "success".to_string();
let msg = "Not currently connected to an access point.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
#[get("/api/v1/network/state")]
pub fn return_state() -> Json<JsonResponse> {
// retrieve state of wlan0 or set to x.x.x.x if not found
let wlan_state = match network_client::state("wlan0") {
Ok(state) => state,
Err(_) => "unavailable".to_string(),
};
// retrieve state for ap0 or set to x.x.x.x if not found
let ap_state = match network_client::state("ap0") {
Ok(state) => state,
Err(_) => "unavailable".to_string(),
};
let data = json!({
"wlan0": wlan_state,
"ap0": ap_state
});
let status = "success".to_string();
Json(build_json_response(status, Some(data), None))
}
#[get("/api/v1/network/status")]
pub fn return_status() -> Json<JsonResponse> {
// retrieve status info for wlan0 interface
match network_client::status("wlan0") {
Ok(network) => {
let status = "success".to_string();
let data = json!(network);
Json(build_json_response(status, Some(data), None))
}
Err(_) => {
let status = "success".to_string();
let msg = "Not currently connected to an access point.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
#[get("/api/v1/network/wifi")]
pub fn scan_networks() -> Json<JsonResponse> {
// retrieve scan results for access-points within range of wlan0
match network_client::available_networks("wlan0") {
Ok(networks) => {
let status = "success".to_string();
let data = json!(networks);
Json(build_json_response(status, Some(data), None))
}
Err(_) => {
let status = "success".to_string();
let msg = "Unable to scan for networks. Interface may be deactivated.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
#[post("/api/v1/network/wifi", data = "<wifi>")]
pub fn add_wifi(wifi: Json<WiFi>) -> Json<JsonResponse> {
// generate and write wifi config to wpa_supplicant
match network_client::add(&wifi.ssid, &wifi.pass) {
Ok(_) => {
debug!("Added WiFi credentials.");
// force reread of wpa_supplicant.conf file with new credentials
match network_client::reconfigure() {
Ok(_) => debug!("Successfully reconfigured wpa_supplicant."),
Err(_) => warn!("Failed to reconfigure wpa_supplicant."),
}
// json response for successful update
let status = "success".to_string();
let msg = "WiFi credentials added.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
Err(_) => {
debug!("Failed to add WiFi credentials.");
// json response for failed update
let status = "error".to_string();
let msg = "Failed to add WiFi credentials.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
#[post("/api/v1/network/wifi/connect", data = "<ssid>")]
pub fn connect_ap(ssid: Json<Ssid>) -> Json<JsonResponse> {
// retrieve the id for the given network ssid
match network_client::id("wlan0", &ssid.ssid) {
// attempt connection with the given network
Ok(id) => match network_client::connect(&id, "wlan0") {
Ok(_) => {
let status = "success".to_string();
let msg = "Connected to chosen network.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
Err(_) => {
let status = "error".to_string();
let msg = "Failed to connect to chosen network.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
},
Err(_) => {
let status = "error".to_string();
let msg = "Failed to retrieve the network ID.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
#[post("/api/v1/network/wifi/disconnect", data = "<ssid>")]
pub fn disconnect_ap(ssid: Json<Ssid>) -> Json<JsonResponse> {
// attempt to disable the current network for wlan0 interface
match network_client::disable("wlan0", &ssid.ssid) {
Ok(_) => {
let status = "success".to_string();
let msg = "Disconnected from WiFi network.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
Err(_) => {
let status = "error".to_string();
let msg = "Failed to disconnect from WiFi network.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
#[post("/api/v1/network/wifi/forget", data = "<network>")]
pub fn forget_ap(network: Json<Ssid>) -> Json<JsonResponse> {
let ssid = &network.ssid;
match network_client::forget("wlan0", ssid) {
Ok(_) => {
debug!("Removed WiFi credentials for chosen network.");
let status = "success".to_string();
let msg = "WiFi network credentials removed.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
Err(_) => {
warn!("Failed to remove WiFi credentials.");
let status = "error".to_string();
let msg = "Failed to remove WiFi network credentials.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
#[post("/api/v1/network/wifi/modify", data = "<wifi>")]
pub fn modify_password(wifi: Json<WiFi>) -> Json<JsonResponse> {
let ssid = &wifi.ssid;
let pass = &wifi.pass;
// we are using a helper function (`update`) to delete the old
// credentials and add the new ones. this is because the wpa_cli method
// for updating the password does not work.
match network_client::update("wlan0", ssid, pass) {
Ok(_) => {
debug!("WiFi password updated for chosen network.");
let status = "success".to_string();
let msg = "WiFi password updated.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
Err(_) => {
warn!("Failed to update WiFi password.");
let status = "error".to_string();
let msg = "Failed to update WiFi password.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
#[post("/api/v1/network/wifi/usage", data = "<thresholds>")]
pub fn update_wifi_alerts(thresholds: Json<Threshold>) -> Json<JsonResponse> {
match monitor::update_store(thresholds.into_inner()) {
Ok(_) => {
debug!("WiFi data usage thresholds updated.");
let status = "success".to_string();
let msg = "Updated alert threshold and flags.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
Err(_) => {
warn!("Failed to update WiFi data usage thresholds.");
let status = "error".to_string();
let msg = "Failed to update WiFi data usage thresholds.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
#[post("/api/v1/network/wifi/usage/reset")]
pub fn reset_data_total() -> Json<JsonResponse> {
match monitor::reset_data() {
Ok(_) => {
debug!("Reset network data usage total.");
let traffic = match network_client::traffic("wlan0") {
Ok(t) => t,
Err(_) => Traffic {
received: 0,
transmitted: 0,
rx_unit: None,
tx_unit: None,
},
};
// current wifi traffic values as bytes
let current_traffic = traffic.received + traffic.transmitted;
let data = json!(current_traffic);
let status = "success".to_string();
let msg = "Reset network data usage total.".to_string();
Json(build_json_response(status, Some(data), Some(msg)))
}
Err(_) => {
warn!("Failed to reset network data usage total.");
let status = "error".to_string();
let msg = "Failed to reset network data usage total.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
// status route: useful for checking connectivity from web client
#[get("/api/v1/ping")]
pub fn ping_pong() -> Json<JsonResponse> {
// ping pong
let status = "success".to_string();
let msg = "pong!".to_string();
Json(build_json_response(status, None, Some(msg)))
}
// test route: useful for ad hoc testing
#[get("/api/v1/test")]
pub fn test_route() -> Json<JsonResponse> {
let val = is_dns_updater_online().unwrap();
let status = "success".to_string();
let msg = val.to_string();
Json(build_json_response(status, None, Some(msg)))
}
// status route: check availability of `peach-network` microservice
#[get("/api/v1/ping/network")]
pub fn ping_network() -> Json<JsonResponse> {
match network_client::ping() {
Ok(_) => {
debug!("peach-network responded successfully");
let status = "success".to_string();
let msg = "peach-network is available.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
Err(_) => {
warn!("peach-network failed to respond");
let status = "error".to_string();
let msg = "peach-network is unavailable.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
// status route: check availability of `peach-oled` microservice
#[get("/api/v1/ping/oled")]
pub fn ping_oled() -> Json<JsonResponse> {
match oled_client::ping() {
Ok(_) => {
debug!("peach-oled responded successfully");
let status = "success".to_string();
let msg = "peach-oled is available.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
Err(_) => {
warn!("peach-oled failed to respond");
let status = "error".to_string();
let msg = "peach-oled is unavailable.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
// status route: check availability of `peach-stats` microservice
#[get("/api/v1/ping/stats")]
pub fn ping_stats() -> Json<JsonResponse> {
match stats_client::ping() {
Ok(_) => {
debug!("peach-stats responded successfully");
let status = "success".to_string();
let msg = "peach-stats is available.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
Err(_) => {
warn!("peach-stats failed to respond");
let status = "error".to_string();
let msg = "peach-stats is unavailable.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
}
}
#[post("/api/v1/dns/configure", data = "<dns_form>")]
pub fn save_dns_configuration_endpoint(dns_form: Json<DnsForm>) -> Json<JsonResponse> {
let result = save_dns_configuration(dns_form.into_inner());
match result {
Ok(_) => {
let status = "success".to_string();
let msg = "New dynamic dns configuration is now enabled".to_string();
Json(build_json_response(status, None, Some(msg)))
}
Err(err) => {
let status = "error".to_string();
let msg = format!("{}", err);
Json(build_json_response(status, None, Some(msg)))
}
}
}
#[post("/api/v1/settings/change_password", data = "<password_form>")]
pub fn save_password_form_endpoint(password_form: Json<PasswordForm>) -> Json<JsonResponse> {
let result = save_password_form(password_form.into_inner());
match result {
Ok(_) => {
let status = "success".to_string();
let msg = "Your password was successfully changed".to_string();
Json(build_json_response(status, None, Some(msg)))
}
Err(err) => {
let status = "error".to_string();
let msg = format!("{}", err);
Json(build_json_response(status, None, Some(msg)))
}
}
}
/// this reset password route is used by a user who is not logged in
/// and is specifically for users who have forgotten their password
/// all routes under /public/* are excluded from nginx basic auth via the nginx config
#[post("/public/api/v1/reset_password", data = "<reset_password_form>")]
pub fn reset_password_form_endpoint(
reset_password_form: Json<ResetPasswordForm>,
) -> Json<JsonResponse> {
let result = save_reset_password_form(reset_password_form.into_inner());
match result {
Ok(_) => {
let status = "success".to_string();
let msg = "New password is now saved. Return home to login.".to_string();
Json(build_json_response(status, None, Some(msg)))
}
Err(err) => {
let status = "error".to_string();
let msg = format!("{}", err);
Json(build_json_response(status, None, Some(msg)))
}
}
}
// HELPER FUNCTIONS
pub fn build_json_response(
status: String,
data: Option<JsonValue>,
msg: Option<String>,
) -> JsonResponse {
JsonResponse { status, data, msg }
}