diff options
Diffstat (limited to 'server/src/routes/ui/account')
-rw-r--r-- | server/src/routes/ui/account/mod.rs | 15 | ||||
-rw-r--r-- | server/src/routes/ui/account/settings.rs | 103 |
2 files changed, 112 insertions, 6 deletions
diff --git a/server/src/routes/ui/account/mod.rs b/server/src/routes/ui/account/mod.rs index 63c01c5..9007558 100644 --- a/server/src/routes/ui/account/mod.rs +++ b/server/src/routes/ui/account/mod.rs @@ -5,9 +5,9 @@ */ pub mod admin; pub mod session; +pub mod settings; use self::session::SessionCookie; - use super::{error::MyError, layout::LayoutPage}; use crate::{ database::{Database, User}, @@ -120,7 +120,7 @@ pub fn r_account_register_post<'a>( Some(&User { display_name: form.username.clone(), name: form.username.clone(), - password: hash_password(&form.password), + password: hash_password(&form.username, &form.password), admin: false, }), ) @@ -148,7 +148,7 @@ pub fn r_account_login_post( }; // hashing the password regardless if the accounts exists to prevent timing attacks - let password = hash_password(&form.password); + let password = hash_password(&form.username, &form.password); let user = database .users @@ -177,7 +177,7 @@ pub fn r_account_logout_post(jar: &CookieJar) -> MyResult<Redirect> { Ok(Redirect::found(uri!(r_home()))) } -fn format_form_error<T>(form: Form<Contextual<T>>) -> MyError { +pub fn format_form_error<T>(form: Form<Contextual<T>>) -> MyError { let mut k = String::from("form validation failed:"); for e in form.context.errors() { k += &format!( @@ -191,9 +191,12 @@ fn format_form_error<T>(form: Form<Contextual<T>>) -> MyError { MyError(anyhow!(k)) } -pub fn hash_password(s: &str) -> Vec<u8> { +pub fn hash_password(username: &str, password: &str) -> Vec<u8> { Argon2::default() - .hash_password(s.as_bytes(), r"IYMa13osbNeLJKnQ1T8LlA") + .hash_password( + format!("{username}\0{password}").as_bytes(), + r"IYMa13osbNeLJKnQ1T8LlA", + ) .unwrap() .hash .unwrap() diff --git a/server/src/routes/ui/account/settings.rs b/server/src/routes/ui/account/settings.rs new file mode 100644 index 0000000..c95c4bb --- /dev/null +++ b/server/src/routes/ui/account/settings.rs @@ -0,0 +1,103 @@ +use std::ops::Range; + +use rocket::{ + form::{self, validate::len, Contextual, Form}, + get, post, FromForm, State, +}; + +use super::{format_form_error, hash_password}; +use crate::{ + database::Database, + routes::ui::{ + account::session::Session, + error::MyResult, + layout::{DynLayoutPage, LayoutPage}, + }, + uri, +}; + +#[derive(FromForm)] +pub struct SettingsForm { + #[field(validate = option_len(4..64))] + password: Option<String>, + #[field(validate = option_len(4..32))] + display_name: Option<String>, +} + +fn option_len<'v>(value: &Option<String>, range: Range<usize>) -> form::Result<'v, ()> { + value.as_ref().map(|v| len(v, range)).unwrap_or(Ok(())) +} + +fn settings_page(session: Session, flash: Option<MyResult<String>>) -> DynLayoutPage<'static> { + LayoutPage { + title: "Settings".to_string(), + content: markup::new! { + h1 { "Settings" } + @if let Some(flash) = &flash { + @match flash { + Ok(mesg) => { section.message { p.success { @mesg } } } + Err(err) => { section.message { p.error { @format!("{err}") } } } + } + } + h2 { "Account" } + form[method="POST", action=uri!(r_account_settings_post())] { + label[for="username"] { "Username" } + input[type="text", id="username", disabled, value=&session.user.name]; + input[type="submit", disabled, value="Immutable"]; + } + form[method="POST", action=uri!(r_account_settings_post())] { + label[for="display_name"] { "Display Name" } + input[type="text", id="display_name", name="display_name", value=&session.user.display_name]; + input[type="submit", value="Update"]; + } + form[method="POST", action=uri!(r_account_settings_post())] { + label[for="password"] { "Password" } + input[type="password", id="password", name="password"]; + input[type="submit", value="Update"]; + } + h2 { "Appearance" } + p.error { "TODO: theming" } + }, + } +} + +#[get("/account/settings")] +pub fn r_account_settings(session: Session) -> DynLayoutPage<'static> { + settings_page(session, None) +} + +#[post("/account/settings", data = "<form>")] +pub fn r_account_settings_post( + session: Session, + database: &State<Database>, + form: Form<Contextual<SettingsForm>>, +) -> MyResult<DynLayoutPage<'static>> { + let form = match &form.value { + Some(v) => v, + None => return Ok(settings_page(session, Some(Err(format_form_error(form))))), + }; + + let mut out = String::new(); + database.users.fetch_and_update(&session.user.name, |k| { + k.map(|mut k| { + if let Some(password) = &form.password { + k.password = hash_password(&session.user.name, password); + out += "Password updated\n"; + } + if let Some(display_name) = &form.display_name { + k.display_name = display_name.clone(); + out += "Display name updated\n"; + } + k + }) + })?; + + Ok(settings_page( + session, + Some(Ok(if out.is_empty() { + "Nothing changed :)".to_string() + } else { + out + })), + )) +} |