social login Fix username validation javascript (#9297)

* fix validation and don't use built-in validation UI

Co-authored-by: Bruno Windels <brunow@element.io>
This commit is contained in:
Richard van der Hoff 2021-02-03 17:52:55 +00:00 committed by GitHub
parent ff55300b91
commit 7a0dcea3e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 85 additions and 21 deletions

1
changelog.d/9297.feature Normal file
View file

@ -0,0 +1 @@
Further improvements to the user experience of registration via single sign-on.

View file

@ -18,6 +18,19 @@
font-size: 12px; font-size: 12px;
} }
.username_input.invalid {
border-color: #FE2928;
}
.username_input.invalid input, .username_input.invalid label {
color: #FE2928;
}
.username_input div, .username_input input {
line-height: 18px;
font-size: 14px;
}
.username_input label { .username_input label {
position: absolute; position: absolute;
top: -8px; top: -8px;
@ -78,6 +91,15 @@
display: block; display: block;
margin-top: 8px; margin-top: 8px;
} }
output {
padding: 0 14px;
display: block;
}
output.error {
color: #FE2928;
}
</style> </style>
</head> </head>
<body> <body>
@ -87,12 +109,13 @@
</header> </header>
<main> <main>
<form method="post" class="form__input" id="form"> <form method="post" class="form__input" id="form">
<div class="username_input"> <div class="username_input" id="username_input">
<label for="field-username">Username</label> <label for="field-username">Username</label>
<div class="prefix">@</div> <div class="prefix">@</div>
<input type="text" name="username" id="field-username" autofocus required pattern="[a-z0-9\-=_\/\.]+"> <input type="text" name="username" id="field-username" autofocus>
<div class="postfix">:{{ server_name }}</div> <div class="postfix">:{{ server_name }}</div>
</div> </div>
<output for="username_input" id="field-username-output"></output>
<input type="submit" value="Continue" class="primary-button"> <input type="submit" value="Continue" class="primary-button">
{% if user_attributes %} {% if user_attributes %}
<section class="idp-pick-details"> <section class="idp-pick-details">

View file

@ -1,14 +1,24 @@
const usernameField = document.getElementById("field-username"); const usernameField = document.getElementById("field-username");
const usernameOutput = document.getElementById("field-username-output");
const form = document.getElementById("form");
// needed to validate on change event when no input was changed
let needsValidation = true;
let isValid = false;
function throttle(fn, wait) { function throttle(fn, wait) {
let timeout; let timeout;
return function() { const throttleFn = function() {
const args = Array.from(arguments); const args = Array.from(arguments);
if (timeout) { if (timeout) {
clearTimeout(timeout); clearTimeout(timeout);
} }
timeout = setTimeout(fn.bind.apply(fn, [null].concat(args)), wait); timeout = setTimeout(fn.bind.apply(fn, [null].concat(args)), wait);
} };
throttleFn.cancelQueued = function() {
clearTimeout(timeout);
};
return throttleFn;
} }
function checkUsernameAvailable(username) { function checkUsernameAvailable(username) {
@ -16,14 +26,14 @@ function checkUsernameAvailable(username) {
return fetch(check_uri, { return fetch(check_uri, {
// include the cookie // include the cookie
"credentials": "same-origin", "credentials": "same-origin",
}).then((response) => { }).then(function(response) {
if(!response.ok) { if(!response.ok) {
// for non-200 responses, raise the body of the response as an exception // for non-200 responses, raise the body of the response as an exception
return response.text().then((text) => { throw new Error(text); }); return response.text().then((text) => { throw new Error(text); });
} else { } else {
return response.json(); return response.json();
} }
}).then((json) => { }).then(function(json) {
if(json.error) { if(json.error) {
return {message: json.error}; return {message: json.error};
} else if(json.available) { } else if(json.available) {
@ -34,33 +44,49 @@ function checkUsernameAvailable(username) {
}); });
} }
const allowedUsernameCharacters = new RegExp("^[a-z0-9\\.\\_\\-\\/\\=]+$");
const allowedCharactersString = "lowercase letters, digits, ., _, -, /, =";
function reportError(error) {
throttledCheckUsernameAvailable.cancelQueued();
usernameOutput.innerText = error;
usernameOutput.classList.add("error");
usernameField.parentElement.classList.add("invalid");
usernameField.focus();
}
function validateUsername(username) { function validateUsername(username) {
usernameField.setCustomValidity(""); isValid = false;
if (usernameField.validity.valueMissing) { needsValidation = false;
usernameField.setCustomValidity("Please provide a username"); usernameOutput.innerText = "";
return; usernameField.parentElement.classList.remove("invalid");
usernameOutput.classList.remove("error");
if (!username) {
return reportError("Please provide a username");
} }
if (usernameField.validity.patternMismatch) { if (username.length > 255) {
usernameField.setCustomValidity("Invalid username, please only use " + allowedCharactersString); return reportError("Too long, please choose something shorter");
return;
} }
usernameField.setCustomValidity("Checking if username is available …"); if (!allowedUsernameCharacters.test(username)) {
return reportError("Invalid username, please only use " + allowedCharactersString);
}
usernameOutput.innerText = "Checking if username is available …";
throttledCheckUsernameAvailable(username); throttledCheckUsernameAvailable(username);
} }
const throttledCheckUsernameAvailable = throttle(function(username) { const throttledCheckUsernameAvailable = throttle(function(username) {
const handleError = function(err) { const handleError = function(err) {
// don't prevent form submission on error // don't prevent form submission on error
usernameField.setCustomValidity(""); usernameOutput.innerText = "";
console.log(err.message); isValid = true;
}; };
try { try {
checkUsernameAvailable(username).then(function(result) { checkUsernameAvailable(username).then(function(result) {
if (!result.available) { if (!result.available) {
usernameField.setCustomValidity(result.message); reportError(result.message);
usernameField.reportValidity();
} else { } else {
usernameField.setCustomValidity(""); isValid = true;
usernameOutput.innerText = "";
} }
}, handleError); }, handleError);
} catch (err) { } catch (err) {
@ -68,9 +94,23 @@ const throttledCheckUsernameAvailable = throttle(function(username) {
} }
}, 500); }, 500);
form.addEventListener("submit", function(evt) {
if (needsValidation) {
validateUsername(usernameField.value);
evt.preventDefault();
return;
}
if (!isValid) {
evt.preventDefault();
usernameField.focus();
return;
}
});
usernameField.addEventListener("input", function(evt) { usernameField.addEventListener("input", function(evt) {
validateUsername(usernameField.value); validateUsername(usernameField.value);
}); });
usernameField.addEventListener("change", function(evt) { usernameField.addEventListener("change", function(evt) {
validateUsername(usernameField.value); if (needsValidation) {
validateUsername(usernameField.value);
}
}); });