diff --git a/code/html/custom.css b/code/html/custom.css index 6f03f475..af494464 100644 --- a/code/html/custom.css +++ b/code/html/custom.css @@ -50,10 +50,6 @@ h2 { display: block; } -.content { - margin: 0; -} - .page { margin-top: 10px; } @@ -98,16 +94,12 @@ div.center { display: none; } -#credentials { - font-size: 200%; - height: 100px; - left: 50%; - margin-left: -200px; - margin-top: -50px; - position: fixed; - text-align: center; - top: 50%; - width: 400px; +.content #password { + margin: 0 auto; +} + +.content #layout { + margin: 0; } div.state { @@ -206,6 +198,10 @@ div.state { background: rgb(255, 128, 0); /* orange */ } +.button-generate-password { + background: rgb(66, 184, 221); /* blue */ +} + .button-upgrade-browse, .button-clear-filters, .button-clear-messages, @@ -448,3 +444,53 @@ table.dataTable.display tbody td { height: 400px; margin-bottom: 10px; } + +/* ----------------------------------------------------------------------------- + Password input controls + -------------------------------------------------------------------------- */ +.password-reveal { + font-family: EmojiSymbols,Segoe UI Symbol; + background: rgba(0,0,0,0); + display: inline-block; + float: right; + z-index: 50; + margin-top: 6px; + margin-left: -30px; + vertical-align: middle; + font-size: 1.2em; + height: 100%; +} + +.password-reveal:after { + content: "👁"; +} + +input[type="password"] + .password-reveal { + color: rgba(205, 205, 205, 0.3); +} + +input[type="text"] + .password-reveal { + color: rgba(66, 184, 221, 0.8); +} + +.no-select { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +input::-ms-clear, +input::-ms-reveal { + display: none; +} + +/* css minifier must not combine these. + * style will not apply otherwise */ +input::-ms-input-placeholder { + color: #ccd; +} + +input::placeholder { + color: #ccc; +} diff --git a/code/html/custom.js b/code/html/custom.js index e0646b30..de0c5af7 100644 --- a/code/html/custom.js +++ b/code/html/custom.js @@ -146,8 +146,7 @@ function loadTimeZones() { } -function validateForm(form) { - +function validatePassword(password) { // http://www.the-art-of-web.com/javascript/validate-password/ // at least one lowercase and one uppercase letter or number // at least eight characters (letters, numbers or special characters) @@ -155,16 +154,26 @@ function validateForm(form) { // MUST be 8..63 printable ASCII characters. See: // https://en.wikipedia.org/wiki/Wi-Fi_Protected_Access#Target_users_(authentication_key_distribution) // https://github.com/xoseperez/espurna/issues/1151 + var re_password = /^(?=.*[A-Z\d])(?=.*[a-z])[\w~!@#$%^&*\(\)<>,.\?;:{}\[\]\\|]{8,63}$/; + return ( + (password !== undefined) + && (typeof password === "string") + && (password.length > 0) + && re_password.test(password) + ); +} + +function validateForm(form) { // password var adminPass1 = $("input[name='adminPass']", form).first().val(); - if (adminPass1.length > 0 && !re_password.test(adminPass1)) { + if (!validatePassword(adminPass1)) { alert("The password you have entered is not valid, it must be 8..63 characters and have at least 1 lowercase and 1 uppercase / number!"); return false; } - var adminPass2 = $("input[name='adminPass']", form).last().val(); + var adminPass2 = $("input[name='adminPass_confirm']", form).last().val(); if (adminPass1 !== adminPass2) { alert("Passwords are different!"); return false; @@ -225,6 +234,12 @@ function addValue(data, name, value) { "node", "key", "topic" ]; + + // join both adminPass and ..._confirm + if (name.startsWith("adminPass")) { + name = "adminPass"; + } + if (name in data) { if (!Array.isArray(data[name])) { data[name] = [data[name]]; @@ -260,26 +275,67 @@ function getData(form) { } -function randomString(length, chars) { - var mask = ""; - if (chars.indexOf("a") > -1) { mask += "abcdefghijklmnopqrstuvwxyz"; } - if (chars.indexOf("A") > -1) { mask += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; } - if (chars.indexOf("#") > -1) { mask += "0123456789"; } - if (chars.indexOf("@") > -1) { mask += "ABCDEF"; } - if (chars.indexOf("!") > -1) { mask += "~`!@#$%^&*()_+-={}[]:\";'<>?,./|\\"; } - var result = ""; - for (var i = length; i > 0; --i) { - result += mask[Math.round(Math.random() * (mask.length - 1))]; +function randomString(length, args) { + if (typeof args === "undefined") { + args = { + lowercase: true, + uppercase: true, + numbers: true, + special: true + } } - return result; + + var mask = ""; + if (args.lowercase) { mask += "abcdefghijklmnopqrstuvwxyz"; } + if (args.uppercase) { mask += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; } + if (args.numbers || args.hex) { mask += "0123456789"; } + if (args.hex) { mask += "ABCDEF"; } + if (args.special) { mask += "~`!@#$%^&*()_+-={}[]:\";'<>?,./|\\"; } + + var source = new Uint32Array(length); + var result = new Array(length); + + window.crypto.getRandomValues(source).forEach(function(value, i) { + result[i] = mask[value % mask.length]; + }); + + return result.join(""); } function generateAPIKey() { - var apikey = randomString(16, "@#"); + var apikey = randomString(16, {hex: true}); $("input[name='apiKey']").val(apikey); return false; } +function generatePassword() { + var password = ""; + do { + password = randomString(10); + } while (!validatePassword(password)); + + return password; +} + +function toggleVisiblePassword() { + var elem = this.previousElementSibling; + if (elem.type === "password") { + elem.type = "text"; + } else { + elem.type = "password"; + } + return false; +} + +function doGeneratePassword() { + $("input", $("#formPassword")) + .val(generatePassword()) + .each(function() { + this.type = "text"; + }); + return false; +} + function getJson(str) { try { return JSON.parse(str); @@ -476,11 +532,11 @@ function doReconnect(ask) { function doUpdate() { - var form = $("#formSave"); - if (validateForm(form)) { + var forms = $(".form-settings"); + if (validateForm(forms)) { // Get data - sendConfig(getData(form)); + sendConfig(getData(forms)); // Empty special fields $(".pwrExpected").val(0); @@ -753,6 +809,7 @@ function addNetwork() { $(this).attr("tabindex", tabindex); tabindex++; }); + $(".password-reveal", line).on("click", toggleVisiblePassword); $(line).find(".button-del-network").on("click", delNetwork); $(line).find(".button-more-network").on("click", moreNetwork); line.appendTo("#networks"); @@ -1575,12 +1632,15 @@ $(function() { createCheckboxes(); setInterval(function() { keepTime(); }, 1000); + $(".password-reveal").on("click", toggleVisiblePassword); + $("#menuLink").on("click", toggleMenu); $(".pure-menu-link").on("click", showPanel); $("progress").attr({ value: 0, max: 100 }); $(".button-update").on("click", doUpdate); $(".button-update-password").on("click", doUpdatePassword); + $(".button-generate-password").on("click", doGeneratePassword); $(".button-reboot").on("click", doReboot); $(".button-reconnect").on("click", doReconnect); $(".button-wifi-scan").on("click", doScan); diff --git a/code/html/index.html b/code/html/index.html index bde3aaa9..4d3b0cb5 100644 --- a/code/html/index.html +++ b/code/html/index.html @@ -28,40 +28,45 @@
-
+ -
+

