From b66e74766f08df7cd54d458557dec3cb96c5a2b7 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Thu, 20 Dec 2018 14:14:37 -0800 Subject: [PATCH] Added password requirements checking. --- common.js | 21 +++++++- meshuser.js | 2 + package.json | 2 +- sample-config.json | 29 +++++------ views/default-min.handlebars | 2 +- views/default-mobile-min.handlebars | 2 +- views/default-mobile.handlebars | 42 +++++++++++++--- views/default.handlebars | 46 +++++++++++++---- views/login-min.handlebars | 2 +- views/login-mobile-min.handlebars | 2 +- views/login-mobile.handlebars | 46 ++++++++++++++--- views/login.handlebars | 46 ++++++++++++++--- views/messenger-min.handlebars | 78 +++++++++++++++++++++-------- webserver.js | 21 +++++--- 14 files changed, 266 insertions(+), 75 deletions(-) diff --git a/common.js b/common.js index 89c9b056..2c24a51e 100644 --- a/common.js +++ b/common.js @@ -152,4 +152,23 @@ module.exports.validateArray = function (array, minlen, maxlen) { return ((array module.exports.validateStrArray = function (array, minlen, maxlen) { if (((array != null) && Array.isArray(array)) == false) return false; for (var i in array) { if ((typeof array[i] != 'string') && ((minlen == null) || (array[i].length >= minlen)) && ((maxlen == null) || (array[i].length <= maxlen))) return false; } return true; }; module.exports.validateObject = function (obj) { return ((obj != null) && (typeof obj == 'object')); }; module.exports.validateEmail = function (email, minlen, maxlen) { if (module.exports.validateString(email, minlen, maxlen) == false) return false; var emailReg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return emailReg.test(email); }; -module.exports.validateUsername = function (username, minlen, maxlen) { return (module.exports.validateString(username, minlen, maxlen) && (username.indexOf(' ') == -1)); }; \ No newline at end of file +module.exports.validateUsername = function (username, minlen, maxlen) { return (module.exports.validateString(username, minlen, maxlen) && (username.indexOf(' ') == -1)); }; + +// Check password requirements +module.exports.checkPasswordRequirements = function(password, requirements) { + if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true; + if (requirements.min) { if (password.length < requirements.min) return false; } + if (requirements.max) { if (password.length > requirements.max) return false; } + var num = 0, lower = 0, upper = 0, nonalpha = 0; + for (var i = 0; i < password.length; i++) { + if (/\d/.test(password[i])) { num++; } + if (/[a-z]/.test(password[i])) { lower++; } + if (/[A-Z]/.test(password[i])) { upper++; } + if (/\W/.test(password[i])) { nonalpha++; } + } + if (requirements.num && (num < requirements.num)) return false; + if (requirements.lower && (lower < requirements.lower)) return false; + if (requirements.upper && (upper < requirements.upper)) return false; + if (requirements.nonalpha && (nonalpha < requirements.nonalpha)) return false; + return true; +} \ No newline at end of file diff --git a/meshuser.js b/meshuser.js index 7c720979..2590c5ee 100644 --- a/meshuser.js +++ b/meshuser.js @@ -517,6 +517,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if ((user.siteadmin & 2) == 0) break; if (obj.common.validateUsername(command.username, 1, 64) == false) break; // Username is between 1 and 64 characters, no spaces if (obj.common.validateString(command.pass, 1, 256) == false) break; // Password is between 1 and 256 characters + if (obj.common.checkPasswordRequirements(command.pass, domain.passwordrequirements) == false) break; // Password does not meet requirements if ((command.email != null) && (obj.common.validateEmail(command.email, 1, 256) == false)) break; // Check if this is a valid email address var newusername = command.username, newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase(); if (newusername == '~') break; // This is a reserved user name @@ -576,6 +577,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (user.siteadmin != 0xFFFFFFFF) break; if (obj.common.validateString(command.user, 1, 256) == false) break; if (obj.common.validateString(command.pass, 1, 256) == false) break; + if (obj.common.checkPasswordRequirements(command.pass, domain.passwordrequirements) == false) break; // Password does not meet requirements var chguserid = 'user/' + domain.id + '/' + command.user.toLowerCase(), chguser = obj.parent.users[chguserid]; if (chguser && chguser.salt) { // Compute the password hash & save it diff --git a/package.json b/package.json index 907961e3..5875434c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.2.4-y", + "version": "0.2.4-z", "keywords": [ "Remote Management", "Intel AMT", diff --git a/sample-config.json b/sample-config.json index 8847ef00..226da6fb 100644 --- a/sample-config.json +++ b/sample-config.json @@ -23,22 +23,23 @@ }, "_domains": { "": { - "title": "MyServer", - "title2": "Servername", - "userQuota": 1048576, - "meshQuota": 248576, - "newAccounts": 1, - "footer": "Twitter", - "_certUrl": "https://192.168.2.106:443/" + "Title": "MyServer", + "Title2": "Servername", + "UserQuota": 1048576, + "MeshQuota": 248576, + "NewAccounts": 1, + "Footer": "Twitter", + "_CertUrl": "https://192.168.2.106:443/", + "_PasswordRequirements": { "min": 8, "max": 128, "upper": 1, "lower": 1, "numeric": 1, "nonalpha": 1 } }, "customer1": { - "dns": "customer1.myserver.com", - "title": "Customer1", - "title2": "TestServer", - "newAccounts": 1, - "auth": "sspi", - "footer": "Test", - "_certUrl": "https://192.168.2.106:443/" + "DNS": "customer1.myserver.com", + "Title": "Customer1", + "Title2": "TestServer", + "NewAccounts": 1, + "Auth": "sspi", + "Footer": "Test", + "_CertUrl": "https://192.168.2.106:443/" }, "info": { "share": "C:\\ExtraWebSite" diff --git a/views/default-min.handlebars b/views/default-min.handlebars index 3eb86de2..2a3ceae0 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - MeshCentral
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file + MeshCentral
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file diff --git a/views/default-mobile-min.handlebars b/views/default-mobile-min.handlebars index 34fc01e0..584e76ac 100644 --- a/views/default-mobile-min.handlebars +++ b/views/default-mobile-min.handlebars @@ -1 +1 @@ - MeshCentral - Login
{{{title}}}
{{{title2}}}
\ No newline at end of file + MeshCentral - Login
{{{title}}}
{{{title2}}}
\ No newline at end of file diff --git a/views/default-mobile.handlebars b/views/default-mobile.handlebars index 0b1eeaef..78ded960 100644 --- a/views/default-mobile.handlebars +++ b/views/default-mobile.handlebars @@ -585,6 +585,8 @@ var attemptWebRTC = ((features & 128) != 0); var StatusStrs = ['Disconnected', 'Connecting...', 'Setup...', 'Connected', 'Intel® AMT Connected']; var files; + var passRequirements = "{{{passRequirements}}}"; + if (passRequirements != "") { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); } function startup() { if ((features & 32) == 0) { @@ -989,15 +991,15 @@ function account_showChangePassword() { if (xxdialogMode) return; var x = "
"; - x += ""; - x += ""; + x += ""; + x += ""; x += ""; x += '
Password:
Password:Password:
Password:
Hint:
'; x += ''; x += ''; x += '

'; setDialogMode(2, "Change Password", 0, null, x); - account_validateDeleteAccount(); + account_validateNewPassword(); Q('apassword1').focus(); } @@ -1024,13 +1026,20 @@ } function account_validateNewPassword() { - QE('account_dlgOkButton', (Q('apassword1').value.length > 0) && (Q('apassword1').value == Q('apassword2').value)); - var r = ''; + var r = '', ok = (Q('apassword1').value.length > 0) && (Q('apassword1').value == Q('apassword2').value); if (Q('apassword1').value != '') { - var passStrength = checkPasswordStrength(Q('apassword1').value); - if (passStrength >= 80) { r = 'Strong'; } else if (passStrength >= 60) { r = 'Good'; } else { r = 'Weak'; } + if (passRequirements == null || passRequirements == '') { + // No password requirements, display password strength + var passStrength = checkPasswordStrength(Q('apassword1').value); + if (passStrength >= 80) { r = ''; } else if (passStrength >= 60) { r = ''; } else { r = ''; } + } else { + // Password requirements provided, use that + var passReq = checkPasswordRequirements(Q('apassword1').value, passRequirements); + if (passReq == false) { ok = false; r = '' } + } } QH('dxPassWarn', r); + QE('account_dlgOkButton', ok); } // Return a password strength score @@ -1042,6 +1051,25 @@ return parseInt(r + (varCount - 1) * 10); } + // Check password requirements + function checkPasswordRequirements(password, requirements) { + if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true; + if (requirements.min) { if (password.length < requirements.min) return false; } + if (requirements.max) { if (password.length > requirements.max) return false; } + var num = 0, lower = 0, upper = 0, nonalpha = 0; + for (var i = 0; i < password.length; i++) { + if (/\d/.test(password[i])) { num++; } + if (/[a-z]/.test(password[i])) { lower++; } + if (/[A-Z]/.test(password[i])) { upper++; } + if (/\W/.test(password[i])) { nonalpha++; } + } + if (requirements.num && (num < requirements.num)) return false; + if (requirements.lower && (lower < requirements.lower)) return false; + if (requirements.upper && (upper < requirements.upper)) return false; + if (requirements.nonalpha && (nonalpha < requirements.nonalpha)) return false; + return true; + } + function updateMeshes() { var r = '', count = 0; for (i in meshes) { diff --git a/views/default.handlebars b/views/default.handlebars index 148dc4e0..0371a07b 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -874,6 +874,8 @@ var webPageFullScreen = getstore('webPageFullScreen', true); if (webPageFullScreen == 'false') { webPageFullScreen = false; } if (webPageFullScreen == 'true') { webPageFullScreen = true; } + var passRequirements = "{{{passRequirements}}}"; + if (passRequirements != "") { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); } function startup() { if ((features & 32) == 0) { @@ -5025,7 +5027,7 @@ function account_validateEmail(e, email) { QE('idx_dlgOkButton', validateEmail(Q('dp2email').value) && (Q('dp2email').value != userinfo.email)); - if ((x == true) && (e != null) && (e.keyCode == 13)) { dialogclose(1); } + if ((e != null) && (e.keyCode == 13)) { dialogclose(1); } } function account_changeEmail() { @@ -5051,16 +5053,16 @@ if (xxdialogMode) return; var x = "Change your account password by entering the new password twice in the boxes below.

"; x += "
"; - x += ""; - x += ""; + x += ""; + x += ""; x += ""; x += '
Password:
Password:Password:
Password:
Password Hint:

'; x += ''; x += ''; x += '

'; setDialogMode(2, "Change Password", 0, null, x); - account_validateDeleteAccount(); Q('apassword1').focus(); + account_validateNewPassword(); } function account_createMesh() { @@ -5087,13 +5089,20 @@ } function account_validateNewPassword() { - QE('account_dlgOkButton', (Q('apassword1').value.length > 0) && (Q('apassword1').value == Q('apassword2').value)); - var r = ''; + var r = '', ok = (Q('apassword1').value.length > 0) && (Q('apassword1').value == Q('apassword2').value); if (Q('apassword1').value != '') { - var passStrength = checkPasswordStrength(Q('apassword1').value); - if (passStrength >= 80) { r = 'Strong'; } else if (passStrength >= 60) { r = 'Good'; } else { r = 'Weak'; } + if (passRequirements == null || passRequirements == '') { + // No password requirements, display password strength + var passStrength = checkPasswordStrength(Q('apassword1').value); + if (passStrength >= 80) { r = 'Strong'; } else if (passStrength >= 60) { r = 'Good'; } else { r = 'Weak'; } + } else { + // Password requirements provided, use that + var passReq = checkPasswordRequirements(Q('apassword1').value, passRequirements); + if (passReq == false) { ok = false; r = 'Policy' } + } } QH('dxPassWarn', r); + QE('account_dlgOkButton', ok); } // Return a password strength score @@ -5105,6 +5114,25 @@ return parseInt(r + (varCount - 1) * 10); } + // Check password requirements + function checkPasswordRequirements(password, requirements) { + if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true; + if (requirements.min) { if (password.length < requirements.min) return false; } + if (requirements.max) { if (password.length > requirements.max) return false; } + var num = 0, lower = 0, upper = 0, nonalpha = 0; + for (var i = 0; i < password.length; i++) { + if (/\d/.test(password[i])) { num++; } + if (/[a-z]/.test(password[i])) { lower++; } + if (/[A-Z]/.test(password[i])) { upper++; } + if (/\W/.test(password[i])) { nonalpha++; } + } + if (requirements.num && (num < requirements.num)) return false; + if (requirements.lower && (lower < requirements.lower)) return false; + if (requirements.upper && (upper < requirements.upper)) return false; + if (requirements.nonalpha && (nonalpha < requirements.nonalpha)) return false; + return true; + } + function updateMeshes() { var r = ''; var c = 0, count = 0; @@ -5826,7 +5854,7 @@ function showCreateNewAccountDialogValidate() { if ((Q('p4email').value.length > 0) && (validateEmail(Q('p4email').value)) == false) { QE('idx_dlgOkButton', false); return; } - QE('idx_dlgOkButton', (!Q('p4name') || ((Q('p4name').value.length > 0) && (Q('p4name').value.indexOf(' ') == -1))) && Q('p4pass1').value.length > 0 && Q('p4pass1').value == Q('p4pass2').value); + QE('idx_dlgOkButton', (!Q('p4name') || ((Q('p4name').value.length > 0) && (Q('p4name').value.indexOf(' ') == -1))) && Q('p4pass1').value.length > 0 && Q('p4pass1').value == Q('p4pass2').value && checkPasswordRequirements(Q('p4pass1').value, passRequirements)); } function showCreateNewAccountDialogEx() { diff --git a/views/login-min.handlebars b/views/login-min.handlebars index 1a0a2ab3..5f3278d3 100644 --- a/views/login-min.handlebars +++ b/views/login-min.handlebars @@ -1 +1 @@ - MeshCentral - Login
{{{title}}}
{{{title2}}}

Welcome

Connect to your home or office devices from anywhere in the world using MeshCentral, the real time, open source remote monitoring and management web site. You will need to download and install a management agent on your computers. Once installed, computers will show up in the "My Devices" section of this web site and you will be able to monitor them and take control of them.


\ No newline at end of file + MeshCentral - Login
{{{title}}}
{{{title2}}}

Welcome

Connect to your home or office devices from anywhere in the world using MeshCentral, the real time, open source remote monitoring and management web site. You will need to download and install a management agent on your computers. Once installed, computers will show up in the "My Devices" section of this web site and you will be able to monitor them and take control of them.


\ No newline at end of file diff --git a/views/login-mobile-min.handlebars b/views/login-mobile-min.handlebars index c13d6e9e..5217cf66 100644 --- a/views/login-mobile-min.handlebars +++ b/views/login-mobile-min.handlebars @@ -1 +1 @@ - MeshCentral - Login
{{{title}}}
{{{title2}}}
\ No newline at end of file + MeshCentral - Login
{{{title}}}
{{{title2}}}
\ No newline at end of file diff --git a/views/login-mobile.handlebars b/views/login-mobile.handlebars index 0abdc1da..4293fb7e 100644 --- a/views/login-mobile.handlebars +++ b/views/login-mobile.handlebars @@ -179,6 +179,8 @@ var newAccountPass = parseInt('{{{newAccountPass}}}'); var emailCheck = ('{{{emailcheck}}}' == 'true'); var features = parseInt('{{{features}}}'); + var passRequirements = "{{{passRequirements}}}"; + if (passRequirements != "") { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); } function startup() { if ((features & 32) == 0) { @@ -233,15 +235,29 @@ setDialogMode(0); var ok = ((Q('ausername').value.length > 0) && (Q('ausername').value.indexOf(' ') == -1) && (validateEmail(Q('aemail').value) == true) && (Q('apassword1').value.length > 0) && (Q('apassword2').value == Q('apassword1').value)); if ((newAccountPass == 1) && (Q('anewaccountpass').value.length == 0)) { ok = false; } - QE('createButton', ok); if (Q('apassword1').value == '') { QH('passWarning', ''); } else { - var passStrength = checkPasswordStrength(Q('apassword1').value); - if (passStrength >= 80) { QH('passWarning', 'Strong Password'); } - else if (passStrength >= 60) { QH('passWarning', 'Good Password'); } - else { QH('passWarning', 'Weak Password'); } + if (passRequirements == null || passRequirements == '') { + // No password requirements, display password strength + var passStrength = checkPasswordStrength(Q('apassword1').value); + if (passStrength >= 80) { QH('passWarning', 'Strong Password'); } + else if (passStrength >= 60) { QH('passWarning', 'Good Password'); } + else { QH('passWarning', 'Weak Password'); } + } else { + // Password requirements provided, use that + var passReq = checkPasswordRequirements(Q('apassword1').value, passRequirements); + if (passReq == false) { + ok = false; + //QS('nuPass1').color = '#7b241c'; + //QS('nuPass2').color = '#7b241c'; + QH('passWarning', 'Password Policy'); // TODO: Display problem hint + } else { + QH('passWarning', ''); + } + } } + QE('createButton', ok); if ((e != null) && (e.keyCode == 13)) { if (box == 1) { Q('aemail').focus(); } if (box == 2) { Q('apassword1').focus(); } @@ -272,7 +288,25 @@ return parseInt(r + (varCount - 1) * 10); } - + // Check password requirements + function checkPasswordRequirements(password, requirements) { + if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true; + if (requirements.min) { if (password.length < requirements.min) return false; } + if (requirements.max) { if (password.length > requirements.max) return false; } + var num = 0, lower = 0, upper = 0, nonalpha = 0; + for (var i = 0; i < password.length; i++) { + if (/\d/.test(password[i])) { num++; } + if (/[a-z]/.test(password[i])) { lower++; } + if (/[A-Z]/.test(password[i])) { upper++; } + if (/\W/.test(password[i])) { nonalpha++; } + } + if (requirements.num && (num < requirements.num)) return false; + if (requirements.lower && (lower < requirements.lower)) return false; + if (requirements.upper && (upper < requirements.upper)) return false; + if (requirements.nonalpha && (nonalpha < requirements.nonalpha)) return false; + return true; + } + // // POPUP DIALOG // diff --git a/views/login.handlebars b/views/login.handlebars index 57be4ab4..9750d48e 100644 --- a/views/login.handlebars +++ b/views/login.handlebars @@ -257,6 +257,8 @@ var passhint = "{{{passhint}}}"; var newAccountPass = parseInt('{{{newAccountPass}}}'); var emailCheck = ('{{{emailcheck}}}' == 'true'); + var passRequirements = "{{{passRequirements}}}"; + if (passRequirements != "") { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); } var features = parseInt('{{{features}}}'); var webPageFullScreen = getstore('webPageFullScreen', true); if (webPageFullScreen == 'false') { webPageFullScreen = false; } @@ -312,7 +314,7 @@ if (e != null) { haltEvent(e); } } - function validateCreate(box,e) { + function validateCreate(box, e) { setDialogMode(0); var userok = (Q('ausername').value.length > 0) && (Q('ausername').value.indexOf(' ') == -1); var emailok = (validateEmail(Q('aemail').value) == true); @@ -328,14 +330,27 @@ QS('nuPass2').color = pass2ok?'black':'#7b241c'; QS('nuToken').color = newAccOk?'black':'#7b241c'; - QE('createButton', ok); if (Q('apassword1').value == '') { QH('passWarning', ''); } else { - var passStrength = checkPasswordStrength(Q('apassword1').value); - if (passStrength >= 80) { QH('passWarning', 'Strong Password'); } - else if (passStrength >= 60) { QH('passWarning', 'Good Password'); } - else { QH('passWarning', 'Weak Password'); } + if (passRequirements == null || passRequirements == '') { + // No password requirements, display password strength + var passStrength = checkPasswordStrength(Q('apassword1').value); + if (passStrength >= 80) { QH('passWarning', 'Strong Password'); } + else if (passStrength >= 60) { QH('passWarning', 'Good Password'); } + else { QH('passWarning', 'Weak Password'); } + } else { + // Password requirements provided, use that + var passReq = checkPasswordRequirements(Q('apassword1').value, passRequirements); + if (passReq == false) { + ok = false; + QS('nuPass1').color = '#7b241c'; + QS('nuPass2').color = '#7b241c'; + QH('passWarning', 'Password Policy'); // TODO: Display problem hint + } else { + QH('passWarning', ''); + } + } } if ((e != null) && (e.keyCode == 13)) { if (box == 1) { Q('aemail').focus(); } @@ -346,6 +361,7 @@ if (box == 6) { Q('createButton').click(); } } if (e != null) { haltEvent(e); } + QE('createButton', ok); } function validateReset(e) { @@ -367,6 +383,24 @@ return parseInt(r + (varCount - 1) * 10); } + // Check password requirements + function checkPasswordRequirements(password, requirements) { + if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true; + if (requirements.min) { if (password.length < requirements.min) return false; } + if (requirements.max) { if (password.length > requirements.max) return false; } + var num = 0, lower = 0, upper = 0, nonalpha = 0; + for (var i = 0; i < password.length; i++) { + if (/\d/.test(password[i])) { num++; } + if (/[a-z]/.test(password[i])) { lower++; } + if (/[A-Z]/.test(password[i])) { upper++; } + if (/\W/.test(password[i])) { nonalpha++; } + } + if (requirements.num && (num < requirements.num)) return false; + if (requirements.lower && (lower < requirements.lower)) return false; + if (requirements.upper && (upper < requirements.upper)) return false; + if (requirements.nonalpha && (nonalpha < requirements.nonalpha)) return false; + return true; + } // // POPUP DIALOG diff --git a/views/messenger-min.handlebars b/views/messenger-min.handlebars index b7d8ae23..b6c9e7a9 100644 --- a/views/messenger-min.handlebars +++ b/views/messenger-min.handlebars @@ -1,4 +1,4 @@ - MeshMessenger
MeshMessenger