diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/src/main.rs | 2 | ||||
-rw-r--r-- | server/src/routes/mod.rs | 13 | ||||
-rw-r--r-- | server/src/routes/ui/account/mod.rs | 15 | ||||
-rw-r--r-- | server/src/routes/ui/account/settings.rs | 103 | ||||
-rw-r--r-- | server/src/routes/ui/layout.rs | 3 | ||||
-rw-r--r-- | server/src/routes/ui/style/forms.css | 20 | ||||
-rw-r--r-- | server/src/routes/ui/style/layout.css | 8 | ||||
-rw-r--r-- | server/src/routes/ui/style/player.css | 12 |
8 files changed, 154 insertions, 22 deletions
diff --git a/server/src/main.rs b/server/src/main.rs index 250e29c..6baaa2a 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -41,7 +41,7 @@ fn rocket() -> _ { admin: true, display_name: "Admin".to_string(), name: CONF.admin_username.clone(), - password: hash_password(&CONF.admin_password), + password: hash_password(&CONF.admin_username, &CONF.admin_password), }, ) .unwrap(); diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index c567d94..98063da 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -5,7 +5,10 @@ */ use crate::{database::Database, library::Library, routes::ui::error::MyResult, CONF}; use jellyremuxer::RemuxerContext; -use rocket::{catchers, config::SecretKey, fs::FileServer, get, routes, Build, Config, Rocket, fairing::AdHoc, http::Header}; +use rocket::{ + catchers, config::SecretKey, fairing::AdHoc, fs::FileServer, get, http::Header, routes, Build, + Config, Rocket, +}; use std::fs::File; use stream::r_stream; use ui::{ @@ -13,6 +16,7 @@ use ui::{ admin::{r_account_admin_dashboard, r_account_admin_invite, r_account_admin_remove_user}, r_account_login, r_account_login_post, r_account_logout, r_account_logout_post, r_account_register, r_account_register_post, + settings::{r_account_settings, r_account_settings_post}, }, error::r_catch, home::{r_home, r_home_unpriv}, @@ -43,8 +47,9 @@ pub fn build_rocket( }) .manage(remuxer) .manage(library) - .manage(database).attach(AdHoc::on_response("set server header", |_req,res| { - res.set_header(Header::new("server","jellything")); + .manage(database) + .attach(AdHoc::on_response("set server header", |_req, res| { + res.set_header(Header::new("server", "jellything")); Box::pin(async {}) })) .register("/", catchers![r_catch]) @@ -69,6 +74,8 @@ pub fn build_rocket( r_account_admin_dashboard, r_account_admin_invite, r_account_admin_remove_user, + r_account_settings, + r_account_settings_post, r_favicon, r_item_assets, ], 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 + })), + )) +} diff --git a/server/src/routes/ui/layout.rs b/server/src/routes/ui/layout.rs index 70cf2fb..0d4e1ef 100644 --- a/server/src/routes/ui/layout.rs +++ b/server/src/routes/ui/layout.rs @@ -7,6 +7,7 @@ use crate::{ routes::ui::account::{ admin::rocket_uri_macro_r_account_admin_dashboard, rocket_uri_macro_r_account_login, rocket_uri_macro_r_account_logout, rocket_uri_macro_r_account_register, session::Session, + settings::rocket_uri_macro_r_account_settings, }, uri, CONF, }; @@ -37,7 +38,7 @@ markup::define! { div.account { @if let Some(session) = session { - span { "Logged in as " @session.user.display_name } + span { "Logged in as " a[href=uri!(r_account_settings())] { @session.user.display_name } } @if session.user.admin { a[href=uri!(r_account_admin_dashboard())] { "Administration" } } diff --git a/server/src/routes/ui/style/forms.css b/server/src/routes/ui/style/forms.css index ab7ca7f..82a6157 100644 --- a/server/src/routes/ui/style/forms.css +++ b/server/src/routes/ui/style/forms.css @@ -11,6 +11,26 @@ input[type="password"] { margin: 0.2em; border: 2px solid var(--accent-light); } +input[type="text"]:disabled, +input[type="password"]:disabled { + border: 2px solid grey; +} + +input[type="submit"] { + padding: 0.5em; + margin: 1em; + justify-self: center; + border: 0px solid transparent; + background-color: var(--accent-dark); + border-radius: 8px; +} +input[type="submit"]:disabled { + background-color: grey; +} + +input[type="submit"]:hover { + filter: hue-rotate(-20deg); +} form.account { padding: 3em; diff --git a/server/src/routes/ui/style/layout.css b/server/src/routes/ui/style/layout.css index f7a21c2..412d1ed 100644 --- a/server/src/routes/ui/style/layout.css +++ b/server/src/routes/ui/style/layout.css @@ -97,11 +97,19 @@ nav .account { margin-bottom: 1em; } +section.message { + background-color: var(--background-light); + border-radius: 8px; +} .error { padding: 1em; color: rgb(255, 117, 117); font-family: monospace; } +.success { + padding: 1em; + color: rgb(117, 255, 117); +} footer { padding: 0.1em; diff --git a/server/src/routes/ui/style/player.css b/server/src/routes/ui/style/player.css index 3e8cea1..8c980b7 100644 --- a/server/src/routes/ui/style/player.css +++ b/server/src/routes/ui/style/player.css @@ -16,14 +16,7 @@ option { font-family: "Cantarell", sans-serif; } -input[type="submit"] { - padding: 0.5em; - margin: 1em; - justify-self: center; - border: 0px solid transparent; - background-color: var(--accent-dark); - border-radius: 8px; -} + fieldset { background-color: var(--background-light); border-radius: 8px; @@ -59,9 +52,6 @@ input[type="radio"]:checked { background-color: var(--accent-light); } -input[type="submit"]:hover { - filter: hue-rotate(-20deg); -} fieldset label { transition: color 0.2s; |