fix and improve all login and password-related workflows

This commit is contained in:
glyph 2022-03-04 10:53:49 +02:00
parent 10049f0bc6
commit 7fdf88eaa8
14 changed files with 169 additions and 163 deletions

View File

@ -5,9 +5,12 @@ authors = ["Andrew Reid <glyph@mycelial.technology>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
async-std = "1.10.0"
chrono = "0.4.19" chrono = "0.4.19"
dirs = "4.0" dirs = "4.0"
fslock="0.1.6" 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-core = "0.5"
jsonrpc-client-http = "0.5" jsonrpc-client-http = "0.5"
jsonrpc-core = "8.0.1" jsonrpc-core = "8.0.1"

23
peach-lib/issues_to_fix Normal file
View File

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

View File

@ -1,12 +1,14 @@
//! Interfaces for writing and reading PeachCloud configurations, stored in yaml. //! 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" //! The configuration file is located at: "/var/lib/peachcloud/config.yml"
use std::fs; use std::fs;
use fslock::LockFile; use fslock::LockFile;
use log::debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::error::PeachError; use crate::error::PeachError;
@ -72,6 +74,7 @@ pub fn load_peach_config() -> Result<PeachConfig, PeachError> {
let peach_config_exists = std::path::Path::new(YAML_PATH).exists(); let peach_config_exists = std::path::Path::new(YAML_PATH).exists();
let peach_config: PeachConfig = if !peach_config_exists { let peach_config: PeachConfig = if !peach_config_exists {
debug!("Loading peach config: {} does not exist", YAML_PATH);
PeachConfig { PeachConfig {
external_domain: "".to_string(), external_domain: "".to_string(),
dyn_domain: "".to_string(), dyn_domain: "".to_string(),
@ -87,6 +90,7 @@ pub fn load_peach_config() -> Result<PeachConfig, PeachError> {
} }
// otherwise we load peach config from disk // otherwise we load peach config from disk
else { else {
debug!("Loading peach config: {} exists", YAML_PATH);
let contents = fs::read_to_string(YAML_PATH).map_err(|source| PeachError::Read { let contents = fs::read_to_string(YAML_PATH).map_err(|source| PeachError::Read {
source, source,
path: YAML_PATH.to_string(), path: YAML_PATH.to_string(),
@ -177,6 +181,7 @@ pub fn set_admin_password_hash(password_hash: &str) -> Result<PeachConfig, Peach
pub fn get_admin_password_hash() -> Result<String, PeachError> { pub fn get_admin_password_hash() -> Result<String, PeachError> {
let peach_config = load_peach_config()?; let peach_config = load_peach_config()?;
debug!("Admin password hash: {}", peach_config.admin_password_hash);
if !peach_config.admin_password_hash.is_empty() { if !peach_config.admin_password_hash.is_empty() {
Ok(peach_config.admin_password_hash) Ok(peach_config.admin_password_hash)
} else { } else {

View File

@ -61,11 +61,8 @@ pub enum PeachError {
/// Represents a failure to parse or compile a regular expression. /// Represents a failure to parse or compile a regular expression.
Regex(regex::Error), Regex(regex::Error),
/// Represents a failure to successfully execute an sbot command. /// Represents a failure to successfully execute an sbot command (via golgi).
SbotCli { Sbot(String),
/// The `stderr` output from the sbot command.
msg: String,
},
/// Represents a failure to serialize or deserialize JSON. /// Represents a failure to serialize or deserialize JSON.
SerdeJson(serde_json::error::Error), SerdeJson(serde_json::error::Error),
@ -117,7 +114,7 @@ impl std::error::Error for PeachError {
PeachError::PasswordNotSet => None, PeachError::PasswordNotSet => None,
PeachError::Read { ref source, .. } => Some(source), PeachError::Read { ref source, .. } => Some(source),
PeachError::Regex(_) => None, PeachError::Regex(_) => None,
PeachError::SbotCli { .. } => None, PeachError::Sbot(_) => None,
PeachError::SerdeJson(_) => None, PeachError::SerdeJson(_) => None,
PeachError::SerdeYaml(_) => None, PeachError::SerdeYaml(_) => None,
PeachError::SsbAdminIdNotFound { .. } => None, PeachError::SsbAdminIdNotFound { .. } => None,
@ -153,22 +150,19 @@ impl std::fmt::Display for PeachError {
write!(f, "Date/time parse error: {}", path) write!(f, "Date/time parse error: {}", path)
} }
PeachError::PasswordIncorrect => { PeachError::PasswordIncorrect => {
write!(f, "Password error: user-supplied password is incorrect") write!(f, "password is incorrect")
} }
PeachError::PasswordMismatch => { PeachError::PasswordMismatch => {
write!(f, "Password error: user-supplied passwords do not match") write!(f, "passwords do not match")
} }
PeachError::PasswordNotSet => { PeachError::PasswordNotSet => {
write!( write!(f, "hash value in YAML configuration file is empty")
f,
"Password error: hash value in YAML configuration file is empty"
)
} }
PeachError::Read { ref path, .. } => { PeachError::Read { ref path, .. } => {
write!(f, "Read error: {}", path) write!(f, "Read error: {}", path)
} }
PeachError::Regex(ref err) => err.fmt(f), PeachError::Regex(ref err) => err.fmt(f),
PeachError::SbotCli { ref msg } => { PeachError::Sbot(ref msg) => {
write!(f, "Sbot error: {}", msg) write!(f, "Sbot error: {}", msg)
} }
PeachError::SerdeJson(ref err) => err.fmt(f), PeachError::SerdeJson(ref err) => err.fmt(f),

View File

@ -1,7 +1,10 @@
use async_std::task;
use golgi::Sbot;
use log::debug;
use nanorand::{Rng, WyRand}; use nanorand::{Rng, WyRand};
use sha3::{Digest, Sha3_256}; 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, /// Returns Ok(()) if the supplied password is correct,
/// and returns Err if the supplied password is incorrect. /// 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 // finally send the message to the admins
let peach_config = config_manager::load_peach_config()?; let peach_config = config_manager::load_peach_config()?;
for ssb_admin_id in peach_config.ssb_admin_ids { for ssb_admin_id in peach_config.ssb_admin_ids {
// TODO: replace with golgi // use golgi to send a private message on scuttlebutt
//sbot_client::private_message(&msg, &ssb_admin_id)?; match task::block_on(publish_private_msg(&msg, &ssb_admin_id)) {
Ok(_) => (),
Err(e) => return Err(PeachError::Sbot(e)),
}
} }
Ok(()) 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)),
}
}

View File

@ -31,9 +31,6 @@ pub fn mount_peachpub_routes(rocket: Rocket<Build>) -> Rocket<Build> {
login, login,
login_post, login_post,
logout, logout,
reboot_cmd,
shutdown_cmd,
power_menu,
settings_menu, settings_menu,
set_theme, set_theme,
], ],
@ -43,7 +40,6 @@ pub fn mount_peachpub_routes(rocket: Rocket<Build>) -> Rocket<Build> {
routes![ routes![
admin_menu, admin_menu,
configure_admin, configure_admin,
add_admin,
add_admin_post, add_admin_post,
delete_admin_post, delete_admin_post,
change_password, change_password,
@ -101,6 +97,7 @@ pub fn mount_peachpub_routes(rocket: Rocket<Build>) -> Rocket<Build> {
/// required to run a complete PeachCloud build. /// required to run a complete PeachCloud build.
pub fn mount_peachcloud_routes(rocket: Rocket<Build>) -> Rocket<Build> { pub fn mount_peachcloud_routes(rocket: Rocket<Build>) -> Rocket<Build> {
mount_peachpub_routes(rocket) mount_peachpub_routes(rocket)
.mount("/", routes![reboot_cmd, shutdown_cmd, power_menu,])
.mount( .mount(
"/settings/network", "/settings/network",
routes![ routes![

View File

@ -6,7 +6,6 @@ use rocket::{
post, post,
request::{self, FlashMessage, FromRequest, Request}, request::{self, FlashMessage, FromRequest, Request},
response::{Flash, Redirect}, response::{Flash, Redirect},
serde::Deserialize,
}; };
use rocket_dyn_templates::{tera::Context, Template}; use rocket_dyn_templates::{tera::Context, Template};
@ -89,17 +88,14 @@ pub fn login(flash: Option<FlashMessage>) -> Template {
Template::render("login", &context.into_json()) Template::render("login", &context.into_json())
} }
#[derive(Debug, Deserialize, FromForm)] #[derive(Debug, FromForm)]
pub struct LoginForm { pub struct LoginForm {
pub username: String,
pub password: String, pub password: String,
} }
/// Takes in a LoginForm and returns Ok(()) if username and password /// Takes in a LoginForm and returns Ok(()) if the password is correct.
/// are correct to authenticate with peach-web.
/// ///
/// Note: currently there is only one user, and the username should always /// Note: there is currently only one user, therefore we don't need a username.
/// be "admin".
pub fn verify_login_form(login_form: LoginForm) -> Result<(), PeachError> { pub fn verify_login_form(login_form: LoginForm) -> Result<(), PeachError> {
password_utils::verify_password(&login_form.password) password_utils::verify_password(&login_form.password)
} }
@ -117,13 +113,14 @@ pub fn login_post(login_form: Form<LoginForm>, cookies: &CookieJar<'_>) -> Templ
TemplateOrRedirect::Redirect(Redirect::to("/")) TemplateOrRedirect::Redirect(Redirect::to("/"))
} }
Err(_) => { Err(e) => {
let err_msg = format!("Invalid password: {}", e);
// if unsuccessful login, render /login page again // if unsuccessful login, render /login page again
let mut context = Context::new(); let mut context = Context::new();
context.insert("back", &Some("/".to_string())); context.insert("back", &Some("/".to_string()));
context.insert("title", &Some("Login".to_string())); context.insert("title", &Some("Login".to_string()));
context.insert("flash_name", &("error".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())) TemplateOrRedirect::Template(Template::render("login", &context.into_json()))
} }
@ -142,7 +139,7 @@ pub fn logout(cookies: &CookieJar<'_>) -> Flash<Redirect> {
// HELPERS AND ROUTES FOR /reset_password // HELPERS AND ROUTES FOR /reset_password
#[derive(Debug, Deserialize, FromForm)] #[derive(Debug, FromForm)]
pub struct ResetPasswordForm { pub struct ResetPasswordForm {
pub temporary_password: String, pub temporary_password: String,
pub new_password1: String, pub new_password1: String,
@ -198,7 +195,7 @@ pub fn reset_password_post(reset_password_form: Form<ResetPasswordForm>) -> Temp
let (flash_name, flash_msg) = match save_reset_password_form(reset_password_form.into_inner()) { let (flash_name, flash_msg) = match save_reset_password_form(reset_password_form.into_inner()) {
Ok(_) => ( Ok(_) => (
"success".to_string(), "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) => ( Err(err) => (
"error".to_string(), "error".to_string(),
@ -266,9 +263,9 @@ pub fn send_password_reset_post() -> Template {
// HELPERS AND ROUTES FOR /settings/change_password // HELPERS AND ROUTES FOR /settings/change_password
#[derive(Debug, Deserialize, FromForm)] #[derive(Debug, FromForm)]
pub struct PasswordForm { pub struct PasswordForm {
pub old_password: String, pub current_password: String,
pub new_password1: String, pub new_password1: String,
pub new_password2: String, pub new_password2: String,
} }
@ -277,9 +274,9 @@ pub struct PasswordForm {
pub fn save_password_form(password_form: PasswordForm) -> Result<(), PeachWebError> { pub fn save_password_form(password_form: PasswordForm) -> Result<(), PeachWebError> {
info!( info!(
"change password!: {} {} {}", "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 // if the previous line did not throw an error, then the old password is correct
password_utils::validate_new_passwords( password_utils::validate_new_passwords(
&password_form.new_password1, &password_form.new_password1,
@ -321,7 +318,7 @@ pub fn change_password_post(password_form: Form<PasswordForm>, _auth: Authentica
let (flash_name, flash_msg) = match save_password_form(password_form.into_inner()) { let (flash_name, flash_msg) = match save_password_form(password_form.into_inner()) {
Ok(_) => ( Ok(_) => (
"success".to_string(), "success".to_string(),
"New password is now saved".to_string(), "New password has been saved".to_string(),
), ),
Err(err) => ( Err(err) => (
"error".to_string(), "error".to_string(),

View File

@ -3,7 +3,6 @@ use rocket::{
get, post, get, post,
request::FlashMessage, request::FlashMessage,
response::{Flash, Redirect}, response::{Flash, Redirect},
serde::Deserialize,
uri, uri,
}; };
use rocket_dyn_templates::{tera::Context, Template}; use rocket_dyn_templates::{tera::Context, Template};
@ -77,50 +76,31 @@ pub fn configure_admin(flash: Option<FlashMessage>, _auth: Authenticated) -> Tem
// HELPERS AND ROUTES FOR /settings/admin/add // HELPERS AND ROUTES FOR /settings/admin/add
#[derive(Debug, Deserialize, FromForm)] #[derive(Debug, FromForm)]
pub struct AddAdminForm { pub struct AddAdminForm {
pub ssb_id: String, pub ssb_id: String,
} }
pub fn save_add_admin_form(admin_form: AddAdminForm) -> Result<(), PeachWebError> { pub fn save_add_admin_form(admin_form: AddAdminForm) -> Result<(), PeachWebError> {
let _result = config_manager::add_ssb_admin_id(&admin_form.ssb_id)?; 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 // if the previous line didn't throw an error then it was a success
Ok(()) Ok(())
} }
#[get("/add")]
pub fn add_admin(flash: Option<FlashMessage>, _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 = "<add_admin_form>")] #[post("/add", data = "<add_admin_form>")]
pub fn add_admin_post(add_admin_form: Form<AddAdminForm>, _auth: Authenticated) -> Flash<Redirect> { pub fn add_admin_post(add_admin_form: Form<AddAdminForm>, _auth: Authenticated) -> Flash<Redirect> {
let result = save_add_admin_form(add_admin_form.into_inner()); let result = save_add_admin_form(add_admin_form.into_inner());
let url = uri!("/settings/admin/configure"); let url = uri!("/settings/admin/configure");
match result { match result {
Ok(_) => Flash::success(Redirect::to(url), "Successfully added new admin"), Ok(_) => Flash::success(Redirect::to(url), "Added SSB administrator"),
Err(_) => Flash::error(Redirect::to(url), "Failed to add new admin"), Err(e) => Flash::error(Redirect::to(url), format!("Failed to add new admin: {}", e)),
} }
} }
// HELPERS AND ROUTES FOR /settings/admin/delete // HELPERS AND ROUTES FOR /settings/admin/delete
#[derive(Debug, Deserialize, FromForm)] #[derive(Debug, FromForm)]
pub struct DeleteAdminForm { pub struct DeleteAdminForm {
pub ssb_id: String, pub ssb_id: String,
} }
@ -131,9 +111,12 @@ pub fn delete_admin_post(
_auth: Authenticated, _auth: Authenticated,
) -> Flash<Redirect> { ) -> Flash<Redirect> {
let result = config_manager::delete_ssb_admin_id(&delete_admin_form.ssb_id); 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 { match result {
Ok(_) => Flash::success(Redirect::to(url), "Successfully removed admin id"), Ok(_) => Flash::success(Redirect::to(url), "Removed SSB administrator"),
Err(_) => Flash::error(Redirect::to(url), "Failed to remove admin id"), Err(e) => Flash::error(
Redirect::to(url),
format!("Failed to remove admin id: {}", e),
),
} }
} }

View File

@ -2,22 +2,19 @@
{%- block card %} {%- block card %}
<!-- LOGIN FORM --> <!-- LOGIN FORM -->
<div class="card center"> <div class="card center">
<div class="card-container"> <form id="login_form" class="center" action="/login" method="post">
<form id="login_form" class="center" action="/login" method="post"> <div style="display: flex; flex-direction: column; margin-bottom: 1rem;">
<!-- input for username -->
<input id="username" name="username" class="center input" type="text" placeholder="Username" title="Username for authentication" autofocus/>
<!-- input for password --> <!-- input for password -->
<input id="password" name="password" class="center input" type="password" placeholder="Password" title="Password for given username"/> <label for="password" class="center label-small font-gray" style="width: 80%;">PASSWORD</label>
<div id="buttonDiv"> <input id="password" name="password" class="center input" type="password" title="Password for given username"/>
<input id="loginUser" class="button button-primary center" title="Login" type="submit" value="Login"> <!-- login (form submission) button -->
<input id="loginUser" class="button button-primary center" title="Login" type="submit" value="Login">
<div class="center-text" style="margin-top: 1rem;">
<a href="/settings/admin/forgot_password" class="label-small link font-gray">Forgot Password?</a>
</div> </div>
</form> </div>
<!-- FLASH MESSAGE --> <!-- FLASH MESSAGE -->
{% include "snippets/flash_message" %} {% include "snippets/flash_message" %}
<div class="center-text" style="margin-top: 25px;"> </form>
<a href="/settings/admin/forgot_password" class="label-small link font-gray">Forgot Password?</a>
</div>
</div>
</div> </div>
{%- endblock card -%} {%- endblock card -%}

View File

@ -1,17 +0,0 @@
{%- extends "nav" -%}
{%- block card %}
<!-- ADD ADMIN FORM -->
<div class="card center">
<div class="card-container">
<form id="addAdminForm" action="/settings/admin/add" method="post">
<input id="ssb_id" name="ssb_id" class="center input" type="text" placeholder="SSB ID" title="SSB ID of Admin" value=""/>
<div id="buttonDiv">
<input id="addAdmin" class="button button-primary center" title="Add" type="submit" value="Add">
<a class="button button-secondary center" href="/settings/admin/configure" title="Cancel">Cancel</a>
</div>
</form>
<!-- FLASH MESSAGE -->
{% include "snippets/flash_message" %}
</div>
</div>
{%- endblock card -%}

View File

@ -3,13 +3,17 @@
<!-- CHANGE PASSWORD FORM --> <!-- CHANGE PASSWORD FORM -->
<div class="card center"> <div class="card center">
<form id="changePassword" class="center" action="/settings/admin/change_password" method="post"> <form id="changePassword" class="center" action="/settings/admin/change_password" method="post">
<!-- input for current password --> <div style="display: flex; flex-direction: column; margin-bottom: 1rem;">
<input id="currentPassword" class="center input" name="current_password" type="password" placeholder="Current password" title="Current password" autofocus> <!-- input for current password -->
<!-- input for new password --> <label for="currentPassword" class="center label-small font-gray" style="width: 80%;">CURRENT PASSWORD</label>
<input id="newPassword" class="center input" name="new_password1" type="password" placeholder="New password" title="New password"> <input id="currentPassword" class="center input" name="current_password" type="password" title="Current password" autofocus>
<!-- input for duplicate new password --> <!-- input for new password -->
<input id="newPasswordDuplicate" class="center input" name="new_password2" type="password" placeholder="Re-enter new password" title="New password duplicate"> <label for="newPassword" class="center label-small font-gray" style="width: 80%;">NEW PASSWORD</label>
<div id="buttonDiv"> <input id="newPassword" class="center input" name="new_password1" type="password" title="New password">
<!-- input for duplicate new password -->
<label for="newPasswordDuplicate" class="center label-small font-gray" style="width: 80%;">RE-ENTER NEW PASSWORD</label>
<input id="newPasswordDuplicate" class="center input" name="new_password2" type="password" title="New password duplicate">
<!-- save (form submission) button -->
<input id="savePassword" class="button button-primary center" title="Add" type="submit" value="Save"> <input id="savePassword" class="button button-primary center" title="Add" type="submit" value="Save">
<a class="button button-secondary center" href="/settings/admin" title="Cancel">Cancel</a> <a class="button button-secondary center" href="/settings/admin" title="Cancel">Cancel</a>
</div> </div>

View File

@ -2,25 +2,31 @@
{%- block card %} {%- block card %}
<!-- CONFIGURE ADMIN PAGE --> <!-- CONFIGURE ADMIN PAGE -->
<div class="card center"> <div class="card center">
<div class="text-container"> <div class="capsule capsule-profile center-text font-normal border-info" style="font-family: var(--sans-serif); font-size: var(--font-size-6); margin-bottom: 1.5rem;">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.</div>
<h4 class="font-normal">Current Admins</h4> {% if not ssb_admin_ids %}
{% if not ssb_admin_ids %} <div class="card-text">
<div class="card-text"> There are no currently configured admins.
There are no currently configured admins. </div>
</div> {% else %}
{% else %} {% for admin in ssb_admin_ids %}
{% for admin in ssb_admin_ids %} <form class="center" action="/settings/admin/delete" method="post">
<div> <div class="center" style="display: flex; justify-content: space-between;">
<form action="/settings/admin/delete" method="post"> <input type="hidden" name="ssb_id" value="{{ admin }}"/>
<input type="hidden" name="ssb_id" value="{{admin}}"/> <p class="label-small label-ellipsis font-gray" style="user-select: all;">{{ admin }}</p>
<input type="submit" value="X" title="Delete"/> <span>{{ admin }}</span> <input style="width: 30%;" type="submit" class="button button-warning" value="Delete" title="Delete SSB administrator"/>
</form>
</div> </div>
{% endfor %} </form>
{% endif %} {% endfor %}
<a class="button button-primary center full-width" style="margin-top: 25px;" href="/settings/admin/add" title="Add Admin">Add Admin</a> {% endif %}
</div> <form id="addAdmin" class="center" style="margin-top: 2rem;" action="/settings/admin/add" method="post">
<!-- FLASH MESSAGE --> <div class="center" style="display: flex; flex-direction: column; margin-bottom: 2rem;" title="Public key (ID) of a desired administrator">
{% include "snippets/flash_message" %} <label for="publicKey" class="label-small font-gray">PUBLIC KEY</label>
<input type="text" id="publicKey" name="ssb_id" placeholder="@xYz...=.ed25519" autofocus>
</div>
<!-- BUTTONS -->
<input class="button button-primary center" type="submit" title="Add SSB administrator" value="Add Admin">
<!-- FLASH MESSAGE -->
{% include "snippets/flash_message" %}
</form>
</div> </div>
{%- endblock card -%} {%- endblock card -%}

View File

@ -2,14 +2,15 @@
{%- block card %} {%- block card %}
<!-- PASSWORD RESET REQUEST CARD --> <!-- PASSWORD RESET REQUEST CARD -->
<div class="card center"> <div class="card center">
<div class="capsule capsule-container info-border"> <div class="capsule capsule-container border-info">
<p class="card-text">Click the button below to send a new temporary password which can be used to change your device password. <p class="card-text">Click the 'Send Password Reset' button to send a new temporary password which can be used to change your device password.</p>
</br></br> <p class="card-text" style="margin-top: 1rem;">The temporary password will be sent in an SSB private message to the admin of this device.</p>
The temporary password will be sent in an SSB private message to the admin of this device.</p> <p class="card-text" style="margin-top: 1rem;">Once you have the temporary password, click the 'Set New Password' button to reach the password reset page.</p>
</div> </div>
<form id="sendPasswordReset" action="/send_password_reset" method="post"> <form id="sendPasswordReset" action="/settings/admin/send_password_reset" method="post">
<div id="buttonDiv"> <div id="buttonDiv">
<input class="button button-primary center" style="margin-top: 1rem;" type="submit" value="Send Password Reset" title="Send Password Reset Link"/> <input class="button button-primary center" style="margin-top: 1rem;" type="submit" value="Send Password Reset" title="Send password reset link"/>
<a href="/settings/admin/reset_password" class="button button-primary center" title="Set a new password using the temporary password">Set New Password</a>
</div> </div>
</form> </form>
<!-- FLASH MESSAGE --> <!-- FLASH MESSAGE -->

View File

@ -2,41 +2,22 @@
{%- block card %} {%- block card %}
<!-- RESET PASSWORD PAGE --> <!-- RESET PASSWORD PAGE -->
<div class="card center"> <div class="card center">
<div class="form-container"> <form id="changePassword" class="center" action="/settings/admin/reset_password" method="post">
<form id="changePassword" action="/reset_password" method="post"> <div style="display: flex; flex-direction: column; margin-bottom: 1rem;">
<div class="input-wrapper"> <!-- input for temporary password -->
<!-- input for temporary password --> <label class="center label-small font-gray" style="width: 80%;" for="temporary_password">TEMPORARY PASSWORD</label>
<label id="temporary_password" class="label-small input-label font-near-black"> <input id="temporary_password" class="center input" name="temporary_password" type="password" title="temporary password" value="">
<label class="label-small input-label font-gray" for="temporary_password" style="padding-top: 0.25rem;">Temporary Password</label> <!-- input for new password1 -->
<input id="temporary_password" class="form-input" style="margin-bottom: 0;" <label class="center label-small font-gray" style="width: 80%;" for="new_password1">NEW PASSWORD</label>
name="temporary_password" type="password" title="temporary password" value=""> <input id="new_password1" class="center input" name="new_password1" title="new_password1" type="password" value="">
</label> <!-- input for new password2 -->
</div> <label class="center label-small font-gray" style="width: 80%;" for="new_password2">RE-ENTER NEW PASSWORD</label>
<input id="new_password2" class="center input" name="new_password2" title="new_password2" type="password" value="">
<div class="input-wrapper"> <!-- save (form submission) button -->
<!-- input for new password1 --> <input id="changePasswordButton" class="button button-primary center" title="Add" type="submit" value="Save">
<label id="new_password1" class="label-small input-label font-near-black"> </div>
<label class="label-small input-label font-gray" for="new_password1" style="padding-top: 0.25rem;">Enter New Password</label> </form>
<input id="new_password1" class="form-input" style="margin-bottom: 0;" <!-- FLASH MESSAGE -->
name="new_password1" title="new_password1" type="password" value=""> {% include "snippets/flash_message" %}
</label>
</div>
<div class="input-wrapper">
<!-- input for new password2 -->
<label id="new_password2" class="label-small input-label font-near-black">
<label class="label-small input-label font-gray" for="new_password2" style="padding-top: 0.25rem;">Re-Enter New Password</label>
<input id="new_password2" class="form-input" style="margin-bottom: 0;"
name="new_password2" title="new_password2" type="password" value="">
</label>
</div>
<div id="buttonDiv">
<input id="changePasswordButton" class="button button-primary center" title="Add" type="submit" value="Save">
</div>
</form>
<!-- FLASH MESSAGE -->
{% include "snippets/flash_message" %}
</div>
</div> </div>
{%- endblock card -%} {%- endblock card -%}