diff --git a/Cargo.lock b/Cargo.lock index 601a7b1e..eca7a4cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -707,6 +707,21 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -776,6 +791,7 @@ dependencies = [ "serde_json", "sqlx", "static-files", + "zxcvbn", ] [[package]] @@ -1010,6 +1026,41 @@ dependencies = [ "cipher", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.72", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.72", +] + [[package]] name = "deranged" version = "0.3.11" @@ -1019,6 +1070,37 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_builder" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" +dependencies = [ + "derive_builder_core", + "syn 2.0.72", +] + [[package]] name = "derive_more" version = "0.99.18" @@ -1160,6 +1242,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fancy-regex" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -1543,6 +1636,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -1602,6 +1701,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1641,6 +1749,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "lettre" version = "0.11.7" @@ -2641,6 +2755,12 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -3259,3 +3379,20 @@ dependencies = [ "cc", "pkg-config", ] + +[[package]] +name = "zxcvbn" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad76e35b00ad53688d6b90c431cabe3cbf51f7a4a154739e04b63004ab1c736c" +dependencies = [ + "chrono", + "derive_builder", + "fancy-regex", + "itertools", + "lazy_static", + "regex", + "time", + "wasm-bindgen", + "web-sys", +] diff --git a/Cargo.toml b/Cargo.toml index 738a4c0e..6084ccff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ lettre = "0.11.7" quick-xml = { version = "0.31.0", features = ["serde", "serialize"] } actix-web-static-files = "4.0" static-files = "0.2.1" +zxcvbn = "3.1.0" [build-dependencies] built = "0.7.4" diff --git a/src/endpoints/user/post_reset.rs b/src/endpoints/user/post_reset.rs index 9124b0bd..97ad987e 100644 --- a/src/endpoints/user/post_reset.rs +++ b/src/endpoints/user/post_reset.rs @@ -7,6 +7,10 @@ use lettre::{ }; use serde::Deserialize; use sqlx::PgPool; +use zxcvbn::{ + feedback::{Suggestion, Warning}, + zxcvbn, Score, +}; use crate::{ auth, @@ -19,6 +23,7 @@ struct ResetPasswordForm { token: Option, password: Option, passwordretyped: Option, + dry: Option, } #[actix_web::post("/reset-password")] @@ -91,6 +96,63 @@ Viele Grüße"##, user.name, reset_url)) return HttpResponse::BadRequest().body("Token existiert nicht bzw. ist abgelaufen!"); } + let user = User::read_by_id(pool.get_ref(), token.as_ref().unwrap().id) + .await + .unwrap(); + let mut split_names: Vec<&str> = user.name.as_str().split_whitespace().collect(); + let mut user_inputs = vec![user.email.as_str()]; + user_inputs.append(&mut split_names); + + let entropy = zxcvbn(form.password.as_ref().unwrap(), &user_inputs); + + if entropy.score() < Score::Three { + let feedback = entropy.feedback().unwrap(); + + let warning = match feedback.warning() { + Some(Warning::ThisIsATop10Password) => { + "Das Passwort ist eins der 10 meist genutzten." + } + Some(Warning::ThisIsATop100Password) => { + "Das Passwort ist eins der 100 meist genutzten." + } + Some(Warning::ThisIsACommonPassword) => "Das ist ein zu übliches Password.", + Some(Warning::DatesAreOftenEasyToGuess) => { + "Datumsangaben lassen sich meist leicht erraten." + } + Some(Warning::RecentYearsAreEasyToGuess) => { + "Vergangene Jahreszahlen lassen sich leicht erraten." + } + Some(Warning::AWordByItselfIsEasyToGuess) => "abc", + Some(Warning::RepeatsLikeAaaAreEasyToGuess) => "abc", + Some(Warning::SequencesLikeAbcAreEasyToGuess) => "abc", + Some(Warning::StraightRowsOfKeysAreEasyToGuess) => "abc", + Some(Warning::ShortKeyboardPatternsAreEasyToGuess) => "abc", + Some(Warning::ThisIsSimilarToACommonlyUsedPassword) => "abc", + Some(Warning::CommonNamesAndSurnamesAreEasyToGuess) => "abc", + Some(Warning::NamesAndSurnamesByThemselvesAreEasyToGuess) => "abc", + Some(Warning::RepeatsLikeAbcAbcAreOnlySlightlyHarderToGuess) => "abc", + _ => "", + }; + + let suggestion = feedback + .suggestions() + .iter() + .map(|s| match s { + Suggestion::AvoidSequences => "abc", + Suggestion::AvoidRecentYears => "abc", + _ => "abc", + }) + .collect::>() + .join("
"); + + return HttpResponse::BadRequest().body(format!("Passwort zu schwach.
Warnung: {warning}
Vorschlag: {suggestion}")); + } + + if form.dry.is_some_and(|b| b) { + return HttpResponse::NoContent().finish(); + } + + if form.password.as_ref().unwrap() != form.passwordretyped.as_ref().unwrap() { return HttpResponse::BadRequest().body("Passwörter stimmen nicht überein!"); } diff --git a/templates/user/reset_password.html b/templates/user/reset_password.html index 2881a979..78f7aaf6 100644 --- a/templates/user/reset_password.html +++ b/templates/user/reset_password.html @@ -4,20 +4,22 @@

Brass - Passwort zurücksetzen

-
+ + +
- +
- +