diff --git a/peach-lib/Cargo.toml b/peach-lib/Cargo.toml index 4d4fe38..fc51192 100644 --- a/peach-lib/Cargo.toml +++ b/peach-lib/Cargo.toml @@ -5,9 +5,12 @@ authors = ["Andrew Reid "] edition = "2018" [dependencies] +async-std = "1.10.0" chrono = "0.4.19" dirs = "4.0" fslock="0.1.6" +#golgi = { git = "https://git.coopcloud.tech/golgi-ssb/golgi" } +golgi = { path = "../../../playground/rust/golgi" } jsonrpc-client-core = "0.5" jsonrpc-client-http = "0.5" jsonrpc-core = "8.0.1" diff --git a/peach-lib/issues_to_fix b/peach-lib/issues_to_fix new file mode 100644 index 0000000..fc8d8d5 --- /dev/null +++ b/peach-lib/issues_to_fix @@ -0,0 +1,23 @@ + + + - permissions for /var/lib/peachcloud + - everything fails if we don't have write permissions + - can't write config + + - configure admin (add new admin) + - isn't persisted to config.yml + - on second try, it is persisted + + - password reset via ssb pm is now working + - it sends a temporary password but says nothing about username + - what is the default username? + - why do we even have a username? + + - login with temporary password fails + - "Invalid password: Password error: hash value in YAML configuration file is empty." + + - things are generally working now :) + + - for all form inputs: + - use a proper label (not just a placeholder) + - login diff --git a/peach-lib/src/config_manager.rs b/peach-lib/src/config_manager.rs index e3894ab..dfffd42 100644 --- a/peach-lib/src/config_manager.rs +++ b/peach-lib/src/config_manager.rs @@ -1,12 +1,14 @@ //! Interfaces for writing and reading PeachCloud configurations, stored in yaml. //! -//! Different PeachCloud microservices import peach-lib, so that they can share this interface. +//! Different PeachCloud microservices import peach-lib, so that they can share +//! this interface. //! //! The configuration file is located at: "/var/lib/peachcloud/config.yml" use std::fs; use fslock::LockFile; +use log::debug; use serde::{Deserialize, Serialize}; use crate::error::PeachError; @@ -72,6 +74,7 @@ pub fn load_peach_config() -> Result { let peach_config_exists = std::path::Path::new(YAML_PATH).exists(); let peach_config: PeachConfig = if !peach_config_exists { + debug!("Loading peach config: {} does not exist", YAML_PATH); PeachConfig { external_domain: "".to_string(), dyn_domain: "".to_string(), @@ -87,6 +90,7 @@ pub fn load_peach_config() -> Result { } // otherwise we load peach config from disk else { + debug!("Loading peach config: {} exists", YAML_PATH); let contents = fs::read_to_string(YAML_PATH).map_err(|source| PeachError::Read { source, path: YAML_PATH.to_string(), @@ -177,6 +181,7 @@ pub fn set_admin_password_hash(password_hash: &str) -> Result Result { let peach_config = load_peach_config()?; + debug!("Admin password hash: {}", peach_config.admin_password_hash); if !peach_config.admin_password_hash.is_empty() { Ok(peach_config.admin_password_hash) } else { diff --git a/peach-lib/src/error.rs b/peach-lib/src/error.rs index ac4ef97..ba396b1 100644 --- a/peach-lib/src/error.rs +++ b/peach-lib/src/error.rs @@ -61,11 +61,8 @@ pub enum PeachError { /// Represents a failure to parse or compile a regular expression. Regex(regex::Error), - /// Represents a failure to successfully execute an sbot command. - SbotCli { - /// The `stderr` output from the sbot command. - msg: String, - }, + /// Represents a failure to successfully execute an sbot command (via golgi). + Sbot(String), /// Represents a failure to serialize or deserialize JSON. SerdeJson(serde_json::error::Error), @@ -117,7 +114,7 @@ impl std::error::Error for PeachError { PeachError::PasswordNotSet => None, PeachError::Read { ref source, .. } => Some(source), PeachError::Regex(_) => None, - PeachError::SbotCli { .. } => None, + PeachError::Sbot(_) => None, PeachError::SerdeJson(_) => None, PeachError::SerdeYaml(_) => None, PeachError::SsbAdminIdNotFound { .. } => None, @@ -153,22 +150,19 @@ impl std::fmt::Display for PeachError { write!(f, "Date/time parse error: {}", path) } PeachError::PasswordIncorrect => { - write!(f, "Password error: user-supplied password is incorrect") + write!(f, "password is incorrect") } PeachError::PasswordMismatch => { - write!(f, "Password error: user-supplied passwords do not match") + write!(f, "passwords do not match") } PeachError::PasswordNotSet => { - write!( - f, - "Password error: hash value in YAML configuration file is empty" - ) + write!(f, "hash value in YAML configuration file is empty") } PeachError::Read { ref path, .. } => { write!(f, "Read error: {}", path) } PeachError::Regex(ref err) => err.fmt(f), - PeachError::SbotCli { ref msg } => { + PeachError::Sbot(ref msg) => { write!(f, "Sbot error: {}", msg) } PeachError::SerdeJson(ref err) => err.fmt(f), diff --git a/peach-lib/src/password_utils.rs b/peach-lib/src/password_utils.rs index fa28904..d0aff4d 100644 --- a/peach-lib/src/password_utils.rs +++ b/peach-lib/src/password_utils.rs @@ -1,7 +1,10 @@ +use async_std::task; +use golgi::Sbot; +use log::debug; use nanorand::{Rng, WyRand}; use sha3::{Digest, Sha3_256}; -use crate::{config_manager, error::PeachError}; +use crate::{config_manager, error::PeachError, sbot::SbotConfig}; /// Returns Ok(()) if the supplied password is correct, /// and returns Err if the supplied password is incorrect. @@ -102,8 +105,37 @@ using this link: http://peach.local/reset_password", // finally send the message to the admins let peach_config = config_manager::load_peach_config()?; for ssb_admin_id in peach_config.ssb_admin_ids { - // TODO: replace with golgi - //sbot_client::private_message(&msg, &ssb_admin_id)?; + // use golgi to send a private message on scuttlebutt + match task::block_on(publish_private_msg(&msg, &ssb_admin_id)) { + Ok(_) => (), + Err(e) => return Err(PeachError::Sbot(e)), + } } Ok(()) } + +async fn publish_private_msg(msg: &str, recipient: &str) -> Result<(), String> { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let msg = msg.to_string(); + let recipient = vec![recipient.to_string()]; + + // initialise sbot connection with ip:port and shscap from config file + let mut sbot_client = match sbot_config { + // TODO: panics if we pass `Some(conf.shscap)` as second arg + Some(conf) => { + let ip_port = conf.lis.clone(); + Sbot::init(Some(ip_port), None) + .await + .map_err(|e| e.to_string())? + } + None => Sbot::init(None, None).await.map_err(|e| e.to_string())?, + }; + + debug!("Publishing a Scuttlebutt private message with temporary password"); + match sbot_client.publish_private(msg, recipient).await { + Ok(_) => Ok(()), + Err(e) => Err(format!("Failed to publish private message: {}", e)), + } +} diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index ee43d47..38aed11 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -31,9 +31,6 @@ pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { login, login_post, logout, - reboot_cmd, - shutdown_cmd, - power_menu, settings_menu, set_theme, ], @@ -43,7 +40,6 @@ pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { routes![ admin_menu, configure_admin, - add_admin, add_admin_post, delete_admin_post, change_password, @@ -101,6 +97,7 @@ pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { /// required to run a complete PeachCloud build. pub fn mount_peachcloud_routes(rocket: Rocket) -> Rocket { mount_peachpub_routes(rocket) + .mount("/", routes![reboot_cmd, shutdown_cmd, power_menu,]) .mount( "/settings/network", routes![ diff --git a/peach-web/src/routes/authentication.rs b/peach-web/src/routes/authentication.rs index 04df16c..3ff8f2b 100644 --- a/peach-web/src/routes/authentication.rs +++ b/peach-web/src/routes/authentication.rs @@ -6,7 +6,6 @@ use rocket::{ post, request::{self, FlashMessage, FromRequest, Request}, response::{Flash, Redirect}, - serde::Deserialize, }; use rocket_dyn_templates::{tera::Context, Template}; @@ -89,17 +88,14 @@ pub fn login(flash: Option) -> Template { Template::render("login", &context.into_json()) } -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, 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. +/// Takes in a LoginForm and returns Ok(()) if the password is correct. /// -/// Note: currently there is only one user, and the username should always -/// be "admin". +/// Note: there is currently only one user, therefore we don't need a username. pub fn verify_login_form(login_form: LoginForm) -> Result<(), PeachError> { password_utils::verify_password(&login_form.password) } @@ -117,13 +113,14 @@ pub fn login_post(login_form: Form, cookies: &CookieJar<'_>) -> Templ TemplateOrRedirect::Redirect(Redirect::to("/")) } - Err(_) => { + Err(e) => { + let err_msg = format!("Invalid password: {}", e); // if unsuccessful login, render /login page again let mut context = Context::new(); context.insert("back", &Some("/".to_string())); context.insert("title", &Some("Login".to_string())); context.insert("flash_name", &("error".to_string())); - context.insert("flash_msg", &("Invalid password".to_string())); + context.insert("flash_msg", &(err_msg)); TemplateOrRedirect::Template(Template::render("login", &context.into_json())) } @@ -142,7 +139,7 @@ pub fn logout(cookies: &CookieJar<'_>) -> Flash { // HELPERS AND ROUTES FOR /reset_password -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct ResetPasswordForm { pub temporary_password: String, pub new_password1: String, @@ -198,7 +195,7 @@ pub fn reset_password_post(reset_password_form: Form) -> Temp let (flash_name, flash_msg) = match save_reset_password_form(reset_password_form.into_inner()) { Ok(_) => ( "success".to_string(), - "New password is now saved. Return home to login".to_string(), + "New password has been saved. Return home to login".to_string(), ), Err(err) => ( "error".to_string(), @@ -266,9 +263,9 @@ pub fn send_password_reset_post() -> Template { // HELPERS AND ROUTES FOR /settings/change_password -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct PasswordForm { - pub old_password: String, + pub current_password: String, pub new_password1: String, pub new_password2: String, } @@ -277,9 +274,9 @@ pub struct PasswordForm { pub fn save_password_form(password_form: PasswordForm) -> Result<(), PeachWebError> { info!( "change password!: {} {} {}", - password_form.old_password, password_form.new_password1, password_form.new_password2 + password_form.current_password, password_form.new_password1, password_form.new_password2 ); - password_utils::verify_password(&password_form.old_password)?; + password_utils::verify_password(&password_form.current_password)?; // if the previous line did not throw an error, then the old password is correct password_utils::validate_new_passwords( &password_form.new_password1, @@ -321,7 +318,7 @@ pub fn change_password_post(password_form: Form, _auth: Authentica let (flash_name, flash_msg) = match save_password_form(password_form.into_inner()) { Ok(_) => ( "success".to_string(), - "New password is now saved".to_string(), + "New password has been saved".to_string(), ), Err(err) => ( "error".to_string(), diff --git a/peach-web/src/routes/settings/admin.rs b/peach-web/src/routes/settings/admin.rs index c2312a3..89b296f 100644 --- a/peach-web/src/routes/settings/admin.rs +++ b/peach-web/src/routes/settings/admin.rs @@ -3,7 +3,6 @@ use rocket::{ get, post, request::FlashMessage, response::{Flash, Redirect}, - serde::Deserialize, uri, }; use rocket_dyn_templates::{tera::Context, Template}; @@ -77,50 +76,31 @@ pub fn configure_admin(flash: Option, _auth: Authenticated) -> Tem // HELPERS AND ROUTES FOR /settings/admin/add -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct AddAdminForm { pub ssb_id: String, } pub fn save_add_admin_form(admin_form: AddAdminForm) -> Result<(), PeachWebError> { let _result = config_manager::add_ssb_admin_id(&admin_form.ssb_id)?; + // if the previous line didn't throw an error then it was a success Ok(()) } -#[get("/add")] -pub fn add_admin(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/settings/admin/configure".to_string())); - context.insert("title", &Some("Add Admin".to_string())); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - // template_dir is set in Rocket.toml - Template::render("settings/admin/add_admin", &context.into_json()) -} - #[post("/add", data = "")] pub fn add_admin_post(add_admin_form: Form, _auth: Authenticated) -> Flash { let result = save_add_admin_form(add_admin_form.into_inner()); let url = uri!("/settings/admin/configure"); match result { - Ok(_) => Flash::success(Redirect::to(url), "Successfully added new admin"), - Err(_) => Flash::error(Redirect::to(url), "Failed to add new admin"), + Ok(_) => Flash::success(Redirect::to(url), "Added SSB administrator"), + Err(e) => Flash::error(Redirect::to(url), format!("Failed to add new admin: {}", e)), } } // HELPERS AND ROUTES FOR /settings/admin/delete -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct DeleteAdminForm { pub ssb_id: String, } @@ -131,9 +111,12 @@ pub fn delete_admin_post( _auth: Authenticated, ) -> Flash { let result = config_manager::delete_ssb_admin_id(&delete_admin_form.ssb_id); - let url = uri!(configure_admin); + let url = uri!("/settings/admin", configure_admin); match result { - Ok(_) => Flash::success(Redirect::to(url), "Successfully removed admin id"), - Err(_) => Flash::error(Redirect::to(url), "Failed to remove admin id"), + Ok(_) => Flash::success(Redirect::to(url), "Removed SSB administrator"), + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to remove admin id: {}", e), + ), } } diff --git a/peach-web/templates/login.html.tera b/peach-web/templates/login.html.tera index 31d7ccf..6235a96 100644 --- a/peach-web/templates/login.html.tera +++ b/peach-web/templates/login.html.tera @@ -2,22 +2,19 @@ {%- block card %}
-
-
- - + +
- -
- + + + + + - +
{% include "snippets/flash_message" %} - -
+
- {%- endblock card -%} diff --git a/peach-web/templates/settings/admin/add_admin.html.tera b/peach-web/templates/settings/admin/add_admin.html.tera deleted file mode 100644 index 158bad9..0000000 --- a/peach-web/templates/settings/admin/add_admin.html.tera +++ /dev/null @@ -1,17 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
-
- -
- - Cancel -
-
- - {% include "snippets/flash_message" %} -
-
-{%- endblock card -%} diff --git a/peach-web/templates/settings/admin/change_password.html.tera b/peach-web/templates/settings/admin/change_password.html.tera index b3b0749..3308593 100644 --- a/peach-web/templates/settings/admin/change_password.html.tera +++ b/peach-web/templates/settings/admin/change_password.html.tera @@ -3,13 +3,17 @@
- - - - - - -
+
+ + + + + + + + + + Cancel
diff --git a/peach-web/templates/settings/admin/configure_admin.html.tera b/peach-web/templates/settings/admin/configure_admin.html.tera index 0127e1b..fa1843a 100644 --- a/peach-web/templates/settings/admin/configure_admin.html.tera +++ b/peach-web/templates/settings/admin/configure_admin.html.tera @@ -2,25 +2,31 @@ {%- block card %}
-
-

