aboutsummaryrefslogtreecommitdiff
path: root/server/src
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2023-01-29 18:23:30 +0100
committermetamuffin <metamuffin@disroot.org>2023-01-29 18:23:30 +0100
commit15d0a83247c3b6091f006df967f54f8399030cf6 (patch)
treeea99621ec7c6f58417c56bf671b2937e37487888 /server/src
parentde8d69d2886ae50e28da210fc690c99457a804bb (diff)
downloadjellything-15d0a83247c3b6091f006df967f54f8399030cf6.tar
jellything-15d0a83247c3b6091f006df967f54f8399030cf6.tar.bz2
jellything-15d0a83247c3b6091f006df967f54f8399030cf6.tar.zst
user settings page
Diffstat (limited to 'server/src')
-rw-r--r--server/src/main.rs2
-rw-r--r--server/src/routes/mod.rs13
-rw-r--r--server/src/routes/ui/account/mod.rs15
-rw-r--r--server/src/routes/ui/account/settings.rs103
-rw-r--r--server/src/routes/ui/layout.rs3
-rw-r--r--server/src/routes/ui/style/forms.css20
-rw-r--r--server/src/routes/ui/style/layout.css8
-rw-r--r--server/src/routes/ui/style/player.css12
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;