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: {}
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
)
}

View File

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

View File

@ -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<String> {
let form_template = html! {
pub fn build_template(request: &Request) -> PreEscaped<String> {
// 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 -->"))
div class="card center" {
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."
}
}
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("<!-- FLASH MESSAGE -->"))
(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();

View File

@ -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<SessionData>) ->
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(),

View File

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

View File

@ -100,7 +100,6 @@ pub fn handle_form(request: &Request) -> Response {
&data.new_password2,
) {
Ok(_) => (
// <cookie-name>=<cookie-value>
"flash_name=success".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)
}