27 Commits

Author SHA1 Message Date
c8d0a2ddf6 Merge pull request 'Add ENV VAR to allow disabling Rocket authentication' (#36) from disable_auth into main
Reviewed-on: PeachCloud/peach-workspace#36
2021-11-25 10:10:54 +00:00
adc1a5bd77 update lockfile 2021-11-25 11:33:25 +02:00
d760f9f92c add init_rocket test wrapper 2021-11-25 11:33:09 +02:00
5c4ef4a529 update auth docs 2021-11-25 11:15:39 +02:00
35ff408365 use rocket config to (en|dis)able auth 2021-11-25 11:12:50 +02:00
c2b785f54b remove unnecessary websocket dependency 2021-11-25 11:12:05 +02:00
1dc740eeae Merge pull request 'Custom error implementation for peach-oled' (#34) from lean_refactor into main
Reviewed-on: PeachCloud/peach-workspace#34
2021-11-24 09:16:19 +00:00
b3c6138e03 Merge pull request 'Fix broken JS and tweak styling (CSS & HTML)' (#33) from js_and_style_fixes into main
Reviewed-on: PeachCloud/peach-workspace#33
2021-11-24 09:15:50 +00:00
b59e62f920 bump crate version 2021-11-23 12:30:34 +02:00
3325706dcb document disable auth flag 2021-11-23 12:30:24 +02:00
bc28a84ad4 disable auth for tests 2021-11-23 12:30:09 +02:00
bb34bdd653 add auth flag check 2021-11-23 12:29:58 +02:00
ac98bde760 lockfile update 2021-11-23 10:53:09 +02:00
361b159299 update version number 2021-11-23 10:52:40 +02:00
7344d6f4e0 minor variable name changes and updated error handling 2021-11-23 10:52:26 +02:00
8d18e712a1 implement custom error 2021-11-23 10:51:54 +02:00
116afe78fd remove snafu and bump deps 2021-11-23 10:51:42 +02:00
7e3c500b1e Merge branch 'main' into js_and_style_fixes 2021-11-22 15:57:44 +02:00
b59eb22082 fix function names and remove flashMsg code duplication 2021-11-22 15:31:39 +02:00
ee1da0599c remove status update logic 2021-11-22 15:30:45 +02:00
e5f9a9be83 remove duplicate flashMsg code 2021-11-22 15:30:18 +02:00
e54ff8829a remove duplicate flashMsg code 2021-11-22 15:29:40 +02:00
554997a5c0 fix context back and title 2021-11-18 13:49:10 +02:00
da51070ccd improve template styling consistency 2021-11-18 11:48:37 +02:00
925051a379 improve not_found template 2021-11-18 11:48:03 +02:00
380ee2683a fix js 2021-11-18 11:47:50 +02:00
bae3b7c2ce tweak styling 2021-11-18 11:47:33 +02:00
35 changed files with 652 additions and 1162 deletions

218
Cargo.lock generated
View File

@ -204,15 +204,6 @@ dependencies = [
"safemem", "safemem",
] ]
[[package]]
name = "base64"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
dependencies = [
"byteorder",
]
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.13.0" version = "0.13.0"
@ -473,22 +464,6 @@ dependencies = [
"version_check 0.9.3", "version_check 0.9.3",
] ]
[[package]]
name = "core-foundation"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.1" version = "0.2.1"
@ -1339,25 +1314,6 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.10.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273"
dependencies = [
"base64 0.9.3",
"httparse",
"language-tags",
"log 0.3.9",
"mime 0.2.6",
"num_cpus",
"time 0.1.44",
"traitobject",
"typeable",
"unicase 1.4.2",
"url",
]
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.11.27" version = "0.11.27"
@ -1372,7 +1328,7 @@ dependencies = [
"iovec", "iovec",
"language-tags", "language-tags",
"log 0.4.14", "log 0.4.14",
"mime 0.3.16", "mime",
"net2", "net2",
"percent-encoding 1.0.1", "percent-encoding 1.0.1",
"relay", "relay",
@ -1381,7 +1337,7 @@ dependencies = [
"tokio-io", "tokio-io",
"tokio-proto", "tokio-proto",
"tokio-service", "tokio-service",
"unicase 2.6.0", "unicase",
"want 0.0.4", "want 0.0.4",
] ]
@ -1674,7 +1630,7 @@ dependencies = [
"jsonrpc-server-utils 11.0.0", "jsonrpc-server-utils 11.0.0",
"log 0.4.14", "log 0.4.14",
"net2", "net2",
"unicase 2.6.0", "unicase",
] ]
[[package]] [[package]]
@ -1690,7 +1646,7 @@ dependencies = [
"log 0.4.14", "log 0.4.14",
"net2", "net2",
"parking_lot 0.11.2", "parking_lot 0.11.2",
"unicase 2.6.0", "unicase",
] ]
[[package]] [[package]]
@ -1734,7 +1690,7 @@ dependencies = [
"num_cpus", "num_cpus",
"tokio 0.1.22", "tokio 0.1.22",
"tokio-codec", "tokio-codec",
"unicase 2.6.0", "unicase",
] ]
[[package]] [[package]]
@ -1752,7 +1708,7 @@ dependencies = [
"tokio 1.13.0", "tokio 1.13.0",
"tokio-stream", "tokio-stream",
"tokio-util", "tokio-util",
"unicase 2.6.0", "unicase",
] ]
[[package]] [[package]]
@ -1972,15 +1928,6 @@ dependencies = [
"autocfg 1.0.1", "autocfg 1.0.1",
] ]
[[package]]
name = "mime"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0"
dependencies = [
"log 0.3.9",
]
[[package]] [[package]]
name = "mime" name = "mime"
version = "0.3.16" version = "0.3.16"
@ -2119,7 +2066,7 @@ dependencies = [
"http 0.2.5", "http 0.2.5",
"httparse", "httparse",
"log 0.4.14", "log 0.4.14",
"mime 0.3.16", "mime",
"spin", "spin",
"tokio 1.13.0", "tokio 1.13.0",
"tokio-util", "tokio-util",
@ -2127,24 +2074,6 @@ dependencies = [
"version_check 0.9.3", "version_check 0.9.3",
] ]
[[package]]
name = "native-tls"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d"
dependencies = [
"lazy_static",
"libc",
"log 0.4.14",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]] [[package]]
name = "nb" name = "nb"
version = "0.1.3" version = "0.1.3"
@ -2409,12 +2338,6 @@ dependencies = [
"openssl-sys", "openssl-sys",
] ]
[[package]]
name = "openssl-probe"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
[[package]] [[package]]
name = "openssl-src" name = "openssl-src"
version = "300.0.2+3.0.0" version = "300.0.2+3.0.0"
@ -2571,7 +2494,7 @@ dependencies = [
[[package]] [[package]]
name = "peach-lib" name = "peach-lib"
version = "1.3.0" version = "1.2.15"
dependencies = [ dependencies = [
"chrono", "chrono",
"env_logger 0.6.2", "env_logger 0.6.2",
@ -2584,8 +2507,10 @@ dependencies = [
"regex", "regex",
"rust-crypto", "rust-crypto",
"serde 1.0.130", "serde 1.0.130",
"serde_derive",
"serde_json", "serde_json",
"serde_yaml", "serde_yaml",
"snafu 0.6.10",
] ]
[[package]] [[package]]
@ -2638,19 +2563,17 @@ dependencies = [
[[package]] [[package]]
name = "peach-oled" name = "peach-oled"
version = "0.1.3" version = "0.1.4"
dependencies = [ dependencies = [
"embedded-graphics", "embedded-graphics",
"env_logger 0.6.2", "env_logger 0.9.0",
"jsonrpc-core 11.0.0", "jsonrpc-core 18.0.0",
"jsonrpc-http-server 11.0.0", "jsonrpc-http-server 18.0.0",
"jsonrpc-test 11.0.0", "jsonrpc-test 18.0.0",
"linux-embedded-hal", "linux-embedded-hal",
"log 0.4.14", "log 0.4.14",
"nix 0.11.1", "nix 0.11.1",
"serde 1.0.130", "serde 1.0.130",
"serde_json",
"snafu 0.4.4",
"ssd1306", "ssd1306",
"tinybmp", "tinybmp",
] ]
@ -2691,7 +2614,7 @@ dependencies = [
[[package]] [[package]]
name = "peach-web" name = "peach-web"
version = "0.4.11" version = "0.4.12"
dependencies = [ dependencies = [
"env_logger 0.8.4", "env_logger 0.8.4",
"log 0.4.14", "log 0.4.14",
@ -2706,7 +2629,6 @@ dependencies = [
"serde_json", "serde_json",
"snafu 0.6.10", "snafu 0.6.10",
"tera", "tera",
"websocket",
"xdg", "xdg",
] ]
@ -3364,7 +3286,7 @@ dependencies = [
"indexmap", "indexmap",
"log 0.4.14", "log 0.4.14",
"memchr", "memchr",
"mime 0.3.16", "mime",
"parking_lot 0.11.2", "parking_lot 0.11.2",
"pear", "pear",
"percent-encoding 2.1.0", "percent-encoding 2.1.0",
@ -3467,16 +3389,6 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "schannel"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
dependencies = [
"lazy_static",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "scoped-tls" name = "scoped-tls"
version = "0.1.2" version = "0.1.2"
@ -3501,29 +3413,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "security-framework"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "semver" name = "semver"
version = "0.1.20" version = "0.1.20"
@ -4431,17 +4320,6 @@ dependencies = [
"tokio-executor", "tokio-executor",
] ]
[[package]]
name = "tokio-tls"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "354b8cd83825b3c20217a9dc174d6a0c67441a2fae5c41bcb1ea6679f6ae0f7c"
dependencies = [
"futures 0.1.31",
"native-tls",
"tokio-io",
]
[[package]] [[package]]
name = "tokio-udp" name = "tokio-udp"
version = "0.1.6" version = "0.1.6"
@ -4580,12 +4458,6 @@ dependencies = [
"tracing-serde", "tracing-serde",
] ]
[[package]]
name = "traitobject"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"
[[package]] [[package]]
name = "try-lock" name = "try-lock"
version = "0.1.0" version = "0.1.0"
@ -4608,12 +4480,6 @@ dependencies = [
"unchecked-index", "unchecked-index",
] ]
[[package]]
name = "typeable"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887"
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.14.0" version = "1.14.0"
@ -4701,15 +4567,6 @@ dependencies = [
"unic-common", "unic-common",
] ]
[[package]]
name = "unicase"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33"
dependencies = [
"version_check 0.1.5",
]
[[package]] [[package]]
name = "unicase" name = "unicase"
version = "2.6.0" version = "2.6.0"
@ -4918,47 +4775,6 @@ version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
[[package]]
name = "websocket"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "723abe6b75286edc51d8ecabb38a2353f62a9e9b0588998b59111474f1dcd637"
dependencies = [
"bytes 0.4.12",
"futures 0.1.31",
"hyper 0.10.16",
"native-tls",
"rand 0.6.5",
"tokio-codec",
"tokio-io",
"tokio-reactor",
"tokio-tcp",
"tokio-tls",
"unicase 1.4.2",
"url",
"websocket-base",
]
[[package]]
name = "websocket-base"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403f3fd505ff930da84156389639932955fb09705b3dccd1a3d60c8e7ff62776"
dependencies = [
"base64 0.10.1",
"bitflags 1.3.2",
"byteorder",
"bytes 0.4.12",
"futures 0.1.31",
"native-tls",
"rand 0.6.5",
"sha-1",
"tokio-codec",
"tokio-io",
"tokio-tcp",
"tokio-tls",
]
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.2.8" version = "0.2.8"

View File

@ -1,9 +1,11 @@
[package] [package]
name = "peach-lib" name = "peach-lib"
version = "1.3.0" version = "1.2.15"
authors = ["Andrew Reid <glyph@mycelial.technology>"] authors = ["Andrew Reid <gnomad@cryptolab.net>"]
edition = "2018" edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
log = "0.4" log = "0.4"
jsonrpc-client-core = "0.5" jsonrpc-client-core = "0.5"
@ -11,9 +13,11 @@ jsonrpc-client-http = "0.5"
jsonrpc-core = "8.0.1" jsonrpc-core = "8.0.1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
rust-crypto = "0.2.36" rust-crypto = "0.2.36"
serde_derive = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_yaml = "0.8" serde_yaml = "0.8"
env_logger = "0.6" env_logger = "0.6"
snafu = "0.6"
regex = "1" regex = "1"
chrono = "0.4.19" chrono = "0.4.19"
rand="0.8.4" rand="0.8.4"

View File

@ -4,12 +4,12 @@
//! //!
//! The configuration file is located at: "/var/lib/peachcloud/config.yml" //! The configuration file is located at: "/var/lib/peachcloud/config.yml"
use fslock::{LockFile};
use serde::{Deserialize, Serialize};
use std::fs; use std::fs;
use fslock::LockFile;
use serde::{Deserialize, Serialize};
use crate::error::PeachError; use crate::error::PeachError;
use crate::error::*;
// main configuration file // main configuration file
pub const YAML_PATH: &str = "/var/lib/peachcloud/config.yml"; pub const YAML_PATH: &str = "/var/lib/peachcloud/config.yml";
@ -48,9 +48,8 @@ fn save_peach_config(peach_config: PeachConfig) -> Result<PeachConfig, PeachErro
let yaml_str = serde_yaml::to_string(&peach_config)?; let yaml_str = serde_yaml::to_string(&peach_config)?;
fs::write(YAML_PATH, yaml_str).map_err(|source| PeachError::Write { fs::write(YAML_PATH, yaml_str).context(WriteConfigError {
source, file: YAML_PATH.to_string(),
path: YAML_PATH.to_string(),
})?; })?;
// unlock file lock // unlock file lock
@ -80,9 +79,8 @@ pub fn load_peach_config() -> Result<PeachConfig, PeachError> {
} }
// otherwise we load peach config from disk // otherwise we load peach config from disk
else { else {
let contents = fs::read_to_string(YAML_PATH).map_err(|source| PeachError::Read { let contents = fs::read_to_string(YAML_PATH).context(ReadConfigError {
source, file: YAML_PATH.to_string(),
path: YAML_PATH.to_string(),
})?; })?;
peach_config = serde_yaml::from_str(&contents)?; peach_config = serde_yaml::from_str(&contents)?;
} }

View File

@ -9,24 +9,24 @@
//! //!
//! The domain for dyndns updates is stored in /var/lib/peachcloud/config.yml //! The domain for dyndns updates is stored in /var/lib/peachcloud/config.yml
//! The tsig key for authenticating the updates is stored in /var/lib/peachcloud/peach-dyndns/tsig.key //! The tsig key for authenticating the updates is stored in /var/lib/peachcloud/peach-dyndns/tsig.key
use std::{ use crate::config_manager::{load_peach_config, set_peach_dyndns_config};
fs, use crate::error::PeachError;
fs::OpenOptions, use crate::error::{
io::Write, ChronoParseError, DecodeNsUpdateOutputError, DecodePublicIpError, GetPublicIpError,
process::{Command, Stdio}, NsCommandError, SaveDynDnsResultError, SaveTsigKeyError,
str::FromStr,
}; };
use chrono::prelude::*; use chrono::prelude::*;
use jsonrpc_client_core::{expand_params, jsonrpc_client}; use jsonrpc_client_core::{expand_params, jsonrpc_client};
use jsonrpc_client_http::HttpTransport; use jsonrpc_client_http::HttpTransport;
use log::{debug, info}; use log::{debug, info};
use regex::Regex; use regex::Regex;
use snafu::ResultExt;
use crate::{ use std::fs;
config_manager::{load_peach_config, set_peach_dyndns_config}, use std::fs::OpenOptions;
error::PeachError, use std::io::Write;
}; use std::process::{Command, Stdio};
use std::str::FromStr;
use std::str::ParseBoolError;
/// constants for dyndns configuration /// constants for dyndns configuration
pub const PEACH_DYNDNS_URL: &str = "http://dynserver.dyn.peachcloud.org"; pub const PEACH_DYNDNS_URL: &str = "http://dynserver.dyn.peachcloud.org";
@ -37,21 +37,20 @@ pub const DYNDNS_LOG_PATH: &str = "/var/lib/peachcloud/peach-dyndns/latest_resul
/// helper function which saves dyndns TSIG key returned by peach-dyndns-server to /var/lib/peachcloud/peach-dyndns/tsig.key /// helper function which saves dyndns TSIG key returned by peach-dyndns-server to /var/lib/peachcloud/peach-dyndns/tsig.key
pub fn save_dyndns_key(key: &str) -> Result<(), PeachError> { pub fn save_dyndns_key(key: &str) -> Result<(), PeachError> {
// create directory if it doesn't exist // create directory if it doesn't exist
fs::create_dir_all(PEACH_DYNDNS_CONFIG_PATH)?; fs::create_dir_all(PEACH_DYNDNS_CONFIG_PATH).context(SaveTsigKeyError {
//.context(SaveTsigKeyError { path: PEACH_DYNDNS_CONFIG_PATH.to_string(),
//path: PEACH_DYNDNS_CONFIG_PATH.to_string(), })?;
//})?;
// write key text // write key text
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
// TODO: consider adding context msg .open(TSIG_KEY_PATH)
.open(TSIG_KEY_PATH)?; .context(SaveTsigKeyError {
writeln!(file, "{}", key).map_err(|source| PeachError::Write { path: TSIG_KEY_PATH.to_string(),
source, })?;
writeln!(file, "{}", key).context(SaveTsigKeyError {
path: TSIG_KEY_PATH.to_string(), path: TSIG_KEY_PATH.to_string(),
})?; })?;
Ok(()) Ok(())
} }
@ -69,17 +68,23 @@ pub fn register_domain(domain: &str) -> std::result::Result<String, PeachError>
let mut client = PeachDynDnsClient::new(transport_handle); let mut client = PeachDynDnsClient::new(transport_handle);
info!("Performing register_domain call to peach-dyndns-server"); info!("Performing register_domain call to peach-dyndns-server");
let key = client.register_domain(domain).call()?; let res = client.register_domain(domain).call();
// save new TSIG key match res {
save_dyndns_key(&key)?; Ok(key) => {
// save new configuration values // save new TSIG key
let set_config_result = set_peach_dyndns_config(domain, PEACH_DYNDNS_URL, TSIG_KEY_PATH, true); save_dyndns_key(&key)?;
match set_config_result { // save new configuration values
Ok(_) => { let set_config_result =
let response = "success".to_string(); set_peach_dyndns_config(domain, PEACH_DYNDNS_URL, TSIG_KEY_PATH, true);
Ok(response) match set_config_result {
Ok(_) => {
let response = "success".to_string();
Ok(response)
}
Err(err) => Err(err),
}
} }
Err(err) => Err(err), Err(err) => Err(PeachError::JsonRpcClientCore { source: err }),
} }
} }
@ -94,20 +99,29 @@ pub fn is_domain_available(domain: &str) -> std::result::Result<bool, PeachError
let mut client = PeachDynDnsClient::new(transport_handle); let mut client = PeachDynDnsClient::new(transport_handle);
info!("Performing register_domain call to peach-dyndns-server"); info!("Performing register_domain call to peach-dyndns-server");
let domain_availability = client.is_domain_available(domain).call()?; let res = client.is_domain_available(domain).call();
info!("Domain availability: {:?}", domain_availability); info!("res: {:?}", res);
// convert availability status to a bool match res {
let available: bool = FromStr::from_str(&domain_availability)?; Ok(result_str) => {
let result: Result<bool, ParseBoolError> = FromStr::from_str(&result_str);
Ok(available) match result {
Ok(result_bool) => Ok(result_bool),
Err(err) => Err(PeachError::PeachParseBoolError { source: err }),
}
}
Err(err) => Err(PeachError::JsonRpcClientCore { source: err }),
}
} }
/// Helper function to get public ip address of PeachCloud device. /// Helper function to get public ip address of PeachCloud device.
fn get_public_ip_address() -> Result<String, PeachError> { fn get_public_ip_address() -> Result<String, PeachError> {
// TODO: consider other ways to get public IP address // TODO: consider other ways to get public IP address
let output = Command::new("/usr/bin/curl").arg("ifconfig.me").output()?; let output = Command::new("/usr/bin/curl")
let command_output = String::from_utf8(output.stdout)?; .arg("ifconfig.me")
Ok(command_output) .output()
.context(GetPublicIpError)?;
let command_output = std::str::from_utf8(&output.stdout).context(DecodePublicIpError)?;
Ok(command_output.to_string())
} }
/// Reads dyndns configurations from config.yml /// Reads dyndns configurations from config.yml
@ -132,12 +146,13 @@ pub fn dyndns_update_ip() -> Result<bool, PeachError> {
Ok(false) Ok(false)
} else { } else {
// call nsupdate passing appropriate configs // call nsupdate passing appropriate configs
let mut nsupdate_command = Command::new("/usr/bin/nsupdate") let nsupdate_command = Command::new("/usr/bin/nsupdate")
.arg("-k") .arg("-k")
.arg(&peach_config.dyn_tsig_key_path) .arg(peach_config.dyn_tsig_key_path)
.arg("-v") .arg("-v")
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.spawn()?; .spawn()
.context(NsCommandError)?;
// pass nsupdate commands via stdin // pass nsupdate commands via stdin
let public_ip_address = get_public_ip_address()?; let public_ip_address = get_public_ip_address()?;
info!("found public ip address: {}", public_ip_address); info!("found public ip address: {}", public_ip_address);
@ -153,15 +168,11 @@ pub fn dyndns_update_ip() -> Result<bool, PeachError> {
DOMAIN = peach_config.dyn_domain, DOMAIN = peach_config.dyn_domain,
PUBLIC_IP_ADDRESS = public_ip_address, PUBLIC_IP_ADDRESS = public_ip_address,
); );
let mut nsupdate_stdin = nsupdate_command.stdin.take().ok_or(PeachError::NsUpdate { write!(nsupdate_command.stdin.as_ref().unwrap(), "{}", ns_commands).unwrap();
msg: "unable to capture stdin handle for `nsupdate` command".to_string(), let nsupdate_output = nsupdate_command
})?; .wait_with_output()
write!(nsupdate_stdin, "{}", ns_commands).map_err(|source| PeachError::Write { .context(NsCommandError)?;
source, info!("output: {:?}", nsupdate_output);
path: peach_config.dyn_tsig_key_path.to_string(),
})?;
let nsupdate_output = nsupdate_command.wait_with_output()?;
info!("nsupdate output: {:?}", nsupdate_output);
// We only return a successful result if nsupdate was successful // We only return a successful result if nsupdate was successful
if nsupdate_output.status.success() { if nsupdate_output.status.success() {
info!("nsupdate succeeded, returning ok"); info!("nsupdate succeeded, returning ok");
@ -171,8 +182,9 @@ pub fn dyndns_update_ip() -> Result<bool, PeachError> {
Ok(true) Ok(true)
} else { } else {
info!("nsupdate failed, returning error"); info!("nsupdate failed, returning error");
let err_msg = String::from_utf8(nsupdate_output.stdout)?; let err_msg =
Err(PeachError::NsUpdate { msg: err_msg }) String::from_utf8(nsupdate_output.stdout).context(DecodeNsUpdateOutputError)?;
Err(PeachError::NsUpdateError { msg: err_msg })
} }
} }
} }
@ -183,12 +195,9 @@ pub fn log_successful_nsupdate() -> Result<bool, PeachError> {
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
// TODO: possibly add a context msg here ("failed to open dynamic dns success log") .open(DYNDNS_LOG_PATH)
.open(DYNDNS_LOG_PATH)?; .context(SaveDynDnsResultError)?;
write!(file, "{}", now_timestamp).map_err(|source| PeachError::Write { write!(file, "{}", now_timestamp).context(SaveDynDnsResultError)?;
source,
path: DYNDNS_LOG_PATH.to_string(),
})?;
Ok(true) Ok(true)
} }
@ -198,19 +207,12 @@ pub fn get_num_seconds_since_successful_dns_update() -> Result<Option<i64>, Peac
if !log_exists { if !log_exists {
Ok(None) Ok(None)
} else { } else {
let contents = fs::read_to_string(DYNDNS_LOG_PATH).map_err(|source| PeachError::Read { let contents =
source, fs::read_to_string(DYNDNS_LOG_PATH).expect("Something went wrong reading the file");
path: DYNDNS_LOG_PATH.to_string(),
})?;
// replace newline if found // replace newline if found
// TODO: maybe we can use `.trim()` instead
let contents = contents.replace("\n", ""); let contents = contents.replace("\n", "");
// TODO: consider adding additional context? let time_ran_dt = DateTime::parse_from_rfc3339(&contents).context(ChronoParseError {
let time_ran_dt = DateTime::parse_from_rfc3339(&contents).map_err(|source| { msg: "Error parsing dyndns time from latest_result.log".to_string(),
PeachError::ParseDateTime {
source,
path: DYNDNS_LOG_PATH.to_string(),
}
})?; })?;
let current_time: DateTime<Utc> = Utc::now(); let current_time: DateTime<Utc> = Utc::now();
let duration = current_time.signed_duration_since(time_ran_dt); let duration = current_time.signed_duration_since(time_ran_dt);
@ -259,7 +261,6 @@ pub fn get_dyndns_subdomain(dyndns_full_domain: &str) -> Option<String> {
// helper function which checks if a dyndns domain is new // helper function which checks if a dyndns domain is new
pub fn check_is_new_dyndns_domain(dyndns_full_domain: &str) -> bool { pub fn check_is_new_dyndns_domain(dyndns_full_domain: &str) -> bool {
// TODO: return `Result<bool, PeachError>` and replace `unwrap` with `?` operator
let peach_config = load_peach_config().unwrap(); let peach_config = load_peach_config().unwrap();
let previous_dyndns_domain = peach_config.dyn_domain; let previous_dyndns_domain = peach_config.dyn_domain;
dyndns_full_domain != previous_dyndns_domain dyndns_full_domain != previous_dyndns_domain

View File

@ -1,222 +1,136 @@
#![warn(missing_docs)] //! Basic error handling for the network, OLED, stats and dyndns JSON-RPC clients.
pub use snafu::ResultExt;
use snafu::Snafu;
use std::error;
pub type BoxError = Box<dyn error::Error>;
//! Error handling for various aspects of the PeachCloud system, including the network, OLED, stats and dyndns JSON-RPC clients, as well as the configuration manager, sbot client and password utilities. #[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
use std::{io, str, string};
/// This type represents all possible errors that can occur when interacting with the PeachCloud library.
#[derive(Debug)]
pub enum PeachError { pub enum PeachError {
/// Represents all other cases of `std::io::Error`. #[snafu(display("{}", source))]
Io(io::Error), JsonRpcHttp { source: jsonrpc_client_http::Error },
#[snafu(display("{}", source))]
/// Represents a JSON-RPC core error returned from a JSON-RPC client. JsonRpcClientCore { source: jsonrpc_client_core::Error },
JsonRpcClientCore(jsonrpc_client_core::Error), #[snafu(display("{}", source))]
Serde { source: serde_json::error::Error },
/// Represents a JSON-RPC core error returned from a JSON-RPC server. #[snafu(display("{}", source))]
JsonRpcCore(jsonrpc_core::Error), PeachParseBoolError { source: std::str::ParseBoolError },
#[snafu(display("{}", source))]
/// Represents a JSON-RPC HTTP error returned from a JSON-RPC client. SetConfigError { source: serde_yaml::Error },
JsonRpcHttp(jsonrpc_client_http::Error), #[snafu(display("Failed to read: {}", file))]
ReadConfigError {
/// Represents a failure to update the nameserver. source: std::io::Error,
NsUpdate { file: String,
/// A message describing the context of the attempted nameserver update.
msg: String,
}, },
#[snafu(display("Failed to save: {}", file))]
/// Represents a failure to parse a string slice to a boolean value. WriteConfigError {
ParseBool(str::ParseBoolError), source: std::io::Error,
file: String,
/// Represents a failure to parse a `DateTime`. Includes the error source and the file path },
/// used in the parse attempt. #[snafu(display("Failed to save tsig key: {} {}", path, source))]
ParseDateTime { SaveTsigKeyError {
/// The underlying source of the error. source: std::io::Error,
path: String,
},
#[snafu(display("{}", msg))]
NsUpdateError { msg: String },
#[snafu(display("Failed to run nsupdate: {}", source))]
NsCommandError { source: std::io::Error },
#[snafu(display("Failed to get public IP address: {}", source))]
GetPublicIpError { source: std::io::Error },
#[snafu(display("Failed to decode public ip: {}", source))]
DecodePublicIpError { source: std::str::Utf8Error },
#[snafu(display("Failed to decode nsupdate output: {}", source))]
DecodeNsUpdateOutputError { source: std::string::FromUtf8Error },
#[snafu(display("{}", source))]
YamlError { source: serde_yaml::Error },
#[snafu(display("{:?}", err))]
JsonRpcCore { err: jsonrpc_core::Error },
#[snafu(display("Error creating regex: {}", source))]
RegexError { source: regex::Error },
#[snafu(display("Failed to decode utf8: {}", source))]
FromUtf8Error { source: std::string::FromUtf8Error },
#[snafu(display("Encountered Utf8Error: {}", source))]
Utf8Error { source: std::str::Utf8Error },
#[snafu(display("Stdio error: {}: {}", msg, source))]
StdIoError { source: std::io::Error, msg: String },
#[snafu(display("Failed to parse time from {} {}", source, msg))]
ChronoParseError {
source: chrono::ParseError, source: chrono::ParseError,
/// The file path for the parse attempt.
path: String,
},
/// Represents the submission of an incorrect admin password.
PasswordIncorrect,
/// Represents the submission of two passwords which do not match.
PasswordMismatch,
/// Represents an unset admin password (empty password hash value) in the config file.
PasswordNotSet,
/// Represents a failure to read from input. Includes the error source and the file path used
/// in the read attempt.
Read {
/// The underlying source of the error.
source: io::Error,
/// The file path for the read attempt.
path: String,
},
/// Represents a failure to parse or compile a regular expression.
Regex(regex::Error),
/// Represents a failure to successfully execute an sbot command.
SbotCli {
/// The `stderr` output from the sbot command.
msg: String, msg: String,
}, },
#[snafu(display("Failed to save dynamic dns success log: {}", source))]
/// Represents a failure to serialize or deserialize JSON. SaveDynDnsResultError { source: std::io::Error },
SerdeJson(serde_json::error::Error), #[snafu(display("New passwords do not match"))]
PasswordsDoNotMatch,
/// Represents a failure to serialize or deserialize YAML. #[snafu(display("No admin password is set"))]
SerdeYaml(serde_yaml::Error), PasswordNotSet,
#[snafu(display("The supplied password was not correct"))]
/// Represents a failure to find the given SSB ID in the config file. InvalidPassword,
SsbAdminIdNotFound { #[snafu(display("Error saving new password: {}", msg))]
/// An SSB ID (public key). FailedToSetNewPassword { msg: String },
id: String, #[snafu(display("Error calling sbotcli: {}", msg))]
}, SbotCliError { msg: String },
#[snafu(display("Error deleting ssb admin id, id not found"))]
/// Represents a failure to interpret a sequence of u8 as a string slice. SsbAdminIdNotFound { id: String },
Utf8ToStr(str::Utf8Error),
/// Represents a failure to interpret a sequence of u8 as a String.
Utf8ToString(string::FromUtf8Error),
/// Represents a failure to write to output. Includes the error source and the file path used
/// in the write attempt.
Write {
/// The underlying source of the error.
source: io::Error,
/// The file path for the write attemp.
path: String,
},
} }
impl std::error::Error for PeachError { impl From<jsonrpc_client_http::Error> for PeachError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { fn from(err: jsonrpc_client_http::Error) -> PeachError {
match *self { PeachError::JsonRpcHttp { source: err }
PeachError::Io(_) => None,
PeachError::JsonRpcClientCore(_) => None,
PeachError::JsonRpcCore(_) => None,
PeachError::JsonRpcHttp(_) => None,
PeachError::NsUpdate { .. } => None,
PeachError::ParseBool(_) => None,
PeachError::ParseDateTime { ref source, .. } => Some(source),
PeachError::PasswordIncorrect => None,
PeachError::PasswordMismatch => None,
PeachError::PasswordNotSet => None,
PeachError::Read { ref source, .. } => Some(source),
PeachError::Regex(_) => None,
PeachError::SbotCli { .. } => None,
PeachError::SerdeJson(_) => None,
PeachError::SerdeYaml(_) => None,
PeachError::SsbAdminIdNotFound { .. } => None,
PeachError::Utf8ToStr(_) => None,
PeachError::Utf8ToString(_) => None,
PeachError::Write { ref source, .. } => Some(source),
}
}
}
impl std::fmt::Display for PeachError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
PeachError::Io(ref err) => err.fmt(f),
PeachError::JsonRpcClientCore(ref err) => err.fmt(f),
PeachError::JsonRpcCore(ref err) => {
write!(f, "{:?}", err)
}
PeachError::JsonRpcHttp(ref err) => err.fmt(f),
PeachError::NsUpdate { ref msg } => {
write!(f, "Nameserver error: {}", msg)
}
PeachError::ParseBool(ref err) => err.fmt(f),
PeachError::ParseDateTime { ref path, .. } => {
write!(f, "Date/time parse error: {}", path)
}
PeachError::PasswordIncorrect => {
write!(f, "Password error: user-supplied password is incorrect")
}
PeachError::PasswordMismatch => {
write!(f, "Password error: user-supplied passwords do not match")
}
PeachError::PasswordNotSet => {
write!(
f,
"Password error: hash value in YAML configuration file is empty"
)
}
PeachError::Read { ref path, .. } => {
write!(f, "Read error: {}", path)
}
PeachError::Regex(ref err) => err.fmt(f),
PeachError::SbotCli { ref msg } => {
write!(f, "Sbot error: {}", msg)
}
PeachError::SerdeJson(ref err) => err.fmt(f),
PeachError::SerdeYaml(ref err) => err.fmt(f),
PeachError::SsbAdminIdNotFound { ref id } => {
write!(f, "Config error: SSB admin ID `{}` not found", id)
}
PeachError::Utf8ToStr(ref err) => err.fmt(f),
PeachError::Utf8ToString(ref err) => err.fmt(f),
PeachError::Write { ref path, .. } => {
write!(f, "Write error: {}", path)
}
}
}
}
impl From<std::io::Error> for PeachError {
fn from(err: std::io::Error) -> PeachError {
PeachError::Io(err)
} }
} }
impl From<jsonrpc_client_core::Error> for PeachError { impl From<jsonrpc_client_core::Error> for PeachError {
fn from(err: jsonrpc_client_core::Error) -> PeachError { fn from(err: jsonrpc_client_core::Error) -> PeachError {
PeachError::JsonRpcClientCore(err) PeachError::JsonRpcClientCore { source: err }
}
}
impl From<jsonrpc_client_http::Error> for PeachError {
fn from(err: jsonrpc_client_http::Error) -> PeachError {
PeachError::JsonRpcHttp(err)
}
}
impl From<str::ParseBoolError> for PeachError {
fn from(err: str::ParseBoolError) -> PeachError {
PeachError::ParseBool(err)
}
}
impl From<regex::Error> for PeachError {
fn from(err: regex::Error) -> PeachError {
PeachError::Regex(err)
} }
} }
impl From<serde_json::error::Error> for PeachError { impl From<serde_json::error::Error> for PeachError {
fn from(err: serde_json::error::Error) -> PeachError { fn from(err: serde_json::error::Error) -> PeachError {
PeachError::SerdeJson(err) PeachError::Serde { source: err }
} }
} }
impl From<serde_yaml::Error> for PeachError { impl From<serde_yaml::Error> for PeachError {
fn from(err: serde_yaml::Error) -> PeachError { fn from(err: serde_yaml::Error) -> PeachError {
PeachError::SerdeYaml(err) PeachError::YamlError { source: err }
} }
} }
impl From<str::Utf8Error> for PeachError { impl From<std::io::Error> for PeachError {
fn from(err: str::Utf8Error) -> PeachError { fn from(err: std::io::Error) -> PeachError {
PeachError::Utf8ToStr(err) PeachError::StdIoError {
source: err,
msg: "".to_string(),
}
} }
} }
impl From<string::FromUtf8Error> for PeachError { impl From<regex::Error> for PeachError {
fn from(err: string::FromUtf8Error) -> PeachError { fn from(err: regex::Error) -> PeachError {
PeachError::Utf8ToString(err) PeachError::RegexError { source: err }
}
}
impl From<std::string::FromUtf8Error> for PeachError {
fn from(err: std::string::FromUtf8Error) -> PeachError {
PeachError::FromUtf8Error { source: err }
}
}
impl From<std::str::Utf8Error> for PeachError {
fn from(err: std::str::Utf8Error) -> PeachError {
PeachError::Utf8Error { source: err }
}
}
impl From<chrono::ParseError> for PeachError {
fn from(err: chrono::ParseError) -> PeachError {
PeachError::ChronoParseError {
source: err,
msg: "".to_string(),
}
} }
} }

