add temporary password reset routes

This commit is contained in:
glyph 2022-03-24 09:05:26 +02:00
parent 4a94f14dc5
commit deaedc4428
7 changed files with 132 additions and 47 deletions

View File

@ -86,7 +86,7 @@ pub fn send_password_reset() -> Result<(), PeachError> {
"Your new temporary password is: {} "Your new temporary password is: {}
If you are on the same WiFi network as your PeachCloud device you can reset your password \ 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 temporary_password
); );
// if there is an external domain, then include remote link in message // 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) => { Some(domain) => {
format!( format!(
"\n\nOr if you are on a different WiFi network, you can reset your password \ "\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 domain
) )
} }

View File

@ -1,3 +1,4 @@
use log::{error, info};
use rouille::{router, Request, Response}; use rouille::{router, Request, Response};
use crate::{ use crate::{
@ -6,8 +7,11 @@ use crate::{
SessionData, SessionData,
}; };
/// Receive an incoming request, mount the fileservers for static assets and /// Request handler.
/// define the publically-accessible routes. ///
/// 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 /// If the request is for a private route (ie. a route requiring successful
/// authentication to view), check the authentication status of the user /// authentication to view), check the authentication status of the user
@ -32,42 +36,68 @@ pub fn handle_route(request: &Request, session_data: &mut Option<SessionData>) -
return rouille::match_assets(&request, &blobstore); return rouille::match_assets(&request, &blobstore);
} }
// handle the routes which are always accessible (ie. whether logged-in // get the current time (for logging purposes)
// or not) let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S%.6f");
router!(request,
(GET) (/auth/forgot) => {
Response::html(routes::authentication::forgot::build_template())
},
(GET) (/auth/login) => { // define the success logger for incoming requests
Response::html(routes::authentication::login::build_template(request)) let log_ok = |req: &Request, _resp: &Response, _elap: std::time::Duration| {
.reset_flash() info!("{} {} {}", now, req.method(), req.raw_url());
}, };
(POST) (/auth/login) => { // define the error logger for incoming requests
routes::authentication::login::handle_form(request, session_data) let log_err = |req: &Request, _elap: std::time::Duration| {
}, error!(
"{} Handler panicked: {} {}",
now,
req.method(),
req.raw_url()
);
};
(GET) (/auth/reset) => { // instantiate request logging
Response::html(routes::authentication::reset::build_template(request)) rouille::log_custom(request, log_ok, log_err, || {
.reset_flash() // 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) => { (GET) (/auth/login) => {
routes::authentication::reset::handle_form(request) Response::html(routes::authentication::login::build_template(request))
}, .reset_flash()
},
_ => { (POST) (/auth/login) => {
// now that we handled all the routes that are accessible in all routes::authentication::login::handle_form(request, session_data)
// circumstances, we check that the user is logged in before proceeding },
if let Some(_session) = session_data.as_ref() {
// logged in: (GET) (/auth/reset) => {
// mount the routes which require authentication to view Response::html(routes::authentication::reset::build_template(request))
private_router::mount_peachpub_routes(request, session_data) .reset_flash()
} else { },
// not logged in:
Response::redirect_303("/auth/login") (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")
}
} }
} )
) })
} }

View File

@ -1,12 +1,19 @@
use maud::{html, PreEscaped}; use maud::{html, PreEscaped};
use rouille::Request;
use crate::{templates, utils::theme}; use crate::{
templates,
utils::{flash::FlashRequest, theme},
};
// ROUTE: /auth/forgot // ROUTE: /auth/forgot
/// Forgot password template builder. /// Forgot password template builder.
pub fn build_template() -> PreEscaped<String> { pub fn build_template(request: &Request) -> PreEscaped<String> {
let form_template = html! { // 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("<!-- PASSWORD RESET REQUEST CARD -->")) (PreEscaped("<!-- PASSWORD RESET REQUEST CARD -->"))
div class="card center" { div class="card center" {
div class="capsule capsule-container border-info" { div class="capsule capsule-container border-info" {
@ -20,20 +27,26 @@ pub fn build_template() -> PreEscaped<String> {
"Once you have the temporary password, click the 'Set New Password' button to reach the password reset page." "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" { 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" { a href="/auth/reset_password" class="button button-primary center" title="Set a new password using the temporary password" {
"Set New 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("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg))
}
} }
}; };
// wrap the nav bars around the settings menu template content // wrap the nav bars around the settings menu template content
// parameters are template, title and back url // 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 // query the current theme so we can pass it into the base template builder
let theme = theme::get_theme(); let theme = theme::get_theme();

View File

@ -1,4 +1,4 @@
use log::info; use log::debug;
use maud::{html, PreEscaped}; use maud::{html, PreEscaped};
use peach_lib::password_utils; use peach_lib::password_utils;
use rouille::{post_input, try_or_400, Request, Response}; use rouille::{post_input, try_or_400, Request, Response};
@ -63,17 +63,17 @@ pub fn handle_form(request: &Request, session_data: &mut Option<SessionData>) ->
match password_utils::verify_password(&data.password) { match password_utils::verify_password(&data.password) {
Ok(_) => { Ok(_) => {
info!("Successful login attempt"); debug!("Successful login attempt");
// if password verification is successful, write to `session_data` // if password verification is successful, write to `session_data`
// to authenticate the user // to authenticate the user
*session_data = Some(SessionData { *session_data = Some(SessionData {
_login: data.password, _login: "success".to_string(),
}); });
Response::redirect_303("/") Response::redirect_303("/")
} }
Err(err) => { Err(err) => {
info!("Unsuccessful login attempt"); debug!("Unsuccessful login attempt");
let err_msg = format!("Invalid password: {}", err); let err_msg = format!("Invalid password: {}", err);
let (flash_name, flash_msg) = ( let (flash_name, flash_msg) = (
"flash_name=error".to_string(), "flash_name=error".to_string(),

View File

@ -3,3 +3,4 @@ pub mod forgot;
pub mod login; pub mod login;
pub mod logout; pub mod logout;
pub mod reset; pub mod reset;
pub mod temporary;

View File

@ -100,7 +100,6 @@ pub fn handle_form(request: &Request) -> Response {
&data.new_password2, &data.new_password2,
) { ) {
Ok(_) => ( Ok(_) => (
// <cookie-name>=<cookie-value>
"flash_name=success".to_string(), "flash_name=success".to_string(),
"flash_msg=New password has been saved. Return home to login".to_string(), "flash_msg=New password has been saved. Return home to login".to_string(),
), ),

View File

@ -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)
}