feat: WIP! password strength indicator
This commit is contained in:
parent
b84b2bd615
commit
e3c1aab6e2
137
Cargo.lock
generated
137
Cargo.lock
generated
@ -707,6 +707,21 @@ dependencies = [
|
|||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
@ -776,6 +791,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"static-files",
|
"static-files",
|
||||||
|
"zxcvbn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1010,6 +1026,41 @@ dependencies = [
|
|||||||
"cipher",
|
"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]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.3.11"
|
version = "0.3.11"
|
||||||
@ -1019,6 +1070,37 @@ dependencies = [
|
|||||||
"powerfmt",
|
"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]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "0.99.18"
|
version = "0.99.18"
|
||||||
@ -1160,6 +1242,17 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"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]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
@ -1543,6 +1636,12 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ident_case"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@ -1602,6 +1701,15 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"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]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
@ -1641,6 +1749,12 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
|
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lettre"
|
name = "lettre"
|
||||||
version = "0.11.7"
|
version = "0.11.7"
|
||||||
@ -2641,6 +2755,12 @@ dependencies = [
|
|||||||
"unicode-properties",
|
"unicode-properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
@ -3259,3 +3379,20 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
"pkg-config",
|
"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",
|
||||||
|
]
|
||||||
|
@ -28,6 +28,7 @@ lettre = "0.11.7"
|
|||||||
quick-xml = { version = "0.31.0", features = ["serde", "serialize"] }
|
quick-xml = { version = "0.31.0", features = ["serde", "serialize"] }
|
||||||
actix-web-static-files = "4.0"
|
actix-web-static-files = "4.0"
|
||||||
static-files = "0.2.1"
|
static-files = "0.2.1"
|
||||||
|
zxcvbn = "3.1.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
built = "0.7.4"
|
built = "0.7.4"
|
||||||
|
@ -7,6 +7,10 @@ use lettre::{
|
|||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
use zxcvbn::{
|
||||||
|
feedback::{Suggestion, Warning},
|
||||||
|
zxcvbn, Score,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
auth,
|
auth,
|
||||||
@ -19,6 +23,7 @@ struct ResetPasswordForm {
|
|||||||
token: Option<String>,
|
token: Option<String>,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
passwordretyped: Option<String>,
|
passwordretyped: Option<String>,
|
||||||
|
dry: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::post("/reset-password")]
|
#[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!");
|
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::<Vec<&str>>()
|
||||||
|
.join("</br>");
|
||||||
|
|
||||||
|
return HttpResponse::BadRequest().body(format!("Passwort zu schwach.</br>Warnung: {warning}</br>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() {
|
if form.password.as_ref().unwrap() != form.passwordretyped.as_ref().unwrap() {
|
||||||
return HttpResponse::BadRequest().body("Passwörter stimmen nicht überein!");
|
return HttpResponse::BadRequest().body("Passwörter stimmen nicht überein!");
|
||||||
}
|
}
|
||||||
|
@ -4,20 +4,22 @@
|
|||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="title">Brass - Passwort zurücksetzen</h1>
|
<h1 class="title">Brass - Passwort zurücksetzen</h1>
|
||||||
<form class="box" hx-post="/reset-password" hx-target-400="#error-message" hx-on:change="document.getElementById('error-message').innerHTML = ''">
|
<form class="box" hx-post="/reset-password" hx-params="not dry" hx-target-400="#error-message" hx-on:change="document.getElementById('error-message').innerHTML = ''">
|
||||||
<input type="hidden" name="token" value="{{ token }}"/>
|
<input type="hidden" name="token" value="{{ token }}"/>
|
||||||
|
|
||||||
|
<input type="hidden" name="dry" value="true"/>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="password">neues Passwort:</label>
|
<label class="label" for="password">neues Passwort:</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input class="input" placeholder="**********" name="password" type="password" required>
|
<input class="input" hx-post="/reset-password?dry=true" hx-params="*" hx-trigger="keyup changed delay:1s" hx-target-400="#error-message" placeholder="**********" name="password" type="password" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="passwordretyped">neues Passwort wiederholen:</label>
|
<label class="label" for="passwordretyped">neues Passwort wiederholen:</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input class="input" placeholder="**********" name="passwordretyped" type="password" required>
|
<input class="input" placeholder="**********" name="passwordretyped" type="password" max=256 required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user