diff --git a/LesionTracker/.meteor/versions b/LesionTracker/.meteor/versions index 3aae6e1e5..785996d0e 100644 --- a/LesionTracker/.meteor/versions +++ b/LesionTracker/.meteor/versions @@ -19,7 +19,7 @@ check@1.1.0 clinical:active-entry@1.5.14 clinical:auto-resizing@0.1.2 clinical:error-pages@0.1.1 -clinical:extended-api@2.2.1 +clinical:extended-api@2.2.2 clinical:fonts@1.0.0 clinical:hipaa-audit-log@2.4.2 clinical:hipaa-logger@1.0.1 @@ -27,7 +27,8 @@ clinical:router@2.0.17 clinical:router-location@2.0.14 clinical:router-middleware-stack@2.0.13 clinical:router-url@2.0.15 -clinical:theming@0.3.1 +clinical:theming@0.4.7 +codetheweb:zxcvbn@4.0.1 coffeescript@1.0.11 cornerstone@0.0.1 ddp@1.2.2 @@ -44,7 +45,7 @@ ejson@1.0.7 email@1.0.8 es5-shim@4.1.14 fastclick@1.0.7 -fortawesome:fontawesome@4.4.0 +fortawesome:fontawesome@4.5.0 geojson-utils@1.0.4 grove:less@0.1.1 hangingprotocols@0.0.1 @@ -52,7 +53,7 @@ hot-code-push@1.0.0 html-tools@1.0.5 htmljs@1.0.5 http@1.1.1 -ian:accounts-ui-bootstrap-3@1.2.83 +ian:accounts-ui-bootstrap-3@1.2.89 id-map@1.0.4 insecure@1.0.4 iron:controller@1.0.12 @@ -72,7 +73,7 @@ minifiers@1.1.7 minimongo@1.0.10 mobile-experience@1.0.1 mobile-status-bar@1.0.6 -momentjs:moment@2.10.6 +momentjs:moment@2.11.1 mongo@1.1.3 mongo-id@1.0.1 mrt:moment@2.8.1 @@ -80,8 +81,8 @@ npm-bcrypt@0.7.8_2 npm-mongo@1.4.39_1 observe-sequence@1.0.7 ordered-dict@1.0.4 -practicalmeteor:chai@1.9.2_3 -practicalmeteor:loglevel@1.1.0_3 +practicalmeteor:chai@2.1.0_1 +practicalmeteor:loglevel@1.2.0_2 promise@0.5.1 random@1.0.5 rate-limit@1.0.0 @@ -102,7 +103,7 @@ stylus@2.511.1 templating@1.1.5 templating-tools@1.0.0 tracker@1.0.9 -twbs:bootstrap@3.3.5 +twbs:bootstrap@3.3.6 ui@1.0.8 underscore@1.0.4 url@1.0.5 @@ -110,3 +111,4 @@ viewerbase@0.0.1 webapp@1.2.3 webapp-hashing@1.0.5 worklist@0.0.1 +zuuk:stale-session@1.0.8 diff --git a/Packages/active-entry/components/entrySignIn/entrySignIn.js b/Packages/active-entry/components/entrySignIn/entrySignIn.js index 523854089..30e0f9519 100755 --- a/Packages/active-entry/components/entrySignIn/entrySignIn.js +++ b/Packages/active-entry/components/entrySignIn/entrySignIn.js @@ -57,7 +57,7 @@ Template.entrySignIn.helpers({ getPasswordValidationStyling: function () { if (ActiveEntry.errorMessages.equals('password', "Password is required")) { return "border: 1px solid #a94442"; - } else if (ActiveEntry.errorMessages.equals('password', "Password is weak")) { + } else if (ActiveEntry.errorMessages.equals('password', "Password is invalid")) { return "border: 1px solid #f2dede"; } else if (ActiveEntry.errorMessages.equals('password', "Password present")) { return "border: 1px solid green"; diff --git a/Packages/active-entry/components/entrySignUp/entrySignUp.html b/Packages/active-entry/components/entrySignUp/entrySignUp.html index b871967ad..9681464a9 100755 --- a/Packages/active-entry/components/entrySignUp/entrySignUp.html +++ b/Packages/active-entry/components/entrySignUp/entrySignUp.html @@ -11,12 +11,20 @@
{{getSignUpMessage}}
- {{#if errorMessages}} + + + {{#if entryErrorMessages}} +
+ {{#each entryErrorMessages}} +
{{this}}
+ {{/each}} +
{{/if}}
diff --git a/Packages/active-entry/components/entrySignUp/entrySignUp.js b/Packages/active-entry/components/entrySignUp/entrySignUp.js index 463a40825..b9ea40561 100755 --- a/Packages/active-entry/components/entrySignUp/entrySignUp.js +++ b/Packages/active-entry/components/entrySignUp/entrySignUp.js @@ -29,6 +29,20 @@ Template.entrySignUp.helpers({ return Session.get('defaultSignInMessage'); } }, + + entryErrorMessages: function () { + var errorMessages = []; + Object.keys(ActiveEntry.errorMessages.all()).forEach(function(key) { + if (key !== "signInError" && ActiveEntry.errorMessages.get(key) && + ActiveEntry.errorMessages.get(key) !== "null" && + ActiveEntry.errorMessages.get(key) !== undefined && + ActiveEntry.errorMessages.get(key) !== null) { + errorMessages.push(ActiveEntry.errorMessages.get(key)); + } + }); + return errorMessages; + }, + getButtonText: function () { if (ActiveEntry.errorMessages.get('signInError')) { return ActiveEntry.errorMessages.get('signInError').message; @@ -41,7 +55,7 @@ Template.entrySignUp.helpers({ return "border: 1px solid #a94442"; } else if (ActiveEntry.errorMessages.equals('email', "Email is poorly formatted")) { return "border: 1px solid #f2dede"; - } else if (ActiveEntry.errorMessages.equals('email', "Email present")) { + } else if (ActiveEntry.successMessages.equals('email', "Email present")) { return "border: 1px solid green"; } else { return "border: 1px solid gray"; @@ -50,9 +64,9 @@ Template.entrySignUp.helpers({ getPasswordStyling: function () { if (ActiveEntry.errorMessages.equals('password', "Password is required")) { return "border: 1px solid #a94442"; - } else if (ActiveEntry.errorMessages.equals('password', "Password is weak")) { + } else if (ActiveEntry.errorMessages.equals('password', "Password is invalid")) { return "border: 1px solid #f2dede"; - } else if (ActiveEntry.errorMessages.equals('password', "Password present")) { + } else if (ActiveEntry.successMessages.equals('password', "Password present")) { return "border: 1px solid green"; } else { return "border: 1px solid gray"; @@ -61,9 +75,9 @@ Template.entrySignUp.helpers({ getConfirmPasswordStyling: function () { if (ActiveEntry.errorMessages.equals('confirm', "Password is required")) { return "border: 1px solid #a94442"; - } else if (ActiveEntry.errorMessages.equals('confirm', "Password is weak")) { + } else if (ActiveEntry.errorMessages.equals('confirm', "Password is invalid")) { return "border: 1px solid #f2dede"; - } else if (ActiveEntry.errorMessages.equals('confirm', "Passwords match")) { + } else if (ActiveEntry.successMessages.equals('confirm', "Passwords match")) { return "border: 1px solid green"; } else { return "border: 1px solid gray"; @@ -74,7 +88,7 @@ Template.entrySignUp.helpers({ return "border: 1px solid #a94442"; } else if (ActiveEntry.errorMessages.equals('fullName', "Name is probably not complete")) { return "border: 1px solid #f2dede"; - } else if (ActiveEntry.errorMessages.equals('fullName', "Name present")) { + } else if (ActiveEntry.successMessages.equals('fullName', "Name present")) { return "border: 1px solid green"; } else { return "border: 1px solid gray"; @@ -120,7 +134,7 @@ Template.entrySignUp.events({ event.preventDefault(); ActiveEntry.errorMessages.set('signInError', null); - ActiveEntry.verifyPassword(event, template); + //ActiveEntry.verifyPassword(event, template); var newUser = { fullName: template.$('[name="fullName"]').val(), @@ -137,3 +151,8 @@ Template.entrySignUp.events({ ); } }); + +Template.entrySignUp.onRendered(function() { + // Password strength meter for password inputs + this.$(':password').pwstrength(pwstrengthOptions); +}); diff --git a/Packages/active-entry/components/entrySignUp/entrySignUp.less b/Packages/active-entry/components/entrySignUp/entrySignUp.less index de57917e4..6c35a257e 100755 --- a/Packages/active-entry/components/entrySignUp/entrySignUp.less +++ b/Packages/active-entry/components/entrySignUp/entrySignUp.less @@ -5,3 +5,7 @@ color: black; } } + +.progress { + height: 5px; +} diff --git a/Packages/active-entry/lib/ActiveEntry.js b/Packages/active-entry/lib/ActiveEntry.js index cc0e897fb..a77cf890f 100755 --- a/Packages/active-entry/lib/ActiveEntry.js +++ b/Packages/active-entry/lib/ActiveEntry.js @@ -31,6 +31,9 @@ if (Meteor.isClient) { ActiveEntry.errorMessages = new ReactiveDict('errorMessages'); ActiveEntry.errorMessages.set('signInError', false); + // Success messages + ActiveEntry.successMessages = new ReactiveDict('successMessages'); + } @@ -46,39 +49,48 @@ ActiveEntry.configure = function (configObject) { ActiveEntry.verifyPassword = function (password) { if (password.length === 0) { ActiveEntry.errorMessages.set('password', 'Password is required'); - } else if (password.length < 8) { - ActiveEntry.errorMessages.set('password', 'Password is weak'); - } else if (password.length >= 8) { - ActiveEntry.errorMessages.set('password', 'Password present'); - } -}; -ActiveEntry.verifyConfirmPassword = function (password, confirmPassword) { - if (confirmPassword.length === 0) { - ActiveEntry.errorMessages.set('confirm', 'Password is required'); - } else if (confirmPassword.length < 8) { - ActiveEntry.errorMessages.set('confirm', 'Password is weak'); + ActiveEntry.successMessages.set('password', null); + } else if (!validatePassword(password)) { + ActiveEntry.errorMessages.set('password', 'Password is invalid'); + ActiveEntry.successMessages.set('password', null); + } else { + //ActiveEntry.errorMessages.set('password', 'Password present'); + ActiveEntry.errorMessages.set('password', null); + ActiveEntry.successMessages.set('password', 'Password present'); } +}; +ActiveEntry.verifyConfirmPassword = function (password, confirmPassword) { if (confirmPassword === password) { - ActiveEntry.errorMessages.set('confirm', 'Passwords match'); + //ActiveEntry.errorMessages.set('confirm', 'Passwords match'); + ActiveEntry.errorMessages.set('confirm', null); + ActiveEntry.successMessages.set('confirm', 'Passwords match'); } }; ActiveEntry.verifyEmail = function (email) { if (email.length === 0) { ActiveEntry.errorMessages.set('email', 'Email is required'); + ActiveEntry.successMessages.set('email', null); } else if (email.indexOf("@") === -1){ ActiveEntry.errorMessages.set('email', 'Email is poorly formatted'); + ActiveEntry.successMessages.set('email', null); } else if (email.indexOf("@") >= 0){ - ActiveEntry.errorMessages.set('email', 'Email present'); + //ActiveEntry.errorMessages.set('email', 'Email present'); + ActiveEntry.errorMessages.set('email', null); + ActiveEntry.successMessages.set('email', 'Email present'); } }; ActiveEntry.verifyFullName = function (fullName) { if (fullName.length === 0) { ActiveEntry.errorMessages.set('fullName', 'Name is required'); + ActiveEntry.successMessages.set('fullName', null); } else if (fullName.indexOf(" ") === -1){ ActiveEntry.errorMessages.set('fullName', 'Name is probably not complete'); + ActiveEntry.successMessages.set('fullName', null); } else if (fullName.indexOf(" ") >= 0){ - ActiveEntry.errorMessages.set('fullName', 'Name present'); + //ActiveEntry.errorMessages.set('fullName', 'Name present'); + ActiveEntry.errorMessages.set('fullName', null); + ActiveEntry.successMessages.set('fullName', 'Name present'); } }; @@ -104,6 +116,18 @@ ActiveEntry.signUp = function (emailValue, passwordValue, confirmPassword, fullN ActiveEntry.verifyConfirmPassword(passwordValue, confirmPassword); ActiveEntry.verifyFullName(fullName); + var errorIsFound = false; + Object.keys(ActiveEntry.errorMessages.keys).forEach(function(key) { + if (ActiveEntry.errorMessages.get(key) !== "null" && ActiveEntry.errorMessages.get(key) !== null) { + errorIsFound = true; + } + }); + + if(errorIsFound) { + // TODO: Show error messages + return; + } + Accounts.createUser({ email: emailValue, password: passwordValue, diff --git a/Packages/active-entry/lib/jquery.pwstrength.bootstrap.js b/Packages/active-entry/lib/jquery.pwstrength.bootstrap.js new file mode 100644 index 000000000..ce466d7fe --- /dev/null +++ b/Packages/active-entry/lib/jquery.pwstrength.bootstrap.js @@ -0,0 +1,708 @@ +/*! + * jQuery Password Strength plugin for Twitter Bootstrap + * + * Copyright (c) 2008-2013 Tane Piper + * Copyright (c) 2013 Alejandro Blanco + * Dual licensed under the MIT and GPL licenses. + */ + +(function (jQuery) { +// Source: src/rules.js + + + + + var rulesEngine = {}; + + try { + if (!jQuery && module && module.exports) { + var jQuery = require("jquery"), + jsdom = require("jsdom").jsdom; + jQuery = jQuery(jsdom().parentWindow); + } + } catch (ignore) {} + + (function ($, rulesEngine) { + "use strict"; + var validation = {}; + + rulesEngine.forbiddenSequences = [ + "0123456789", "abcdefghijklmnopqrstuvwxyz", "qwertyuiop", "asdfghjkl", + "zxcvbnm", "!@#$%^&*()_+" + ]; + + validation.wordNotEmail = function (options, word, score) { + if (word.match(/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i)) { + return score; + } + return 0; + }; + + validation.wordLength = function (options, word, score) { + var wordlen = word.length, + lenScore = Math.pow(wordlen, options.rules.raisePower); + if (wordlen < options.common.minChar) { + lenScore = (lenScore + score); + } + return lenScore; + }; + + validation.wordSimilarToUsername = function (options, word, score) { + var username = $(options.common.usernameField).val(); + if (username && word.toLowerCase().match(username.replace(/[\-\[\]\/\{\}\(\)\*\+\=\?\:\.\\\^\$\|\!\,]/g, "\\$&").toLowerCase())) { + return score; + } + return 0; + }; + + validation.wordTwoCharacterClasses = function (options, word, score) { + if (word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) || + (word.match(/([a-zA-Z])/) && word.match(/([0-9])/)) || + (word.match(/(.[!,@,#,$,%,\^,&,*,?,_,~])/) && word.match(/[a-zA-Z0-9_]/))) { + return score; + } + return 0; + }; + + validation.wordRepetitions = function (options, word, score) { + if (word.match(/(.)\1\1/)) { return score; } + return 0; + }; + + validation.wordSequences = function (options, word, score) { + var found = false, + j; + if (word.length > 2) { + $.each(rulesEngine.forbiddenSequences, function (idx, seq) { + if (found) { return; } + var sequences = [seq, seq.split('').reverse().join('')]; + $.each(sequences, function (idx, sequence) { + for (j = 0; j < (word.length - 2); j += 1) { // iterate the word trough a sliding window of size 3: + if (sequence.indexOf(word.toLowerCase().substring(j, j + 3)) > -1) { + found = true; + } + } + }); + }); + if (found) { return score; } + } + return 0; + }; + + validation.wordLowercase = function (options, word, score) { + return word.match(/[a-z]/) && score; + }; + + validation.wordUppercase = function (options, word, score) { + return word.match(/[A-Z]/) && score; + }; + + validation.wordOneNumber = function (options, word, score) { + return word.match(/\d+/) && score; + }; + + validation.wordThreeNumbers = function (options, word, score) { + return word.match(/(.*[0-9].*[0-9].*[0-9])/) && score; + }; + + validation.wordOneSpecialChar = function (options, word, score) { + return word.match(/[!,@,#,$,%,\^,&,*,?,_,~]/) && score; + }; + + validation.wordTwoSpecialChar = function (options, word, score) { + return word.match(/(.*[!,@,#,$,%,\^,&,*,?,_,~].*[!,@,#,$,%,\^,&,*,?,_,~])/) && score; + }; + + validation.wordUpperLowerCombo = function (options, word, score) { + return word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) && score; + }; + + validation.wordLetterNumberCombo = function (options, word, score) { + return word.match(/([a-zA-Z])/) && word.match(/([0-9])/) && score; + }; + + validation.wordLetterNumberCharCombo = function (options, word, score) { + return word.match(/([a-zA-Z0-9].*[!,@,#,$,%,\^,&,*,?,_,~])|([!,@,#,$,%,\^,&,*,?,_,~].*[a-zA-Z0-9])/) && score; + }; + + rulesEngine.validation = validation; + + rulesEngine.executeRules = function (options, word) { + var totalScore = 0; + + $.each(options.rules.activated, function (rule, active) { + if (active) { + var score = options.rules.scores[rule], + funct = rulesEngine.validation[rule], + result, + errorMessage; + + if (!$.isFunction(funct)) { + funct = options.rules.extra[rule]; + } + + if ($.isFunction(funct)) { + result = funct(options, word, score); + if (result) { + totalScore += result; + } + if (result < 0 || (!$.isNumeric(result) && !result)) { + errorMessage = options.ui.spanError(options, rule); + if (errorMessage.length > 0) { + options.instances.errors.push(errorMessage); + } + } + } + } + }); + + return totalScore; + }; + }(jQuery, rulesEngine)); + + try { + if (module && module.exports) { + module.exports = rulesEngine; + } + } catch (ignore) {} + +// Source: src/options.js + + + + + var defaultOptions = {}; + + defaultOptions.common = {}; + defaultOptions.common.minChar = 6; + defaultOptions.common.usernameField = "#username"; + defaultOptions.common.userInputs = [ + // Selectors for input fields with user input + ]; + defaultOptions.common.onLoad = undefined; + defaultOptions.common.onKeyUp = undefined; + defaultOptions.common.zxcvbn = false; + defaultOptions.common.zxcvbnTerms = [ + // List of disrecommended words + ]; + defaultOptions.common.debug = false; + + defaultOptions.rules = {}; + defaultOptions.rules.extra = {}; + defaultOptions.rules.scores = { + wordNotEmail: -100, + wordLength: -50, + wordSimilarToUsername: -100, + wordSequences: -20, + wordTwoCharacterClasses: 2, + wordRepetitions: -25, + wordLowercase: 1, + wordUppercase: 3, + wordOneNumber: 3, + wordThreeNumbers: 5, + wordOneSpecialChar: 3, + wordTwoSpecialChar: 5, + wordUpperLowerCombo: 2, + wordLetterNumberCombo: 2, + wordLetterNumberCharCombo: 2 + }; + defaultOptions.rules.activated = { + wordNotEmail: true, + wordLength: true, + wordSimilarToUsername: true, + wordSequences: true, + wordTwoCharacterClasses: false, + wordRepetitions: false, + wordLowercase: true, + wordUppercase: true, + wordOneNumber: true, + wordThreeNumbers: true, + wordOneSpecialChar: true, + wordTwoSpecialChar: true, + wordUpperLowerCombo: true, + wordLetterNumberCombo: true, + wordLetterNumberCharCombo: true + }; + defaultOptions.rules.raisePower = 1.4; + + defaultOptions.ui = {}; + defaultOptions.ui.bootstrap2 = false; + defaultOptions.ui.bootstrap4 = false; + defaultOptions.ui.colorClasses = ["danger", "warning", "success"]; + defaultOptions.ui.showProgressBar = true; + defaultOptions.ui.showPopover = false; + defaultOptions.ui.popoverPlacement = "bottom"; + defaultOptions.ui.showStatus = false; + defaultOptions.ui.spanError = function (options, key) { + "use strict"; + var text = options.ui.errorMessages[key]; + if (!text) { return ''; } + return '' + text + ''; + }; + defaultOptions.ui.popoverError = function (errors) { + "use strict"; + var message = "
Errors:
    "; + + jQuery.each(errors, function (idx, err) { + message += "
  • " + err + "
  • "; + }); + message += "
"; + return message; + }; + defaultOptions.ui.errorMessages = { + wordLength: "Your password is too short", + wordNotEmail: "Do not use your email as your password", + wordSimilarToUsername: "Your password cannot contain your username", + wordTwoCharacterClasses: "Use different character classes", + wordRepetitions: "Too many repetitions", + wordSequences: "Your password contains sequences" + }; + defaultOptions.ui.verdicts = ["Weak", "Normal", "Medium", "Strong", "Very Strong"]; + defaultOptions.ui.showVerdicts = true; + defaultOptions.ui.showVerdictsInsideProgressBar = false; + defaultOptions.ui.useVerdictCssClass = false; + defaultOptions.ui.showErrors = false; + defaultOptions.ui.container = undefined; + defaultOptions.ui.viewports = { + progress: undefined, + verdict: undefined, + errors: undefined + }; + defaultOptions.ui.scores = [14, 26, 38, 50]; + +// Source: src/ui.js + + + + + var ui = {}; + + (function ($, ui) { + "use strict"; + + var statusClasses = ["error", "warning", "success"]; + + ui.getContainer = function (options, $el) { + var $container; + + $container = $(options.ui.container); + if (!($container && $container.length === 1)) { + $container = $el.parent(); + } + return $container; + }; + + ui.findElement = function ($container, viewport, cssSelector) { + if (viewport) { + return $container.find(viewport).find(cssSelector); + } + return $container.find(cssSelector); + }; + + ui.getUIElements = function (options, $el) { + var $container, selector, result; + + if (options.instances.viewports) { + return options.instances.viewports; + } + + $container = ui.getContainer(options, $el); + + result = {}; + if (options.ui.bootstrap4) { + selector = "progress.progress"; + } else { + selector = "div.progress"; + } + result.$progressbar = ui.findElement($container, options.ui.viewports.progress, selector); + if (options.ui.showVerdictsInsideProgressBar) { + result.$verdict = result.$progressbar.find("span.password-verdict"); + } + + if (!options.ui.showPopover) { + if (!options.ui.showVerdictsInsideProgressBar) { + result.$verdict = ui.findElement($container, options.ui.viewports.verdict, "span.password-verdict"); + } + result.$errors = ui.findElement($container, options.ui.viewports.errors, "ul.error-list"); + } + + options.instances.viewports = result; + return result; + }; + + ui.initProgressBar = function (options, $el) { + var $container = ui.getContainer(options, $el), + progressbar = "
"; + if (options.ui.bootstrap4) { + // Boostrap 4 + progressbar = ""; + } + if (options.ui.showVerdictsInsideProgressBar) { + progressbar += ""; + } + if (options.ui.bootstrap4) { + progressbar += ""; + } else { + progressbar += "
"; + } + + if (options.ui.viewports.progress) { + $container.find(options.ui.viewports.progress).append(progressbar); + } else { + $(progressbar).insertAfter($el); + } + }; + + ui.initHelper = function (options, $el, html, viewport) { + var $container = ui.getContainer(options, $el); + if (viewport) { + $container.find(viewport).append(html); + } else { + $(html).insertAfter($el); + } + }; + + ui.initVerdict = function (options, $el) { + ui.initHelper(options, $el, "", + options.ui.viewports.verdict); + }; + + ui.initErrorList = function (options, $el) { + ui.initHelper(options, $el, "", + options.ui.viewports.errors); + }; + + ui.initPopover = function (options, $el) { + $el.popover("destroy"); + $el.popover({ + html: true, + placement: options.ui.popoverPlacement, + trigger: "manual", + content: " " + }); + }; + + ui.initUI = function (options, $el) { + if (options.ui.showPopover) { + ui.initPopover(options, $el); + } else { + if (options.ui.showErrors) { ui.initErrorList(options, $el); } + if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) { + ui.initVerdict(options, $el); + } + } + if (options.ui.showProgressBar) { + ui.initProgressBar(options, $el); + } + }; + + ui.updateProgressBar = function (options, $el, cssClass, percentage) { + var $progressbar = ui.getUIElements(options, $el).$progressbar, + $bar = $progressbar.find(".progress-bar"), + cssPrefix = "progress-"; + + if (options.ui.bootstrap2) { + $bar = $progressbar.find(".bar"); + cssPrefix = ""; + } + + $.each(options.ui.colorClasses, function (idx, value) { + if (options.ui.bootstrap4) { + $progressbar.removeClass(cssPrefix + value); + } else { + $bar.removeClass(cssPrefix + "bar-" + value); + } + }); + if (options.ui.bootstrap4) { + $progressbar.addClass(cssPrefix + options.ui.colorClasses[cssClass]); + $progressbar.val(percentage); + } else { + $bar.addClass(cssPrefix + "bar-" + options.ui.colorClasses[cssClass]); + $bar.css("width", percentage + '%'); + } + }; + + ui.updateVerdict = function (options, $el, cssClass, text) { + var $verdict = ui.getUIElements(options, $el).$verdict; + $verdict.removeClass(options.ui.colorClasses.join(' ')); + if (cssClass > -1) { + $verdict.addClass(options.ui.colorClasses[cssClass]); + } + $verdict.html(text); + }; + + ui.updateErrors = function (options, $el) { + var $errors = ui.getUIElements(options, $el).$errors, + html = ""; + $.each(options.instances.errors, function (idx, err) { + html += "
  • " + err + "
  • "; + }); + $errors.html(html); + }; + + ui.updatePopover = function (options, $el, verdictText) { + var popover = $el.data("bs.popover"), + html = "", + hide = true; + + if (options.ui.showVerdicts && + !options.ui.showVerdictsInsideProgressBar && + verdictText.length > 0) { + html = "
    " + verdictText + + "
    "; + hide = false; + } + if (options.ui.showErrors) { + if (options.instances.errors.length > 0) { + hide = false; + } + html += options.ui.popoverError(options.instances.errors); + } + + if (hide) { + $el.popover("hide"); + return; + } + + if (options.ui.bootstrap2) { popover = $el.data("popover"); } + + if (popover.$arrow && popover.$arrow.parents("body").length > 0) { + $el.find("+ .popover .popover-content").html(html); + } else { + // It's hidden + popover.options.content = html; + $el.popover("show"); + } + }; + + ui.updateFieldStatus = function (options, $el, cssClass) { + var targetClass = options.ui.bootstrap2 ? ".control-group" : ".form-group", + $container = $el.parents(targetClass).first(); + + $.each(statusClasses, function (idx, css) { + if (!options.ui.bootstrap2) { css = "has-" + css; } + $container.removeClass(css); + }); + + cssClass = statusClasses[cssClass]; + if (!options.ui.bootstrap2) { cssClass = "has-" + cssClass; } + $container.addClass(cssClass); + }; + + ui.percentage = function (score, maximun) { + var result = Math.floor(100 * score / maximun); + result = result <= 0 ? 1 : result; // Don't show the progress bar empty + result = result > 100 ? 100 : result; + return result; + }; + + ui.getVerdictAndCssClass = function (options, score) { + var cssClass, verdictText, level; + + if (score <= 0) { + cssClass = 0; + level = -1; + verdictText = options.ui.verdicts[0]; + } else if (score < options.ui.scores[0]) { + cssClass = 0; + level = 0; + verdictText = options.ui.verdicts[0]; + } else if (score < options.ui.scores[1]) { + cssClass = 0; + level = 1; + verdictText = options.ui.verdicts[1]; + } else if (score < options.ui.scores[2]) { + cssClass = 1; + level = 2; + verdictText = options.ui.verdicts[2]; + } else if (score < options.ui.scores[3]) { + cssClass = 1; + level = 3; + verdictText = options.ui.verdicts[3]; + } else { + cssClass = 2; + level = 4; + verdictText = options.ui.verdicts[4]; + } + + return [verdictText, cssClass, level]; + }; + + ui.updateUI = function (options, $el, score) { + var cssClass, barPercentage, verdictText, verdictCssClass; + + cssClass = ui.getVerdictAndCssClass(options, score); + verdictText = score === 0 ? '' : cssClass[0]; + cssClass = cssClass[1]; + verdictCssClass = options.ui.useVerdictCssClass ? cssClass : -1; + + if (options.ui.showProgressBar) { + barPercentage = ui.percentage(score, options.ui.scores[3]); + ui.updateProgressBar(options, $el, cssClass, barPercentage); + if (options.ui.showVerdictsInsideProgressBar) { + ui.updateVerdict(options, $el, verdictCssClass, verdictText); + } + } + + if (options.ui.showStatus) { + ui.updateFieldStatus(options, $el, cssClass); + } + + if (options.ui.showPopover) { + ui.updatePopover(options, $el, verdictText); + } else { + if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) { + ui.updateVerdict(options, $el, verdictCssClass, verdictText); + } + if (options.ui.showErrors) { + ui.updateErrors(options, $el); + } + } + }; + }(jQuery, ui)); + +// Source: src/methods.js + + + + + var methods = {}; + + (function ($, methods) { + "use strict"; + var onKeyUp, applyToAll; + + onKeyUp = function (event) { + var $el = $(event.target), + options = $el.data("pwstrength-bootstrap"), + word = $el.val(), + userInputs, + verdictText, + verdictLevel, + score; + + if (options === undefined) { return; } + + options.instances.errors = []; + if (word.length === 0) { + score = 0; + } else { + if (options.common.zxcvbn) { + userInputs = []; + $.each(options.common.userInputs.concat([options.common.usernameField]), function (idx, selector) { + var value = $(selector).val(); + if (value) { userInputs.push(value); } + }); + userInputs = userInputs.concat(options.common.zxcvbnTerms); + score = Math.log2(zxcvbn(word, userInputs).guesses); + } else { + score = rulesEngine.executeRules(options, word); + } + } + ui.updateUI(options, $el, score); + verdictText = ui.getVerdictAndCssClass(options, score); + verdictLevel = verdictText[2]; + verdictText = verdictText[0]; + + if (options.common.debug) { console.log(score + ' - ' + verdictText); } + + if ($.isFunction(options.common.onKeyUp)) { + options.common.onKeyUp(event, { + score: score, + verdictText: verdictText, + verdictLevel: verdictLevel + }); + } + }; + + methods.init = function (settings) { + this.each(function (idx, el) { + // Make it deep extend (first param) so it extends too the + // rules and other inside objects + var clonedDefaults = $.extend(true, {}, defaultOptions), + localOptions = $.extend(true, clonedDefaults, settings), + $el = $(el); + + localOptions.instances = {}; + $el.data("pwstrength-bootstrap", localOptions); + $el.on("keyup", onKeyUp); + $el.on("change", onKeyUp); + $el.on("paste", onKeyUp); + + ui.initUI(localOptions, $el); + if ($.trim($el.val())) { // Not empty, calculate the strength + $el.trigger("keyup"); + } + + if ($.isFunction(localOptions.common.onLoad)) { + localOptions.common.onLoad(); + } + }); + + return this; + }; + + methods.destroy = function () { + this.each(function (idx, el) { + var $el = $(el), + options = $el.data("pwstrength-bootstrap"), + elements = ui.getUIElements(options, $el); + elements.$progressbar.remove(); + elements.$verdict.remove(); + elements.$errors.remove(); + $el.removeData("pwstrength-bootstrap"); + }); + }; + + methods.forceUpdate = function () { + this.each(function (idx, el) { + var event = { target: el }; + onKeyUp(event); + }); + }; + + methods.addRule = function (name, method, score, active) { + this.each(function (idx, el) { + var options = $(el).data("pwstrength-bootstrap"); + + options.rules.activated[name] = active; + options.rules.scores[name] = score; + options.rules.extra[name] = method; + }); + }; + + applyToAll = function (rule, prop, value) { + this.each(function (idx, el) { + $(el).data("pwstrength-bootstrap").rules[prop][rule] = value; + }); + }; + + methods.changeScore = function (rule, score) { + applyToAll.call(this, rule, "scores", score); + }; + + methods.ruleActive = function (rule, active) { + applyToAll.call(this, rule, "activated", active); + }; + + $.fn.pwstrength = function (method) { + var result; + + if (methods[method]) { + result = methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else if (typeof method === "object" || !method) { + result = methods.init.apply(this, arguments); + } else { + $.error("Method " + method + " does not exist on jQuery.pwstrength-bootstrap"); + } + + return result; + }; + }(jQuery, methods)); +}(jQuery)); \ No newline at end of file diff --git a/Packages/active-entry/lib/validatePassword.js b/Packages/active-entry/lib/validatePassword.js new file mode 100644 index 000000000..a6594a188 --- /dev/null +++ b/Packages/active-entry/lib/validatePassword.js @@ -0,0 +1,19 @@ +pwstrengthOptions = { + common: { + minChar: 8, + zxcvbn: true + }, + ui: { + showVerdictsInsideProgressBar: true, + showStatus: true + } +}; + +// Validate Password: at least 8 characters in length and contain at least 1 uppercase, 1 lowercase and 1 number and 1 special character +validatePassword = function(password) { + var result = password.search(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*])[0-9a-zA-Z!@#$%^&*]{8,}$/i); + if (result > -1) { + return true; + } + return false; +}; \ No newline at end of file diff --git a/Packages/active-entry/package.js b/Packages/active-entry/package.js index df73cd9ad..3f1129c79 100755 --- a/Packages/active-entry/package.js +++ b/Packages/active-entry/package.js @@ -17,7 +17,8 @@ Package.onUse(function (api) { 'session', 'reactive-dict', 'accounts-base', - 'accounts-password' + 'accounts-password', + 'codetheweb:zxcvbn' ], ['client']); api.use([ @@ -25,11 +26,20 @@ Package.onUse(function (api) { 'accounts-password' ], ['server']); + api.use([ + 'zuuk:stale-session@1.0.8' + ], ['client', 'server']); + api.addFiles([ 'lib/ActiveEntry.js', 'lib/Accounts.js' ]); + api.addFiles([ + 'lib/jquery.pwstrength.bootstrap.js', + 'lib/validatePassword.js' + ], ['client']); + api.imply('accounts-base'); api.imply('accounts-password'); diff --git a/Packages/active-entry/tests/gagarin/activeEntryTests.js b/Packages/active-entry/tests/gagarin/activeEntryTests.js index f93225997..84ff7e484 100644 --- a/Packages/active-entry/tests/gagarin/activeEntryTests.js +++ b/Packages/active-entry/tests/gagarin/activeEntryTests.js @@ -43,7 +43,7 @@ describe('clinical:active-entry', function () { it('Email validation confirms it is a properly formatted email.', function () { return client.execute(function (a) { ActiveEntry.verifyEmail('janedoe@somewhere.com'); - expect(ActiveEntry.errorMessages.get('email')).to.equal("Email present"); + expect(ActiveEntry.successMessages.get('email')).to.equal("Email present"); ActiveEntry.verifyEmail(''); expect(ActiveEntry.errorMessages.get('email')).to.equal("Email is required"); @@ -61,24 +61,18 @@ describe('clinical:active-entry', function () { expect(ActiveEntry.errorMessages.get('password')).to.equal("Password is required"); ActiveEntry.verifyPassword('kittens'); - expect(ActiveEntry.errorMessages.get('password')).to.equal("Password is weak"); + expect(ActiveEntry.errorMessages.get('password')).to.equal("Password is invalid"); - ActiveEntry.verifyPassword('kittens123'); - expect(ActiveEntry.errorMessages.get('password')).to.equal("Password present"); + ActiveEntry.verifyPassword('K1tt#ns123'); + expect(ActiveEntry.successMessages.get('password')).to.equal("Password present"); }); }); // ActiveEntry.verifyConfirmPassword it('Password match validation confirms that two passwords are the same.', function () { return client.execute(function (a) { - ActiveEntry.verifyConfirmPassword('kittens123', ''); - expect(ActiveEntry.errorMessages.get('confirm')).to.equal("Password is required"); - - ActiveEntry.verifyConfirmPassword('kittens123', 'kittens'); - expect(ActiveEntry.errorMessages.get('confirm')).to.equal("Password is weak"); - - ActiveEntry.verifyConfirmPassword('kittens123', 'kittens123'); - expect(ActiveEntry.errorMessages.get('confirm')).to.equal("Passwords match"); + ActiveEntry.verifyConfirmPassword('K1tt#ns123', 'K1tt#ns123'); + expect(ActiveEntry.successMessages.get('confirm')).to.equal("Passwords match"); }); }); @@ -92,7 +86,7 @@ describe('clinical:active-entry', function () { expect(ActiveEntry.errorMessages.get('fullName')).to.equal("Name is probably not complete"); ActiveEntry.verifyFullName('Jane Doe'); - expect(ActiveEntry.errorMessages.get('fullName')).to.equal("Name present"); + expect(ActiveEntry.successMessages.get('fullName')).to.equal("Name present"); }); }); diff --git a/Packages/active-entry/tests/nightwatch/walkthroughs/activeEntryWalkthrough.js b/Packages/active-entry/tests/nightwatch/walkthroughs/activeEntryWalkthrough.js index b3b6ff046..936a9cbd6 100755 --- a/Packages/active-entry/tests/nightwatch/walkthroughs/activeEntryWalkthrough.js +++ b/Packages/active-entry/tests/nightwatch/walkthroughs/activeEntryWalkthrough.js @@ -45,7 +45,7 @@ module.exports = { "user should be able to request be able to create new account" : function (client) { client.verify.elementPresent("#signUpPageEmailInput") .verify.elementPresent("#signUpPagePasswordInput") - .verify.elementPresent("#signUpPagePasswordInput") + .verify.elementPresent("#signUpPagePasswordConfirmInput") .verify.elementPresent("#signUpPageJoinNowButton") }, @@ -56,14 +56,14 @@ module.exports = { .verify.cssProperty('#signUpPagePasswordInput', 'border', '1px solid gray') .setValue("#signUpPagePasswordInput", "jan") .verify.cssProperty('#signUpPagePasswordInput', 'border', '1px solid rgb(242, 222, 222)') - .setValue("#signUpPagePasswordInput", "icedoe123") + .setValue("#signUpPagePasswordInput", "iceD*e123") .verify.cssProperty('#signUpPagePasswordInput', 'border', '1px solid green') .verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid gray') .setValue("#signUpPagePasswordConfirmInput", "ja") .verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid rgb(242, 222, 222)') .clearValue("#signUpPagePasswordConfirmInput") - .setValue("#signUpPagePasswordConfirmInput", "janicedoe123") + .setValue("#signUpPagePasswordConfirmInput", "Janiced*e123") .verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid green') }, "guest should be notified if passwords do not match" : function (client) { @@ -74,10 +74,10 @@ module.exports = { .pause(500) .verify.cssProperty('#signUpPagePasswordInput', 'border', '1px solid gray') .verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid gray') - .setValue("#signUpPagePasswordInput", "janicedoe123") + .setValue("#signUpPagePasswordInput", "Janiced*e123") .verify.cssProperty('#signUpPagePasswordInput', 'border', '1px solid green') .verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid gray') - .setValue("#signUpPagePasswordConfirmInput", "janicedoe123") + .setValue("#signUpPagePasswordConfirmInput", "Janiced*e123") .verify.cssProperty('#signUpPagePasswordInput', 'border', '1px solid green') .verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid green') }, @@ -104,8 +104,8 @@ module.exports = { .setValue("#signUpPageFullNameInput", "Janice Doe") .setValue("#signUpPageEmailInput", "janicedoe@symptomatic.io") - .setValue("#signUpPagePasswordInput", "janicedoe123") - .setValue("#signUpPagePasswordConfirmInput", "janicedoe123") + .setValue("#signUpPagePasswordInput", "Janiced*e123") + .setValue("#signUpPagePasswordConfirmInput", "Janiced*e123") .click("#signUpPageJoinNowButton").pause(1000) @@ -131,7 +131,7 @@ module.exports = { .url("http://localhost:3000/entrySignIn") .resizeWindow(1600, 1200) .verify.containsText("#usernameLink", "Sign In") - .signIn("janicedoe@symptomatic.io", "janicedoe123").pause(500) + .signIn("janicedoe@symptomatic.io", "Janiced*e123").pause(500) .verify.containsText("#usernameLink", "janicedoe@symptomatic.io") .click("#logoutButton").pause(200) .verify.containsText("#usernameLink", "Sign In") @@ -141,7 +141,7 @@ module.exports = { .url("http://localhost:3000/entrySignIn") .resizeWindow(1024, 768) .verify.containsText("#usernameLink", "Sign In") - .signIn("janicedoe@symptomatic.io", "janicedoe123").pause(500) + .signIn("janicedoe@symptomatic.io", "Janiced*e123").pause(500) .verify.containsText("#usernameLink", "janicedoe@symptomatic.io") .click("#logoutButton").pause(200) .verify.containsText("#usernameLink", "Sign In") @@ -151,7 +151,7 @@ module.exports = { .url("http://localhost:3000/entrySignIn") .resizeWindow(320, 960) // .verify.containsText("#usernameLink", "Sign In") - .signIn("janicedoe@symptomatic.io", "janicedoe123").pause(500) + .signIn("janicedoe@symptomatic.io", "Janiced*e123").pause(500) .click("#sidebarToggle").pause(300) .verify.containsText("#usernameLink", "janicedoe@symptomatic.io") .click("#logoutButton").pause(200) @@ -171,7 +171,7 @@ module.exports = { client .url("http://localhost:3000/entrySignUp") .resizeWindow(1024, 768) - .signUp("janicedoe@symptomatic.io", "janicedoe123").pause(500) + .signUp("janicedoe@symptomatic.io", "Janiced*e123").pause(500) .click("#signUpPageEmailInput").pause(500) .verify.elementPresent("#signUpPageMessage") .verify.containsText("#signUpPageMessage", "Email already exists. [403]") diff --git a/config/localhostOrthanc.json b/config/localhostOrthanc.json index 0097b2f56..8535b2975 100644 --- a/config/localhostOrthanc.json +++ b/config/localhostOrthanc.json @@ -17,5 +17,11 @@ } } ] + }, + "public": { + "staleSessionInactivityTimeout": 1800000, + "staleSessionHeartbeatInterval": 180000, + "staleSessionPurgeInterval": 60000, + "staleSessionActivityEvents": "mousemove click keydown" } } diff --git a/config/medicalConnections.json b/config/medicalConnections.json index faf254197..fd35535ec 100644 --- a/config/medicalConnections.json +++ b/config/medicalConnections.json @@ -15,5 +15,11 @@ } } ] + }, + "public": { + "staleSessionInactivityTimeout": 1800000, + "staleSessionHeartbeatInterval": 180000, + "staleSessionPurgeInterval": 60000, + "staleSessionActivityEvents": "mousemove click keydown" } } \ No newline at end of file diff --git a/config/medkenOrthanc.json b/config/medkenOrthanc.json index 8c142d90b..21996628a 100644 --- a/config/medkenOrthanc.json +++ b/config/medkenOrthanc.json @@ -16,5 +16,11 @@ } } ] + }, + "public": { + "staleSessionInactivityTimeout": 1800000, + "staleSessionHeartbeatInterval": 180000, + "staleSessionPurgeInterval": 60000, + "staleSessionActivityEvents": "mousemove click keydown" } } \ No newline at end of file diff --git a/config/siimDCM4CHEE.json b/config/siimDCM4CHEE.json index 30408e8f8..d8f1253ef 100644 --- a/config/siimDCM4CHEE.json +++ b/config/siimDCM4CHEE.json @@ -15,5 +15,11 @@ } } ] + }, + "public": { + "staleSessionInactivityTimeout": 1800000, + "staleSessionHeartbeatInterval": 180000, + "staleSessionPurgeInterval": 60000, + "staleSessionActivityEvents": "mousemove click keydown" } } \ No newline at end of file