/* This file is part of jellything (https://codeberg.org/metamuffin/jellything) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin */ pub mod settings; use super::error::MyError; use crate::{ helper::A, locale::AcceptLanguage, ui::{error::MyResult, home::rocket_uri_macro_r_home}, }; use anyhow::anyhow; use jellycommon::user::User; use jellyimport::is_importing; use jellylogic::{ login::{hash_password, login_logic}, session::Session, Database, }; use jellyui::{ account::{AccountLogin, AccountLogout, AccountRegister, AccountRegisterSuccess}, render_page, scaffold::{RenderInfo, SessionInfo}, }; use rocket::{ form::{Contextual, Form}, get, http::{Cookie, CookieJar}, post, response::{content::RawHtml, Redirect}, FromForm, State, }; use serde::{Deserialize, Serialize}; #[derive(FromForm)] pub struct RegisterForm { #[field(validate = len(8..128))] pub invitation: String, #[field(validate = len(4..32))] pub username: String, #[field(validate = len(4..64))] pub password: String, } #[get("/account/register")] pub async fn r_account_register(lang: AcceptLanguage) -> RawHtml { let AcceptLanguage(lang) = lang; RawHtml(render_page( &AccountRegister { lang: &lang }, RenderInfo { importing: false, session: None, }, lang, )) } #[derive(FromForm, Serialize, Deserialize)] pub struct LoginForm { #[field(validate = len(4..32))] pub username: String, #[field(validate = len(..64))] pub password: String, #[field(default = 604800)] // one week pub expire: u64, } #[get("/account/login")] pub fn r_account_login(session: Option>, lang: AcceptLanguage) -> RawHtml { let AcceptLanguage(lang) = lang; let logged_in = session.is_some(); RawHtml(render_page( &AccountLogin { lang: &lang, logged_in, }, RenderInfo { session: session.map(|s| SessionInfo { user: s.0.user }), importing: is_importing(), }, lang, )) } #[get("/account/logout")] pub fn r_account_logout(session: Option>, lang: AcceptLanguage) -> RawHtml { let AcceptLanguage(lang) = lang; RawHtml(render_page( &AccountLogout { lang: &lang }, RenderInfo { session: session.map(|s| SessionInfo { user: s.0.user }), importing: is_importing(), }, lang, )) } #[post("/account/register", data = "
")] pub fn r_account_register_post<'a>( database: &'a State, session: Option>, form: Form>, lang: AcceptLanguage, ) -> MyResult> { let AcceptLanguage(lang) = lang; let logged_in = session.is_some(); let form = match &form.value { Some(v) => v, None => return Err(MyError(anyhow!(format_form_error(form)))), }; database.register_user( &form.invitation, &form.username, User { display_name: form.username.clone(), name: form.username.clone(), password: hash_password(&form.username, &form.password), ..Default::default() }, )?; Ok(RawHtml(render_page( &AccountRegisterSuccess { lang: &lang, logged_in, }, RenderInfo { session: session.map(|s| SessionInfo { user: s.0.user }), importing: is_importing(), }, lang, ))) } #[post("/account/login", data = "")] pub fn r_account_login_post( database: &State, jar: &CookieJar, form: Form>, ) -> MyResult { let form = match &form.value { Some(v) => v, None => return Err(MyError(anyhow!(format_form_error(form)))), }; jar.add( Cookie::build(( "session", login_logic(database, &form.username, &form.password, None, None)?, )) .permanent() .build(), ); Ok(Redirect::found(rocket::uri!(r_home()))) } #[post("/account/logout")] pub fn r_account_logout_post(jar: &CookieJar) -> MyResult { jar.remove_private(Cookie::build("session")); Ok(Redirect::found(rocket::uri!(r_home()))) } pub fn format_form_error(form: Form>) -> String { 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("".to_string()) ) } k }