Working version of password complexity meter

pull/7567/head
Vasia Patov 2020-11-10 00:29:09 -05:00
parent 8aeb37738d
commit 356763f66d
14 changed files with 129 additions and 36 deletions

View File

@ -196,6 +196,17 @@ trait FormHelper { self: I18nHelper =>
def passwordModified(field: Field, content: Frag)(modifiers: Modifier*)(implicit ctx: Context): Frag =
group(field, content)(input(_, typ = "password")(required)(modifiers))
def passwordComplexityMeter(): Frag =
frag(
div(cls := "password-complexity")(
label(cls := "password-complexity-label")(""),
div(cls := "password-complexity-meter")(
for ((index) <- 1 to 4)
yield span()
)
)
)
def globalError(form: Form[_])(implicit ctx: Context): Option[Frag] =
form.globalError map { err =>
div(cls := "form-group is-invalid")(error(err))

View File

@ -12,7 +12,12 @@ object passwd {
def apply(form: play.api.data.Form[_])(implicit ctx: Context) =
account.layout(
title = trans.changePassword.txt(),
active = "password"
active = "password",
evenMoreJs = frag(
jsModule("passwordComplexity"),
embedJsUnsafeLoadThen("""passwordComplexity.addPasswordChangeListener('form3-newPasswd1')"""),
jsAt("javascripts/vendor/zxcvbn.min.js"),
)
) {
div(cls := "account box box-pad")(
h1(trans.changePassword()),
@ -23,6 +28,7 @@ object passwd {
autocomplete := "current-password"
),
form3.passwordModified(form("newPasswd1"), trans.newPassword())(autocomplete := "new-password"),
form3.passwordComplexityMeter(),
form3.passwordModified(form("newPasswd2"), trans.newPasswordAgain())(
autocomplete := "new-password"
),

View File

@ -27,10 +27,7 @@ object bits {
autocomplete := (if (register) "new-password" else "current-password")
),
if (register)
div(cls := "password-complexity-meter")(
for ((index) <- 1 to 5)
yield span(id := s"password-complexity-bar-$index")
),
form3.passwordComplexityMeter(),
emailOption.map { email =>
form3.group(email, trans.email(), help = frag("We will only use it for password reset.").some)(
form3.input(_, typ = "email")(required)

View File

@ -16,7 +16,8 @@ object signup {
jsModule("login"),
embedJsUnsafeLoadThen("""loginSignup.signupStart()"""),
views.html.base.recaptcha.script(form),
fingerprintTag
fingerprintTag,
jsAt("javascripts/vendor/zxcvbn.min.js"),
),
moreCss = cssTag("auth"),
csp = defaultCsp.withRecaptcha.some

View File

@ -153,7 +153,9 @@ object layout {
depsTag,
jsModule("site")
),
span("start_more_js"),
moreJs,
span("end_more_js"),
ctx.pageData.inquiry.isDefined option jsTag("inquiry.js")
)

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,7 @@ mkdir -p public/compiled
apps1="common"
apps2="chess ceval game tree chat nvui"
apps3="site swiss msg chat cli challenge notify learn insight editor puzzle round analyse lobby tournament tournamentSchedule tournamentCalendar simul dasher speech palantir serviceWorker dgt"
site_plugins="tvEmbed puzzleEmbed analyseEmbed user modUser clas coordinate captcha expandText team forum account coachShow coachForm challengePage checkout login tourForm teamBattleForm gameSearch userComplete infiniteScroll flatpickr teamAdmin"
site_plugins="tvEmbed puzzleEmbed analyseEmbed user modUser clas coordinate captcha expandText team forum account coachShow coachForm challengePage checkout login passwordComplexity tourForm teamBattleForm gameSearch userComplete infiniteScroll flatpickr teamAdmin"
round_plugins="nvui keyboardMove"
analyse_plugins="nvui studyTopicForm"

View File

@ -7,7 +7,7 @@ export { isEvalBetter, renderEval, sanIrreversible } from './util';
export { ctrl, view, winningChances };
// stop when another tab starts. Listen only once here,
// as the ctrl can be instanciated several times.
// as the ctrl can be instantiated several times.
// gotta do the click on the toggle to have it visually change.
lichess.storage.make('ceval.disable').listen(_ => {
const toggle = document.getElementById('analyse-toggle-ceval') as HTMLInputElement | undefined;

View File

@ -99,17 +99,18 @@ textarea.form-control {
border-top: $border;
}
.password-complexity {
margin-bottom: 2rem;
}
.password-complexity-meter {
display: flex;
grid-gap: 0.25rem;
height: 0.35rem;
height: 0.4rem;
margin-top: 1rem;
> * {
background-color: gray;
width: 25%;
}
}
.password-complexity-green {
background-color: green;
}

View File

@ -12,6 +12,7 @@
},
"dependencies": {
"@fnando/sparkline": "^0.3.10",
"@types/zxcvbn": "^4.4.0",
"@yaireo/tagify": "3.9.3",
"debounce-promise": "^3.1.2",
"flatpickr": "^4.6.6",

View File

@ -121,6 +121,11 @@ export default rollupProject({
input: 'src/account.ts',
output: 'account',
},
passwordComplexity: {
input: 'src/passwordComplexity.ts',
output: 'passwordComplexity',
name: 'passwordComplexity'
},
coachForm: {
input: 'src/coachForm.ts',
output: 'coach.form',

View File

@ -1,6 +1,7 @@
import * as xhr from "common/xhr";
import debounce from "common/debounce";
import spinnerHtml from "./component/spinner";
import { addPasswordChangeListener } from "./passwordComplexity";
export function loginStart() {
const selector = ".auth-login form";
@ -62,30 +63,9 @@ export function signupStart() {
.on("change keyup paste", () => {
$exists.hide();
usernameCheck();
}),
$passwordComplexityMeter = $form.find(
'div[class="password-complexity-meter"]'
),
$password = $form
.find('input[name="password"]')
.on("change keyup paste", () => {
passwordComplexityCheck();
});
const passwordComplexity = (password: string) => {};
const passwordComplexityCheck = debounce(() => {
const passwd = $password.val() as string;
var bar = $passwordComplexityMeter.find(
'span[id="password-complexity-bar-1"'
);
if (passwd.length >= 8) {
bar.addClass("password-complexity-green");
} else {
bar.removeClass("password-complexity-green");
}
console.log("PASSWORDCOMPLEXITYCHECK");
}, 300);
addPasswordChangeListener("form3-password");
const usernameCheck = debounce(() => {
const name = $username.val() as string;
@ -93,7 +73,6 @@ export function signupStart() {
xhr
.json(xhr.url("/player/autocomplete", { term: name, exists: 1 }))
.then((res) => $exists.toggle(res));
console.log("USERNAMECHECK");
}, 300);
$form.on("submit", () =>

View File

@ -0,0 +1,57 @@
export function addPasswordChangeListener(id: string) {
const passwordInput = document.getElementById(id) as HTMLInputElement;
passwordInput.addEventListener('input', () => {updatePasswordComplexityMeter(passwordInput.value)});
}
function updatePasswordComplexityMeter(password: string): void {
const analysis = window.zxcvbn(password);
updateMeter(analysis.score);
updateLabel(password.length, analysis.score);
}
function updateMeter(score: number): void {
const color =
score > 3 ? "green" : score > 2 ? "yellow" : score > 1 ? "orange" : "red";
const meter = document.querySelector(".password-complexity-meter");
const children = meter?.children || [];
for (var i = 0; i < children.length; i++) {
if (i < score) {
(children[i] as HTMLElement).style.backgroundColor = color;
} else {
(children[i] as HTMLElement).style.backgroundColor = "";
}
}
}
function updateLabel(passwordLength: number, score: number):void {
const suggestionLabel = document.querySelector(
".password-complexity-label"
) as HTMLElement;
if (passwordLength < 4) {
suggestionLabel.textContent = "Password must be at least four characters.";
} else {
switch (score) {
case 0:
suggestionLabel.textContent = "Password is short and easy to guess."
break;
case 1:
suggestionLabel.textContent = "Very weak password.";
break;
case 2:
suggestionLabel.textContent = "Weak password.";
break;
case 3:
suggestionLabel.textContent = "Decent password.";
break;
case 4:
suggestionLabel.textContent = "Strong password!";
break;
}
}
}

View File

@ -175,6 +175,11 @@
resolved "https://registry.yarnpkg.com/@types/webrtc/-/webrtc-0.0.25.tgz#bd2b4e7b4c13250b3d58439623f2b9adfd7dee9e"
integrity sha512-ep/e+p2uUKV1h96GBgRhwomrBch/bPDHPOKbCHODLGRUDuuKe2s7sErlFVKw+5BYUzvpxSmUNqoadaZ44MePoQ==
"@types/zxcvbn@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@types/zxcvbn/-/zxcvbn-4.4.0.tgz#fbc1d941cc6d9d37d18405c513ba6b294f89b609"
integrity sha512-GQLOT+SN20a+AI51y3fAimhyTF4Y0RG+YP3gf91OibIZ7CJmPFgoZi+ZR5a+vRbS01LbQosITWum4ATmJ1Z6Pg==
"@yaireo/tagify@3.9.3":
version "3.9.3"
resolved "https://registry.yarnpkg.com/@yaireo/tagify/-/tagify-3.9.3.tgz#a6671345b9a3a38eb4a7b18728db2526023e1662"