diff --git a/Cargo.lock b/Cargo.lock index 611a250..9465229 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" @@ -2673,6 +2846,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" @@ -3445,10 +3629,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 +3641,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 +3942,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 +4740,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" diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index 979a9ec..c94e96c 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -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" diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index cdfcaf5..edd17bd 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -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 { help, // WEB ROUTE index, // WEB ROUTE login, // WEB ROUTE + login_post, // WEB ROUTE logout, // WEB ROUTE messages, // WEB ROUTE network_home, // WEB ROUTE @@ -121,7 +122,7 @@ fn init_rocket() -> Rocket { ], ) .mount("/", FileServer::from("static")) - .register("/", catchers![not_found, internal_error]) + .register("/", catchers![not_found, internal_error, forbidden]) .attach(Template::fairing()) } diff --git a/peach-web/src/routes/authentication.rs b/peach-web/src/routes/authentication.rs index a82bcba..ef780d5 100644 --- a/peach-web/src/routes/authentication.rs +++ b/peach-web/src/routes/authentication.rs @@ -8,10 +8,60 @@ 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}; +use rocket::outcome::IntoOutcome; +use websocket::futures::future::Ok; + + +// HELPERS AND STRUCTS FOR AUTHENTICATION WITH COOKIES + +pub const AUTH_COOKIE_KEY: &str = "peachweb_auth"; +pub const ADMIN_USERNAME: &str = "admin"; + +pub struct Authenticated { + is_authenticated: bool, +} + +#[derive(Debug)] +pub enum LoginError { + InvalidData, + UsernameDoesNotExist, + WrongPassword +} + +/// Request guard which returns an Authenticated struct with is_authenticated=true +/// iff the user has a cookie which proves they are authenticated with peach-web. +/// +/// 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 { + let authenticated = req + .cookies() + .get_private(AUTH_COOKIE_KEY) + .and_then(|cookie| cookie.value().parse().ok()) + .map(|value: String| { info!("cookie value: {}", value); Authenticated { is_authenticated: true } }); + match authenticated { + Some(auth) => { + info!("Authenticated!"); + request::Outcome::Success(auth) + }, + None => { + info!("not authenticated!"); + request::Outcome::Failure((Status::Forbidden, LoginError::UsernameDoesNotExist)) + } + } + } +} // HELPERS AND ROUTES FOR /login @@ -48,24 +98,60 @@ pub fn login(flash: Option) -> 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="")] +pub fn login_post(login_form: Form, cookies: &CookieJar<'_>) -> TemplateOrRedirect { + info!("call to login post"); + 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. + info!("successfull password form"); + cookies.add_private(Cookie::new(AUTH_COOKIE_KEY, ADMIN_USERNAME)); + TemplateOrRedirect::Redirect(Redirect::to("/")) + } + Err(_) => { + info!("invalid password form"); + // if unsuccessful login, render /login page again + let mut context = LoginContext::build(); + context.back = Some("/".to_string()); + context.title = Some("Login".to_string()); + TemplateOrRedirect::Template(Template::render("login", &context)) + } + } +} + + // HELPERS AND ROUTES FOR /logout -#[post("/logout")] -pub fn logout() -> Flash { +#[get("/logout")] +pub fn logout(cookies: &CookieJar<'_>) -> Flash { // 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", - ), - } - */ + cookies.remove_private(Cookie::named(AUTH_COOKIE_KEY)); Flash::success(Redirect::to("/"), "Logged out") } + // HELPERS AND ROUTES FOR /reset_password #[derive(Debug, Deserialize, FromForm)] diff --git a/peach-web/src/routes/helpers.rs b/peach-web/src/routes/catchers.rs similarity index 88% rename from peach-web/src/routes/helpers.rs rename to peach-web/src/routes/catchers.rs index c1fdf6c..e494395 100644 --- a/peach-web/src/routes/helpers.rs +++ b/peach-web/src/routes/catchers.rs @@ -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 ROUNTES FOR 403 FORBIDDEN + +#[catch(403)] +pub fn forbidden() -> Redirect { + debug!("403 Forbidden"); + Redirect::to("/login") +} \ No newline at end of file diff --git a/peach-web/src/routes/mod.rs b/peach-web/src/routes/mod.rs index 87368cc..be18d09 100644 --- a/peach-web/src/routes/mod.rs +++ b/peach-web/src/routes/mod.rs @@ -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; diff --git a/peach-web/src/routes/ping.rs b/peach-web/src/routes/ping.rs index 8e87cd0..e9fc7c1 100644 --- a/peach-web/src/routes/ping.rs +++ b/peach-web/src/routes/ping.rs @@ -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(); diff --git a/peach-web/src/utils.rs b/peach-web/src/utils.rs index 9021338..ff4c883 100644 --- a/peach-web/src/utils.rs +++ b/peach-web/src/utils.rs @@ -1,5 +1,8 @@ pub mod monitor; +use rocket_dyn_templates::Template; +use rocket::request::Request; +use rocket::response::{Redirect, Responder}; use rocket::serde::json::{Value, json}; use rocket::serde::{Serialize}; @@ -18,3 +21,12 @@ pub struct FlashContext { pub flash_name: Option, pub flash_msg: Option, } + + +/// A helper enum which allows routes to either return a Template or a Redirect +/// from: https://github.com/SergioBenitez/Rocket/issues/253#issuecomment-532356066 +#[derive(Debug, Responder)] +pub enum TemplateOrRedirect { + Template(Template), + Redirect(Redirect), +} \ No newline at end of file diff --git a/peach-web/templates/login.html.tera b/peach-web/templates/login.html.tera index 39c1093..97390ed 100644 --- a/peach-web/templates/login.html.tera +++ b/peach-web/templates/login.html.tera @@ -3,11 +3,11 @@
-
+ - + - +
Cancel @@ -27,5 +27,5 @@ {%- endif -%}
- + {%- endblock card -%}