use crate::db::{Dao, TenebrousDbConn}; use crate::models::users::{self, NewUser, User}; use log::error; use rocket::http::{Cookie, CookieJar}; use rocket::request::{FlashMessage, Form}; use rocket::response::{Flash, Redirect}; use rocket_contrib::templates::Template; use std::collections::HashMap; pub(crate) fn routes() -> Vec { routes![ login, logout, logged_in_user, login_page, register_page, register ] } #[derive(FromForm)] struct Login { username: String, password: String, } #[derive(FromForm)] struct Registration { username: String, password: String, } fn add_login_cookie(cookies: &CookieJar<'_>, user: &User) { cookies.add_private(Cookie::new("user_id", user.id.to_string())); } fn remove_login_cookie(cookies: &CookieJar<'_>) { cookies.remove_private(Cookie::named("user_id")); } fn login_error_redirect>(message: S) -> Flash { Flash::error(Redirect::to(uri!(login_page)), message.as_ref()) } fn registration_error_redirect>(message: S) -> Flash { Flash::error(Redirect::to(uri!(register_page)), message.as_ref()) } #[post("/login", data = "")] async fn login( cookies: &CookieJar<'_>, login: Form, conn: TenebrousDbConn, ) -> Result> { let user = conn .load_user(login.username.clone()) .await .map_err(|e| { error!("login - error loading user user: {}", e); login_error_redirect("Internal error.") })? .ok_or_else(|| login_error_redirect("Invalid username or password."))?; if user.verify_password(&login.password) { add_login_cookie(cookies, &user); Ok(Redirect::to(uri!(super::root::index))) } else { Err(login_error_redirect("Invalid username or password.")) } } #[post("/logout")] fn logout(cookies: &CookieJar<'_>) -> Flash { remove_login_cookie(cookies); Flash::success(Redirect::to(uri!(login_page)), "Successfully logged out.") } #[get("/login")] fn logged_in_user(_user: &User) -> Redirect { Redirect::to(uri!(super::root::index)) } #[get("/login", rank = 2)] fn login_page(flash: Option) -> Template { let mut context = HashMap::new(); if let Some(ref msg) = flash { context.insert("flash", msg.msg()); } Template::render("login", &context) } #[get("/register")] fn register_page(flash: Option) -> Template { let mut context = HashMap::new(); if let Some(ref msg) = flash { context.insert("flash", msg.msg()); } Template::render("registration", &context) } #[post("/register", data = "")] async fn register( mut cookies: &CookieJar<'_>, registration: Form, conn: TenebrousDbConn, ) -> Result> { let existing_user = conn .load_user(registration.username.clone()) .await .map_err(|e| { error!("registration - error loading existing user: {}", e); registration_error_redirect("There was an error attempting to register.") })?; if existing_user.is_some() { return Err(registration_error_redirect(format!( "The username {} is already taken.", registration.username ))); } let hashed_pw = users::hash_password(®istration.password).map_err(|e| { error!("registration - password hashing error: {}", e); registration_error_redirect("There was an error attempting to register.") })?; let user = NewUser { username: registration.username.clone(), password: hashed_pw, }; let user = conn.insert_user(user).await.map_err(|e| { error!("registration - could not insert user: {}", e); registration_error_redirect("There was an error completing registration.") })?; add_login_cookie(&mut cookies, &user); Ok(Redirect::to(uri!(super::root::index))) }