use crate::{ mail::{build_mail, Mailer}, routes::{errors::ErrorPage, RespondOrRedirect}, template_utils::{IntoContext, Ructe}, }; use plume_models::{ db_conn::DbConn, email_signups::EmailSignup, instance::Instance, lettre::Transport, signups, Error, PlumeRocket, CONFIG, }; use rocket::{ http::Status, request::LenientForm, response::{Flash, Redirect}, State, }; use std::sync::{Arc, Mutex}; use tracing::warn; use validator::{Validate, ValidationError, ValidationErrors}; #[derive(Default, FromForm, Validate)] #[validate(schema( function = "emails_match", skip_on_field_errors = false, message = "Emails are not matching" ))] pub struct EmailSignupForm { #[validate(email(message = "Invalid email"))] pub email: String, #[validate(email(message = "Invalid email"))] pub email_confirmation: String, } fn emails_match(form: &EmailSignupForm) -> Result<(), ValidationError> { if form.email_confirmation == form.email { Ok(()) } else { Err(ValidationError::new("emails_match")) } } #[derive(Default, FromForm, Validate)] #[validate(schema( function = "passwords_match", skip_on_field_errors = false, message = "Passwords are not matching" ))] pub struct NewUserForm { #[validate(length(min = 1, message = "Username should be at least 1 characters long"))] pub username: String, #[validate(length(min = 8, message = "Password should be at least 8 characters long"))] pub password: String, #[validate(length(min = 8, message = "Password should be at least 8 characters long"))] pub password_confirmation: String, pub email: String, pub token: String, } pub fn passwords_match(form: &NewUserForm) -> Result<(), ValidationError> { if form.password != form.password_confirmation { Err(ValidationError::new("password_match")) } else { Ok(()) } } #[post("/email_signups/new", data = "
")] pub fn create( mail: State<'_, Arc>>, form: LenientForm, conn: DbConn, rockets: PlumeRocket, _enabled: signups::Email, ) -> Result { let registration_open = Instance::get_local() .map(|i| i.open_registrations) .unwrap_or(true); if !registration_open { return Ok(Flash::error( Redirect::to(uri!(super::user::new)), i18n!( rockets.intl.catalog, "Registrations are closed on this instance." ), ) .into()); // Actually, it is an error } let mut form = form.into_inner(); form.email = form.email.trim().to_owned(); form.validate().map_err(|err| { render!(email_signups::new( &(&conn, &rockets).to_context(), registration_open, &form, err )) })?; let res = EmailSignup::start(&conn, &form.email); if let Some(err) = res.as_ref().err() { return Ok(match err { Error::UserAlreadyExists => { // TODO: Notify to admin (and the user?) warn!("Registration attempted for existing user: {}. Registraion halted and email sending skipped.", &form.email); render!(email_signups::create(&(&conn, &rockets).to_context())).into() } Error::NotFound => render!(errors::not_found(&(&conn, &rockets).to_context())).into(), _ => render!(errors::not_found(&(&conn, &rockets).to_context())).into(), // FIXME }); } let token = res.unwrap(); let url = format!( "https://{}{}", CONFIG.base_url, uri!(show: token = token.to_string()) ); let message = build_mail( form.email, i18n!(rockets.intl.catalog, "User registration"), i18n!(rockets.intl.catalog, "Here is the link for registration: {0}"; url), ) .expect("Mail configuration has already been done at ignition process"); // TODO: Render error page if let Some(ref mut mailer) = *mail.lock().unwrap() { mailer.send(message.into()).ok(); // TODO: Render error page } Ok(render!(email_signups::create(&(&conn, &rockets).to_context())).into()) } #[get("/email_signups/new")] pub fn created(conn: DbConn, rockets: PlumeRocket, _enabled: signups::Email) -> Ructe { render!(email_signups::create(&(&conn, &rockets).to_context())) } #[get("/email_signups/")] pub fn show( token: String, conn: DbConn, rockets: PlumeRocket, _enabled: signups::Email, ) -> Result { let signup = EmailSignup::find_by_token(&conn, token.into())?; let confirmation = signup.confirm(&conn); if let Some(err) = confirmation.err() { match err { Error::Expired => { return Ok(render!(email_signups::new( &(&conn, &rockets).to_context(), Instance::get_local()?.open_registrations, &EmailSignupForm::default(), ValidationErrors::default() ))) } // TODO: Flash and redirect Error::NotFound => return Err(Error::NotFound.into()), _ => return Err(Error::NotFound.into()), // FIXME } } let form = NewUserForm { email: signup.email, token: signup.token, ..NewUserForm::default() }; Ok(render!(email_signups::edit( &(&conn, &rockets).to_context(), Instance::get_local()?.open_registrations, &form, ValidationErrors::default() ))) } #[post("/email_signups/signup", data = "")] pub fn signup( form: LenientForm, conn: DbConn, rockets: PlumeRocket, _enabled: signups::Email, ) -> Result { use RespondOrRedirect::{FlashRedirect, Response}; let instance = Instance::get_local().map_err(|e| { warn!("{:?}", e); Status::InternalServerError })?; if let Some(err) = form.validate().err() { return Ok(Response(render!(email_signups::edit( &(&conn, &rockets).to_context(), instance.open_registrations, &form, err )))); } let signup = EmailSignup::find_by_token(&conn, form.token.clone().into()) .map_err(|_| Status::NotFound)?; if form.email != signup.email { let mut err = ValidationErrors::default(); err.add("email", ValidationError::new("Email couldn't changed")); let form = NewUserForm { email: signup.email, ..form.into_inner() }; return Ok(Response(render!(email_signups::edit( &(&conn, &rockets).to_context(), instance.open_registrations, &form, err )))); } let _user = signup .complete(&conn, form.username.clone(), form.password.clone()) .map_err(|e| { warn!("{:?}", e); Status::UnprocessableEntity })?; Ok(FlashRedirect(Flash::success( Redirect::to(uri!(super::session::new: m = _)), i18n!( rockets.intl.catalog, "Your account has been created. Now you just need to log in, before you can use it." ), ))) }