Working version of password complexity meter
parent
8aeb37738d
commit
356763f66d
|
@ -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))
|
||||
|
|
|
@ -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"
|
||||
),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
2
ui/build
2
ui/build
|
@ -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"
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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", () =>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue