diff --git a/Cargo.lock b/Cargo.lock index 8f2e0653..cdcd1503 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,6 +300,16 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bstr" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -649,18 +659,6 @@ dependencies = [ "log", ] -[[package]] -name = "filetime" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "windows-sys", -] - [[package]] name = "finl_unicode" version = "1.2.0" @@ -862,6 +860,30 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "globset" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + [[package]] name = "h2" version = "0.3.21" @@ -1088,6 +1110,23 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "ignore" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" +dependencies = [ + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1149,12 +1188,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" -[[package]] -name = "itoap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9028f49264629065d057f340a86acb84867925865f73bbf8d47b4d149a7e88b8" - [[package]] name = "jobserver" version = "0.1.27" @@ -2119,41 +2152,12 @@ dependencies = [ ] [[package]] -name = "sailfish" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7861181faa2e413410444757deca246c70959cee725fbfd8f736a94a660eb377" -dependencies = [ - "itoap", - "ryu", - "sailfish-macros", - "version_check", -] - -[[package]] -name = "sailfish-compiler" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c38d77ced03b393e820ac70109857bd857f93e746f5d7d631829c9ee2e4f3fa" -dependencies = [ - "filetime", - "home", - "memchr", - "proc-macro2", - "quote", - "serde", - "syn 2.0.38", - "toml 0.7.8", -] - -[[package]] -name = "sailfish-macros" -version = "0.8.1" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8f73db14456f861a5c89166ab6ac76afd94b4d2a9416638ae2952ae051089c5" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "proc-macro2", - "sailfish-compiler", + "winapi-util", ] [[package]] @@ -2735,6 +2739,22 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "tera" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "970dff17c11e884a4a09bc76e3a17ef71e01bb13447a11e85226e254fe6d10b8" +dependencies = [ + "globwalk", + "lazy_static", + "pest", + "pest_derive", + "regex", + "serde", + "serde_json", + "unic-segment", +] + [[package]] name = "text-colorizer" version = "1.0.0" @@ -2782,6 +2802,16 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.29" @@ -2922,18 +2952,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.19.15", -] - [[package]] name = "toml" version = "0.8.2" @@ -2943,7 +2961,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.20.2", + "toml_edit", ] [[package]] @@ -2955,19 +2973,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.0.2", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - [[package]] name = "toml_edit" version = "0.20.2" @@ -3000,6 +3005,7 @@ dependencies = [ "hyper", "indexmap 1.9.3", "jsonwebtoken", + "lazy_static", "lettre", "log", "pbkdf2", @@ -3007,7 +3013,6 @@ dependencies = [ "rand_core", "regex", "reqwest", - "sailfish", "serde", "serde_bencode", "serde_bytes", @@ -3016,6 +3021,7 @@ dependencies = [ "sha-1", "sqlx", "tempfile", + "tera", "text-colorizer", "text-to-png", "thiserror", @@ -3133,6 +3139,56 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +dependencies = [ + "unic-ucd-segment", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicase" version = "2.7.0" @@ -3276,6 +3332,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3410,6 +3476,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index afbec95d..7b86356b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,9 @@ rust-version.workspace = true version.workspace = true [workspace.package] -authors = ["Nautilus Cyberneering , Mick van Dijke "] +authors = [ + "Nautilus Cyberneering , Mick van Dijke ", +] categories = ["network-programming", "web-programming"] description = "A BitTorrent Index" documentation = "https://docs.rs/crate/torrust-tracker/" @@ -74,7 +76,7 @@ lettre = { version = "0", features = [ "tokio1-native-tls", "smtp-transport", ] } -sailfish = "0" +tera = { version = "1", default-features = false } regex = "1" pbkdf2 = { version = "0", features = ["simple"] } text-colorizer = "1" @@ -91,6 +93,7 @@ tower-http = { version = "0", features = ["cors", "compression-full"] } email_address = "0" hex = "0" uuid = { version = "1", features = ["v4"] } +lazy_static = "1.4.0" [dev-dependencies] rand = "0" diff --git a/project-words.txt b/project-words.txt index 5a52dc61..fdd47e8a 100644 --- a/project-words.txt +++ b/project-words.txt @@ -77,6 +77,7 @@ Swatinem taiki tempdir tempfile +tera thiserror torrust Torrust diff --git a/src/mailer.rs b/src/mailer.rs index 3ef83e0d..0c48acd6 100644 --- a/src/mailer.rs +++ b/src/mailer.rs @@ -1,17 +1,53 @@ +use std::collections::HashMap; use std::sync::Arc; use jsonwebtoken::{encode, EncodingKey, Header}; +use lazy_static::lazy_static; use lettre::message::{MessageBuilder, MultiPart, SinglePart}; use lettre::transport::smtp::authentication::{Credentials, Mechanism}; use lettre::{AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor}; -use sailfish::TemplateOnce; use serde::{Deserialize, Serialize}; +use serde_json::value::{to_value, Value}; +use tera::{try_get_value, Context, Tera}; use crate::config::Configuration; use crate::errors::ServiceError; use crate::utils::clock; use crate::web::api::v1::routes::API_VERSION_URL_PREFIX; +lazy_static! { + pub static ref TEMPLATES: Tera = { + let mut tera = Tera::default(); + + match tera.add_template_file("templates/verify.html", Some("html_verify_email")) { + Ok(()) => {} + Err(e) => { + println!("Parsing error(s): {e}"); + ::std::process::exit(1); + } + }; + + tera.autoescape_on(vec![".html", ".sql"]); + tera.register_filter("do_nothing", do_nothing_filter); + tera + }; +} + +/// This function is a dummy filter for tera. +/// +/// # Panics +/// +/// Panics if unable to convert values. +/// +/// # Errors +/// +/// This function will return an error if... +#[allow(clippy::implicit_hasher)] +pub fn do_nothing_filter(value: &Value, _: &HashMap) -> tera::Result { + let s = try_get_value!("do_nothing_filter", "value", String, value); + Ok(to_value(s).unwrap()) +} + pub struct Service { cfg: Arc, mailer: Arc, @@ -24,13 +60,6 @@ pub struct VerifyClaims { pub exp: u64, } -#[derive(TemplateOnce)] -#[template(path = "../templates/verify.html")] -struct VerifyTemplate { - username: String, - verification_url: String, -} - impl Service { pub async fn new(cfg: Arc) -> Service { let mailer = Arc::new(Self::get_mailer(&cfg).await); @@ -77,41 +106,7 @@ impl Service { let builder = self.get_builder(to).await; let verification_url = self.get_verification_url(user_id, base_url).await; - let mail_body = format!( - r#" - Welcome to Torrust, {username}! - - Please click the confirmation link below to verify your account. - {verification_url} - - If this account wasn't made by you, you can ignore this email. - "# - ); - - let ctx = VerifyTemplate { - username: String::from(username), - verification_url, - }; - - let mail = builder - .subject("Torrust - Email verification") - .multipart( - MultiPart::alternative() - .singlepart( - SinglePart::builder() - .header(lettre::message::header::ContentType::TEXT_PLAIN) - .body(mail_body), - ) - .singlepart( - SinglePart::builder() - .header(lettre::message::header::ContentType::TEXT_HTML) - .body( - ctx.render_once() - .expect("value `ctx` must have some internal error passed into it"), - ), - ), - ) - .expect("the `multipart` builder had an error"); + let mail = build_letter(verification_url.as_str(), username, builder)?; match self.mailer.send(mail).await { Ok(_res) => Ok(()), @@ -155,4 +150,70 @@ impl Service { } } +fn build_letter(verification_url: &str, username: &str, builder: MessageBuilder) -> Result { + let (plain_body, html_body) = build_content(verification_url, username).map_err(|e| { + log::error!("{e}"); + ServiceError::InternalServerError + })?; + + Ok(builder + .subject("Torrust - Email verification") + .multipart( + MultiPart::alternative() + .singlepart( + SinglePart::builder() + .header(lettre::message::header::ContentType::TEXT_PLAIN) + .body(plain_body), + ) + .singlepart( + SinglePart::builder() + .header(lettre::message::header::ContentType::TEXT_HTML) + .body(html_body), + ), + ) + .expect("the `multipart` builder had an error")) +} + +fn build_content(verification_url: &str, username: &str) -> Result<(String, String), tera::Error> { + let plain_body = format!( + r#" + Welcome to Torrust, {username}! + + Please click the confirmation link below to verify your account. + {verification_url} + + If this account wasn't made by you, you can ignore this email. + "# + ); + let mut context = Context::new(); + context.insert("verification", &verification_url); + context.insert("username", &username); + let html_body = TEMPLATES.render("html_verify_email", &context)?; + Ok((plain_body, html_body)) +} + pub type Mailer = AsyncSmtpTransport; + +#[cfg(test)] +mod tests { + use lettre::Message; + + use super::{build_content, build_letter}; + + #[test] + fn it_should_build_a_letter() { + let builder = Message::builder() + .from("from@a.b.c".parse().unwrap()) + .reply_to("reply@a.b.c".parse().unwrap()) + .to("to@a.b.c".parse().unwrap()); + + let _letter = build_letter("https://a.b.c/", "user", builder).unwrap(); + } + + #[test] + fn it_should_build_content() { + let (plain_body, html_body) = build_content("https://a.b.c/", "user").unwrap(); + assert_ne!(plain_body, ""); + assert_ne!(html_body, ""); + } +} diff --git a/templates/verify.html b/templates/verify.html index e43ad6f0..c54bbfb4 100644 --- a/templates/verify.html +++ b/templates/verify.html @@ -1,23 +1,176 @@ - - + + + + + + + + + + + + +
Torrust

Welcome to Torrust, <%= username %>.
Please click the confirmation link below to verify your account.
Verify account
Or copy and paste the following link into your browser:
© Copyright Torrust 2023
\ No newline at end of file + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ Torrust
+
+

+

+ +
+
+ Welcome to Torrust, {{ username }}.
+
+
+ Please click the confirmation link below to verify your account.
+
+ + + + +
+ Verify account +
+
+
+ Or copy and paste the following link into your browser:
+
+ +
+
+ +
+
+ +
+ + + \ No newline at end of file