View File

@ -1,3 +1,7 @@
// this is to ignore a clippy warning that suggests
// to replace code with the same code that is already there (possibly a bug)
#![allow(clippy::nonstandard_macro_braces)]
pub mod config_manager; pub mod config_manager;
pub mod dyndns_client; pub mod dyndns_client;
pub mod error; pub mod error;

View File

@ -9,6 +9,9 @@
//! Several helper methods are also included here which bundle multiple client //! Several helper methods are also included here which bundle multiple client
//! calls to achieve the desired functionality. //! calls to achieve the desired functionality.
// TODO: fix these clippy errors so this allow can be removed
#![allow(clippy::needless_borrow)]
use std::env; use std::env;
use jsonrpc_client_core::{expand_params, jsonrpc_client}; use jsonrpc_client_core::{expand_params, jsonrpc_client};
@ -163,9 +166,9 @@ pub fn disable(iface: &str, ssid: &str) -> std::result::Result<String, PeachErro
let mut client = PeachNetworkClient::new(transport_handle); let mut client = PeachNetworkClient::new(transport_handle);
info!("Performing id call to peach-network microservice."); info!("Performing id call to peach-network microservice.");
let id = client.id(iface, ssid).call()?; let id = client.id(&iface, &ssid).call()?;
info!("Performing disable call to peach-network microservice."); info!("Performing disable call to peach-network microservice.");
client.disable(&id, iface).call()?; client.disable(&id, &iface).call()?;
let response = "success".to_string(); let response = "success".to_string();
@ -191,12 +194,12 @@ pub fn forget(iface: &str, ssid: &str) -> std::result::Result<String, PeachError
let mut client = PeachNetworkClient::new(transport_handle); let mut client = PeachNetworkClient::new(transport_handle);
info!("Performing id call to peach-network microservice."); info!("Performing id call to peach-network microservice.");
let id = client.id(iface, ssid).call()?; let id = client.id(&iface, &ssid).call()?;
info!("Performing delete call to peach-network microservice."); info!("Performing delete call to peach-network microservice.");
// WEIRD BUG: the parameters below are technically in the wrong order: // WEIRD BUG: the parameters below are technically in the wrong order:
// it should be id first and then iface, but somehow they get twisted. // it should be id first and then iface, but somehow they get twisted.
// i don't understand computers. // i don't understand computers.
client.delete(iface, &id).call()?; client.delete(&iface, &id).call()?;
info!("Performing save call to peach-network microservice."); info!("Performing save call to peach-network microservice.");
client.save().call()?; client.save().call()?;
@ -354,7 +357,8 @@ pub fn saved_ap(ssid: &str) -> std::result::Result<bool, PeachError> {
// retrieve a list of access points with saved credentials // retrieve a list of access points with saved credentials
let saved_aps = match client.saved_networks().call() { let saved_aps = match client.saved_networks().call() {
Ok(ssids) => { Ok(ssids) => {
let networks: Vec<Networks> = serde_json::from_str(ssids.as_str())?; let networks: Vec<Networks> = serde_json::from_str(ssids.as_str())
.expect("Failed to deserialize saved_networks response");
networks networks
} }
// return an empty vector if there are no saved access point credentials // return an empty vector if there are no saved access point credentials
@ -475,7 +479,7 @@ pub fn traffic(iface: &str) -> std::result::Result<Traffic, PeachError> {
let mut client = PeachNetworkClient::new(transport_handle); let mut client = PeachNetworkClient::new(transport_handle);
let response = client.traffic(iface).call()?; let response = client.traffic(iface).call()?;
let t: Traffic = serde_json::from_str(&response)?; let t: Traffic = serde_json::from_str(&response).unwrap();
Ok(t) Ok(t)
} }
@ -502,13 +506,13 @@ pub fn update(iface: &str, ssid: &str, pass: &str) -> std::result::Result<String
// get the id of the network // get the id of the network
info!("Performing id call to peach-network microservice."); info!("Performing id call to peach-network microservice.");
let id = client.id(iface, ssid).call()?; let id = client.id(&iface, &ssid).call()?;
// delete the old credentials // delete the old credentials
// WEIRD BUG: the parameters below are technically in the wrong order: // WEIRD BUG: the parameters below are technically in the wrong order:
// it should be id first and then iface, but somehow they get twisted. // it should be id first and then iface, but somehow they get twisted.
// i don't understand computers. // i don't understand computers.
info!("Performing delete call to peach-network microservice."); info!("Performing delete call to peach-network microservice.");
client.delete(iface, &id).call()?; client.delete(&iface, &id).call()?;
// save the updates to wpa_supplicant.conf // save the updates to wpa_supplicant.conf
info!("Performing save call to peach-network microservice."); info!("Performing save call to peach-network microservice.");
client.save().call()?; client.save().call()?;

View File

@ -1,19 +1,23 @@
use crate::config_manager::{get_peachcloud_domain, load_peach_config,
set_admin_password_hash, get_admin_password_hash,
get_temporary_password_hash, set_temporary_password_hash};
use crate::error::PeachError;
use crate::sbot_client;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use std::iter; use std::iter;
use crypto::digest::Digest;
use crypto::{digest::Digest, sha3::Sha3}; use crypto::sha3::Sha3;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use crate::{config_manager, error::PeachError, sbot_client};
/// Returns Ok(()) if the supplied password is correct, /// Returns Ok(()) if the supplied password is correct,
/// and returns Err if the supplied password is incorrect. /// and returns Err if the supplied password is incorrect.
pub fn verify_password(password: &str) -> Result<(), PeachError> { pub fn verify_password(password: &str) -> Result<(), PeachError> {
let real_admin_password_hash = config_manager::get_admin_password_hash()?; let real_admin_password_hash = get_admin_password_hash()?;
let password_hash = hash_password(&password.to_string()); let password_hash = hash_password(&password.to_string());
if real_admin_password_hash == password_hash { if real_admin_password_hash == password_hash {
Ok(()) Ok(())
} else { } else {
Err(PeachError::PasswordIncorrect) Err(PeachError::InvalidPassword)
} }
} }
@ -25,16 +29,22 @@ pub fn validate_new_passwords(new_password1: &str, new_password2: &str) -> Resul
if new_password1 == new_password2 { if new_password1 == new_password2 {
Ok(()) Ok(())
} else { } else {
Err(PeachError::PasswordMismatch) Err(PeachError::PasswordsDoNotMatch)
} }
} }
/// Sets a new password for the admin user /// Sets a new password for the admin user
pub fn set_new_password(new_password: &str) -> Result<(), PeachError> { pub fn set_new_password(new_password: &str) -> Result<(), PeachError> {
let new_password_hash = hash_password(&new_password.to_string()); let new_password_hash = hash_password(&new_password.to_string());
config_manager::set_admin_password_hash(&new_password_hash)?; let result = set_admin_password_hash(&new_password_hash);
match result {
Ok(()) Ok(_) => {
Ok(())
},
Err(_err) => {
Err(PeachError::FailedToSetNewPassword { msg: "failed to save password hash".to_string() })
}
}
} }
/// Creates a hash from a password string /// Creates a hash from a password string
@ -48,20 +58,26 @@ pub fn hash_password(password: &str) -> String {
/// which can be used to reset the permanent password /// which can be used to reset the permanent password
pub fn set_new_temporary_password(new_password: &str) -> Result<(), PeachError> { pub fn set_new_temporary_password(new_password: &str) -> Result<(), PeachError> {
let new_password_hash = hash_password(&new_password.to_string()); let new_password_hash = hash_password(&new_password.to_string());
config_manager::set_temporary_password_hash(&new_password_hash)?; let result = set_temporary_password_hash(&new_password_hash);
match result {
Ok(()) Ok(_) => {
Ok(())
},
Err(_err) => {
Err(PeachError::FailedToSetNewPassword { msg: "failed to save temporary password hash".to_string() })
}
}
} }
/// Returns Ok(()) if the supplied temp_password is correct, /// Returns Ok(()) if the supplied temp_password is correct,
/// and returns Err if the supplied temp_password is incorrect /// and returns Err if the supplied temp_password is incorrect
pub fn verify_temporary_password(password: &str) -> Result<(), PeachError> { pub fn verify_temporary_password(password: &str) -> Result<(), PeachError> {
let temporary_admin_password_hash = config_manager::get_temporary_password_hash()?; let temporary_admin_password_hash = get_temporary_password_hash()?;
let password_hash = hash_password(&password.to_string()); let password_hash = hash_password(&password.to_string());
if temporary_admin_password_hash == password_hash { if temporary_admin_password_hash == password_hash {
Ok(()) Ok(())
} else { } else {
Err(PeachError::PasswordIncorrect) Err(PeachError::InvalidPassword)
} }
} }
@ -77,7 +93,7 @@ pub fn send_password_reset() -> Result<(), PeachError> {
.collect(); .collect();
// save this string as a new temporary password // save this string as a new temporary password
set_new_temporary_password(&temporary_password)?; set_new_temporary_password(&temporary_password)?;
let domain = config_manager::get_peachcloud_domain()?; let domain = get_peachcloud_domain()?;
// then send temporary password as a private ssb message to admin // then send temporary password as a private ssb message to admin
let mut msg = format!( let mut msg = format!(
@ -101,7 +117,7 @@ using this link: http://peach.local/reset_password",
}; };
msg += &remote_link; msg += &remote_link;
// finally send the message to the admins // finally send the message to the admins
let peach_config = config_manager::load_peach_config()?; let peach_config = load_peach_config()?;
for ssb_admin_id in peach_config.ssb_admin_ids { for ssb_admin_id in peach_config.ssb_admin_ids {
sbot_client::private_message(&msg, &ssb_admin_id)?; sbot_client::private_message(&msg, &ssb_admin_id)?;
} }

View File

@ -1,10 +1,8 @@
//! Interfaces for monitoring and configuring go-sbot using sbotcli. //! Interfaces for monitoring and configuring go-sbot using sbotcli.
//!
use std::process::Command;
use serde::{Deserialize, Serialize};
use crate::error::PeachError; use crate::error::PeachError;
use serde::{Deserialize, Serialize};
use std::process::Command;
pub fn is_sbot_online() -> Result<bool, PeachError> { pub fn is_sbot_online() -> Result<bool, PeachError> {
let output = Command::new("/usr/bin/systemctl") let output = Command::new("/usr/bin/systemctl")
@ -38,7 +36,7 @@ pub fn post(msg: &str) -> Result<(), PeachError> {
Ok(()) Ok(())
} else { } else {
let stderr = std::str::from_utf8(&output.stderr)?; let stderr = std::str::from_utf8(&output.stderr)?;
Err(PeachError::SbotCli { Err(PeachError::SbotCliError {
msg: format!("Error making ssb post: {}", stderr), msg: format!("Error making ssb post: {}", stderr),
}) })
} }
@ -85,7 +83,7 @@ pub fn update_pub_name(new_name: &str) -> Result<(), PeachError> {
Ok(()) Ok(())
} else { } else {
let stderr = std::str::from_utf8(&output.stderr)?; let stderr = std::str::from_utf8(&output.stderr)?;
Err(PeachError::SbotCli { Err(PeachError::SbotCliError {
msg: format!("Error updating pub name: {}", stderr), msg: format!("Error updating pub name: {}", stderr),
}) })
} }
@ -104,7 +102,7 @@ pub fn private_message(msg: &str, recipient: &str) -> Result<(), PeachError> {
Ok(()) Ok(())
} else { } else {
let stderr = std::str::from_utf8(&output.stderr)?; let stderr = std::str::from_utf8(&output.stderr)?;
Err(PeachError::SbotCli { Err(PeachError::SbotCliError {
msg: format!("Error sending ssb private message: {}", stderr), msg: format!("Error sending ssb private message: {}", stderr),
}) })
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "peach-oled" name = "peach-oled"
version = "0.1.3" version = "0.1.4"
authors = ["Andrew Reid <gnomad@cryptolab.net>"] authors = ["Andrew Reid <gnomad@cryptolab.net>"]
edition = "2018" edition = "2018"
description = "Write and draw to OLED display using JSON-RPC over HTTP." description = "Write and draw to OLED display using JSON-RPC over HTTP."
@ -27,18 +27,16 @@ travis-ci = { repository = "peachcloud/peach-oled", branch = "master" }
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
[dependencies] [dependencies]
jsonrpc-core = "11.0.0"
jsonrpc-http-server = "11.0.0"
linux-embedded-hal = "0.2.2"
embedded-graphics = "0.4.7" embedded-graphics = "0.4.7"
tinybmp = "0.1.0" env_logger = "0.9"
ssd1306 = "0.2.6" jsonrpc-core = "18"
serde = { version = "1.0.87", features = ["derive"] } jsonrpc-http-server = "18"
serde_json = "1.0.39" linux-embedded-hal = "0.2.2"
log = "0.4.0" log = "0.4"
env_logger = "0.6.1" serde = { version = "1", features = ["derive"] }
snafu = "0.4.1"
nix="0.11" nix="0.11"
ssd1306 = "0.2.6"
tinybmp = "0.1.0"
[dev-dependencies] [dev-dependencies]
jsonrpc-test = "11.0.0" jsonrpc-test = "18"

View File

@ -1,6 +1,6 @@
# peach-oled # peach-oled
[![Build Status](https://travis-ci.com/peachcloud/peach-oled.svg?branch=master)](https://travis-ci.com/peachcloud/peach-oled) ![Generic badge](https://img.shields.io/badge/version-0.1.3-<COLOR>.svg) [![Build Status](https://travis-ci.com/peachcloud/peach-oled.svg?branch=master)](https://travis-ci.com/peachcloud/peach-oled) ![Generic badge](https://img.shields.io/badge/version-0.1.4-<COLOR>.svg)
OLED microservice module for PeachCloud. Write to a 128x64 OLED display with SDD1306 driver (I2C) using [JSON-RPC](https://www.jsonrpc.org/specification) over http. OLED microservice module for PeachCloud. Write to a 128x64 OLED display with SDD1306 driver (I2C) using [JSON-RPC](https://www.jsonrpc.org/specification) over http.

View File

@ -1,44 +1,68 @@
use std::error; use std::{error, fmt};
use jsonrpc_core::{types::error::Error, ErrorCode}; use jsonrpc_core::types::error::Error as JsonRpcError;
use linux_embedded_hal as hal; use jsonrpc_core::ErrorCode;
use snafu::Snafu; use linux_embedded_hal::i2cdev::linux::LinuxI2CError;
pub type BoxError = Box<dyn error::Error>; #[derive(Debug)]
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum OledError { pub enum OledError {
#[snafu(display("Failed to create interface for I2C device: {}", source))]
I2CError { I2CError {
source: hal::i2cdev::linux::LinuxI2CError, source: LinuxI2CError,
}, },
#[snafu(display("Coordinate {} out of range {}: {}", coord, range, value))]
InvalidCoordinate { InvalidCoordinate {
coord: String, coord: String,
range: String, range: String,
value: i32, value: i32,
}, },
InvalidFontSize {
// TODO: implement for validate() in src/lib.rs font: String,
#[snafu(display("Font size invalid: {}", font))] },
InvalidFontSize { font: String }, InvalidString {
len: usize,
#[snafu(display("String length out of range 0-21: {}", len))] },
InvalidString { len: usize }, MissingParameter {
source: JsonRpcError,
#[snafu(display("Missing expected parameter: {}", e))] },
MissingParameter { e: Error }, ParseError {
source: JsonRpcError,
#[snafu(display("Failed to parse parameter: {}", e))] },
ParseError { e: Error },
} }
impl From<OledError> for Error { impl error::Error for OledError {}
impl fmt::Display for OledError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
OledError::ParseError { ref source } => {
write!(f, "Failed to parse parameter: {}", source)
}
OledError::MissingParameter { ref source } => {
write!(f, "Missing expected parameter: {}", source)
}
OledError::InvalidString { len } => {
write!(f, "String length out of range 0-21: {}", len)
}
OledError::InvalidFontSize { ref font } => {
write!(f, "Invalid font size: {}", font)
}
OledError::InvalidCoordinate {
ref coord,
ref range,
value,
} => {
write!(f, "Coordinate {} out of range {}: {}", coord, range, value)
}
OledError::I2CError { ref source } => {
write!(f, "Failed to create interface for I2C device: {}", source)
}
}
}
}
impl From<OledError> for JsonRpcError {
fn from(err: OledError) -> Self { fn from(err: OledError) -> Self {
match &err { match &err {
OledError::I2CError { source } => Error { OledError::I2CError { source } => JsonRpcError {
code: ErrorCode::ServerError(-32000), code: ErrorCode::ServerError(-32000),
message: format!("Failed to create interface for I2C device: {}", source), message: format!("Failed to create interface for I2C device: {}", source),
data: None, data: None,
@ -47,7 +71,7 @@ impl From<OledError> for Error {
coord, coord,
value, value,
range, range,
} => Error { } => JsonRpcError {
code: ErrorCode::ServerError(-32001), code: ErrorCode::ServerError(-32001),
message: format!( message: format!(
"Validation error: coordinate {} out of range {}: {}", "Validation error: coordinate {} out of range {}: {}",
@ -55,18 +79,18 @@ impl From<OledError> for Error {
), ),
data: None, data: None,
}, },
OledError::InvalidFontSize { font } => Error { OledError::InvalidFontSize { font } => JsonRpcError {
code: ErrorCode::ServerError(-32002), code: ErrorCode::ServerError(-32002),
message: format!("Validation error: {} is not an accepted font size. Use 6x8, 6x12, 8x16 or 12x16 instead", font), message: format!("Validation error: {} is not an accepted font size. Use 6x8, 6x12, 8x16 or 12x16 instead", font),
data: None, data: None,
}, },
OledError::InvalidString { len } => Error { OledError::InvalidString { len } => JsonRpcError {
code: ErrorCode::ServerError(-32003), code: ErrorCode::ServerError(-32003),
message: format!("Validation error: string length {} out of range 0-21", len), message: format!("Validation error: string length {} out of range 0-21", len),
data: None, data: None,
}, },
OledError::MissingParameter { e } => e.clone(), OledError::MissingParameter { source } => source.clone(),
OledError::ParseError { e } => e.clone(), OledError::ParseError { source } => source.clone(),
} }
} }
} }

View File

@ -6,23 +6,23 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use embedded_graphics::coord::Coord; use embedded_graphics::{
use embedded_graphics::fonts::{Font12x16, Font6x12, Font6x8, Font8x16}; coord::Coord,
use embedded_graphics::image::Image1BPP; fonts::{Font12x16, Font6x12, Font6x8, Font8x16},
use embedded_graphics::prelude::*; image::Image1BPP,
prelude::*,
};
use hal::I2cdev; use hal::I2cdev;
use jsonrpc_core::{types::error::Error, IoHandler, Params, Value}; use jsonrpc_core::{types::error::Error, IoHandler, Params, Value};
use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder}; use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder};
use linux_embedded_hal as hal; use linux_embedded_hal as hal;
use log::{debug, error, info}; use log::{debug, error, info};
use serde::Deserialize; use serde::Deserialize;
use snafu::{ensure, ResultExt}; use ssd1306::{prelude::*, Builder};
use ssd1306::prelude::*;
use ssd1306::Builder;
use crate::error::{BoxError, I2CError, InvalidCoordinate, InvalidString, OledError}; use crate::error::OledError;
//define the Graphic struct for receiving draw commands // define the Graphic struct for receiving draw commands
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Graphic { pub struct Graphic {
bytes: Vec<u8>, bytes: Vec<u8>,
@ -32,7 +32,7 @@ pub struct Graphic {
y_coord: i32, y_coord: i32,
} }
//define the Msg struct for receiving write commands // define the Msg struct for receiving write commands
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Msg { pub struct Msg {
x_coord: i32, x_coord: i32,
@ -41,86 +41,61 @@ pub struct Msg {
font_size: String, font_size: String,
} }
//definte the On struct for receiving power on/off commands // definte the On struct for receiving power on/off commands
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct On { pub struct On {
on: bool, on: bool,
} }
fn validate(m: &Msg) -> Result<(), OledError> { fn validate(msg: &Msg) -> Result<(), OledError> {
ensure!( if msg.string.len() > 21 {
m.string.len() <= 21, Err(OledError::InvalidString {
InvalidString { len: msg.string.len(),
len: m.string.len() })
} } else if msg.x_coord < 0 || msg.x_coord > 128 {
); Err(OledError::InvalidCoordinate {
ensure!(
m.x_coord >= 0,
InvalidCoordinate {
coord: "x".to_string(), coord: "x".to_string(),
range: "0-128".to_string(), range: "0-128".to_string(),
value: m.x_coord, value: msg.x_coord,
} })
); } else if msg.y_coord < 0 || msg.y_coord > 147 {
Err(OledError::InvalidCoordinate {
ensure!(
m.x_coord < 129,
InvalidCoordinate {
coord: "x".to_string(),
range: "0-128".to_string(),
value: m.x_coord,
}
);
ensure!(
m.y_coord >= 0,
InvalidCoordinate {
coord: "y".to_string(), coord: "y".to_string(),
range: "0-47".to_string(), range: "0-47".to_string(),
value: m.y_coord, value: msg.y_coord,
} })
); } else {
Ok(())
ensure!( }
m.y_coord < 148,
InvalidCoordinate {
coord: "y".to_string(),
range: "0-47".to_string(),
value: m.y_coord,
}
);
Ok(())
} }
pub fn run() -> Result<(), BoxError> { pub fn run() -> Result<(), OledError> {
info!("Starting up."); info!("Starting up.");
debug!("Creating interface for I2C device."); debug!("Creating interface for I2C device.");
let i2c = I2cdev::new("/dev/i2c-1").context(I2CError)?; let i2c = I2cdev::new("/dev/i2c-1").map_err(|source| OledError::I2CError { source })?;
let mut disp: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into(); let mut display: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into();
info!("Initializing the display."); info!("Initializing the display.");
disp.init().unwrap_or_else(|_| { display.init().unwrap_or_else(|_| {
error!("Problem initializing the OLED display."); error!("Problem initializing the OLED display.");
process::exit(1); process::exit(1);
}); });
debug!("Flushing the display."); debug!("Flushing the display.");
disp.flush().unwrap_or_else(|_| { display.flush().unwrap_or_else(|_| {
error!("Problem flushing the OLED display."); error!("Problem flushing the OLED display.");
process::exit(1); process::exit(1);
}); });
let oled = Arc::new(Mutex::new(disp)); let oled = Arc::new(Mutex::new(display));
let oled_clone = Arc::clone(&oled); let oled_clone = Arc::clone(&oled);
info!("Creating JSON-RPC I/O handler."); info!("Creating JSON-RPC I/O handler.");
let mut io = IoHandler::default(); let mut io = IoHandler::default();
io.add_method("clear", move |_| { io.add_sync_method("clear", move |_| {
let mut oled = oled_clone.lock().unwrap(); let mut oled = oled_clone.lock().unwrap();
info!("Clearing the display."); info!("Clearing the display.");
oled.clear(); oled.clear();
@ -134,21 +109,20 @@ pub fn run() -> Result<(), BoxError> {
let oled_clone = Arc::clone(&oled); let oled_clone = Arc::clone(&oled);
io.add_method("draw", move |params: Params| { io.add_sync_method("draw", move |params: Params| {
let g: Result<Graphic, Error> = params.parse(); let graphic: Graphic = params.parse()?;
let g: Graphic = g?;
// TODO: add simple byte validation function // TODO: add simple byte validation function
let mut oled = oled_clone.lock().unwrap(); let mut oled = oled_clone.lock().unwrap();
info!("Drawing image to the display."); info!("Drawing image to the display.");
let im = let image = Image1BPP::new(&graphic.bytes, graphic.width, graphic.height)
Image1BPP::new(&g.bytes, g.width, g.height).translate(Coord::new(g.x_coord, g.y_coord)); .translate(Coord::new(graphic.x_coord, graphic.y_coord));
oled.draw(im.into_iter()); oled.draw(image.into_iter());
Ok(Value::String("success".into())) Ok(Value::String("success".into()))
}); });
let oled_clone = Arc::clone(&oled); let oled_clone = Arc::clone(&oled);
io.add_method("flush", move |_| { io.add_sync_method("flush", move |_| {
let mut oled = oled_clone.lock().unwrap(); let mut oled = oled_clone.lock().unwrap();
info!("Flushing the display."); info!("Flushing the display.");
oled.flush().unwrap_or_else(|_| { oled.flush().unwrap_or_else(|_| {
@ -160,9 +134,9 @@ pub fn run() -> Result<(), BoxError> {
let oled_clone = Arc::clone(&oled); let oled_clone = Arc::clone(&oled);
io.add_method("ping", |_| Ok(Value::String("success".to_string()))); io.add_sync_method("ping", |_| Ok(Value::String("success".to_string())));
io.add_method("power", move |params: Params| { io.add_sync_method("power", move |params: Params| {
let o: Result<On, Error> = params.parse(); let o: Result<On, Error> = params.parse();
let o: On = o?; let o: On = o?;
let mut oled = oled_clone.lock().unwrap(); let mut oled = oled_clone.lock().unwrap();
@ -180,37 +154,36 @@ pub fn run() -> Result<(), BoxError> {
let oled_clone = Arc::clone(&oled); let oled_clone = Arc::clone(&oled);
io.add_method("write", move |params: Params| { io.add_sync_method("write", move |params: Params| {
info!("Received a 'write' request."); info!("Received a 'write' request.");
let m: Result<Msg, Error> = params.parse(); let msg = params.parse()?;
let m: Msg = m?; validate(&msg)?;
validate(&m)?;
let mut oled = oled_clone.lock().unwrap(); let mut oled = oled_clone.lock().unwrap();
info!("Writing to the display."); info!("Writing to the display.");
if m.font_size == "6x8" { if msg.font_size == "6x8" {
oled.draw( oled.draw(
Font6x8::render_str(&m.string) Font6x8::render_str(&msg.string)
.translate(Coord::new(m.x_coord, m.y_coord)) .translate(Coord::new(msg.x_coord, msg.y_coord))
.into_iter(), .into_iter(),
); );
} else if m.font_size == "6x12" { } else if msg.font_size == "6x12" {
oled.draw( oled.draw(
Font6x12::render_str(&m.string) Font6x12::render_str(&msg.string)
.translate(Coord::new(m.x_coord, m.y_coord)) .translate(Coord::new(msg.x_coord, msg.y_coord))
.into_iter(), .into_iter(),
); );
} else if m.font_size == "8x16" { } else if msg.font_size == "8x16" {
oled.draw( oled.draw(
Font8x16::render_str(&m.string) Font8x16::render_str(&msg.string)
.translate(Coord::new(m.x_coord, m.y_coord)) .translate(Coord::new(msg.x_coord, msg.y_coord))
.into_iter(), .into_iter(),
); );
} else if m.font_size == "12x16" { } else if msg.font_size == "12x16" {
oled.draw( oled.draw(
Font12x16::render_str(&m.string) Font12x16::render_str(&msg.string)
.translate(Coord::new(m.x_coord, m.y_coord)) .translate(Coord::new(msg.x_coord, msg.y_coord))
.into_iter(), .into_iter(),
); );
} }
@ -255,7 +228,7 @@ mod tests {
fn rpc_success() { fn rpc_success() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_success_response", |_| { io.add_sync_method("rpc_success_response", |_| {
Ok(Value::String("success".into())) Ok(Value::String("success".into()))
}); });
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
@ -269,7 +242,7 @@ mod tests {
fn rpc_internal_error() { fn rpc_internal_error() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_internal_error", |_| Err(Error::internal_error())); io.add_sync_method("rpc_internal_error", |_| Err(Error::internal_error()));
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
}; };
@ -287,7 +260,7 @@ mod tests {
fn rpc_i2c_io_error() { fn rpc_i2c_io_error() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_i2c_io_error", |_| { io.add_sync_method("rpc_i2c_io_error", |_| {
let io_err = IoError::new(ErrorKind::PermissionDenied, "oh no!"); let io_err = IoError::new(ErrorKind::PermissionDenied, "oh no!");
let source = LinuxI2CError::Io(io_err); let source = LinuxI2CError::Io(io_err);
Err(Error::from(OledError::I2CError { source })) Err(Error::from(OledError::I2CError { source }))
@ -310,7 +283,7 @@ mod tests {
fn rpc_i2c_nix_error() { fn rpc_i2c_nix_error() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_i2c_nix_error", |_| { io.add_sync_method("rpc_i2c_nix_error", |_| {
let nix_err = NixError::InvalidPath; let nix_err = NixError::InvalidPath;
let source = LinuxI2CError::Nix(nix_err); let source = LinuxI2CError::Nix(nix_err);
Err(Error::from(OledError::I2CError { source })) Err(Error::from(OledError::I2CError { source }))
@ -326,14 +299,14 @@ mod tests {
}"# }"#
); );
} }
*/ */
// test to ensure correct InvalidCoordinate error response // test to ensure correct InvalidCoordinate error response
#[test] #[test]
fn rpc_invalid_coord() { fn rpc_invalid_coord() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_invalid_coord", |_| { io.add_sync_method("rpc_invalid_coord", |_| {
Err(Error::from(OledError::InvalidCoordinate { Err(Error::from(OledError::InvalidCoordinate {
coord: "x".to_string(), coord: "x".to_string(),
range: "0-128".to_string(), range: "0-128".to_string(),
@ -357,7 +330,7 @@ mod tests {
fn rpc_invalid_fontsize() { fn rpc_invalid_fontsize() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_invalid_fontsize", |_| { io.add_sync_method("rpc_invalid_fontsize", |_| {
Err(Error::from(OledError::InvalidFontSize { Err(Error::from(OledError::InvalidFontSize {
font: "24x32".to_string(), font: "24x32".to_string(),
})) }))
@ -379,7 +352,7 @@ mod tests {
fn rpc_invalid_string() { fn rpc_invalid_string() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_invalid_string", |_| { io.add_sync_method("rpc_invalid_string", |_| {
Err(Error::from(OledError::InvalidString { len: 22 })) Err(Error::from(OledError::InvalidString { len: 22 }))
}); });
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
@ -399,15 +372,15 @@ mod tests {
fn rpc_invalid_params() { fn rpc_invalid_params() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_invalid_params", |_| { io.add_sync_method("rpc_invalid_params", |_| {
let e = Error { let source = Error {
code: ErrorCode::InvalidParams, code: ErrorCode::InvalidParams,
message: String::from("invalid params"), message: String::from("invalid params"),
data: Some(Value::String( data: Some(Value::String(
"Invalid params: invalid type: null, expected struct Msg.".into(), "Invalid params: invalid type: null, expected struct Msg.".into(),
)), )),
}; };
Err(Error::from(OledError::MissingParameter { e })) Err(Error::from(OledError::MissingParameter { source }))
}); });
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
}; };
@ -427,13 +400,13 @@ mod tests {
fn rpc_parse_error() { fn rpc_parse_error() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_parse_error", |_| { io.add_sync_method("rpc_parse_error", |_| {
let e = Error { let source = Error {
code: ErrorCode::ParseError, code: ErrorCode::ParseError,
message: String::from("Parse error"), message: String::from("Parse error"),
data: None, data: None,
}; };
Err(Error::from(OledError::ParseError { e })) Err(Error::from(OledError::ParseError { source }))
}); });
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
}; };

View File

@ -1,6 +1,6 @@
[package] [package]
name = "peach-web" name = "peach-web"
version = "0.4.11" version = "0.4.12"
authors = ["Andrew Reid <gnomad@cryptolab.net>"] authors = ["Andrew Reid <gnomad@cryptolab.net>"]
edition = "2018" edition = "2018"
description = "peach-web is a web application which provides a web interface for monitoring and interacting with the PeachCloud device. This allows administration of the single-board computer (ie. Raspberry Pi) running PeachCloud, as well as the ssb-server and related plugins." description = "peach-web is a web application which provides a web interface for monitoring and interacting with the PeachCloud device. This allows administration of the single-board computer (ie. Raspberry Pi) running PeachCloud, as well as the ssb-server and related plugins."
@ -38,17 +38,16 @@ maintenance = { status = "actively-developed" }
env_logger = "0.8" env_logger = "0.8"
log = "0.4" log = "0.4"
nest = "1.0.0" nest = "1.0.0"
openssl = { version = "0.10", features = ["vendored"] }
peach-lib = { path = "../peach-lib" } peach-lib = { path = "../peach-lib" }
percent-encoding = "2.1.0" percent-encoding = "2.1.0"
regex = "1"
rocket = { version = "0.5.0-rc.1", features = ["json", "secrets"] } rocket = { version = "0.5.0-rc.1", features = ["json", "secrets"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
snafu = "0.6" snafu = "0.6"
tera = { version = "1.12.1", features = ["builtins"] } tera = { version = "1.12.1", features = ["builtins"] }
websocket = "0.26"
regex = "1"
xdg = "2.2.0" xdg = "2.2.0"
openssl = { version = "0.10", features = ["vendored"] }
[dependencies.rocket_dyn_templates] [dependencies.rocket_dyn_templates]
version = "0.1.0-rc.1" version = "0.1.0-rc.1"

View File

@ -1,6 +1,6 @@
# peach-web # peach-web
[![Build Status](https://travis-ci.com/peachcloud/peach-web.svg?branch=master)](https://travis-ci.com/peachcloud/peach-web) ![Generic badge](https://img.shields.io/badge/version-0.4.6-<COLOR>.svg) [![Build Status](https://travis-ci.com/peachcloud/peach-web.svg?branch=master)](https://travis-ci.com/peachcloud/peach-web) ![Generic badge](https://img.shields.io/badge/version-0.4.12-<COLOR>.svg)
## Web Interface for PeachCloud ## Web Interface for PeachCloud
@ -39,12 +39,22 @@ _Note: Networking functionality requires peach-network microservice to be runnin
### Environment ### Environment
**Deployment Mode**
The web application deployment mode is configured with the `ROCKET_ENV` environment variable: The web application deployment mode is configured with the `ROCKET_ENV` environment variable:
`export ROCKET_ENV=stage` `export ROCKET_ENV=stage`
Other deployment modes are `dev` and `prod`. Read the [Rocket Environment Configurations docs](https://rocket.rs/v0.5-rc/guide/configuration/#environment-variables) for further information. Other deployment modes are `dev` and `prod`. Read the [Rocket Environment Configurations docs](https://rocket.rs/v0.5-rc/guide/configuration/#environment-variables) for further information.
**Authentication**
Authentication is disabled in `development` mode and enabled by default when running the application in `production` mode. It can be disabled by setting the `ROCKET_DISABLE_AUTH` environment variable to `true`:
`export ROCKET_DISABLE_AUTH=true`
**Logging**
Logging is made available with `env_logger`: Logging is made available with `env_logger`:
`export RUST_LOG=info` `export RUST_LOG=info`

View File

@ -1,5 +1,7 @@
[development] [development]
template_dir = "templates/" template_dir = "templates/"
disable_auth = true
[production] [production]
template_dir = "templates/" template_dir = "templates/"
disable_auth = false

View File

@ -1,10 +1,13 @@
use log::info; use log::info;
use rocket::form::{Form, FromForm}; use rocket::form::{Form, FromForm};
use rocket::request::FlashMessage; use rocket::http::{Cookie, CookieJar, Status};
use rocket::request::{self, FlashMessage, FromRequest, Request};
use rocket::response::{Flash, Redirect}; use rocket::response::{Flash, Redirect};
use rocket::serde::json::Json; use rocket::serde::{
use rocket::serde::{Deserialize, Serialize}; json::{Json, Value},
use rocket::{get, post}; Deserialize, Serialize,
};
use rocket::{get, post, Config};
use rocket_dyn_templates::Template; use rocket_dyn_templates::Template;
use peach_lib::error::PeachError; use peach_lib::error::PeachError;
@ -12,9 +15,6 @@ use peach_lib::password_utils;
use crate::error::PeachWebError; use crate::error::PeachWebError;
use crate::utils::{build_json_response, TemplateOrRedirect}; use crate::utils::{build_json_response, TemplateOrRedirect};
use rocket::http::{Cookie, CookieJar, Status};
use rocket::request::{self, FromRequest, Request};
use rocket::serde::json::Value;
// HELPERS AND STRUCTS FOR AUTHENTICATION WITH COOKIES // HELPERS AND STRUCTS FOR AUTHENTICATION WITH COOKIES
@ -42,14 +42,27 @@ impl<'r> FromRequest<'r> for Authenticated {
type Error = LoginError; type Error = LoginError;
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> { async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let authenticated = req // check for `disable_auth` config value; set to `false` if unset
.cookies() // can be set via the `ROCKET_DISABLE_AUTH` environment variable
.get_private(AUTH_COOKIE_KEY) // - env var, if set, takes precedence over value defined in `Rocket.toml`
.and_then(|cookie| cookie.value().parse().ok()) let authentication_is_disabled: bool = match Config::figment().find_value("disable_auth") {
.map(|_value: String| Authenticated {}); // deserialize the boolean value; set to `false` if an error is encountered
match authenticated { Ok(value) => value.deserialize().unwrap_or(false),
Some(auth) => request::Outcome::Success(auth), Err(_) => false,
None => request::Outcome::Failure((Status::Forbidden, LoginError::UserNotLoggedIn)), };
if authentication_is_disabled {
let auth = Authenticated {};
request::Outcome::Success(auth)
} else {
let authenticated = req
.cookies()
.get_private(AUTH_COOKIE_KEY)
.and_then(|cookie| cookie.value().parse().ok())
.map(|_value: String| Authenticated {});
match authenticated {
Some(auth) => request::Outcome::Success(auth),
None => request::Outcome::Failure((Status::Forbidden, LoginError::UserNotLoggedIn)),
}
} }
} }
} }

View File

@ -4,17 +4,29 @@ use std::io::Read;
use rocket::http::{ContentType, Status}; use rocket::http::{ContentType, Status};
use rocket::local::blocking::Client; use rocket::local::blocking::Client;
use rocket::serde::json::{json, Value}; use rocket::serde::json::{json, Value};
use rocket::{Build, Config, Rocket};
use crate::utils::build_json_response; use crate::utils::build_json_response;
use super::init_rocket; use super::init_rocket;
// define authentication mode
const DISABLE_AUTH: bool = true;
/// Wrapper around `init_rocket()` to simplify the process of invoking the application with the desired authentication status. This is particularly useful for testing purposes.
fn init_test_rocket(disable_auth: bool) -> Rocket<Build> {
// set authentication based on provided `disable_auth` value
Config::figment().merge(("disable_auth", disable_auth));
init_rocket()
}
// helper function to test correct retrieval and content of a file // helper function to test correct retrieval and content of a file
fn test_query_file<T>(path: &str, file: T, status: Status) fn test_query_file<T>(path: &str, file: T, status: Status)
where where
T: Into<Option<&'static str>>, T: Into<Option<&'static str>>,
{ {
let client = Client::tracked(init_rocket()).unwrap(); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).unwrap();
let response = client.get(path).dispatch(); let response = client.get(path).dispatch();
assert_eq!(response.status(), status); assert_eq!(response.status(), status);
@ -39,7 +51,7 @@ fn read_file_content(path: &str) -> Vec<u8> {
#[test] #[test]
fn index_html() { fn index_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/").dispatch(); let response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -54,7 +66,7 @@ fn index_html() {
#[test] #[test]
fn help_html() { fn help_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/help").dispatch(); let response = client.get("/help").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -64,7 +76,7 @@ fn help_html() {
#[test] #[test]
fn login_html() { fn login_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/login").dispatch(); let response = client.get("/login").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -74,7 +86,7 @@ fn login_html() {
#[test] #[test]
fn logout_html() { fn logout_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/logout").dispatch(); let response = client.get("/logout").dispatch();
// check for 303 status (redirect to "/login") // check for 303 status (redirect to "/login")
assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.status(), Status::SeeOther);
@ -83,7 +95,7 @@ fn logout_html() {
#[test] #[test]
fn power_html() { fn power_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/power").dispatch(); let response = client.get("/power").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -97,7 +109,7 @@ NOTE: these tests are comment-out for the moment, due to the fact that they invo
#[test] #[test]
fn reboot() { fn reboot() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/power/reboot").dispatch(); let response = client.get("/power/reboot").dispatch();
// check for redirect // check for redirect
assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.status(), Status::SeeOther);
@ -105,7 +117,7 @@ fn reboot() {
#[test] #[test]
fn shutdown() { fn shutdown() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/power/shutdown").dispatch(); let response = client.get("/power/shutdown").dispatch();
// check for redirect // check for redirect
assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.status(), Status::SeeOther);
@ -116,7 +128,7 @@ fn shutdown() {
#[test] #[test]
fn block() { fn block() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/scuttlebutt/block") .post("/scuttlebutt/block")
.header(ContentType::Form) .header(ContentType::Form)
@ -127,7 +139,7 @@ fn block() {
#[test] #[test]
fn blocks_html() { fn blocks_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/scuttlebutt/blocks").dispatch(); let response = client.get("/scuttlebutt/blocks").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -137,7 +149,7 @@ fn blocks_html() {
#[test] #[test]
fn follow() { fn follow() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/scuttlebutt/follow") .post("/scuttlebutt/follow")
.header(ContentType::Form) .header(ContentType::Form)
@ -149,7 +161,7 @@ fn follow() {
#[test] #[test]
fn follows_html() { fn follows_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/scuttlebutt/follows").dispatch(); let response = client.get("/scuttlebutt/follows").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -159,7 +171,7 @@ fn follows_html() {
#[test] #[test]
fn followers_html() { fn followers_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/scuttlebutt/followers").dispatch(); let response = client.get("/scuttlebutt/followers").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -169,7 +181,7 @@ fn followers_html() {
#[test] #[test]
fn friends_html() { fn friends_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/scuttlebutt/friends").dispatch(); let response = client.get("/scuttlebutt/friends").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -179,7 +191,7 @@ fn friends_html() {
#[test] #[test]
fn peers_html() { fn peers_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/scuttlebutt/peers").dispatch(); let response = client.get("/scuttlebutt/peers").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -189,7 +201,7 @@ fn peers_html() {
#[test] #[test]
fn private_html() { fn private_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/scuttlebutt/private").dispatch(); let response = client.get("/scuttlebutt/private").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -199,7 +211,7 @@ fn private_html() {
#[test] #[test]
fn profile_html() { fn profile_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/scuttlebutt/profile").dispatch(); let response = client.get("/scuttlebutt/profile").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -209,7 +221,7 @@ fn profile_html() {
#[test] #[test]
fn publish_post() { fn publish_post() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/scuttlebutt/publish") .post("/scuttlebutt/publish")
.header(ContentType::Form) .header(ContentType::Form)
@ -220,7 +232,7 @@ fn publish_post() {
#[test] #[test]
fn unfollow() { fn unfollow() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/scuttlebutt/unfollow") .post("/scuttlebutt/unfollow")
.header(ContentType::Form) .header(ContentType::Form)
@ -233,7 +245,7 @@ fn unfollow() {
#[test] #[test]
fn admin_settings_menu_html() { fn admin_settings_menu_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/admin").dispatch(); let response = client.get("/settings/admin").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -245,7 +257,7 @@ fn admin_settings_menu_html() {
#[test] #[test]
fn add_admin_html() { fn add_admin_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/admin/add").dispatch(); let response = client.get("/settings/admin/add").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -258,7 +270,7 @@ fn add_admin_html() {
#[test] #[test]
fn add_admin() { fn add_admin() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/settings/admin/add") .post("/settings/admin/add")
.header(ContentType::Form) .header(ContentType::Form)
@ -270,21 +282,21 @@ fn add_admin() {
#[test] #[test]
fn change_password_html() { fn change_password_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/admin/change_password").dispatch(); let response = client.get("/settings/admin/change_password").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
let body = response.into_string().unwrap(); let body = response.into_string().unwrap();
assert!(body.contains("Change Password")); assert!(body.contains("Change Password"));
assert!(body.contains("Old Password")); assert!(body.contains("Current password"));
assert!(body.contains("Enter New Password")); assert!(body.contains("New password"));
assert!(body.contains("Re-Enter New Password")); assert!(body.contains("New password duplicate"));
assert!(body.contains("Save")); assert!(body.contains("Save"));
} }
#[test] #[test]
fn configure_admin_html() { fn configure_admin_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/admin/configure").dispatch(); let response = client.get("/settings/admin/configure").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -296,7 +308,7 @@ fn configure_admin_html() {
#[test] #[test]
fn forgot_password_html() { fn forgot_password_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/admin/forgot_password").dispatch(); let response = client.get("/settings/admin/forgot_password").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -308,7 +320,7 @@ fn forgot_password_html() {
#[test] #[test]
fn network_settings_menu_html() { fn network_settings_menu_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/network").dispatch(); let response = client.get("/settings/network").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -318,7 +330,7 @@ fn network_settings_menu_html() {
#[test] #[test]
fn deploy_ap() { fn deploy_ap() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/network/ap/activate").dispatch(); let response = client.get("/settings/network/ap/activate").dispatch();
// check for 303 status (redirect) // check for 303 status (redirect)
assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.status(), Status::SeeOther);
@ -327,7 +339,7 @@ fn deploy_ap() {
#[test] #[test]
fn dns_settings_html() { fn dns_settings_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/network/dns").dispatch(); let response = client.get("/settings/network/dns").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -341,7 +353,7 @@ fn dns_settings_html() {
#[test] #[test]
fn list_aps_html() { fn list_aps_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/network/wifi").dispatch(); let response = client.get("/settings/network/wifi").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -353,7 +365,7 @@ fn list_aps_html() {
// TODO: needs further testing once template has been refactored // TODO: needs further testing once template has been refactored
#[test] #[test]
fn ap_details_html() { fn ap_details_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/network/wifi?ssid=Home").dispatch(); let response = client.get("/settings/network/wifi?ssid=Home").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -363,7 +375,7 @@ fn ap_details_html() {
#[test] #[test]
fn deploy_client() { fn deploy_client() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/network/wifi/activate").dispatch(); let response = client.get("/settings/network/wifi/activate").dispatch();
// check for 303 status (redirect) // check for 303 status (redirect)
assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.status(), Status::SeeOther);
@ -372,7 +384,7 @@ fn deploy_client() {
#[test] #[test]
fn add_ap_html() { fn add_ap_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/network/wifi/add").dispatch(); let response = client.get("/settings/network/wifi/add").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -386,7 +398,7 @@ fn add_ap_html() {
#[test] #[test]
fn add_ap_ssid_html() { fn add_ap_ssid_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.get("/settings/network/wifi/add?ssid=Home") .get("/settings/network/wifi/add?ssid=Home")
.dispatch(); .dispatch();
@ -402,7 +414,7 @@ fn add_ap_ssid_html() {
#[test] #[test]
fn add_credentials() { fn add_credentials() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/settings/network/wifi/add") .post("/settings/network/wifi/add")
.header(ContentType::Form) .header(ContentType::Form)
@ -414,7 +426,7 @@ fn add_credentials() {
#[test] #[test]
fn forget_wifi() { fn forget_wifi() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/settings/network/wifi/forget") .post("/settings/network/wifi/forget")
.header(ContentType::Form) .header(ContentType::Form)
@ -426,7 +438,7 @@ fn forget_wifi() {
#[test] #[test]
fn modify_password() { fn modify_password() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/settings/network/wifi/modify") .post("/settings/network/wifi/modify")
.header(ContentType::Form) .header(ContentType::Form)
@ -438,7 +450,7 @@ fn modify_password() {
#[test] #[test]
fn data_usage_html() { fn data_usage_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/network/wifi/usage").dispatch(); let response = client.get("/settings/network/wifi/usage").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -453,7 +465,7 @@ fn data_usage_html() {
#[test] #[test]
fn scuttlebutt_settings_menu_html() { fn scuttlebutt_settings_menu_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/scuttlebutt").dispatch(); let response = client.get("/settings/scuttlebutt").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -472,7 +484,7 @@ fn scuttlebutt_settings_menu_html() {
#[test] #[test]
fn status_html() { fn status_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/status").dispatch(); let response = client.get("/status").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -485,7 +497,7 @@ fn status_html() {
#[test] #[test]
fn network_status_html() { fn network_status_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/status/network").dispatch(); let response = client.get("/status/network").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -502,7 +514,7 @@ fn network_status_html() {
#[test] #[test]
fn activate_ap() { fn activate_ap() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/api/v1/network/activate_ap") .post("/api/v1/network/activate_ap")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -513,7 +525,7 @@ fn activate_ap() {
#[test] #[test]
fn activate_client() { fn activate_client() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/api/v1/network/activate_client") .post("/api/v1/network/activate_client")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -524,7 +536,7 @@ fn activate_client() {
#[test] #[test]
fn return_ip() { fn return_ip() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.get("/api/v1/network/ip") .get("/api/v1/network/ip")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -538,7 +550,7 @@ fn return_ip() {
#[test] #[test]
fn return_rssi() { fn return_rssi() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.get("/api/v1/network/rssi") .get("/api/v1/network/rssi")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -551,7 +563,7 @@ fn return_rssi() {
#[test] #[test]
fn return_ssid() { fn return_ssid() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.get("/api/v1/network/ssid") .get("/api/v1/network/ssid")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -564,7 +576,7 @@ fn return_ssid() {
#[test] #[test]
fn return_state() { fn return_state() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.get("/api/v1/network/state") .get("/api/v1/network/state")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -579,7 +591,7 @@ fn return_state() {
#[test] #[test]
fn return_status() { fn return_status() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.get("/api/v1/network/status") .get("/api/v1/network/status")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -592,7 +604,7 @@ fn return_status() {
#[test] #[test]
fn scan_networks() { fn scan_networks() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.get("/api/v1/network/wifi") .get("/api/v1/network/wifi")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -605,7 +617,7 @@ fn scan_networks() {
#[test] #[test]
fn add_wifi() { fn add_wifi() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/api/v1/network/wifi") .post("/api/v1/network/wifi")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -619,7 +631,7 @@ fn add_wifi() {
#[test] #[test]
fn remove_wifi() { fn remove_wifi() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/api/v1/network/wifi/forget") .post("/api/v1/network/wifi/forget")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -633,7 +645,7 @@ fn remove_wifi() {
#[test] #[test]
fn new_password() { fn new_password() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/api/v1/network/wifi/modify") .post("/api/v1/network/wifi/modify")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -647,7 +659,7 @@ fn new_password() {
#[test] #[test]
fn ping_pong() { fn ping_pong() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.get("/api/v1/ping") .get("/api/v1/ping")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -709,7 +721,7 @@ fn invalid_path() {
#[test] #[test]
fn invalid_get_request() { fn invalid_get_request() {
let client = Client::tracked(init_rocket()).unwrap(); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).unwrap();
// try to get a path that doesn't exist // try to get a path that doesn't exist
let res = client let res = client

View File

@ -220,12 +220,18 @@ body {
} }
.capsule-container { .capsule-container {
margin-left: 2rem; margin-left: 1rem;
margin-right: 2rem; margin-right: 1rem;
padding-top: 1rem;
padding-bottom: 1rem; padding-bottom: 1rem;
} }
@media only screen and (min-width: 600px) {
.capsule-container {
margin-left: 0;
margin-right: 0;
}
}
/* /*
* CARDS * CARDS
*/ */
@ -235,6 +241,7 @@ body {
max-height: 90vh; max-height: 90vh;
position: relative; position: relative;
width: 100%; width: 100%;
margin-top: 1rem;
} }
@media only screen and (min-width: 600px) { @media only screen and (min-width: 600px) {
@ -248,8 +255,6 @@ body {
.card-container { .card-container {
justify-content: center; justify-content: center;
padding: 0.5rem; padding: 0.5rem;
/* padding-top: 1rem; */
/* padding-bottom: 1rem; */
} }
.form-container { .form-container {
@ -560,6 +565,7 @@ html {
font-size: var(--font-size-6); font-size: var(--font-size-6);
margin-left: 2rem; margin-left: 2rem;
margin-right: 2rem; margin-right: 2rem;
margin-top: 1rem;
} }
/* /*

View File

@ -1,16 +1,28 @@
/* /*
* behavioural layer for the `change_password.html.tera` template,
*/ behavioural layer for the `change_password.html.tera` template
- intercept button click for save (form submission of passwords)
- perform json api call
- update the dom
methods:
PEACH_AUTH.changePassword();
*/
var PEACH_AUTH = {};
// catch click of 'Save' button and make POST request // catch click of 'Save' button and make POST request
PEACH.add = function() { PEACH_AUTH.changePassword = function() {
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
document.body.addEventListener('submit', function(e) { document.body.addEventListener('submit', function(e) {
// prevent redirect on button press (default behavior) // prevent redirect on button press (default behavior)
e.preventDefault(); e.preventDefault();
// capture form data // capture form data
var formElement = document.querySelector("form"); var formElement = document.querySelector("form");
// create form data object from the wifiCreds form element // create form data object from the changePassword form element
var formData = new FormData(formElement); var formData = new FormData(formElement);
var object = {}; var object = {};
// assign values from form // assign values from form
@ -22,7 +34,7 @@ PEACH.add = function() {
var jsonData = JSON.stringify(object); var jsonData = JSON.stringify(object);
// write in-progress status message to ui // write in-progress status message to ui
PEACH.flashMsg("info", "Saving new password."); PEACH.flashMsg("info", "Saving new password.");
// send add_wifi POST request // send change_password POST request
fetch("/api/v1/admin/change_password", { fetch("/api/v1/admin/change_password", {
method: "post", method: "post",
headers: { headers: {
@ -41,5 +53,5 @@ PEACH.add = function() {
}); });
} }
var addInstance = PEACH; var changePassInstance = PEACH_AUTH;
addInstance.add(); changePassInstance.changePassword();

View File

@ -43,5 +43,4 @@ PEACH.flashMsg = function(status, msg) {
} }
} }
var addInstance = PEACH; var commonInstance = PEACH;
addInstance.add();

View File

@ -1,9 +1,9 @@
/* /*
behavioural layer for the `configure_dns.html.tera` template, behavioural layer for the `configure_dns.html.tera` template,
corresponding to the web route `/network/dns` corresponding to the web route `/settings/network/dns`
- intercept button click for add (form submission of credentials) - intercept button click for save (form submission of dns settings)
- perform json api call - perform json api call
- update the dom - update the dom
@ -12,14 +12,14 @@ corresponding to the web route `/network/dns`
var PEACH_DNS = {}; var PEACH_DNS = {};
// catch click of 'Add' button and make POST request // catch click of 'Add' button and make POST request
PEACH_DNS.add = function() { PEACH_DNS.configureDns = function() {
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
document.body.addEventListener('submit', function(e) { document.body.addEventListener('submit', function(e) {
// prevent redirect on button press (default behavior) // prevent redirect on button press (default behavior)
e.preventDefault(); e.preventDefault();
// capture form data // capture form data
var formElement = document.querySelector("form"); var formElement = document.querySelector("form");
// create form data object from the wifiCreds form element // create form data object from the configureDNS form element
var formData = new FormData(formElement); var formData = new FormData(formElement);
var object = {}; var object = {};
// set checkbox to false (the value is only passed to formData if it is "on") // set checkbox to false (the value is only passed to formData if it is "on")
@ -36,7 +36,7 @@ PEACH_DNS.add = function() {
console.log(object); console.log(object);
var jsonData = JSON.stringify(object); var jsonData = JSON.stringify(object);
// write in-progress status message to ui // write in-progress status message to ui
PEACH_DNS.flashMsg("info", "Saving new DNS configurations"); PEACH.flashMsg("info", "Saving new DNS configurations");
// send add_wifi POST request // send add_wifi POST request
fetch("/api/v1/network/dns/configure", { fetch("/api/v1/network/dns/configure", {
method: "post", method: "post",
@ -50,49 +50,14 @@ PEACH_DNS.add = function() {
}) })
.then( (jsonData) => { .then( (jsonData) => {
// write json response message to ui // write json response message to ui
PEACH_DNS.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
let statusIndicator = document.getElementById("dyndns-status-indicator"); let statusIndicator = document.getElementById("dyndns-status-indicator");
statusIndicator.remove(); // only remove the "dyndns-status-indicator" element if it exists
if (statusIndicator != null ) statusIndicator.remove();
}) })
}, false); }, false);
}); });
} }
// display a message by appending a paragraph element var configureDnsInstance = PEACH_DNS;
PEACH_DNS.flashMsg = function(status, msg) { configureDnsInstance.configureDns();
// set the class of the element according to status
var elementClass;
if (status === "success") {
elementClass = "capsule center-text flash-message font-success";
} else if (status === "info") {
elementClass = "capsule center-text flash-message font-info";
} else {
elementClass = "capsule center-text flash-message font-failure";
};
var flashElement = document.getElementById("flashMsg");
// if flashElement exists, update the class & text
if (flashElement) {
flashElement.className = elementClass;
flashElement.innerText = msg;
// if flashElement does not exist, create it, set id, class, text & append
} else {
// create new div for flash message
var flashDiv = document.createElement("DIV");
// set div attributes
flashDiv.id = "flashMsg";
flashDiv.className = elementClass;
// add json response message to flash message div
var flashMsg = document.createTextNode(msg);
flashDiv.appendChild(flashMsg);
// insert the flash message div below the button div
var buttonDiv = document.getElementById("buttonDiv");
// flashDiv will be added to the end since buttonDiv is the last
// child within the parent element (card-container div)
buttonDiv.parentNode.insertBefore(flashDiv, buttonDiv.nextSibling);
}
}
var addInstance = PEACH_DNS;
addInstance.add();

View File

@ -10,7 +10,6 @@ corresponding to the web route `/network/wifi/add`
methods: methods:
PEACH_NETWORK.add(); PEACH_NETWORK.add();
PEACH_NETWORK.flashMsg(status, msg);
*/ */
@ -34,7 +33,7 @@ PEACH_NETWORK.add = function() {
// perform json serialization // perform json serialization
var jsonData = JSON.stringify(object); var jsonData = JSON.stringify(object);
// write in-progress status message to ui // write in-progress status message to ui
PEACH_NETWORK.flashMsg("info", "Adding WiFi credentials..."); PEACH.flashMsg("info", "Adding WiFi credentials...");
// send add_wifi POST request // send add_wifi POST request
fetch("/api/v1/network/wifi", { fetch("/api/v1/network/wifi", {
method: "post", method: "post",
@ -48,46 +47,11 @@ PEACH_NETWORK.add = function() {
}) })
.then( (jsonData) => { .then( (jsonData) => {
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
}) })
}, false); }, false);
}); });
} }
// display a message by appending a paragraph element
PEACH_NETWORK.flashMsg = function(status, msg) {
// set the class of the element according to status
var elementClass;
if (status === "success") {
elementClass = "capsule center-text flash-message font-success";
} else if (status === "info") {
elementClass = "capsule center-text flash-message font-info";
} else {
elementClass = "capsule center-text flash-message font-failure";
};
var flashElement = document.getElementById("flashMsg");
// if flashElement exists, update the class & text
if (flashElement) {
flashElement.className = elementClass;
flashElement.innerText = msg;
// if flashElement does not exist, create it, set id, class, text & append
} else {
// create new div for flash message
var flashDiv = document.createElement("DIV");
// set div attributes
flashDiv.id = "flashMsg";
flashDiv.className = elementClass;
// add json response message to flash message div
var flashMsg = document.createTextNode(msg);
flashDiv.appendChild(flashMsg);
// insert the flash message div below the button div
var buttonDiv = document.getElementById("buttonDiv");
// flashDiv will be added to the end since buttonDiv is the last
// child within the parent element (card-container div)
buttonDiv.parentNode.insertBefore(flashDiv, buttonDiv.nextSibling);
}
}
var addInstance = PEACH_NETWORK; var addInstance = PEACH_NETWORK;
addInstance.add(); addInstance.add();

View File

@ -1,7 +1,7 @@
/* /*
behavioural layer for the `network_card.html.tera` template, behavioural layer for the `network_card.html.tera` template,
corresponding to the web route `/network` corresponding to the web route `/settings/network`
- intercept form submissions - intercept form submissions
- perform json api calls - perform json api calls
@ -11,10 +11,8 @@ methods:
PEACH_NETWORK.activateAp(); PEACH_NETWORK.activateAp();
PEACH_NETWORK.activateClient(); PEACH_NETWORK.activateClient();
PEACH_NETWORK.apOnline(); PEACH_NETWORK.apMode();
PEACH_NETWORK.clientOffline(); PEACH_NETWORK.clientMode();
PEACH_NETWORK.clientOnline();
PEACH_NETWORK.flashMsg(status, msg);
*/ */
@ -29,7 +27,7 @@ PEACH_NETWORK.activateAp = function() {
// prevent form submission (default behavior) // prevent form submission (default behavior)
e.preventDefault(); e.preventDefault();
// write in-progress status message to ui // write in-progress status message to ui
PEACH_NETWORK.flashMsg("info", "Deploying access point..."); PEACH.flashMsg("info", "Deploying access point...");
// send activate_ap POST request // send activate_ap POST request
fetch("/api/v1/network/activate_ap", { fetch("/api/v1/network/activate_ap", {
method: "post", method: "post",
@ -44,10 +42,10 @@ PEACH_NETWORK.activateAp = function() {
.then( (jsonData) => { .then( (jsonData) => {
console.log(jsonData.msg); console.log(jsonData.msg);
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
// if ap activation is successful, update the ui // if ap activation is successful, update the ui
if (jsonData.status === "success") { if (jsonData.status === "success") {
PEACH_NETWORK.apOnline(); PEACH_NETWORK.apMode();
} }
}) })
}, false); }, false);
@ -64,7 +62,7 @@ PEACH_NETWORK.activateClient = function() {
// prevent form submission (default behavior) // prevent form submission (default behavior)
e.preventDefault(); e.preventDefault();
// write in-progress status message to ui // write in-progress status message to ui
PEACH_NETWORK.flashMsg("info", "Enabling WiFi client..."); PEACH.flashMsg("info", "Enabling WiFi client...");
// send activate_ap POST request // send activate_ap POST request
fetch("/api/v1/network/activate_client", { fetch("/api/v1/network/activate_client", {
method: "post", method: "post",
@ -79,10 +77,10 @@ PEACH_NETWORK.activateClient = function() {
.then( (jsonData) => { .then( (jsonData) => {
console.log(jsonData.msg); console.log(jsonData.msg);
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
// if client activation is successful, update the ui // if client activation is successful, update the ui
if (jsonData.status === "success") { if (jsonData.status === "success") {
PEACH_NETWORK.clientOnline(); PEACH_NETWORK.clientMode();
} }
}) })
}, false); }, false);
@ -90,21 +88,12 @@ PEACH_NETWORK.activateClient = function() {
}); });
} }
// update ui for access point mode (status: online) // replace 'Deploy Access Point' button with 'Enable WiFi' button
PEACH_NETWORK.apOnline = function() { PEACH_NETWORK.apMode = function() {
console.log('Activating AP Mode');
// update network mode and status (icon & label)
let i = document.getElementById("netModeIcon");
i.className = "center icon icon-active";
i.src = "icons/router.svg";
let l = document.getElementById("netModeLabel");
l.textContent = "ONLINE";
// create Enable WiFi button and add it to button div // create Enable WiFi button and add it to button div
var wifiButton = document.createElement("A"); var wifiButton = document.createElement("A");
wifiButton.className = "button center"; wifiButton.className = "button center";
wifiButton.href = "/network/wifi/activate"; wifiButton.href = "/settings/network/wifi/activate";
wifiButton.id = "connectWifi"; wifiButton.id = "connectWifi";
var label = "Enable WiFi"; var label = "Enable WiFi";
var buttonText = document.createTextNode(label); var buttonText = document.createTextNode(label);
@ -114,88 +103,31 @@ PEACH_NETWORK.apOnline = function() {
let buttons = document.getElementById("buttons"); let buttons = document.getElementById("buttons");
buttons.appendChild(wifiButton); buttons.appendChild(wifiButton);
// remove the old 'Activate Access Point' button // remove the old 'Deploy Access Point' button
let apButton = document.getElementById("deployAccessPoint"); let apButton = document.getElementById("deployAccessPoint");
apButton.style = "display: none;"; apButton.remove();
} }
// update ui for wifi client mode (status: online) // replace 'Enable WiFi' button with 'Deploy Access Point' button
PEACH_NETWORK.clientOnline = function() { PEACH_NETWORK.clientMode = function() {
console.log('Activating Client Mode'); // create Deploy Access Point button and add it to button div
var apButton = document.createElement("A");
apButton.className = "button center";
apButton.href = "/settings/network/ap/activate";
apButton.id = "deployAccessPoint";
var label = "Deploy Access Point";
var buttonText = document.createTextNode(label);
apButton.appendChild(buttonText);
// update network mode and status (icon & label) // append the new button to the buttons div
let i = document.getElementById("netModeIcon"); let buttons = document.getElementById("buttons");
i.className = "center icon icon-active"; buttons.appendChild(apButton);
i.src = "icons/wifi.svg";
let l = document.getElementById("netModeLabel");
l.textContent = "ONLINE";
// TODO: think about updates for buttons (transition from ap mode) // remove the old 'Enable Wifi' button
} let wifiButton = document.getElementById("connectWifi");
wifiButton.remove();
// update ui for wifi client mode (status: offline)
PEACH_NETWORK.clientOffline = function() {
console.log('Activating Client Mode');
// update network mode and status (icon & label)
let i = document.getElementById("netModeIcon");
i.className = "center icon icon-inactive";
i.src = "icons/wifi.svg";
let l = document.getElementById("netModeLabel");
l.textContent = "OFFLINE";
// TODO: think about updates for buttons (transition from ap mode)
}
// display a message by appending a paragraph element
PEACH_NETWORK.flashMsg = function(status, msg) {
// set the class of the element according to status
var elementClass;
if (status === "success") {
elementClass = "capsule center-text flash-message font-success";
} else if (status === "info") {
elementClass = "capsule center-text flash-message font-info";
} else {
elementClass = "capsule center-text flash-message font-failure";
};
var flashElement = document.getElementById("flashMsg");
// if flashElement exists, update the class & text
if (flashElement) {
flashElement.className = elementClass;
flashElement.innerText = msg;
// if flashElement does not exist, create it, set id, class, text & append
} else {
// create new div for flash message
var flashDiv = document.createElement("DIV");
// set div attributes
flashDiv.id = "flashMsg";
flashDiv.className = elementClass;
// add json response message to flash message div
var flashMsg = document.createTextNode(msg);
flashDiv.appendChild(flashMsg);
// insert the flash message div above the three icon grid div
var gridDiv = document.getElementById("gridDiv");
gridDiv.parentNode.insertBefore(flashDiv, gridDiv);
}
} }
var networkInstance = PEACH_NETWORK; var networkInstance = PEACH_NETWORK;
networkInstance.activateAp(); networkInstance.activateAp();
networkInstance.activateClient(); networkInstance.activateClient();
/*
async function exampleFetch() {
const response = await fetch('/api/v1/network/state');
const myJson = await response.json();
//const jsonData = JSON.parse(myJson);
console.log(myJson.data.wlan0);
//var state = document.createElement("P");
//state.innerText = ""jsonData.wlan0;
//document.body.appendChild(state);
}
exampleFetch()
*/

View File

@ -1,7 +1,7 @@
/* /*
behavioural layer for the `network_detail.html.tera` template, behavioural layer for the `network_detail.html.tera` template,
corresponding to the web route `/network/wifi?<ssid>` corresponding to the web route `/settings/network/wifi?<ssid>`
- intercept button clicks for connect, disconnect and forget - intercept button clicks for connect, disconnect and forget
- perform json api call - perform json api call
@ -12,7 +12,6 @@ methods:
PEACH_NETWORK.connect(); PEACH_NETWORK.connect();
PEACH_NETWORK.disconnect(); PEACH_NETWORK.disconnect();
PEACH_NETWORK.forget(); PEACH_NETWORK.forget();
PEACH_NETWORK.flashMsg(status, msg);
*/ */
@ -33,7 +32,7 @@ PEACH_NETWORK.connect = function() {
// perform json serialization // perform json serialization
var jsonData = JSON.stringify(ssidData); var jsonData = JSON.stringify(ssidData);
// write in-progress status message to ui // write in-progress status message to ui
PEACH_NETWORK.flashMsg("info", "Connecting to access point..."); PEACH.flashMsg("info", "Connecting to access point...");
// send add_wifi POST request // send add_wifi POST request
fetch("/api/v1/network/wifi/connect", { fetch("/api/v1/network/wifi/connect", {
method: "post", method: "post",
@ -47,7 +46,7 @@ PEACH_NETWORK.connect = function() {
}) })
.then( (jsonData) => { .then( (jsonData) => {
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
}) })
}, false); }, false);
}; };
@ -69,7 +68,7 @@ PEACH_NETWORK.disconnect = function() {
// perform json serialization // perform json serialization
var jsonData = JSON.stringify(ssidData); var jsonData = JSON.stringify(ssidData);
// write in-progress status message to ui // write in-progress status message to ui
PEACH_NETWORK.flashMsg("info", "Disconnecting from access point..."); PEACH.flashMsg("info", "Disconnecting from access point...");
// send disconnect_wifi POST request // send disconnect_wifi POST request
fetch("/api/v1/network/wifi/disconnect", { fetch("/api/v1/network/wifi/disconnect", {
method: "post", method: "post",
@ -83,7 +82,7 @@ PEACH_NETWORK.disconnect = function() {
}) })
.then( (jsonData) => { .then( (jsonData) => {
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
}) })
}, false); }, false);
}; };
@ -105,7 +104,7 @@ PEACH_NETWORK.forget = function() {
// perform json serialization // perform json serialization
var jsonData = JSON.stringify(ssidData); var jsonData = JSON.stringify(ssidData);
// write in-progress status message to ui // write in-progress status message to ui
PEACH_NETWORK.flashMsg("info", "Removing credentials for access point..."); PEACH.flashMsg("info", "Removing credentials for access point...");
// send forget_ap POST request // send forget_ap POST request
fetch("/api/v1/network/wifi/forget", { fetch("/api/v1/network/wifi/forget", {
method: "post", method: "post",
@ -119,48 +118,13 @@ PEACH_NETWORK.forget = function() {
}) })
.then( (jsonData) => { .then( (jsonData) => {
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
}) })
}, false); }, false);
}; };
}); });
} }
// display a message by appending a paragraph element
PEACH_NETWORK.flashMsg = function(status, msg) {
// set the class of the element according to status
var elementClass;
if (status === "success") {
elementClass = "capsule center-text flash-message font-success";
} else if (status === "info") {
elementClass = "capsule center-text flash-message font-info";
} else {
elementClass = "capsule center-text flash-message font-failure";
};
var flashElement = document.getElementById("flashMsg");
// if flashElement exists, update the class & text
if (flashElement) {
flashElement.className = elementClass;
flashElement.innerText = msg;
// if flashElement does not exist, create it, set id, class, text & append
} else {
// create new div for flash message
var flashDiv = document.createElement("DIV");
// set div attributes
flashDiv.id = "flashMsg";
flashDiv.className = elementClass;
// add json response message to flash message div
var flashMsg = document.createTextNode(msg);
flashDiv.appendChild(flashMsg);
// insert the flash message div below the button div
var buttonDiv = document.getElementById("buttonDiv");
// flashDiv will be added to the end since buttonDiv is the last
// child within the parent element (card-container div)
buttonDiv.parentNode.insertBefore(flashDiv, buttonDiv.nextSibling);
}
}
var detailInstance = PEACH_NETWORK; var detailInstance = PEACH_NETWORK;
detailInstance.connect(); detailInstance.connect();
detailInstance.disconnect(); detailInstance.disconnect();

View File

@ -9,7 +9,6 @@ behavioural layer for the `network_modify.html.tera` template
methods: methods:
PEACH_NETWORK.modify(); PEACH_NETWORK.modify();
PEACH_NETWORK.flashMsg(status, msg);
*/ */
@ -33,7 +32,7 @@ PEACH_NETWORK.modify = function() {
// perform json serialization // perform json serialization
var jsonData = JSON.stringify(object); var jsonData = JSON.stringify(object);
// write in-progress status message to ui // write in-progress status message to ui
PEACH_NETWORK.flashMsg("info", "Updating WiFi password..."); PEACH.flashMsg("info", "Updating WiFi password...");
// send new_password POST request // send new_password POST request
fetch("/api/v1/network/wifi/modify", { fetch("/api/v1/network/wifi/modify", {
method: "post", method: "post",
@ -47,46 +46,11 @@ PEACH_NETWORK.modify = function() {
}) })
.then( (jsonData) => { .then( (jsonData) => {
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
}) })
}, false); }, false);
}); });
} }
// display a message by appending a paragraph element
PEACH_NETWORK.flashMsg = function(status, msg) {
// set the class of the element according to status
var elementClass;
if (status === "success") {
elementClass = "capsule center-text flash-message font-success";
} else if (status === "info") {
elementClass = "capsule center-text flash-message font-info";
} else {
elementClass = "capsule center-text flash-message font-failure";
};
var flashElement = document.getElementById("flashMsg");
// if flashElement exists, update the class & text
if (flashElement) {
flashElement.className = elementClass;
flashElement.innerText = msg;
// if flashElement does not exist, create it, set id, class, text & append
} else {
// create new div for flash message
var flashDiv = document.createElement("DIV");
// set div attributes
flashDiv.id = "flashMsg";
flashDiv.className = elementClass;
// add json response message to flash message div
var flashMsg = document.createTextNode(msg);
flashDiv.appendChild(flashMsg);
// insert the flash message div below the button div
var buttonDiv = document.getElementById("buttonDiv");
// flashDiv will be added to the end since buttonDiv is the last
// child within the parent element (card-container div)
buttonDiv.parentNode.insertBefore(flashDiv, buttonDiv.nextSibling);
}
}
var modifyInstance = PEACH_NETWORK; var modifyInstance = PEACH_NETWORK;
modifyInstance.modify(); modifyInstance.modify();

View File

@ -1,7 +1,7 @@
/* /*
behavioural layer for the `network_usage.html.tera` template, behavioural layer for the `network_usage.html.tera` template,
corresponding to the web route `/network/wifi/usage` corresponding to the web route `/settings/network/wifi/usage`
- intercept form submissions - intercept form submissions
- perform json api calls - perform json api calls
@ -13,7 +13,6 @@ methods:
PEACH_NETWORK.resetUsage(); PEACH_NETWORK.resetUsage();
PEACH_NETWORK.toggleWarning(); PEACH_NETWORK.toggleWarning();
PEACH_NETWORK.toggleCutoff(); PEACH_NETWORK.toggleCutoff();
PEACH_NETWORK.flashMsg(status, msg);
*/ */
@ -51,7 +50,7 @@ PEACH_NETWORK.updateAlerts = function() {
}) })
.then( (jsonData) => { .then( (jsonData) => {
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
}) })
}, false); }, false);
}); });
@ -79,7 +78,7 @@ PEACH_NETWORK.resetUsage = function() {
.then( (jsonData) => { .then( (jsonData) => {
console.log(jsonData.msg); console.log(jsonData.msg);
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
// if reset is successful, update the ui // if reset is successful, update the ui
if (jsonData.status === "success") { if (jsonData.status === "success") {
console.log(jsonData.data); console.log(jsonData.data);
@ -133,39 +132,6 @@ PEACH_NETWORK.toggleCutoff = function() {
}); });
}; };
// display a message by appending a paragraph element
PEACH_NETWORK.flashMsg = function(status, msg) {
// set the class of the element according to status
var elementClass;
if (status === "success") {
elementClass = "capsule center-text flash-message font-success";
} else if (status === "info") {
elementClass = "capsule center-text flash-message font-info";
} else {
elementClass = "capsule center-text flash-message font-failure";
};
var flashElement = document.getElementById("flashMsg");
// if flashElement exists, update the class & text
if (flashElement) {
flashElement.className = elementClass;
flashElement.innerText = msg;
// if flashElement does not exist, create it, set id, class, text & append
} else {
// create new div for flash message
var flashDiv = document.createElement("DIV");
// set div attributes
flashDiv.id = "flashMsg";
flashDiv.className = elementClass;
// add json response message to flash message div
var flashMsg = document.createTextNode(msg);
flashDiv.appendChild(flashMsg);
// insert the flash message div below the button div
var buttonDiv = document.getElementById("buttonDiv");
buttonDiv.parentNode.insertBefore(flashDiv, buttonDiv.nextSibling);
}
}
var usageInstance = PEACH_NETWORK; var usageInstance = PEACH_NETWORK;
usageInstance.resetUsage(); usageInstance.resetUsage();
usageInstance.toggleWarning(); usageInstance.toggleWarning();

View File

@ -11,7 +11,6 @@ methods:
PEACH_DEVICE.reboot(); PEACH_DEVICE.reboot();
PEACH_DEVICE.shutdown(); PEACH_DEVICE.shutdown();
PEACH_DEVICE.flashMsg(status, msg);
*/ */
@ -26,7 +25,7 @@ PEACH_DEVICE.reboot = function() {
// prevent redirect on button press (default behavior) // prevent redirect on button press (default behavior)
e.preventDefault(); e.preventDefault();
// write reboot flash message // write reboot flash message
PEACH_DEVICE.flashMsg("success", "Rebooting the device..."); PEACH.flashMsg("success", "Rebooting the device...");
// send reboot_device POST request // send reboot_device POST request
fetch("/api/v1/admin/reboot", { fetch("/api/v1/admin/reboot", {
method: "post", method: "post",
@ -41,7 +40,7 @@ PEACH_DEVICE.reboot = function() {
.then( (jsonData) => { .then( (jsonData) => {
console.log(jsonData.msg); console.log(jsonData.msg);
// write json response message to ui // write json response message to ui
PEACH_DEVICE.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
}) })
}, false); }, false);
} }
@ -57,7 +56,7 @@ PEACH_DEVICE.shutdown = function() {
// prevent form submission (default behavior) // prevent form submission (default behavior)
e.preventDefault(); e.preventDefault();
// write shutdown flash message // write shutdown flash message
PEACH_DEVICE.flashMsg("success", "Shutting down the device..."); PEACH.flashMsg("success", "Shutting down the device...");
// send shutdown_device POST request // send shutdown_device POST request
fetch("/api/v1/shutdown", { fetch("/api/v1/shutdown", {
method: "post", method: "post",
@ -72,48 +71,13 @@ PEACH_DEVICE.shutdown = function() {
.then( (jsonData) => { .then( (jsonData) => {
console.log(jsonData.msg); console.log(jsonData.msg);
// write json response message to ui // write json response message to ui
PEACH_DEVICE.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
}) })
}, false); }, false);
} }
}); });
} }
// display a message by appending a paragraph element
PEACH_DEVICE.flashMsg = function(status, msg) {
// set the class of the element according to status
var elementClass;
if (status === "success") {
elementClass = "capsule center-text flash-message font-success";
} else if (status === "info") {
elementClass = "capsule center-text flash-message font-info";
} else {
elementClass = "capsule center-text flash-message font-failure";
};
var flashElement = document.getElementById("flashMsg");
// if flashElement exists, update the class & text
if (flashElement) {
flashElement.className = elementClass;
flashElement.innerText = msg;
// if flashElement does not exist, create it, set id, class, text & append
} else {
// create new div for flash message
var flashDiv = document.createElement("DIV");
// set div attributes
flashDiv.id = "flashMsg";
flashDiv.className = elementClass;
// add json response message to flash message div
var flashMsg = document.createTextNode(msg);
flashDiv.appendChild(flashMsg);
// insert the flash message div below the button div
var buttonDiv = document.getElementById("buttonDiv");
// flashDiv will be added to the end since buttonDiv is the last
// child within the parent element (card-container div)
buttonDiv.parentNode.insertBefore(flashDiv, buttonDiv.nextSibling);
}
}
var deviceInstance = PEACH_DEVICE; var deviceInstance = PEACH_DEVICE;
deviceInstance.reboot(); deviceInstance.reboot();
deviceInstance.shutdown(); deviceInstance.shutdown();

View File

@ -2,15 +2,17 @@
* behavioural layer for the `reset_password.html.tera` template, * behavioural layer for the `reset_password.html.tera` template,
*/ */
var PEACH_AUTH = {};
// catch click of 'Save' button and make POST request // catch click of 'Save' button and make POST request
PEACH.add = function() { PEACH_AUTH.resetPassword = function() {
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
document.body.addEventListener('submit', function(e) { document.body.addEventListener('submit', function(e) {
// prevent redirect on button press (default behavior) // prevent redirect on button press (default behavior)
e.preventDefault(); e.preventDefault();
// capture form data // capture form data
var formElement = document.querySelector("form"); var formElement = document.querySelector("form");
// create form data object from the wifiCreds form element // create form data object from the changePassword form element
var formData = new FormData(formElement); var formData = new FormData(formElement);
var object = {}; var object = {};
// assign values from form // assign values from form
@ -41,5 +43,5 @@ PEACH.add = function() {
}); });
} }
var addInstance = PEACH; var resetPassInstance = PEACH_AUTH;
addInstance.add(); resetPassInstance.resetPassword();

View File

@ -1,9 +1,11 @@
{%- extends "nav" -%} {%- extends "nav" -%}
{%- block card %} {%- block card %}
<div class="card center"> <div class="card center">
<div class="card-container capsule info-border"> <div class="capsule-container">
<p>No PeachCloud resource exists for this URL. Please ensure that the URL in the address bar is correct.</p> <div class="capsule info-border">
<p>Click the back arrow in the top-left or the PeachCloud logo at the bottom of your screen to return Home.</p> <p>No PeachCloud resource exists for this URL. Please ensure that the URL in the address bar is correct.</p>
</div> <p>Click the back arrow in the top-left or the PeachCloud logo at the bottom of your screen to return Home.</p>
</div>
</div> </div>
</div>
{%- endblock card -%} {%- endblock card -%}

View File

@ -4,47 +4,22 @@
<div class="card center"> <div class="card center">
<div class="form-container"> <div class="form-container">
<form id="changePassword" action="/settings/change_password" method="post"> <form id="changePassword" action="/settings/change_password" method="post">
<div class="input-wrapper"> <!-- input for current password -->
<!-- input for old password --> <input id="currentPassword" class="center input" name="current_password" type="password" placeholder="Current password" title="Current password" autofocus>
<label id="old_password" class="label-small input-label font-near-black"> <!-- input for new password -->
<label class="label-small input-label font-gray" for="old_password" style="padding-top: 0.25rem;">Old Password</label> <input id="newPassword" class="center input" name="new_password1" type="password" placeholder="New password" title="New password">
<input id="old_password" class="form-input" style="margin-bottom: 0;" <!-- input for duplicate new password -->
name="old_password" type="password" title="old password" value=""> <input id="newPasswordDuplicate" class="center input" name="new_password2" type="password" placeholder="Re-enter new password" title="New password duplicate">
</label>
</div>
<div class="input-wrapper">
<!-- input for new password1 -->
<label id="new_password1" class="label-small input-label font-near-black">
<label class="label-small input-label font-gray" for="new_password1" style="padding-top: 0.25rem;">Enter New Password</label>
<input id="new_password1" class="form-input" style="margin-bottom: 0;"
name="new_password1" title="new_password1" type="password" value="">
</label>
</div>
<div class="input-wrapper">
<!-- input for new password2 -->
<label id="new_password2" class="label-small input-label font-near-black">
<label class="label-small input-label font-gray" for="new_password2" style="padding-top: 0.25rem;">Re-Enter New Password</label>
<input id="new_password2" class="form-input" style="margin-bottom: 0;"
name="new_password2" title="new_password2" type="password" value="">
</label>
</div>
<div id="buttonDiv"> <div id="buttonDiv">
<input id="changePasswordButton" class="button button-primary center" title="Add" type="submit" value="Save"> <input id="savePassword" class="button button-primary center" title="Add" type="submit" value="Save">
<a class="button button-secondary center" href="/settings/admin" title="Cancel">Cancel</a>
</div> </div>
<a class="button button-secondary center" href="/settings/admin" title="Cancel">Cancel</a>
</form> </form>
<!-- FLASH MESSAGE --> <!-- FLASH MESSAGE -->
{% include "snippets/flash_message" %} {% include "snippets/flash_message" %}
<!-- NO SCRIPT FOR WHEN JS IS DISABLED --> <!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
{% include "snippets/noscript" %} {% include "snippets/noscript" %}
</div> </div>
</div> </div>
<script type="text/javascript" src="/js/change_password.js"></script> <script type="text/javascript" src="/js/change_password.js"></script>
{%- endblock card -%} {%- endblock card -%}

View File

@ -1,25 +1,20 @@
{%- extends "nav" -%} {%- extends "nav" -%}
{%- block card %} {%- block card %}
<!--PUBLIC PAGE FOR SENDING A NEW TEMPORARY PASSWORD TO BE USED TO RESET YOUR PASSWORD --> <!-- PASSWORD RESET REQUEST CARD -->
<div class="card center"> <div class="card center">
<p class="text-notice" style="width: 80%; margin:auto; margin-bottom: 35px; margin-top: 20px;"> <div class="capsule capsule-container info-border">
Click the button below to send a new temporary password which can be used to change your device password. <p class="card-text">Click the button below to send a new temporary password which can be used to change your device password.
<br/><br/> </br></br>
The temporary password will be sent in an SSB private message to the admin of this device. The temporary password will be sent in an SSB private message to the admin of this device.</p>
</p> </div>
<form id="sendPasswordReset" action="/send_password_reset" method="post"> <form id="sendPasswordReset" action="/send_password_reset" method="post">
<div id="buttonDiv"> <div id="buttonDiv">
<input type="submit" class="button center button-secondary" value="Send Password Reset" title="Send Password Reset Link"/> <input class="button button-primary center" style="margin-top: 1rem;" type="submit" value="Send Password Reset" title="Send Password Reset Link"/>
</div> </div>
</form> </form>
<!-- FLASH MESSAGE --> <!-- FLASH MESSAGE -->
{% include "snippets/flash_message" %} {% include "snippets/flash_message" %}
<!-- NO SCRIPT FOR WHEN JS IS DISABLED --> <!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
{% include "snippets/noscript" %} {% include "snippets/noscript" %}
</div>
</div> </div>
{%- endblock card -%} {%- endblock card -%}

View File

@ -2,12 +2,10 @@
{%- block card %} {%- block card %}
<!-- ADMIN SETTINGS MENU --> <!-- ADMIN SETTINGS MENU -->
<div class="card center"> <div class="card center">
<div class="card-container"> <!-- BUTTONS -->
<!-- BUTTONS --> <div id="settingsButtons">
<div id="settingsButtons"> <a id="change" class="button button-primary center" href="/settings/admin/change_password" title="Change Password">Change Password</a>
<a id="change" class="button button-primary center" href="/settings/admin/change_password" title="Change Password">Change Password</a> <a id="configure" class="button button-primary center" href="/settings/admin/configure" title="Configure Admin">Configure Admin</a>
<a id="configure" class="button button-primary center" href="/settings/admin/configure" title="Configure Admin">Configure Admin</a>
</div>
</div> </div>
</div> </div>
{%- endblock card -%} {%- endblock card -%}

View File

@ -2,13 +2,11 @@
{%- block card %} {%- block card %}
<!-- SETTINGS MENU --> <!-- SETTINGS MENU -->
<div class="card center"> <div class="card center">
<div class="card-container"> <!-- BUTTONS -->
<!-- BUTTONS --> <div id="settingsButtons">
<div id="settingsButtons"> <a id="network" class="button button-primary center" href="/settings/network" title="Network Settings">Network</a>
<a id="network" class="button button-primary center" href="/settings/network" title="Network Settings">Network</a> <a id="scuttlebutt" class="button button-primary center" href="/settings/scuttlebutt" title="Scuttlebutt Settings">Scuttlebutt</a>
<a id="scuttlebutt" class="button button-primary center" href="/settings/scuttlebutt" title="Scuttlebutt Settings">Scuttlebutt</a> <a id="admin" class="button button-primary center" href="/settings/admin" title="Administrator Settings">Administration</a>
<a id="admin" class="button button-primary center" href="/settings/admin" title="Administrator Settings">Administration</a>
</div>
</div> </div>
</div> </div>
{%- endblock card -%} {%- endblock card -%}

View File

@ -2,19 +2,17 @@
{%- block card %} {%- block card %}
<!-- SCUTTLEBUTT SETTINGS MENU --> <!-- SCUTTLEBUTT SETTINGS MENU -->
<div class="card center"> <div class="card center">
<div class="card-container"> <!-- BUTTONS -->
<!-- BUTTONS --> <div id="settingsButtons">
<div id="settingsButtons"> <a id="networkKey" class="button button-primary center" href="/settings/scuttlebutt/network_key" title="Set Network Key">Set Network Key</a>
<a id="networkKey" class="button button-primary center" href="/settings/scuttlebutt/network_key" title="Set Network Key">Set Network Key</a> <a id="replicationHops" class="button button-primary center" href="/settings/scuttlebutt/hops" title="Set Replication Hops">Set Replication Hops</a>
<a id="replicationHops" class="button button-primary center" href="/settings/scuttlebutt/hops" title="Set Replication Hops">Set Replication Hops</a> <a id="removeFeeds" class="button button-primary center" href="/settings/scuttlebutt/remove_feeds" title="Remove Blocked Feeds">Remove Blocked Feeds</a>
<a id="removeFeeds" class="button button-primary center" href="/settings/scuttlebutt/remove_feeds" title="Remove Blocked Feeds">Remove Blocked Feeds</a> <a id="setDirectory" class="button button-primary center" href="/settings/scuttlebutt/set_directory" title="Set Database Directory">Set Database Directory</a>
<a id="setDirectory" class="button button-primary center" href="/settings/scuttlebutt/set_directory" title="Set Database Directory">Set Database Directory</a> <a id="checkFilesystem" class="button button-primary center" href="/settings/scuttlebutt/check_fs" title="Check Filesystem">Check Filesystem</a>
<a id="checkFilesystem" class="button button-primary center" href="/settings/scuttlebutt/check_fs" title="Check Filesystem">Check Filesystem</a> <a id="repairFilesystem" class="button button-primary center" href="/settings/scuttlebutt/repair" title="Repair Filesystem">Repair Filesystem</a>
<a id="repairFilesystem" class="button button-primary center" href="/settings/scuttlebutt/repair" title="Repair Filesystem">Repair Filesystem</a> <a id="disable" class="button button-primary center" href="/settings/scuttlebutt/disable" title="Disable Sbot">Disable Sbot</a>
<a id="disable" class="button button-primary center" href="/settings/scuttlebutt/disable" title="Disable Sbot">Disable Sbot</a> <a id="enable" class="button button-primary center" href="/settings/scuttlebutt/enable" title="Enable Sbot">Enable Sbot</a>
<a id="enable" class="button button-primary center" href="/settings/scuttlebutt/enable" title="Enable Sbot">Enable Sbot</a> <a id="restart" class="button button-primary center" href="/settings/scuttlebutt/restart" title="Restart Sbot">Restart Sbot</a>
<a id="restart" class="button button-primary center" href="/settings/scuttlebutt/restart" title="Restart Sbot">Restart Sbot</a>
</div>
</div> </div>
</div> </div>
{%- endblock card -%} {%- endblock card -%}