From deaedc44283a3c6c0ada5132cd432a1b8ac6fb63 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 24 Mar 2022 09:05:26 +0200 Subject: [PATCH] add temporary password reset routes --- peach-lib/src/password_utils.rs | 4 +- peach-web/src/public_router.rs | 98 ++++++++++++------- peach-web/src/routes/authentication/forgot.rs | 25 +++-- peach-web/src/routes/authentication/login.rs | 8 +- peach-web/src/routes/authentication/mod.rs | 1 + peach-web/src/routes/authentication/reset.rs | 1 - .../src/routes/authentication/temporary.rs | 42 ++++++++ 7 files changed, 132 insertions(+), 47 deletions(-) create mode 100644 peach-web/src/routes/authentication/temporary.rs diff --git a/peach-lib/src/password_utils.rs b/peach-lib/src/password_utils.rs index d0aff4d..a806648 100644 --- a/peach-lib/src/password_utils.rs +++ b/peach-lib/src/password_utils.rs @@ -86,7 +86,7 @@ pub fn send_password_reset() -> Result<(), PeachError> { "Your new temporary password is: {} If you are on the same WiFi network as your PeachCloud device you can reset your password \ -using this link: http://peach.local/reset_password", +using this link: http://peach.local/auth/reset", temporary_password ); // if there is an external domain, then include remote link in message @@ -95,7 +95,7 @@ using this link: http://peach.local/reset_password", Some(domain) => { format!( "\n\nOr if you are on a different WiFi network, you can reset your password \ - using the the following link: {}/reset_password", + using the the following link: {}/auth/reset", domain ) } diff --git a/peach-web/src/public_router.rs b/peach-web/src/public_router.rs index 8d0cc69..fd433f3 100644 --- a/peach-web/src/public_router.rs +++ b/peach-web/src/public_router.rs @@ -1,3 +1,4 @@ +use log::{error, info}; use rouille::{router, Request, Response}; use crate::{ @@ -6,8 +7,11 @@ use crate::{ SessionData, }; -/// Receive an incoming request, mount the fileservers for static assets and -/// define the publically-accessible routes. +/// Request handler. +/// +/// Mount the fileservers for static assets and define the +/// publically-accessible routes (including per-route handlers). Includes +/// logging of all incoming requests. /// /// If the request is for a private route (ie. a route requiring successful /// authentication to view), check the authentication status of the user @@ -32,42 +36,68 @@ pub fn handle_route(request: &Request, session_data: &mut Option) - return rouille::match_assets(&request, &blobstore); } - // handle the routes which are always accessible (ie. whether logged-in - // or not) - router!(request, - (GET) (/auth/forgot) => { - Response::html(routes::authentication::forgot::build_template()) - }, + // get the current time (for logging purposes) + let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S%.6f"); - (GET) (/auth/login) => { - Response::html(routes::authentication::login::build_template(request)) - .reset_flash() - }, + // define the success logger for incoming requests + let log_ok = |req: &Request, _resp: &Response, _elap: std::time::Duration| { + info!("{} {} {}", now, req.method(), req.raw_url()); + }; - (POST) (/auth/login) => { - routes::authentication::login::handle_form(request, session_data) - }, + // define the error logger for incoming requests + let log_err = |req: &Request, _elap: std::time::Duration| { + error!( + "{} Handler panicked: {} {}", + now, + req.method(), + req.raw_url() + ); + }; - (GET) (/auth/reset) => { - Response::html(routes::authentication::reset::build_template(request)) - .reset_flash() - }, + // instantiate request logging + rouille::log_custom(request, log_ok, log_err, || { + // handle the routes which are always accessible (ie. whether logged-in + // or not) + router!(request, + (GET) (/auth/forgot) => { + Response::html(routes::authentication::forgot::build_template(request)) + .reset_flash() + }, - (POST) (/auth/reset) => { - routes::authentication::reset::handle_form(request) - }, + (GET) (/auth/login) => { + Response::html(routes::authentication::login::build_template(request)) + .reset_flash() + }, - _ => { - // now that we handled all the routes that are accessible in all - // circumstances, we check that the user is logged in before proceeding - if let Some(_session) = session_data.as_ref() { - // logged in: - // mount the routes which require authentication to view - private_router::mount_peachpub_routes(request, session_data) - } else { - // not logged in: - Response::redirect_303("/auth/login") + (POST) (/auth/login) => { + routes::authentication::login::handle_form(request, session_data) + }, + + (GET) (/auth/reset) => { + Response::html(routes::authentication::reset::build_template(request)) + .reset_flash() + }, + + (POST) (/auth/reset) => { + routes::authentication::reset::handle_form(request) + }, + + (POST) (/auth/temporary) => { + routes::authentication::temporary::handle_form() + }, + + _ => { + // now that we handled all the routes that are accessible in all + // circumstances, we check that the user is logged in before proceeding + if let Some(_session) = session_data.as_ref() { + // logged in: + // mount the routes which require authentication to view + private_router::mount_peachpub_routes(request, session_data) + } else { + // not logged in: + Response::redirect_303("/auth/login") + } } - } - ) + ) + }) } diff --git a/peach-web/src/routes/authentication/forgot.rs b/peach-web/src/routes/authentication/forgot.rs index f9c256f..d61f90d 100644 --- a/peach-web/src/routes/authentication/forgot.rs +++ b/peach-web/src/routes/authentication/forgot.rs @@ -1,12 +1,19 @@ use maud::{html, PreEscaped}; +use rouille::Request; -use crate::{templates, utils::theme}; +use crate::{ + templates, + utils::{flash::FlashRequest, theme}, +}; // ROUTE: /auth/forgot /// Forgot password template builder. -pub fn build_template() -> PreEscaped { - let form_template = html! { +pub fn build_template(request: &Request) -> PreEscaped { + // check for flash cookies; will be (None, None) if no flash cookies are found + let (flash_name, flash_msg) = request.retrieve_flash(); + + let password_reset_template = html! { (PreEscaped("")) div class="card center" { div class="capsule capsule-container border-info" { @@ -20,20 +27,26 @@ pub fn build_template() -> PreEscaped { "Once you have the temporary password, click the 'Set New Password' button to reach the password reset page." } } - form id="sendPasswordReset" action="/auth/send_password_reset" method="post" { + form id="sendPasswordReset" action="/auth/temporary" method="post" { div id="buttonDiv" { - input class="button button-primary center" style="margin-top: 1rem;" type="submit" value="Send Temporary Password" title="Send temporary password to Scuttlebutt admin"; + input class="button button-primary center" style="margin-top: 1rem;" type="submit" value="Send Temporary Password" title="Send temporary password to Scuttlebutt admin(s)"; a href="/auth/reset_password" class="button button-primary center" title="Set a new password using the temporary password" { "Set New Password" } } } + // render flash message if cookies were found in the request + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, msg)) + } } }; // wrap the nav bars around the settings menu template content // parameters are template, title and back url - let body = templates::nav::build_template(form_template, "Send Password Reset", Some("/")); + let body = + templates::nav::build_template(password_reset_template, "Send Password Reset", Some("/")); // query the current theme so we can pass it into the base template builder let theme = theme::get_theme(); diff --git a/peach-web/src/routes/authentication/login.rs b/peach-web/src/routes/authentication/login.rs index 3ef76c2..f914e30 100644 --- a/peach-web/src/routes/authentication/login.rs +++ b/peach-web/src/routes/authentication/login.rs @@ -1,4 +1,4 @@ -use log::info; +use log::debug; use maud::{html, PreEscaped}; use peach_lib::password_utils; use rouille::{post_input, try_or_400, Request, Response}; @@ -63,17 +63,17 @@ pub fn handle_form(request: &Request, session_data: &mut Option) -> match password_utils::verify_password(&data.password) { Ok(_) => { - info!("Successful login attempt"); + debug!("Successful login attempt"); // if password verification is successful, write to `session_data` // to authenticate the user *session_data = Some(SessionData { - _login: data.password, + _login: "success".to_string(), }); Response::redirect_303("/") } Err(err) => { - info!("Unsuccessful login attempt"); + debug!("Unsuccessful login attempt"); let err_msg = format!("Invalid password: {}", err); let (flash_name, flash_msg) = ( "flash_name=error".to_string(), diff --git a/peach-web/src/routes/authentication/mod.rs b/peach-web/src/routes/authentication/mod.rs index b848d99..291d221 100644 --- a/peach-web/src/routes/authentication/mod.rs +++ b/peach-web/src/routes/authentication/mod.rs @@ -3,3 +3,4 @@ pub mod forgot; pub mod login; pub mod logout; pub mod reset; +pub mod temporary; diff --git a/peach-web/src/routes/authentication/reset.rs b/peach-web/src/routes/authentication/reset.rs index 3464ae0..e352490 100644 --- a/peach-web/src/routes/authentication/reset.rs +++ b/peach-web/src/routes/authentication/reset.rs @@ -100,7 +100,6 @@ pub fn handle_form(request: &Request) -> Response { &data.new_password2, ) { Ok(_) => ( - // = "flash_name=success".to_string(), "flash_msg=New password has been saved. Return home to login".to_string(), ), diff --git a/peach-web/src/routes/authentication/temporary.rs b/peach-web/src/routes/authentication/temporary.rs new file mode 100644 index 0000000..9bf17fb --- /dev/null +++ b/peach-web/src/routes/authentication/temporary.rs @@ -0,0 +1,42 @@ +use log::debug; +use peach_lib::password_utils; +use rouille::Response; + +use crate::utils::flash::FlashResponse; + +// ROUTE: /auth/temporary (POST) + +/// Send a temporary password as a Scuttlebutt private message to the admin(s). +/// +/// This route is used by a user who is not logged in and is specifically for +/// users who have forgotten their password. A successful request results +/// in a Scuttlebutt private message being sent to the account of the device +/// admin. +/// +/// Redirects to the Send Password Reset page a flash message describing the +/// outcome of the action (may be successful or unsuccessful). +pub fn handle_form() -> Response { + // save submitted admin id to file + let (flash_name, flash_msg) = match password_utils::send_password_reset() { + Ok(_) => { + debug!("Sent temporary password to device admin(s)"); + ( + "flash_name=success".to_string(), + "flash_msg=A temporary password has been sent to the admin(s) of this device" + .to_string(), + ) + } + Err(err) => { + debug!( + "Received an error while trying to send temporary password to device admin(s): {}", + err + ); + ( + "error".to_string(), + format!("Failed to send temporary password: {}", err), + ) + } + }; + + Response::redirect_303("/auth/forgot").add_flash(flash_name, flash_msg) +}