Create authentication request guard #17

Merged
notplants merged 7 commits from auth into main 2021-11-10 11:35:47 +00:00
21 changed files with 516 additions and 159 deletions

245
Cargo.lock generated
View File

@ -17,6 +17,60 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aead"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "aes"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561"
dependencies = [
"aes-soft",
"aesni",
"cipher",
]
[[package]]
name = "aes-gcm"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da"
dependencies = [
"aead",
"aes",
"cipher",
"ctr",
"ghash",
"subtle",
]
[[package]]
name = "aes-soft"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072"
dependencies = [
"cipher",
"opaque-debug 0.3.0",
]
[[package]]
name = "aesni"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce"
dependencies = [
"cipher",
"opaque-debug 0.3.0",
]
[[package]]
name = "aho-corasick"
version = "0.7.18"
@ -159,6 +213,12 @@ dependencies = [
"byteorder",
]
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "binascii"
version = "0.1.4"
@ -192,7 +252,16 @@ dependencies = [
"block-padding",
"byte-tools",
"byteorder",
"generic-array",
"generic-array 0.12.4",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
@ -322,6 +391,15 @@ dependencies = [
"phf_codegen",
]
[[package]]
name = "cipher"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "clap"
version = "2.33.3"
@ -384,7 +462,13 @@ version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d"
dependencies = [
"aes-gcm",
"base64 0.13.0",
"hkdf",
"percent-encoding 2.1.0",
"rand 0.8.4",
"sha2",
"subtle",
"time 0.2.27",
"version_check 0.9.3",
]
@ -405,6 +489,21 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "cpufeatures"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
dependencies = [
"libc",
]
[[package]]
name = "cpuid-bool"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
[[package]]
name = "crossbeam-channel"
version = "0.3.9"
@ -482,6 +581,25 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "crypto-mac"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a"
dependencies = [
"generic-array 0.14.4",
"subtle",
]
[[package]]
name = "ctr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f"
dependencies = [
"cipher",
]
[[package]]
name = "ctrlc"
version = "3.2.1"
@ -550,7 +668,16 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
dependencies = [
"generic-array",
"generic-array 0.12.4",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
@ -941,6 +1068,16 @@ dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
dependencies = [
"typenum",
"version_check 0.9.3",
]
[[package]]
name = "get_if_addrs"
version = "0.5.3"
@ -985,6 +1122,16 @@ dependencies = [
"wasi 0.10.0+wasi-snapshot-preview1",
]
[[package]]
name = "ghash"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375"
dependencies = [
"opaque-debug 0.3.0",
"polyval",
]
[[package]]
name = "gimli"
version = "0.26.1"
@ -1094,6 +1241,26 @@ dependencies = [
"libc",
]
[[package]]
name = "hkdf"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f"
dependencies = [
"digest 0.9.0",
"hmac",
]
[[package]]
name = "hmac"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
dependencies = [
"crypto-mac",
"digest 0.9.0",
]
[[package]]
name = "http"
version = "0.1.21"
@ -2222,6 +2389,12 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
version = "0.10.38"
@ -2409,6 +2582,7 @@ dependencies = [
"log 0.4.14",
"rand 0.8.4",
"regex",
"rust-crypto",
"serde 1.0.130",
"serde_derive",
"serde_json",
@ -2673,6 +2847,17 @@ version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f"
[[package]]
name = "polyval"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd"
dependencies = [
"cpuid-bool",
"opaque-debug 0.3.0",
"universal-hash",
]
[[package]]
name = "ppv-lite86"
version = "0.2.15"
@ -2686,7 +2871,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f77e66f6d6d898cbbd4a09c48fd3507cfc210b7c83055de02a38b5f7a1e6d216"
dependencies = [
"libc",
"time 0.1.44",
"time 0.2.27",
]
[[package]]
@ -3196,12 +3381,31 @@ dependencies = [
"uncased",
]
[[package]]
name = "rust-crypto"
version = "0.2.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a"
dependencies = [
"gcc",
"libc",
"rand 0.3.23",
"rustc-serialize",
"time 0.1.44",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "rustc-serialize"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
[[package]]
name = "rustc_version"
version = "0.1.7"
@ -3445,10 +3649,10 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
dependencies = [
"block-buffer",
"digest",
"block-buffer 0.7.3",
"digest 0.8.1",
"fake-simd",
"opaque-debug",
"opaque-debug 0.2.3",
]
[[package]]
@ -3457,6 +3661,19 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
[[package]]
name = "sha2"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
dependencies = [
"block-buffer 0.9.0",
"cfg-if 1.0.0",
"cpufeatures",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
[[package]]
name = "sharded-slab"
version = "0.1.4"
@ -3745,6 +3962,12 @@ dependencies = [
"syn 1.0.81",
]
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "0.15.44"
@ -4537,6 +4760,16 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "universal-hash"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05"
dependencies = [
"generic-array 0.14.4",
"subtle",
]
[[package]]
name = "url"
version = "1.7.2"

View File

@ -12,6 +12,7 @@ jsonrpc-client-core = "0.5"
jsonrpc-client-http = "0.5"
jsonrpc-core = "8.0.1"
serde = { version = "1.0", features = ["derive"] }
rust-crypto = "0.2.36"
serde_derive = "1.0"
serde_json = "1.0"
serde_yaml = "0.8"

View File

@ -4,7 +4,7 @@
//!
//! The configuration file is located at: "/var/lib/peachcloud/config.yml"
use fslock::LockFile;
use fslock::{LockFile};
use serde::{Deserialize, Serialize};
use std::fs;
@ -34,6 +34,10 @@ pub struct PeachConfig {
pub dyn_enabled: bool,
#[serde(default)] // default is empty vector
pub ssb_admin_ids: Vec<String>,
#[serde(default)]
pub admin_password_hash: String,
#[serde(default)]
pub temporary_password_hash: String,
}
// helper functions for serializing and deserializing PeachConfig from disc
@ -69,6 +73,8 @@ pub fn load_peach_config() -> Result<PeachConfig, PeachError> {
dyn_tsig_key_path: "".to_string(),
dyn_enabled: false,
ssb_admin_ids: Vec::new(),
admin_password_hash: "".to_string(),
temporary_password_hash: "".to_string(),
};
}
// otherwise we load peach config from disk
@ -141,3 +147,33 @@ pub fn delete_ssb_admin_id(ssb_id: &str) -> Result<PeachConfig, PeachError> {
}),
}
}
pub fn set_admin_password_hash(password_hash: &str) -> Result<PeachConfig, PeachError> {
let mut peach_config = load_peach_config()?;
peach_config.admin_password_hash = password_hash.to_string();
save_peach_config(peach_config)
}
pub fn get_admin_password_hash() -> Result<String, PeachError> {
let peach_config = load_peach_config()?;
if !peach_config.admin_password_hash.is_empty() {
Ok(peach_config.admin_password_hash)
} else {
Err(PeachError::PasswordNotSet)
}
}
pub fn set_temporary_password_hash(password_hash: &str) -> Result<PeachConfig, PeachError> {
let mut peach_config = load_peach_config()?;
peach_config.temporary_password_hash = password_hash.to_string();
save_peach_config(peach_config)
}
pub fn get_temporary_password_hash() -> Result<String, PeachError> {
let peach_config = load_peach_config()?;
if !peach_config.temporary_password_hash.is_empty() {
Ok(peach_config.temporary_password_hash)
} else {
Err(PeachError::PasswordNotSet)
}
}

View File

@ -63,6 +63,8 @@ pub enum PeachError {
SaveDynDnsResultError { source: std::io::Error },
#[snafu(display("New passwords do not match"))]
PasswordsDoNotMatch,
#[snafu(display("No admin password is set"))]
PasswordNotSet,
#[snafu(display("The supplied password was not correct"))]
InvalidPassword,
#[snafu(display("Error saving new password: {}", msg))]

View File

@ -1,34 +1,21 @@
use crate::config_manager::{get_peachcloud_domain, load_peach_config};
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::error::StdIoError;
use crate::sbot_client;
use log::info;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use snafu::ResultExt;
use std::iter;
use std::process::Command;
/// filepath where nginx basic auth passwords are stored
pub const HTPASSWD_FILE: &str = "/var/lib/peachcloud/passwords/htpasswd";
/// filepath where random temporary password is stored for password resets
pub const HTPASSWD_TEMPORARY_PASSWORD_FILE: &str =
"/var/lib/peachcloud/passwords/temporary_password";
/// the username of the user for nginx basic auth
pub const PEACHCLOUD_AUTH_USER: &str = "admin";
use crypto::digest::Digest;
use crypto::sha3::Sha3;
/// Returns Ok(()) if the supplied password is correct,
/// and returns Err if the supplied password is incorrect.
pub fn verify_password(password: &str) -> Result<(), PeachError> {
let output = Command::new("/usr/bin/htpasswd")
.arg("-vb")
.arg(HTPASSWD_FILE)
.arg(PEACHCLOUD_AUTH_USER)
.arg(password)
.output()
.context(StdIoError {
msg: "htpasswd is not installed",
})?;
if output.status.success() {
let real_admin_password_hash = get_admin_password_hash()?;
let password_hash = hash_password(&password.to_string());
if real_admin_password_hash == password_hash {
Ok(())
} else {
Err(PeachError::InvalidPassword)
@ -47,66 +34,55 @@ pub fn validate_new_passwords(new_password1: &str, new_password2: &str) -> Resul
}
}
/// Uses htpasswd to set a new password for the admin user
/// Sets a new password for the admin user
notplants marked this conversation as resolved Outdated
Outdated
Review

Don't forget to update the doc comment here.

Don't forget to update the doc comment here.
pub fn set_new_password(new_password: &str) -> Result<(), PeachError> {
let output = Command::new("/usr/bin/htpasswd")
.arg("-cb")
.arg(HTPASSWD_FILE)
.arg(PEACHCLOUD_AUTH_USER)
.arg(new_password)
.output()
.context(StdIoError {
msg: "htpasswd is not installed",
})?;
if output.status.success() {
Ok(())
} else {
let err_output = String::from_utf8(output.stderr)?;
Err(PeachError::FailedToSetNewPassword { msg: err_output })
let new_password_hash = hash_password(&new_password.to_string());
let result = set_admin_password_hash(&new_password_hash);
match result {
notplants marked this conversation as resolved
Review

I like the readability of the match here. It's quite straightforward for a new Rustacean to read the code and grok what's happening.

Another option is to transform the Result type directly:

pub fn set_new_password(new_password: &str) -> Result<(), PeachError> {
    let new_password_hash = hash_password(&new_password.to_string());
    set_admin_password_hash(&new_password_hash)
        // map `Result<PeachConfig, PeachError>` to `Result<(), PeachError>`
        .map(|_| ())
        // map `PeachError` to the `FailedToSetNewPassword` variant
        .map_err(|_| PeachError::FailedToSetNewPassword {
            msg: "failed to save password hash".to_string(),
        })
}

I don't have a strong preference but thought it might be interesting to share another approach :)

I like the readability of the `match` here. It's quite straightforward for a new Rustacean to read the code and grok what's happening. Another option is to transform the `Result` type directly: ```rust pub fn set_new_password(new_password: &str) -> Result<(), PeachError> { let new_password_hash = hash_password(&new_password.to_string()); set_admin_password_hash(&new_password_hash) // map `Result<PeachConfig, PeachError>` to `Result<(), PeachError>` .map(|_| ()) // map `PeachError` to the `FailedToSetNewPassword` variant .map_err(|_| PeachError::FailedToSetNewPassword { msg: "failed to save password hash".to_string(), }) } ``` I don't have a strong preference but thought it might be interesting to share another approach :)
Review

wow I am super happy to know about this as a possible alternate syntax for handling success/error, thanks for sharing

this seems quite ergonomic

I also dont have a strong preference

wow I am super happy to know about this as a possible alternate syntax for handling success/error, thanks for sharing this seems quite ergonomic I also dont have a strong preference
Ok(_) => {
Ok(())
},
Err(_err) => {
Err(PeachError::FailedToSetNewPassword { msg: "failed to save password hash".to_string() })
}
}
}
/// Uses htpasswd to set a new temporary password for the admin user
/// Creates a hash from a password string
pub fn hash_password(password: &str) -> String {
let mut hasher = Sha3::sha3_256();
hasher.input_str(password);
hasher.result_str()
}
/// Sets a new temporary password for the admin user
notplants marked this conversation as resolved Outdated
Outdated
Review

Another small doc comment update needed here.

Another small doc comment update needed here.

thanks!

stale comments are a source of great possible confusion

thanks! stale comments are a source of great possible confusion
/// which can be used to reset the permanent password
pub fn set_new_temporary_password(new_password: &str) -> Result<(), PeachError> {
let output = Command::new("/usr/bin/htpasswd")
.arg("-cb")
.arg(HTPASSWD_TEMPORARY_PASSWORD_FILE)
.arg(PEACHCLOUD_AUTH_USER)
.arg(new_password)
.output()
.context(StdIoError {
msg: "htpasswd is not installed",
})?;
if output.status.success() {
Ok(())
} else {
let err_output = String::from_utf8(output.stderr)?;
Err(PeachError::FailedToSetNewPassword { msg: err_output })
let new_password_hash = hash_password(&new_password.to_string());
let result = set_temporary_password_hash(&new_password_hash);
match result {
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,
/// and returns Err if the supplied temp_password is incorrect
pub fn verify_temporary_password(password: &str) -> Result<(), PeachError> {
// TODO: confirm temporary password has not expired
let output = Command::new("/usr/bin/htpasswd")
.arg("-vb")
.arg(HTPASSWD_TEMPORARY_PASSWORD_FILE)
.arg(PEACHCLOUD_AUTH_USER)
.arg(password)
.output()
.context(StdIoError {
msg: "htpasswd is not installed",
})?;
if output.status.success() {
let temporary_admin_password_hash = get_temporary_password_hash()?;
let password_hash = hash_password(&password.to_string());
if temporary_admin_password_hash == password_hash {
Ok(())
} else {
Err(PeachError::InvalidPassword)
}
}
/// generates a temporary password and sends it via ssb dm
/// Generates a temporary password and sends it via ssb dm
/// to the ssb id configured to be the admin of the peachcloud device
pub fn send_password_reset() -> Result<(), PeachError> {
// first generate a new random password of ascii characters

View File

@ -40,7 +40,7 @@ log = "0.4"
nest = "1.0.0"
peach-lib = { path = "../peach-lib" }
percent-encoding = "2.1.0"
rocket = { version = "0.5.0-rc.1", features = ["json"] }
rocket = { version = "0.5.0-rc.1", features = ["json", "secrets"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
snafu = "0.6"

View File

@ -38,7 +38,7 @@ use rocket_dyn_templates::Template;
use crate::routes::authentication::*;
use crate::routes::device::*;
use crate::routes::helpers::*;
use crate::routes::catchers::*;
use crate::routes::index::*;
use crate::routes::ping::*;
use crate::routes::scuttlebutt::*;
@ -66,6 +66,7 @@ fn init_rocket() -> Rocket<Build> {
help, // WEB ROUTE
index, // WEB ROUTE
login, // WEB ROUTE
login_post, // WEB ROUTE
logout, // WEB ROUTE
messages, // WEB ROUTE
network_home, // WEB ROUTE
@ -86,6 +87,7 @@ fn init_rocket() -> Rocket<Build> {
configure_dns, // WEB ROUTE
configure_dns_post, // WEB ROUTE
change_password, // WEB ROUTE
change_password_post, // WEB ROUTE
reset_password, // WEB ROUTE
reset_password_post, // WEB ROUTE
send_password_reset_page, // WEB ROUTE
@ -121,7 +123,7 @@ fn init_rocket() -> Rocket<Build> {
],
)
.mount("/", FileServer::from("static"))
.register("/", catchers![not_found, internal_error])
.register("/", catchers![not_found, internal_error, forbidden])
.attach(Template::fairing())
}

View File

@ -1,4 +1,4 @@
use log::{debug, info};
use log::{info};
use rocket::request::{FlashMessage};
use rocket::form::{Form, FromForm};
use rocket::response::{Flash, Redirect};
@ -8,10 +8,58 @@ use rocket_dyn_templates::Template;
use rocket::serde::{Deserialize, Serialize};
use peach_lib::password_utils;
use peach_lib::error::PeachError;
use crate::error::PeachWebError;
use crate::utils::build_json_response;
use crate::utils::{build_json_response, TemplateOrRedirect};
use rocket::serde::json::Value;
use rocket::request::{self, FromRequest, Request};
use rocket::http::{Cookie, CookieJar, Status};
// HELPERS AND STRUCTS FOR AUTHENTICATION WITH COOKIES
pub const AUTH_COOKIE_KEY: &str = "peachweb_auth";
pub const ADMIN_USERNAME: &str = "admin";
/// Note: Currently we use an empty struct for the Authenticated request guard
/// because there is only one user to be authenticated, and no data needs to be stored here.
/// In a multi-user authentication scheme, we would store the user_id in this struct,
/// and retrieve the correct user via the user_id stored in the cookie.
pub struct Authenticated;
#[derive(Debug)]
pub enum LoginError {
UserNotLoggedIn
}
/// Request guard which returns an empty Authenticated struct from the request
/// if and only if the user has a cookie which proves they are authenticated with peach-web.
notplants marked this conversation as resolved Outdated
Outdated
Review

Tiny typo here on iff.

Tiny typo here on `iff`.

"iff" is a shorthand that means "if and only if" (https://en.wikipedia.org/wiki/If_and_only_if),

for now I changed the comment to say "if and only if",
but "iff" is a pretty useful acronym, gets used a fair amount in technical docs, maybe you will see it around more now.

"iff" is a shorthand that means "if and only if" (https://en.wikipedia.org/wiki/If_and_only_if), for now I changed the comment to say "if and only if", but "iff" is a pretty useful acronym, gets used a fair amount in technical docs, maybe you will see it around more now.
Outdated
Review

Oh wow, I had no idea! Thanks for pointing that out.

Oh wow, I had no idea! Thanks for pointing that out.
///
/// Note that cookies.get_private uses encryption, which means that this private cookie
/// cannot be inspected, tampered with, or manufactured by clients.
#[rocket::async_trait]
impl<'r> FromRequest<'r> for Authenticated {
type Error = LoginError;
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
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))
}
}
}
}
// HELPERS AND ROUTES FOR /login
@ -48,24 +96,60 @@ pub fn login(flash: Option<FlashMessage>) -> Template {
Template::render("login", &context)
}
#[derive(Debug, Deserialize, FromForm)]
pub struct LoginForm {
pub username: String,
pub password: String,
}
/// Takes in a LoginForm and returns Ok(()) if username and password
/// are correct to authenticate with peach-web.
///
/// Note: currently there is only one user, and the username should always
/// be "admin".
pub fn verify_login_form(login_form: LoginForm) -> Result<(), PeachError> {
password_utils::verify_password(&login_form.password)
}
#[post("/login", data="<login_form>")]
pub fn login_post(login_form: Form<LoginForm>, cookies: &CookieJar<'_>) -> TemplateOrRedirect {
let result = verify_login_form(login_form.into_inner());
match result {
Ok(_) => {
// if successful login, add a cookie indicating the user is authenticated
// and redirect to home page
// NOTE: since we currently have just one user, the value of the cookie
// is just admin (this is arbitrary).
// If we had multiple users, we could put the user_id here.
cookies.add_private(Cookie::new(AUTH_COOKIE_KEY, ADMIN_USERNAME));
TemplateOrRedirect::Redirect(Redirect::to("/"))
notplants marked this conversation as resolved
Review

I love how simple the logic is here. Really clean and simple.

I love how simple the logic is here. Really clean and simple.
Review

agreed, its nice when not using oauth-challenge-craziness etc., login cookie logic is actually pretty easy to follow

agreed, its nice when not using oauth-challenge-craziness etc., login cookie logic is actually pretty easy to follow
}
Err(_) => {
// if unsuccessful login, render /login page again
let mut context = LoginContext::build();
context.back = Some("/".to_string());
context.title = Some("Login".to_string());
context.flash_name = Some("error".to_string());
let flash_msg = "Invalid password".to_string();
context.flash_msg = Some(flash_msg);
TemplateOrRedirect::Template(Template::render("login", &context))
}
}
}
// HELPERS AND ROUTES FOR /logout
#[post("/logout")]
pub fn logout() -> Flash<Redirect> {
#[get("/logout")]
pub fn logout(cookies: &CookieJar<'_>) -> Flash<Redirect> {
// logout authenticated user
debug!("Attempting deauthentication of user.");
/*
match logout_user() {
Ok(_) => Flash::success(Redirect::to("/"), "Logout success"),
Err(_) => Flash::error(
Redirect::to("/"),
"Failed to logout",
),
}
*/
Flash::success(Redirect::to("/"), "Logged out")
info!("Attempting deauthentication of user.");
cookies.remove_private(Cookie::named(AUTH_COOKIE_KEY));
Flash::success(Redirect::to("/login"), "Logged out")
}
// HELPERS AND ROUTES FOR /reset_password
#[derive(Debug, Deserialize, FromForm)]
@ -290,7 +374,7 @@ pub fn save_password_form(password_form: PasswordForm) -> Result<(), PeachWebErr
/// Change password request handler. This is used by a user who is already logged in.
#[get("/settings/change_password")]
pub fn change_password(flash: Option<FlashMessage>) -> Template {
pub fn change_password(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
let mut context = ChangePasswordContext::build();
// set back icon link to network route
context.back = Some("/network".to_string());
@ -306,7 +390,7 @@ pub fn change_password(flash: Option<FlashMessage>) -> Template {
/// Change password form request handler. This route is used by a user who is already logged in.
#[post("/settings/change_password", data = "<password_form>")]
pub fn change_password_post(password_form: Form<PasswordForm>) -> Template {
pub fn change_password_post(password_form: Form<PasswordForm>, _auth: Authenticated) -> Template {
let result = save_password_form(password_form.into_inner());
match result {
Ok(_) => {
@ -333,7 +417,7 @@ pub fn change_password_post(password_form: Form<PasswordForm>) -> Template {
/// JSON change password form request handler.
#[post("/api/v1/settings/change_password", data = "<password_form>")]
pub fn save_password_form_endpoint(password_form: Json<PasswordForm>) -> Value {
pub fn save_password_form_endpoint(password_form: Json<PasswordForm>, _auth: Authenticated) -> Value {
let result = save_password_form(password_form.into_inner());
match result {
Ok(_) => {

View File

@ -1,6 +1,7 @@
use log::debug;
use rocket::{catch};
use rocket_dyn_templates::Template;
use rocket::response::Redirect;
use serde::Serialize;
// HELPERS AND ROUTES FOR 404 ERROR
@ -49,3 +50,11 @@ pub fn internal_error() -> Template {
Template::render("internal_error", context)
}
// HELPERS AND ROUTES FOR 403 FORBIDDEN
#[catch(403)]
pub fn forbidden() -> Redirect {
debug!("403 Forbidden");
Redirect::to("/login")
}

View File

@ -17,6 +17,7 @@ use peach_lib::stats_client::{CpuStatPercentages, DiskUsage, LoadAverage, MemSta
use peach_lib::{dyndns_client, network_client, oled_client, sbot_client, stats_client};
use crate::utils::build_json_response;
use crate::routes::authentication::Authenticated;
use rocket::serde::json::Value;
// HELPERS AND ROUTES FOR /device
@ -150,7 +151,7 @@ impl DeviceContext {
}
#[get("/device")]
pub fn device_stats(flash: Option<FlashMessage>) -> Template {
pub fn device_stats(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
// assign context through context_builder call
let mut context = DeviceContext::build();
context.back = Some("/".to_string());
@ -181,7 +182,7 @@ pub fn reboot() -> io::Result<Output> {
}
#[get("/device/reboot")]
pub fn reboot_cmd() -> Flash<Redirect> {
pub fn reboot_cmd(_auth: Authenticated) -> Flash<Redirect> {
match reboot() {
Ok(_) => Flash::success(Redirect::to("/shutdown"), "Rebooting the device"),
Err(_) => Flash::error(Redirect::to("/shutdown"), "Failed to reboot the device"),
@ -190,7 +191,7 @@ pub fn reboot_cmd() -> Flash<Redirect> {
/// JSON request handler for device reboot.
#[post("/api/v1/device/reboot")]
pub fn reboot_device() -> Value {
pub fn reboot_device(_auth: Authenticated) -> Value {
match reboot() {
Ok(_) => {
debug!("Going down for reboot...");
@ -219,7 +220,7 @@ pub fn shutdown() -> io::Result<Output> {
}
#[get("/device/shutdown")]
pub fn shutdown_cmd() -> Flash<Redirect> {
pub fn shutdown_cmd(_auth: Authenticated) -> Flash<Redirect> {
match shutdown() {
Ok(_) => Flash::success(Redirect::to("/shutdown"), "Shutting down the device"),
Err(_) => Flash::error(Redirect::to("/shutdown"), "Failed to shutdown the device"),
@ -228,7 +229,7 @@ pub fn shutdown_cmd() -> Flash<Redirect> {
// shutdown the device
#[post("/api/v1/device/shutdown")]
pub fn shutdown_device() -> Value {
pub fn shutdown_device(_auth: Authenticated) -> Value {
match shutdown() {
Ok(_) => {
debug!("Going down for shutdown...");
@ -267,7 +268,7 @@ impl ShutdownContext {
}
#[get("/shutdown")]
pub fn shutdown_menu(flash: Option<FlashMessage>) -> Template {
pub fn shutdown_menu(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
let mut context = ShutdownContext::build();
context.back = Some("/".to_string());
context.title = Some("Shutdown Device".to_string());

View File

@ -2,6 +2,8 @@ use rocket::{get, request::FlashMessage};
use rocket_dyn_templates::Template;
use serde::Serialize;
use crate::routes::authentication::Authenticated;
// HELPERS AND ROUTES FOR / (HOME PAGE)
#[derive(Debug, Serialize)]
@ -22,7 +24,7 @@ impl HomeContext {
}
#[get("/")]
pub fn index() -> Template {
pub fn index(_auth: Authenticated) -> Template {
let context = HomeContext {
flash_name: None,
flash_msg: None,

View File

@ -1,6 +1,6 @@
pub mod authentication;
pub mod device;
pub mod helpers;
pub mod catchers;
pub mod index;
pub mod ping;
pub mod scuttlebutt;

View File

@ -8,10 +8,12 @@ use peach_lib::oled_client;
use peach_lib::stats_client;
use crate::utils::build_json_response;
use crate::routes::authentication::Authenticated;
/// Status route: useful for checking connectivity from web client.
#[get("/api/v1/ping")]
pub fn ping_pong() -> Value {
pub fn ping_pong(_auth: Authenticated) -> Value {
//pub fn ping_pong() -> Value {
// ping pong
let status = "success".to_string();
let msg = "pong!".to_string();
@ -20,7 +22,7 @@ pub fn ping_pong() -> Value {
/// Status route: check availability of `peach-network` microservice.
#[get("/api/v1/ping/network")]
pub fn ping_network() -> Value {
pub fn ping_network(_auth: Authenticated) -> Value {
match network_client::ping() {
Ok(_) => {
debug!("peach-network responded successfully");
@ -39,7 +41,7 @@ pub fn ping_network() -> Value {
/// Status route: check availability of `peach-oled` microservice.
#[get("/api/v1/ping/oled")]
pub fn ping_oled() -> Value {
pub fn ping_oled(_auth: Authenticated) -> Value {
match oled_client::ping() {
Ok(_) => {
debug!("peach-oled responded successfully");
@ -58,7 +60,7 @@ pub fn ping_oled() -> Value {
/// Status route: check availability of `peach-stats` microservice.
#[get("/api/v1/ping/stats")]
pub fn ping_stats() -> Value {
pub fn ping_stats(_auth: Authenticated) -> Value {
match stats_client::ping() {
Ok(_) => {
debug!("peach-stats responded successfully");

View File

@ -4,6 +4,8 @@ use rocket::{get, request::FlashMessage};
use rocket_dyn_templates::Template;
use serde::Serialize;
use crate::routes::authentication::Authenticated;
// HELPERS AND ROUTES FOR /messages
#[derive(Debug, Serialize)]
@ -26,7 +28,7 @@ impl MessageContext {
}
#[get("/messages")]
pub fn messages(flash: Option<FlashMessage>) -> Template {
pub fn messages(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
let mut context = MessageContext::build();
context.back = Some("/".to_string());
context.title = Some("Private Messages".to_string());
@ -61,7 +63,7 @@ impl PeerContext {
}
#[get("/peers")]
pub fn peers(flash: Option<FlashMessage>) -> Template {
pub fn peers(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
let mut context = PeerContext::build();
context.back = Some("/".to_string());
context.title = Some("Scuttlebutt Peers".to_string());
@ -96,7 +98,7 @@ impl ProfileContext {
}
#[get("/profile")]
pub fn profile(flash: Option<FlashMessage>) -> Template {
pub fn profile(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
let mut context = ProfileContext::build();
context.back = Some("/".to_string());
context.title = Some("Profile".to_string());

View File

@ -12,6 +12,7 @@ use peach_lib::config_manager;
use peach_lib::config_manager::load_peach_config;
use crate::error::PeachWebError;
use crate::routes::authentication::Authenticated;
// HELPERS AND ROUTES FOR /settings/configure_admin
@ -40,7 +41,7 @@ impl ConfigureAdminContext {
/// View and delete currently configured admin.
#[get("/settings/configure_admin")]
pub fn configure_admin(flash: Option<FlashMessage>) -> Template {
pub fn configure_admin(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
let mut context = ConfigureAdminContext::build();
// set back icon link to network route
context.back = Some("/network".to_string());
@ -87,7 +88,7 @@ pub fn save_add_admin_form(admin_form: AddAdminForm) -> Result<(), PeachWebError
}
#[get("/settings/admin/add")]
pub fn add_admin(flash: Option<FlashMessage>) -> Template {
pub fn add_admin(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
let mut context = AddAdminContext::build();
context.back = Some("/settings/configure_admin".to_string());
context.title = Some("Add Admin".to_string());
@ -102,7 +103,7 @@ pub fn add_admin(flash: Option<FlashMessage>) -> Template {
}
#[post("/settings/admin/add", data = "<add_admin_form>")]
pub fn add_admin_post(add_admin_form: Form<AddAdminForm>) -> Flash<Redirect> {
pub fn add_admin_post(add_admin_form: Form<AddAdminForm>, _auth: Authenticated) -> Flash<Redirect> {
let result = save_add_admin_form(add_admin_form.into_inner());
let url = uri!(configure_admin);
match result {
@ -119,7 +120,7 @@ pub struct DeleteAdminForm {
}
#[post("/settings/admin/delete", data = "<delete_admin_form>")]
pub fn delete_admin_post(delete_admin_form: Form<DeleteAdminForm>) -> Flash<Redirect> {
pub fn delete_admin_post(delete_admin_form: Form<DeleteAdminForm>, _auth: Authenticated) -> Flash<Redirect> {
let result = config_manager::delete_ssb_admin_id(&delete_admin_form.ssb_id);
let url = uri!(configure_admin);
match result {

View File

@ -20,6 +20,7 @@ use peach_lib::jsonrpc_client_core::{Error, ErrorKind};
use peach_lib::jsonrpc_core::types::error::ErrorCode;
use crate::error::PeachWebError;
use crate::routes::authentication::Authenticated;
use crate::utils::build_json_response;
use rocket::serde::json::Value;
@ -113,7 +114,7 @@ impl ConfigureDNSContext {
}
#[get("/network/dns")]
pub fn configure_dns(flash: Option<FlashMessage>) -> Template {
pub fn configure_dns(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
let mut context = ConfigureDNSContext::build();
// set back icon link to network route
context.back = Some("/network".to_string());
@ -128,7 +129,7 @@ pub fn configure_dns(flash: Option<FlashMessage>) -> Template {
}
#[post("/network/dns", data = "<dns>")]
pub fn configure_dns_post(dns: Form<DnsForm>) -> Template {
pub fn configure_dns_post(dns: Form<DnsForm>, _auth: Authenticated) -> Template {
let result = save_dns_configuration(dns.into_inner());
match result {
Ok(_) => {
@ -153,7 +154,7 @@ pub fn configure_dns_post(dns: Form<DnsForm>) -> Template {
}
#[post("/api/v1/dns/configure", data = "<dns_form>")]
pub fn save_dns_configuration_endpoint(dns_form: Json<DnsForm>) -> Value {
pub fn save_dns_configuration_endpoint(dns_form: Json<DnsForm>, _auth: Authenticated) -> Value {
let result = save_dns_configuration(dns_form.into_inner());
match result {
Ok(_) => {

View File

@ -20,6 +20,7 @@ use peach_lib::stats_client::Traffic;
use crate::utils::monitor;
use crate::utils::monitor::{Alert, Data, Threshold};
use crate::utils::build_json_response;
use crate::routes::authentication::Authenticated;
use rocket::serde::json::Value;
// STRUCTS USED BY NETWORK ROUTES
@ -38,7 +39,7 @@ pub struct WiFi {
// HELPERS AND ROUTES FOR /network/wifi/usage/reset
#[get("/network/wifi/usage/reset")]
pub fn wifi_usage_reset() -> Flash<Redirect> {
pub fn wifi_usage_reset(_auth: Authenticated) -> Flash<Redirect> {
let url = uri!(wifi_usage);
match monitor::reset_data() {
Ok(_) => Flash::success(Redirect::to(url), "Reset stored network traffic total"),
@ -50,7 +51,7 @@ pub fn wifi_usage_reset() -> Flash<Redirect> {
}
#[post("/network/wifi/connect", data = "<network>")]
pub fn connect_wifi(network: Form<Ssid>) -> Flash<Redirect> {
pub fn connect_wifi(network: Form<Ssid>, _auth: Authenticated) -> Flash<Redirect> {
let ssid = &network.ssid;
let url = uri!(network_detail(ssid = ssid));
match network_client::id("wlan0", ssid) {
@ -63,7 +64,7 @@ pub fn connect_wifi(network: Form<Ssid>) -> Flash<Redirect> {
}
#[post("/network/wifi/disconnect", data = "<network>")]
pub fn disconnect_wifi(network: Form<Ssid>) -> Flash<Redirect> {
pub fn disconnect_wifi(network: Form<Ssid>, _auth: Authenticated) -> Flash<Redirect> {
let ssid = &network.ssid;
let url = uri!(network_home);
match network_client::disable("wlan0", ssid) {
@ -73,7 +74,7 @@ pub fn disconnect_wifi(network: Form<Ssid>) -> Flash<Redirect> {
}
#[post("/network/wifi/forget", data = "<network>")]
pub fn forget_wifi(network: Form<Ssid>) -> Flash<Redirect> {
pub fn forget_wifi(network: Form<Ssid>, _auth: Authenticated) -> Flash<Redirect> {
let ssid = &network.ssid;
let url = uri!(network_home);
match network_client::forget("wlan0", ssid) {
@ -86,7 +87,7 @@ pub fn forget_wifi(network: Form<Ssid>) -> Flash<Redirect> {
}
#[get("/network/wifi/modify?<ssid>")]
pub fn wifi_password(ssid: &str, flash: Option<FlashMessage>) -> Template {
pub fn wifi_password(ssid: &str, flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
let mut context = NetworkAddContext {
back: Some("/network/wifi".to_string()),
flash_name: None,
@ -105,7 +106,7 @@ pub fn wifi_password(ssid: &str, flash: Option<FlashMessage>) -> Template {
}
#[post("/network/wifi/modify", data = "<wifi>")]
pub fn wifi_set_password(wifi: Form<WiFi>) -> Flash<Redirect> {
pub fn wifi_set_password(wifi: Form<WiFi>, _auth: Authenticated) -> Flash<Redirect> {
let ssid = &wifi.ssid;
let pass = &wifi.pass;
let url = uri!(network_detail(ssid = ssid));
@ -273,7 +274,7 @@ impl NetworkContext {
}
#[get("/network")]
pub fn network_home(flash: Option<FlashMessage>) -> Template {
pub fn network_home(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
// assign context through context_builder call
let mut context = NetworkContext::build();
// set back button (nav) url
@ -293,7 +294,7 @@ pub fn network_home(flash: Option<FlashMessage>) -> Template {
// HELPERS AND ROUTES FOR /network/ap/activate
#[get("/network/ap/activate")]
pub fn deploy_ap() -> Flash<Redirect> {
pub fn deploy_ap(_auth: Authenticated) -> Flash<Redirect> {
// activate the wireless access point
debug!("Activating WiFi access point.");
match network_client::activate_ap() {
@ -375,7 +376,7 @@ impl NetworkListContext {
}
#[get("/network/wifi")]
pub fn wifi_list(flash: Option<FlashMessage>) -> Template {
pub fn wifi_list(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
// assign context through context_builder call
let mut context = NetworkListContext::build();
context.back = Some("/network".to_string());
@ -540,7 +541,7 @@ impl NetworkDetailContext {
}
#[get("/network/wifi?<ssid>")]
pub fn network_detail(ssid: &str, flash: Option<FlashMessage>) -> Template {
pub fn network_detail(ssid: &str, flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
// assign context through context_builder call
let mut context = NetworkDetailContext::build();
context.back = Some("/network/wifi".to_string());
@ -559,7 +560,7 @@ pub fn network_detail(ssid: &str, flash: Option<FlashMessage>) -> Template {
// HELPERS AND ROUTES FOR /network/wifi/activate
#[get("/network/wifi/activate")]
pub fn deploy_client() -> Flash<Redirect> {
pub fn deploy_client(_auth: Authenticated) -> Flash<Redirect> {
// activate the wireless client
debug!("Activating WiFi client mode.");
match network_client::activate_client() {
@ -571,7 +572,7 @@ pub fn deploy_client() -> Flash<Redirect> {
// HELPERS AND ROUTES FOR /network/wifi/add
#[get("/network/wifi/add")]
pub fn network_add_wifi(flash: Option<FlashMessage>) -> Template {
pub fn network_add_wifi(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
let mut context = NetworkContext::build();
// set back icon link to network route
context.back = Some("/network".to_string());
@ -609,7 +610,7 @@ impl NetworkAddContext {
}
#[get("/network/wifi/add?<ssid>")]
pub fn network_add_ssid(ssid: &str, flash: Option<FlashMessage>) -> Template {
pub fn network_add_ssid(ssid: &str, flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
let mut context = NetworkAddContext::build();
context.back = Some("/network/wifi".to_string());
context.selected = Some(ssid.to_string());
@ -625,7 +626,7 @@ pub fn network_add_ssid(ssid: &str, flash: Option<FlashMessage>) -> Template {
}
#[post("/network/wifi/add", data = "<wifi>")]
pub fn add_credentials(wifi: Form<WiFi>) -> Template {
pub fn add_credentials(wifi: Form<WiFi>, _auth: Authenticated) -> Template {
// check if the credentials already exist for this access point
// note: this is nicer but it's an unstable feature:
// if check_saved_aps(&wifi.ssid).contains(true)
@ -719,7 +720,7 @@ impl NetworkAlertContext {
}
#[get("/network/wifi/usage")]
pub fn wifi_usage(flash: Option<FlashMessage>) -> Template {
pub fn wifi_usage(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
let mut context = NetworkAlertContext::build();
// set back icon link to network route
context.back = Some("/network".to_string());
@ -735,7 +736,7 @@ pub fn wifi_usage(flash: Option<FlashMessage>) -> Template {
}
#[post("/network/wifi/usage", data = "<thresholds>")]
pub fn wifi_usage_alerts(thresholds: Form<Threshold>) -> Flash<Redirect> {
pub fn wifi_usage_alerts(thresholds: Form<Threshold>, _auth: Authenticated) -> Flash<Redirect> {
match monitor::update_store(thresholds.into_inner()) {
Ok(_) => {
debug!("WiFi data usage thresholds updated.");
@ -755,7 +756,7 @@ pub fn wifi_usage_alerts(thresholds: Form<Threshold>) -> Flash<Redirect> {
}
#[post("/api/v1/network/wifi/usage", data = "<thresholds>")]
pub fn update_wifi_alerts(thresholds: Json<Threshold>) -> Value {
pub fn update_wifi_alerts(thresholds: Json<Threshold>, _auth: Authenticated) -> Value {
match monitor::update_store(thresholds.into_inner()) {
Ok(_) => {
debug!("WiFi data usage thresholds updated.");
@ -773,7 +774,7 @@ pub fn update_wifi_alerts(thresholds: Json<Threshold>) -> Value {
}
#[post("/api/v1/network/wifi/usage/reset")]
pub fn reset_data_total() -> Value {
pub fn reset_data_total(_auth: Authenticated) -> Value {
match monitor::reset_data() {
Ok(_) => {
debug!("Reset network data usage total.");
@ -805,7 +806,7 @@ pub fn reset_data_total() -> Value {
// HELPERS AND ROUTES FOR ACCESS POINT ACTIVATION
#[post("/api/v1/network/activate_ap")]
pub fn activate_ap() -> Value {
pub fn activate_ap(_auth: Authenticated) -> Value {
// activate the wireless access point
debug!("Activating WiFi access point.");
match network_client::activate_ap() {
@ -824,7 +825,7 @@ pub fn activate_ap() -> Value {
// HELPERS AND ROUTES FOR WIFI CLIENT MANAGEMENT
#[post("/api/v1/network/activate_client")]
pub fn activate_client() -> Value {
pub fn activate_client(_auth: Authenticated) -> Value {
// activate the wireless client
debug!("Activating WiFi client mode.");
match network_client::activate_client() {
@ -841,7 +842,7 @@ pub fn activate_client() -> Value {
}
#[post("/api/v1/network/wifi", data = "<wifi>")]
pub fn add_wifi(wifi: Json<WiFi>) -> Value {
pub fn add_wifi(wifi: Json<WiFi>, _auth: Authenticated) -> Value {
// generate and write wifi config to wpa_supplicant
match network_client::add(&wifi.ssid, &wifi.pass) {
Ok(_) => {
@ -867,7 +868,7 @@ pub fn add_wifi(wifi: Json<WiFi>) -> Value {
}
#[post("/api/v1/network/wifi/connect", data = "<ssid>")]
pub fn connect_ap(ssid: Json<Ssid>) -> Value {
pub fn connect_ap(ssid: Json<Ssid>, _auth: Authenticated) -> Value {
// retrieve the id for the given network ssid
match network_client::id("wlan0", &ssid.ssid) {
// attempt connection with the given network
@ -892,7 +893,7 @@ pub fn connect_ap(ssid: Json<Ssid>) -> Value {
}
#[post("/api/v1/network/wifi/disconnect", data = "<ssid>")]
pub fn disconnect_ap(ssid: Json<Ssid>) -> Value {
pub fn disconnect_ap(ssid: Json<Ssid>, _auth: Authenticated) -> Value {
// attempt to disable the current network for wlan0 interface
match network_client::disable("wlan0", &ssid.ssid) {
Ok(_) => {
@ -909,7 +910,7 @@ pub fn disconnect_ap(ssid: Json<Ssid>) -> Value {
}
#[post("/api/v1/network/wifi/forget", data = "<network>")]
pub fn forget_ap(network: Json<Ssid>) -> Value {
pub fn forget_ap(network: Json<Ssid>, _auth: Authenticated) -> Value {
let ssid = &network.ssid;
match network_client::forget("wlan0", ssid) {
Ok(_) => {
@ -928,7 +929,7 @@ pub fn forget_ap(network: Json<Ssid>) -> Value {
}
#[post("/api/v1/network/wifi/modify", data = "<wifi>")]
pub fn modify_password(wifi: Json<WiFi>) -> Value {
pub fn modify_password(wifi: Json<WiFi>, _auth: Authenticated) -> Value {
let ssid = &wifi.ssid;
let pass = &wifi.pass;
// we are using a helper function (`update`) to delete the old
@ -953,7 +954,7 @@ pub fn modify_password(wifi: Json<WiFi>) -> Value {
// HELPERS AND ROUTES FOR NETWORK STATE QUERIES
#[get("/api/v1/network/ip")]
pub fn return_ip() -> Value {
pub fn return_ip(_auth: Authenticated) -> Value {
// retrieve ip for wlan0 or set to x.x.x.x if not found
let wlan_ip = match network_client::ip("wlan0") {
Ok(ip) => ip,
@ -973,7 +974,7 @@ pub fn return_ip() -> Value {
}
#[get("/api/v1/network/rssi")]
pub fn return_rssi() -> Value {
pub fn return_rssi(_auth: Authenticated) -> Value {
// retrieve rssi for connected network
match network_client::rssi("wlan0") {
Ok(rssi) => {
@ -990,7 +991,7 @@ pub fn return_rssi() -> Value {
}
#[get("/api/v1/network/ssid")]
pub fn return_ssid() -> Value {
pub fn return_ssid(_auth: Authenticated) -> Value {
// retrieve ssid for connected network
match network_client::ssid("wlan0") {
Ok(network) => {
@ -1007,7 +1008,7 @@ pub fn return_ssid() -> Value {
}
#[get("/api/v1/network/state")]
pub fn return_state() -> Value {
pub fn return_state(_auth: Authenticated) -> Value {
// retrieve state of wlan0 or set to x.x.x.x if not found
let wlan_state = match network_client::state("wlan0") {
Ok(state) => state,
@ -1027,7 +1028,7 @@ pub fn return_state() -> Value {
}
#[get("/api/v1/network/status")]
pub fn return_status() -> Value {
pub fn return_status(_auth: Authenticated) -> Value {
// retrieve status info for wlan0 interface
match network_client::status("wlan0") {
Ok(network) => {
@ -1044,7 +1045,7 @@ pub fn return_status() -> Value {
}
#[get("/api/v1/network/wifi")]
pub fn scan_networks() -> Value {
pub fn scan_networks(_auth: Authenticated) -> Value {
// retrieve scan results for access-points within range of wlan0
match network_client::available_networks("wlan0") {
Ok(networks) => {

View File

@ -1,5 +1,8 @@
pub mod monitor;
use rocket_dyn_templates::Template;
use rocket::response::{Redirect, Responder};
use rocket::serde::json::{Value, json};
use rocket::serde::{Serialize};
@ -18,3 +21,13 @@ pub struct FlashContext {
pub flash_name: Option<String>,
pub flash_msg: Option<String>,
}
/// A helper enum which allows routes to either return a Template or a Redirect
/// from: https://github.com/SergioBenitez/Rocket/issues/253#issuecomment-532356066
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Responder)]
pub enum TemplateOrRedirect {
Template(Template),
Redirect(Redirect),
}

View File

@ -43,14 +43,5 @@ PEACH.flashMsg = function(status, msg) {
}
}
// add click event to logout button which logs out of http basic auth
// by "trying to login" with invalid credentials (user@logout)
document.getElementById('logoutButton').onclick = function(e){
e.preventDefault();
var logoutUrl = "http://user:logout@" + window.location.hostname
window.location = logoutUrl;
}
var addInstance = PEACH;
addInstance.add();
addInstance.logout();

View File

@ -3,11 +3,11 @@
<!-- LOGIN FORM -->
<div class="card center">
<div class="card-container">
<form id="authCreds" action="/login" method="post">
<form id="login_form" action="/login" method="post">
<!-- input for username -->
<input id="username" name="user" class="center input" type="text" placeholder="Username" title="Username for authentication" autofocus>
<input id="username" name="username" class="center input" type="text" placeholder="Username" title="Username for authentication" autofocus/>
<!-- input for password -->
<input id="pass" name="pass" class="center input" type="password" placeholder="Password" title="Password for given username">
<input id="password" name="password" class="center input" type="password" placeholder="Password" title="Password for given username"/>
<div id="buttonDiv">
<input id="loginUser" class="button button-primary center" title="Login" type="submit" value="Login">
<a class="button button-secondary center" href="/" title="Cancel">Cancel</a>
@ -27,5 +27,5 @@
{%- endif -%}
</div>
</div>
<script type="text/javascript" src="/js/login.js"></script>
{%- endblock card -%}

View File

@ -6,7 +6,7 @@
<img class="icon-medium nav-icon-left icon-active" src="/icons/back.svg" alt="Back">
</a>
<h1 class="nav-title">{{ title }}</h1>
<a class="nav-item" id="logoutButton" href="http://user:logout@peach.local/" title="Logout">
<a class="nav-item" id="logoutButton" href="/logout" title="Logout">
<img class="icon-medium nav-icon-right icon-active" src="/icons/enter.svg" alt="Enter">
</a>
</nav>