SECURITY

-

Before using this device you have to change the default password for the user 'admin'. This password will be used for the AP mode hotspot, the web interface (where you are now) and the over-the-air updates.

+

Before using this device you have to change the default password for the user admin. This password will be used for the AP mode hotspot, the web interface (where you are now) and the over-the-air updates.

-
- - -
-
- The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).
- It must be 8..63 characters (numbers and letters and any of these special characters: _,.;:~!?@#$%^&*<>\|(){}[]) and have at least one lowercase and one uppercase or one number.
+ + +
- - + + +
+
-
- +
+
+ Password must be 8..63 characters (numbers and letters and any of these special characters: _,.;:~!?@#$%^&*<>\|(){}[]) and have at least one lowercase and one uppercase or one number.
+
+ + +
+ +
+ +
-
-
@@ -309,8 +314,7 @@
-
- +
@@ -382,7 +386,9 @@
+
+
@@ -414,8 +420,10 @@
+
- + +
@@ -501,8 +509,10 @@
- +
+ +
@@ -523,18 +533,18 @@
- + + +
+ + +
The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).
It must be 8..63 characters (numbers and letters and any of these special characters: _,.;:~!?@#$%^&*<>\|(){}[]) and have at least one lowercase and one uppercase or one number.
-
- - -
-
@@ -615,7 +625,9 @@
+ +
@@ -657,7 +669,9 @@
+
+
@@ -681,8 +695,10 @@
+
- + +
@@ -711,10 +727,12 @@
+
+
+
-

MESSAGES

@@ -753,8 +771,10 @@

- +
+ +
@@ -783,12 +803,13 @@
- +
- + +
@@ -873,7 +894,9 @@
+ +
@@ -917,7 +940,9 @@
+
+
@@ -964,7 +989,9 @@
+
+
@@ -1018,7 +1045,9 @@
+
+
@@ -1060,7 +1089,9 @@
+
+
@@ -1096,19 +1127,22 @@
- +
- + +
+
+
@@ -1140,8 +1174,10 @@
+
- + +
@@ -1297,9 +1333,11 @@
- +
+ - + +
@@ -1319,10 +1357,10 @@
-
- +
+
@@ -1363,7 +1401,8 @@
- + + @@ -1586,7 +1625,7 @@
-
+