aboutsummaryrefslogtreecommitdiff
path: root/server/src/routes/ui/account/mod.rs
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2023-01-22 13:56:06 +0100
committermetamuffin <metamuffin@disroot.org>2023-01-22 13:56:06 +0100
commitec76bbe5398f51ffa55bfd315b30c0a07245d4e6 (patch)
treefa0e1723f861de6fee21a35524bb7768fab0b6ce /server/src/routes/ui/account/mod.rs
parent84e093afa908dc68a7b0ae97ba8dc76aa0901d26 (diff)
downloadjellything-ec76bbe5398f51ffa55bfd315b30c0a07245d4e6.tar
jellything-ec76bbe5398f51ffa55bfd315b30c0a07245d4e6.tar.bz2
jellything-ec76bbe5398f51ffa55bfd315b30c0a07245d4e6.tar.zst
this is *horrible*
Diffstat (limited to 'server/src/routes/ui/account/mod.rs')
-rw-r--r--server/src/routes/ui/account/mod.rs148
1 files changed, 120 insertions, 28 deletions
diff --git a/server/src/routes/ui/account/mod.rs b/server/src/routes/ui/account/mod.rs
index 7e329a1..74710d9 100644
--- a/server/src/routes/ui/account/mod.rs
+++ b/server/src/routes/ui/account/mod.rs
@@ -1,8 +1,20 @@
-use super::HtmlTemplate;
+pub mod session;
+
+use super::error::MyError;
+use super::layout::LayoutPage;
+use crate::database::Database;
use crate::database::User;
-use crate::{AppState, CONF};
+use crate::routes::ui::error::MyResult;
+use crate::routes::ui::home::rocket_uri_macro_r_home;
+use crate::routes::ui::layout::DynLayoutPage;
+use crate::CONF;
+use anyhow::anyhow;
+use argon2::{Argon2, PasswordHasher};
+use rocket::form::Contextual;
use rocket::form::Form;
-use rocket::{get, post, FromForm, State};
+use rocket::http::{Cookie, CookieJar};
+use rocket::response::Redirect;
+use rocket::{get, post, uri, FromForm, State};
#[derive(FromForm)]
pub struct RegisterForm {
@@ -15,10 +27,10 @@ pub struct RegisterForm {
}
#[get("/account/register")]
-pub fn r_account_register() -> HtmlTemplate<markup::DynRender<'static>> {
- HtmlTemplate(
- "Register".to_string(),
- markup::new! {
+pub async fn r_account_register() -> DynLayoutPage<'static> {
+ LayoutPage {
+ title: "Register".to_string(),
+ content: markup::new! {
h1 { "Register for " @CONF.brand }
form[method="POST", action=""] {
label[for="inp-invitation"] { "Invite Code: " }
@@ -32,41 +44,121 @@ pub fn r_account_register() -> HtmlTemplate<markup::DynRender<'static>> {
input[type="submit", value="Register now!"];
}
},
- )
+ }
+}
+
+#[derive(FromForm)]
+pub struct LoginForm {
+ #[field(validate = len(4..32))]
+ pub username: String,
+ #[field(validate = len(..64))]
+ pub password: String,
}
#[get("/account/login")]
-pub fn r_account_login() -> HtmlTemplate<markup::DynRender<'static>> {
- HtmlTemplate(
- "Log in".to_string(),
- markup::new! {
+pub fn r_account_login() -> DynLayoutPage<'static> {
+ LayoutPage {
+ title: "Log in".to_string(),
+ content: markup::new! {
h1 { "Log in to your Account" }
+ form[method="POST", action=""] {
+ label[for="inp-username"] { "Username: " }
+ input[type="text", id="inp-username", name="username"]; br;
+ label[for="inp-password"] { "Password: " }
+ input[type="password", id="inp-password", name="password"]; br;
+ input[type="submit", value="Login"];
+ }
+ p { "While logged in, a cookie will be used to identify you." }
},
- )
+ }
}
#[post("/account/register", data = "<form>")]
-pub fn r_account_register_post(
- state: &State<AppState>,
- form: Form<RegisterForm>,
-) -> HtmlTemplate<markup::DynRender<'static>> {
- state
- .database
+pub fn r_account_register_post<'a>(
+ database: &'a State<Database>,
+ form: Form<Contextual<'a, RegisterForm>>,
+) -> MyResult<DynLayoutPage<'a>> {
+ let form = match &form.value {
+ Some(v) => v,
+ None => return Err(format_form_error(form)),
+ };
+
+ if database.invites.remove(&form.invitation).unwrap().is_none() {
+ return Err(MyError(anyhow!("invitation invalid")));
+ }
+ match database
.users
- .insert(
+ .compare_and_swap(
&form.username,
- &User {
+ None,
+ Some(&User {
display_name: form.username.clone(),
name: form.username.clone(),
password: form.password.clone().into(), // TODO hash it
+ admin: false,
+ }),
+ )
+ .unwrap()
+ {
+ Ok(_) => Ok(LayoutPage {
+ title: "Registration successful".to_string(),
+ content: markup::new! {
+ h1 { "Registration successful, you may log in now." }
},
+ }),
+ Err(_) => Err(MyError(anyhow!("username is taken"))),
+ }
+}
+
+#[post("/account/login", data = "<form>")]
+pub fn r_account_login_post(
+ database: &State<Database>,
+ jar: &CookieJar,
+ form: Form<Contextual<LoginForm>>,
+) -> MyResult<Redirect> {
+ let form = match &form.value {
+ Some(v) => v,
+ None => return Err(format_form_error(form)),
+ };
+
+ // hashing the password regardless if the accounts exists to prevent timing attacks
+ let password = hash_password(&form.password);
+
+ let user = database
+ .users
+ .get(&form.username)?
+ .ok_or(anyhow!("invalid password"))?;
+
+ if user.password != password {
+ Err(anyhow!("invalid password"))?
+ }
+
+ jar.add_private(Cookie::build("user", user.name).permanent().finish());
+
+ Ok(Redirect::found(uri!(r_home())))
+}
+
+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!(
+ "\n\t{}: {e}",
+ e.name
+ .as_ref()
+ .map(|e| e.to_string())
+ .unwrap_or("<unknown>".to_string())
)
- .unwrap();
- HtmlTemplate(
- "Registration successful".to_string(),
- markup::new! {
- h1 { "Registration successful." }
- },
- )
+ }
+ MyError(anyhow!(k))
+}
+
+pub fn hash_password(s: &str) -> Vec<u8> {
+ Argon2::default()
+ .hash_password(s.as_bytes(), r"IYMa13osbNeLJKnQ1T8LlA")
+ .unwrap()
+ .hash
+ .unwrap()
+ .as_bytes()
+ .to_vec()
}