Current Admins

- {% if not ssb_admin_ids %} -
- There are no currently configured admins. -
- {% else %} - {% for admin in ssb_admin_ids %} -
- - - {{ admin }} - +
Administrators are identified and added by their Scuttlebutt public keys. These accounts will be sent private messages on Scuttlebutt when a password reset is requested.
+ {% if not ssb_admin_ids %} +
+ There are no currently configured admins. +
+ {% else %} + {% for admin in ssb_admin_ids %} +
+
+ +

{{ admin }}

+
- {% endfor %} - {% endif %} - Add Admin -
- - {% include "snippets/flash_message" %} + + {% endfor %} + {% endif %} +
+
+ + +
+ + + + {% include "snippets/flash_message" %} +
{%- endblock card -%} diff --git a/peach-web/templates/settings/admin/forgot_password.html.tera b/peach-web/templates/settings/admin/forgot_password.html.tera index f0a254e..f664224 100644 --- a/peach-web/templates/settings/admin/forgot_password.html.tera +++ b/peach-web/templates/settings/admin/forgot_password.html.tera @@ -2,14 +2,15 @@ {%- block card %}
-
-

Click the button below to send a new temporary password which can be used to change your device password. -

- The temporary password will be sent in an SSB private message to the admin of this device.

+
+

Click the 'Send Password Reset' button to send a new temporary password which can be used to change your device password.

+

The temporary password will be sent in an SSB private message to the admin of this device.

+

Once you have the temporary password, click the 'Set New Password' button to reach the password reset page.

-
+
diff --git a/peach-web/templates/settings/admin/reset_password.html.tera b/peach-web/templates/settings/admin/reset_password.html.tera index 0d4d4b6..7fb4bd9 100644 --- a/peach-web/templates/settings/admin/reset_password.html.tera +++ b/peach-web/templates/settings/admin/reset_password.html.tera @@ -2,41 +2,22 @@ {%- block card %}
-
-
-
- - -
- -
- - -
- -
- - -
- -
- -
-
- - {% include "snippets/flash_message" %} -
+
+
+ + + + + + + + + + + +
+
+ + {% include "snippets/flash_message" %}
{%- endblock card -%}