feat: reset password strength
This commit is contained in:
parent
e3c1aab6e2
commit
0fd46e184b
@ -7,14 +7,12 @@ use lettre::{
|
|||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use zxcvbn::{
|
use zxcvbn::{zxcvbn, Score};
|
||||||
feedback::{Suggestion, Warning},
|
|
||||||
zxcvbn, Score,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
auth,
|
auth::{self},
|
||||||
models::{PasswordReset, User},
|
models::{PasswordReset, User},
|
||||||
|
utils::password_help,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -106,53 +104,21 @@ Viele Grüße"##, user.name, reset_url))
|
|||||||
let entropy = zxcvbn(form.password.as_ref().unwrap(), &user_inputs);
|
let entropy = zxcvbn(form.password.as_ref().unwrap(), &user_inputs);
|
||||||
|
|
||||||
if entropy.score() < Score::Three {
|
if entropy.score() < Score::Three {
|
||||||
let feedback = entropy.feedback().unwrap();
|
let message = password_help::generate_for_entropy(&entropy);
|
||||||
|
|
||||||
let warning = match feedback.warning() {
|
return HttpResponse::BadRequest().body(message);
|
||||||
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) {
|
if form.dry.is_some_and(|b| b) {
|
||||||
return HttpResponse::NoContent().finish();
|
if entropy.score() == Score::Three {
|
||||||
|
return HttpResponse::Ok()
|
||||||
|
.body("<div id=\"password-strength\" class=\"mb-3 help content is-success\">Sicheres Passwort.</div>");
|
||||||
|
} else {
|
||||||
|
return HttpResponse::Ok()
|
||||||
|
.body("<div id=\"password-strength\" class=\"mb-3 help content is-success\">Sehr sicheres Passwort.</div>");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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!");
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
pub mod email;
|
pub mod email;
|
||||||
pub mod manage_commands;
|
pub mod manage_commands;
|
||||||
|
pub mod password_help;
|
||||||
|
74
src/utils/password_help.rs
Normal file
74
src/utils/password_help.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
use zxcvbn::{feedback::{Suggestion, Warning}, Entropy};
|
||||||
|
|
||||||
|
pub fn generate_for_entropy(entropy: &Entropy) -> String {
|
||||||
|
let feedback = entropy.feedback().unwrap();
|
||||||
|
|
||||||
|
let warning = match feedback.warning() {
|
||||||
|
Some(Warning::StraightRowsOfKeysAreEasyToGuess) => {
|
||||||
|
"Gerade Linien von Tasten auf der Tastatur sind leicht zu erraten."
|
||||||
|
}
|
||||||
|
Some(Warning::ShortKeyboardPatternsAreEasyToGuess) => {
|
||||||
|
"Kurze Tastaturmuster sind leicht zu erraten."
|
||||||
|
}
|
||||||
|
Some(Warning::RepeatsLikeAaaAreEasyToGuess) => {
|
||||||
|
"Sich wiederholende Zeichen wie 'aaa' sind leicht zu erraten."
|
||||||
|
}
|
||||||
|
Some(Warning::RepeatsLikeAbcAbcAreOnlySlightlyHarderToGuess) => {
|
||||||
|
"Sich wiederholende Zeichenmuster wie 'abcabcabc' sind leicht zu erraten."
|
||||||
|
}
|
||||||
|
Some(Warning::ThisIsATop10Password) => "Dies ist ein sehr häufig verwendetes Passwort.",
|
||||||
|
Some(Warning::ThisIsATop100Password) => "Dies ist ein häufig verwendetes Passwort.",
|
||||||
|
Some(Warning::ThisIsACommonPassword) => "Dies ist ein oft verwendetes Passwort.",
|
||||||
|
Some(Warning::ThisIsSimilarToACommonlyUsedPassword) => {
|
||||||
|
"Dieses Passwort weist Ähnlichkeit zu anderen, oft verwendeten Passwörtern auf."
|
||||||
|
}
|
||||||
|
Some(Warning::SequencesLikeAbcAreEasyToGuess) => {
|
||||||
|
"Häufige Zeichenfolgen wie 'abc' oder '1234' sind leicht zu erraten."
|
||||||
|
}
|
||||||
|
Some(Warning::RecentYearsAreEasyToGuess) => {
|
||||||
|
"Die jüngsten Jahreszahlen sind leicht zu erraten."
|
||||||
|
}
|
||||||
|
Some(Warning::AWordByItselfIsEasyToGuess) => "Einzelne Wörter sind leicht zu erraten.",
|
||||||
|
Some(Warning::DatesAreOftenEasyToGuess) => "Ein Datum ist leicht zu erraten.",
|
||||||
|
Some(Warning::NamesAndSurnamesByThemselvesAreEasyToGuess) => {
|
||||||
|
"Einzelne Namen oder Nachnamen sind leicht zu erraten."
|
||||||
|
}
|
||||||
|
Some(Warning::CommonNamesAndSurnamesAreEasyToGuess) => {
|
||||||
|
"Vornamen und Nachnamen sind leicht zu erraten."
|
||||||
|
}
|
||||||
|
_ => "Passwort ist zu schwach.",
|
||||||
|
};
|
||||||
|
|
||||||
|
let vorschlag_text = if feedback.suggestions().len() > 1 {
|
||||||
|
"Vorschläge"
|
||||||
|
} else {
|
||||||
|
"Vorschlag"
|
||||||
|
};
|
||||||
|
|
||||||
|
let suggestion = feedback
|
||||||
|
.suggestions()
|
||||||
|
.iter()
|
||||||
|
.map(|s| {
|
||||||
|
let inner = match s {
|
||||||
|
Suggestion::UseAFewWordsAvoidCommonPhrases => "Mehrere Wörter verwenden, aber allgemeine Phrasen vermeiden.",
|
||||||
|
Suggestion::NoNeedForSymbolsDigitsOrUppercaseLetters => "Es ist möglich, starke Passwörter zu erstellen, ohne Symbole, Zahlen oder Großbuchstaben zu verwenden.",
|
||||||
|
Suggestion::AddAnotherWordOrTwo => "Weitere Wörter, die weniger häufig vorkommen, hinzufügen.",
|
||||||
|
Suggestion::CapitalizationDoesntHelpVeryMuch => "Nicht nur den ersten Buchstaben groß schreiben.",
|
||||||
|
Suggestion::AllUppercaseIsAlmostAsEasyToGuessAsAllLowercase => "Einige, aber nicht alle Buchstaben groß schreiben.",
|
||||||
|
Suggestion::ReversedWordsArentMuchHarderToGuess => "Umgekehrte Schreibweise von gebräuchlichen Wörtern vermeiden.",
|
||||||
|
Suggestion::PredictableSubstitutionsDontHelpVeryMuch => "Vorhersehbare Buchstabenersetzungen wie '@' für 'a' vermeiden.",
|
||||||
|
Suggestion::UseALongerKeyboardPatternWithMoreTurns => "Längere Tastaturmuster in unterschiedlicher Tipprichtung verwenden.",
|
||||||
|
Suggestion::AvoidRepeatedWordsAndCharacters => "Wort- und Zeichenwiederholungen vermeiden.",
|
||||||
|
Suggestion::AvoidSequences => "Häufige Zeichenfolgen vermeiden.",
|
||||||
|
Suggestion::AvoidRecentYears => "Die jüngsten Jahreszahlen vermeiden.",
|
||||||
|
Suggestion::AvoidYearsThatAreAssociatedWithYou => "Jahre, die mit persönlichen Daten in Verbindung gebracht werden können, vermeiden.",
|
||||||
|
Suggestion::AvoidDatesAndYearsThatAreAssociatedWithYou => "Daten, die mit persönlichen Daten in Verbindung gebracht werden können, vermeiden.",
|
||||||
|
};
|
||||||
|
|
||||||
|
format!("<li>{inner}</li>")
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
format!("<div id=\"password-strength\" class=\"mb-3 help content is-danger\"><p>{warning}</p><p>{vorschlag_text}:<ul>{suggestion}</ul></p></div>")
|
||||||
|
}
|
@ -2,33 +2,38 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<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-params="not dry" 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-retype"
|
||||||
<input type="hidden" name="token" value="{{ token }}"/>
|
hx-on:keyup="document.getElementById('error-message-retype').innerHTML = ''">
|
||||||
|
<input type="hidden" name="token" value="{{ token }}" />
|
||||||
|
|
||||||
<input type="hidden" name="dry" value="true"/>
|
<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" 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>
|
<input class="input" hx-post="/reset-password?dry=true" hx-params="*" hx-trigger="keyup changed delay:1s"
|
||||||
</div>
|
hx-target="#password-strength" hx-target-400="#password-strength" placeholder="**********" name="password" type="password" required
|
||||||
</div>
|
hx-swap="outerHTML" hx-on:keyup="document.getElementById('password-strength').innerHTML = ''">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div id="password-strength" class="mb-3 help content"></div>
|
||||||
<label class="label" for="passwordretyped">neues Passwort wiederholen:</label>
|
|
||||||
<div class="control">
|
|
||||||
<input class="input" placeholder="**********" name="passwordretyped" type="password" max=256 required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="error-message" class="mb-3 help is-danger"></div>
|
<div class="field">
|
||||||
|
<label class="label" for="passwordretyped">neues Passwort wiederholen:</label>
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" placeholder="**********" name="passwordretyped" type="password" max=256 required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="level">
|
<div id="error-message-retype" class="mb-3 help is-danger"></div>
|
||||||
<input class="button is-primary level-left" type="submit" value="Passwort zurücksetzen" />
|
|
||||||
</div>
|
<div class="level">
|
||||||
</form>
|
<input class="button is-primary level-left" type="submit" value="Passwort zurücksetzen" />
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user