From 3394b9ed64eb63d5bd705a5289c2af1acd4fc785 Mon Sep 17 00:00:00 2001 From: Aysel Afsar Date: Wed, 27 Jan 2016 08:24:36 -0500 Subject: [PATCH 01/13] LT-160: Create user accounts and require login for access by using active-entry package --- LesionTracker/activeEntry.js | 2 +- .../lesionTrackerLayout.html | 13 +++++ .../lesionTrackerLayout.js | 13 +++++ .../lesionTrackerLayout.styl | 58 ++++++++++--------- LesionTracker/client/routes.js | 14 ++++- 5 files changed, 71 insertions(+), 29 deletions(-) create mode 100644 LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.js diff --git a/LesionTracker/activeEntry.js b/LesionTracker/activeEntry.js index a1413b8c8..595cd4890 100644 --- a/LesionTracker/activeEntry.js +++ b/LesionTracker/activeEntry.js @@ -12,7 +12,7 @@ if (Meteor.isClient){ destination: '/worklist' }, themeColors: { - primary: 'black' + primary: "" } }); } diff --git a/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.html b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.html index a7116b58c..58b0f4a4c 100644 --- a/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.html +++ b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.html @@ -4,6 +4,19 @@

Open Health Imaging Foundation

+ {{#if currentUser}} +
+ +
+ {{/if}} {{> yield}} \ No newline at end of file diff --git a/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.js b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.js new file mode 100644 index 000000000..cfc269744 --- /dev/null +++ b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.js @@ -0,0 +1,13 @@ +Template.layoutLesionTracker.events({ + 'click #logoutButton': function() { + Meteor.logout(function(){ + Router.go('/entrySignIn'); + }); + } +}); + +Template.layoutLesionTracker.helpers({ + 'fullName': function() { + return Meteor.user().profile.fullName; + } +}); \ No newline at end of file diff --git a/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.styl b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.styl index bf8fd3071..45b6d6535 100644 --- a/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.styl +++ b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.styl @@ -7,32 +7,38 @@ @media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation : portrait) width: 15% -.navbar-brand - display: inline-block - padding: 0 - margin-right: 5px - height: 100% - width: 100% + #userProfile + position: relative + float: right - img - height: 30px - float: left - margin-right: 10px - margin-top: 5px; - /* Media queries for iPhone6*/ - @media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation : portrait) - height: 20px - margin-top: 10px - margin-bottom: 10px - margin-right: 5px - - h4.name - float: left - margin: 10px 0; - font-family: "Sanchez" + .dropdown-toggle color: #C1C1C1 - @media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation : portrait) - font-size: 0.3em - font-size: 0.3em - width: 50%; + + .navbar-brand + display: inline-block + padding: 0 + margin-right: 5px + height: 100% + + img + height: 30px + float: left + margin-right: 10px + margin-top: 5px; + /* Media queries for iPhone6*/ + @media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation : portrait) + height: 20px + margin-top: 10px + margin-bottom: 10px + margin-right: 5px + + h4.name + float: left + margin: 10px 0; + font-family: "Sanchez" + color: #C1C1C1 + @media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation : portrait) + font-size: 0.3em + font-size: 0.3em + width: 50%; diff --git a/LesionTracker/client/routes.js b/LesionTracker/client/routes.js index 85846aea1..eeb88b991 100644 --- a/LesionTracker/client/routes.js +++ b/LesionTracker/client/routes.js @@ -28,11 +28,21 @@ var routerOptions = { }; Router.route('/', function() { - this.render('worklist', routerOptions); + // Check user is logged in + if (!Meteor.user() || !Meteor.userId()) { + this.render('entrySignIn', routerOptions); + } else{ + this.render('worklist', routerOptions); + } }); Router.route('/worklist', function() { - this.render('worklist', routerOptions); + // Check user is logged in + if (!Meteor.user() || !Meteor.userId()) { + this.render('entrySignIn', routerOptions); + } else{ + this.render('worklist', routerOptions); + } }); Router.route('/viewer/:_id', { From 4e38bbfa0ba92cde01c77127a8aa3d172f060ab2 Mon Sep 17 00:00:00 2001 From: Aysel Afsar Date: Wed, 27 Jan 2016 08:25:22 -0500 Subject: [PATCH 02/13] Revert "LT-160: Create user accounts and require login for access by using active-entry package" This reverts commit 3394b9ed64eb63d5bd705a5289c2af1acd4fc785. --- LesionTracker/activeEntry.js | 2 +- .../lesionTrackerLayout.html | 13 ----- .../lesionTrackerLayout.js | 13 ----- .../lesionTrackerLayout.styl | 58 +++++++++---------- LesionTracker/client/routes.js | 14 +---- 5 files changed, 29 insertions(+), 71 deletions(-) delete mode 100644 LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.js diff --git a/LesionTracker/activeEntry.js b/LesionTracker/activeEntry.js index 595cd4890..a1413b8c8 100644 --- a/LesionTracker/activeEntry.js +++ b/LesionTracker/activeEntry.js @@ -12,7 +12,7 @@ if (Meteor.isClient){ destination: '/worklist' }, themeColors: { - primary: "" + primary: 'black' } }); } diff --git a/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.html b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.html index 58b0f4a4c..a7116b58c 100644 --- a/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.html +++ b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.html @@ -4,19 +4,6 @@

Open Health Imaging Foundation

- {{#if currentUser}} -
- -
- {{/if}} {{> yield}} \ No newline at end of file diff --git a/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.js b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.js deleted file mode 100644 index cfc269744..000000000 --- a/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.js +++ /dev/null @@ -1,13 +0,0 @@ -Template.layoutLesionTracker.events({ - 'click #logoutButton': function() { - Meteor.logout(function(){ - Router.go('/entrySignIn'); - }); - } -}); - -Template.layoutLesionTracker.helpers({ - 'fullName': function() { - return Meteor.user().profile.fullName; - } -}); \ No newline at end of file diff --git a/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.styl b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.styl index 45b6d6535..bf8fd3071 100644 --- a/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.styl +++ b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.styl @@ -7,38 +7,32 @@ @media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation : portrait) width: 15% - #userProfile - position: relative - float: right +.navbar-brand + display: inline-block + padding: 0 + margin-right: 5px + height: 100% + width: 100% - .dropdown-toggle + img + height: 30px + float: left + margin-right: 10px + margin-top: 5px; + /* Media queries for iPhone6*/ + @media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation : portrait) + height: 20px + margin-top: 10px + margin-bottom: 10px + margin-right: 5px + + h4.name + float: left + margin: 10px 0; + font-family: "Sanchez" color: #C1C1C1 - - .navbar-brand - display: inline-block - padding: 0 - margin-right: 5px - height: 100% - - img - height: 30px - float: left - margin-right: 10px - margin-top: 5px; - /* Media queries for iPhone6*/ - @media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation : portrait) - height: 20px - margin-top: 10px - margin-bottom: 10px - margin-right: 5px - - h4.name - float: left - margin: 10px 0; - font-family: "Sanchez" - color: #C1C1C1 - @media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation : portrait) - font-size: 0.3em - font-size: 0.3em - width: 50%; + @media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation : portrait) + font-size: 0.3em + font-size: 0.3em + width: 50%; diff --git a/LesionTracker/client/routes.js b/LesionTracker/client/routes.js index eeb88b991..85846aea1 100644 --- a/LesionTracker/client/routes.js +++ b/LesionTracker/client/routes.js @@ -28,21 +28,11 @@ var routerOptions = { }; Router.route('/', function() { - // Check user is logged in - if (!Meteor.user() || !Meteor.userId()) { - this.render('entrySignIn', routerOptions); - } else{ - this.render('worklist', routerOptions); - } + this.render('worklist', routerOptions); }); Router.route('/worklist', function() { - // Check user is logged in - if (!Meteor.user() || !Meteor.userId()) { - this.render('entrySignIn', routerOptions); - } else{ - this.render('worklist', routerOptions); - } + this.render('worklist', routerOptions); }); Router.route('/viewer/:_id', { From 59a6905eb0dcf994a6e5ab67443425720e6d05f5 Mon Sep 17 00:00:00 2001 From: Aysel Afsar Date: Wed, 27 Jan 2016 08:35:06 -0500 Subject: [PATCH 03/13] LT-160: Create user accounts and require login for access by using active-entry package --- LesionTracker/activeEntry.js | 2 +- .../lesionTrackerLayout/lesionTrackerLayout.html | 13 +++++++++++++ .../lesionTrackerLayout/lesionTrackerLayout.js | 13 +++++++++++++ .../lesionTrackerLayout/lesionTrackerLayout.styl | 10 ++++++++-- LesionTracker/client/routes.js | 14 ++++++++++++-- 5 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.js diff --git a/LesionTracker/activeEntry.js b/LesionTracker/activeEntry.js index a1413b8c8..595cd4890 100644 --- a/LesionTracker/activeEntry.js +++ b/LesionTracker/activeEntry.js @@ -12,7 +12,7 @@ if (Meteor.isClient){ destination: '/worklist' }, themeColors: { - primary: 'black' + primary: "" } }); } diff --git a/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.html b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.html index a7116b58c..58b0f4a4c 100644 --- a/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.html +++ b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.html @@ -4,6 +4,19 @@

Open Health Imaging Foundation

+ {{#if currentUser}} +
+ +
+ {{/if}} {{> yield}} \ No newline at end of file diff --git a/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.js b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.js new file mode 100644 index 000000000..05436d7a9 --- /dev/null +++ b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.js @@ -0,0 +1,13 @@ +Template.layoutLesionTracker.events({ + 'click #logoutButton': function() { + Meteor.logout(function(){ + Router.go('/entrySignIn'); + }); + } +}); + +Template.layoutLesionTracker.helpers({ + 'fullName': function() { + return Meteor.user().profile.fullName; + } +}); diff --git a/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.styl b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.styl index bf8fd3071..c21a9d6d6 100644 --- a/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.styl +++ b/LesionTracker/client/components/lesionTrackerLayout/lesionTrackerLayout.styl @@ -7,12 +7,18 @@ @media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation : portrait) width: 15% -.navbar-brand + #userProfile + position: relative + float: right + + .dropdown-toggle + color: #C1C1C1 + + .navbar-brand display: inline-block padding: 0 margin-right: 5px height: 100% - width: 100% img height: 30px diff --git a/LesionTracker/client/routes.js b/LesionTracker/client/routes.js index 85846aea1..eeb88b991 100644 --- a/LesionTracker/client/routes.js +++ b/LesionTracker/client/routes.js @@ -28,11 +28,21 @@ var routerOptions = { }; Router.route('/', function() { - this.render('worklist', routerOptions); + // Check user is logged in + if (!Meteor.user() || !Meteor.userId()) { + this.render('entrySignIn', routerOptions); + } else{ + this.render('worklist', routerOptions); + } }); Router.route('/worklist', function() { - this.render('worklist', routerOptions); + // Check user is logged in + if (!Meteor.user() || !Meteor.userId()) { + this.render('entrySignIn', routerOptions); + } else{ + this.render('worklist', routerOptions); + } }); Router.route('/viewer/:_id', { From d3671a5bdd5d733b3b880ffd1ecfed19224ae04c Mon Sep 17 00:00:00 2001 From: Aysel Afsar Date: Tue, 2 Feb 2016 10:25:02 -0500 Subject: [PATCH 04/13] LT-97: Password validation LT-107: Login session timeout after 30 min --- LesionTracker/.meteor/versions | 18 +- .../components/entrySignIn/entrySignIn.js | 2 +- .../components/entrySignUp/entrySignUp.html | 10 +- .../components/entrySignUp/entrySignUp.js | 33 +- .../components/entrySignUp/entrySignUp.less | 4 + Packages/active-entry/lib/ActiveEntry.js | 52 +- .../lib/jquery.pwstrength.bootstrap.js | 708 ++++++++++++++++++ Packages/active-entry/lib/validatePassword.js | 19 + Packages/active-entry/package.js | 12 +- .../tests/gagarin/activeEntryTests.js | 20 +- .../walkthroughs/activeEntryWalkthrough.js | 22 +- config/localhostOrthanc.json | 6 + config/medicalConnections.json | 6 + config/medkenOrthanc.json | 6 + config/siimDCM4CHEE.json | 6 + 15 files changed, 868 insertions(+), 56 deletions(-) create mode 100644 Packages/active-entry/lib/jquery.pwstrength.bootstrap.js create mode 100644 Packages/active-entry/lib/validatePassword.js 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 From 236ab64addd824cfa29e0abd778517195a514956 Mon Sep 17 00:00:00 2001 From: Aysel Afsar Date: Tue, 2 Feb 2016 15:31:01 -0500 Subject: [PATCH 05/13] - added optional pwstrength and zxcvbn and disabled by default. - Reset ActiveEntry.errorMessages after active-entry buttons are clicked - "Password is invalid" message is replaced by "Password must have at least 8 characters. It must contain at least 1 uppercase, 1 lowercase, 1 number and 1 special character." --- .../components/entrySignIn/entrySignIn.js | 5 +++- .../components/entrySignUp/entrySignUp.js | 19 ++++++++------- .../components/entrySignUp/entrySignUp.less | 3 ++- Packages/active-entry/lib/ActiveEntry.js | 9 ++++++-- Packages/active-entry/lib/validatePassword.js | 23 +++++++++++-------- .../tests/gagarin/activeEntryTests.js | 5 +++- config/localhostOrthanc.json | 4 +++- config/medicalConnections.json | 4 +++- config/medkenOrthanc.json | 4 +++- config/siimDCM4CHEE.json | 4 +++- 10 files changed, 52 insertions(+), 28 deletions(-) diff --git a/Packages/active-entry/components/entrySignIn/entrySignIn.js b/Packages/active-entry/components/entrySignIn/entrySignIn.js index 30e0f9519..9f1b109c4 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 invalid")) { + } else if (ActiveEntry.errorMessages.equals('password', "Password must have at least 8 characters. It must contain at least 1 uppercase, 1 lowercase, 1 number and 1 special character.")) { return "border: 1px solid #f2dede"; } else if (ActiveEntry.errorMessages.equals('password', "Password present")) { return "border: 1px solid green"; @@ -77,10 +77,12 @@ Template.entrySignIn.events({ }, 'click #forgotPasswordButton': function (event) { event.preventDefault(); + ActiveEntry.reset(); Router.go('/forgotPassword'); }, "click #needAnAccountButton": function (event) { event.preventDefault(); + ActiveEntry.reset(); Router.go('/entrySignUp'); }, 'keyup input[name="email"]': function (event, template) { @@ -116,6 +118,7 @@ Template.entrySignIn.events({ // }, 'click #signInToAppButton': function (event, template){ console.log('click #signInToAppButton'); + ActiveEntry.reset(); // var emailValue = template.$('[name=email]').val(); // var passwordValue = template.$('[name=password]').val(); var emailValue = template.$('#signInPageEmailInput').val(); diff --git a/Packages/active-entry/components/entrySignUp/entrySignUp.js b/Packages/active-entry/components/entrySignUp/entrySignUp.js index b9ea40561..4614546b8 100755 --- a/Packages/active-entry/components/entrySignUp/entrySignUp.js +++ b/Packages/active-entry/components/entrySignUp/entrySignUp.js @@ -33,10 +33,7 @@ Template.entrySignUp.helpers({ 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) { + if (key !== "signInError" && ActiveEntry.errorMessages.get(key)) { errorMessages.push(ActiveEntry.errorMessages.get(key)); } }); @@ -64,7 +61,7 @@ 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 invalid")) { + } else if (ActiveEntry.errorMessages.equals('password', "Password must have at least 8 characters. It must contain at least 1 uppercase, 1 lowercase, 1 number and 1 special character.")) { return "border: 1px solid #f2dede"; } else if (ActiveEntry.successMessages.equals('password', "Password present")) { return "border: 1px solid green"; @@ -75,7 +72,7 @@ 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 invalid")) { + } else if (ActiveEntry.errorMessages.equals('confirm', "Passwords do not match")) { return "border: 1px solid #f2dede"; } else if (ActiveEntry.successMessages.equals('confirm', "Passwords match")) { return "border: 1px solid green"; @@ -98,6 +95,7 @@ Template.entrySignUp.helpers({ Template.entrySignUp.events({ "click #signUpPageSignInButton": function (event) { + ActiveEntry.reset(); event.preventDefault(); Router.go('/entrySignIn'); }, @@ -133,9 +131,7 @@ Template.entrySignUp.events({ 'click #signUpPageJoinNowButton': function (event, template) { event.preventDefault(); - ActiveEntry.errorMessages.set('signInError', null); - //ActiveEntry.verifyPassword(event, template); - + ActiveEntry.reset(); var newUser = { fullName: template.$('[name="fullName"]').val(), email: template.$('[name="email"]').val(), @@ -154,5 +150,8 @@ Template.entrySignUp.events({ Template.entrySignUp.onRendered(function() { // Password strength meter for password inputs - this.$(':password').pwstrength(pwstrengthOptions); + console.log(passwordValidationSettings) + if (passwordValidationSettings.usePwstrength) { + this.$('#signUpPagePasswordInput').pwstrength(passwordValidationSettings.pwstrengthOptions); + } }); diff --git a/Packages/active-entry/components/entrySignUp/entrySignUp.less b/Packages/active-entry/components/entrySignUp/entrySignUp.less index 6c35a257e..4295e1b46 100755 --- a/Packages/active-entry/components/entrySignUp/entrySignUp.less +++ b/Packages/active-entry/components/entrySignUp/entrySignUp.less @@ -6,6 +6,7 @@ } } -.progress { +#entrySignUp .progress{ height: 5px; + margin-bottom: 1px } diff --git a/Packages/active-entry/lib/ActiveEntry.js b/Packages/active-entry/lib/ActiveEntry.js index a77cf890f..a013dacd3 100755 --- a/Packages/active-entry/lib/ActiveEntry.js +++ b/Packages/active-entry/lib/ActiveEntry.js @@ -51,7 +51,7 @@ ActiveEntry.verifyPassword = function (password) { ActiveEntry.errorMessages.set('password', 'Password is required'); ActiveEntry.successMessages.set('password', null); } else if (!validatePassword(password)) { - ActiveEntry.errorMessages.set('password', 'Password is invalid'); + ActiveEntry.errorMessages.set('password', 'Password must have at least 8 characters. It must contain at least 1 uppercase, 1 lowercase, 1 number and 1 special character.'); ActiveEntry.successMessages.set('password', null); } else { //ActiveEntry.errorMessages.set('password', 'Password present'); @@ -65,6 +65,9 @@ ActiveEntry.verifyConfirmPassword = function (password, confirmPassword) { //ActiveEntry.errorMessages.set('confirm', 'Passwords match'); ActiveEntry.errorMessages.set('confirm', null); ActiveEntry.successMessages.set('confirm', 'Passwords match'); + } else{ + ActiveEntry.errorMessages.set('confirm', 'Passwords do not match'); + ActiveEntry.successMessages.set('confirm', null); } }; ActiveEntry.verifyEmail = function (email) { @@ -115,6 +118,7 @@ ActiveEntry.signUp = function (emailValue, passwordValue, confirmPassword, fullN ActiveEntry.verifyPassword(passwordValue); ActiveEntry.verifyConfirmPassword(passwordValue, confirmPassword); ActiveEntry.verifyFullName(fullName); + ActiveEntry.errorMessages.set('signInError', null); var errorIsFound = false; Object.keys(ActiveEntry.errorMessages.keys).forEach(function(key) { @@ -124,7 +128,6 @@ ActiveEntry.signUp = function (emailValue, passwordValue, confirmPassword, fullN }); if(errorIsFound) { - // TODO: Show error messages return; } @@ -159,10 +162,12 @@ ActiveEntry.signUp = function (emailValue, passwordValue, confirmPassword, fullN // }); }; ActiveEntry.signOut = function (){ + ActiveEntry.reset(); Meteor.logout(); }; ActiveEntry.reset = function (){ + ActiveEntry.errorMessages.set('signInError', false); ActiveEntry.errorMessages.set('fullName', false); ActiveEntry.errorMessages.set('email', false); ActiveEntry.errorMessages.set('confirm', false); diff --git a/Packages/active-entry/lib/validatePassword.js b/Packages/active-entry/lib/validatePassword.js index a6594a188..674a19ffe 100644 --- a/Packages/active-entry/lib/validatePassword.js +++ b/Packages/active-entry/lib/validatePassword.js @@ -1,13 +1,18 @@ -pwstrengthOptions = { - common: { - minChar: 8, - zxcvbn: true - }, - ui: { - showVerdictsInsideProgressBar: true, - showStatus: true +passwordValidationSettings = {}; + +Meteor.startup(function(){ + passwordValidationSettings.usePwstrength = Meteor.settings.public.usePwstrength; + passwordValidationSettings.pwstrengthOptions = { + common: { + minChar: 8, + zxcvbn: (Meteor.settings.public.useZxcvbn || false) + }, + 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) { diff --git a/Packages/active-entry/tests/gagarin/activeEntryTests.js b/Packages/active-entry/tests/gagarin/activeEntryTests.js index 84ff7e484..8903f5863 100644 --- a/Packages/active-entry/tests/gagarin/activeEntryTests.js +++ b/Packages/active-entry/tests/gagarin/activeEntryTests.js @@ -61,7 +61,7 @@ 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 invalid"); + expect(ActiveEntry.errorMessages.get('password')).to.equal("Password must have at least 8 characters. It must contain at least 1 uppercase, 1 lowercase, 1 number and 1 special character."); ActiveEntry.verifyPassword('K1tt#ns123'); expect(ActiveEntry.successMessages.get('password')).to.equal("Password present"); @@ -71,6 +71,9 @@ describe('clinical:active-entry', function () { // ActiveEntry.verifyConfirmPassword it('Password match validation confirms that two passwords are the same.', function () { return client.execute(function (a) { + ActiveEntry.verifyConfirmPassword('K1tt#kittens', 'kittens'); + expect(ActiveEntry.successMessages.get('confirm')).to.equal("Passwords do not match"); + ActiveEntry.verifyConfirmPassword('K1tt#ns123', 'K1tt#ns123'); expect(ActiveEntry.successMessages.get('confirm')).to.equal("Passwords match"); }); diff --git a/config/localhostOrthanc.json b/config/localhostOrthanc.json index 8535b2975..652c44c30 100644 --- a/config/localhostOrthanc.json +++ b/config/localhostOrthanc.json @@ -22,6 +22,8 @@ "staleSessionInactivityTimeout": 1800000, "staleSessionHeartbeatInterval": 180000, "staleSessionPurgeInterval": 60000, - "staleSessionActivityEvents": "mousemove click keydown" + "staleSessionActivityEvents": "mousemove click keydown", + "usePwstrength": false, + "useZxcvbn": false } } diff --git a/config/medicalConnections.json b/config/medicalConnections.json index fd35535ec..df017489f 100644 --- a/config/medicalConnections.json +++ b/config/medicalConnections.json @@ -20,6 +20,8 @@ "staleSessionInactivityTimeout": 1800000, "staleSessionHeartbeatInterval": 180000, "staleSessionPurgeInterval": 60000, - "staleSessionActivityEvents": "mousemove click keydown" + "staleSessionActivityEvents": "mousemove click keydown", + "usePwstrength": false, + "useZxcvbn": false } } \ No newline at end of file diff --git a/config/medkenOrthanc.json b/config/medkenOrthanc.json index 21996628a..daed86dd6 100644 --- a/config/medkenOrthanc.json +++ b/config/medkenOrthanc.json @@ -21,6 +21,8 @@ "staleSessionInactivityTimeout": 1800000, "staleSessionHeartbeatInterval": 180000, "staleSessionPurgeInterval": 60000, - "staleSessionActivityEvents": "mousemove click keydown" + "staleSessionActivityEvents": "mousemove click keydown", + "usePwstrength": false, + "useZxcvbn": false } } \ No newline at end of file diff --git a/config/siimDCM4CHEE.json b/config/siimDCM4CHEE.json index d8f1253ef..e5d250a16 100644 --- a/config/siimDCM4CHEE.json +++ b/config/siimDCM4CHEE.json @@ -20,6 +20,8 @@ "staleSessionInactivityTimeout": 1800000, "staleSessionHeartbeatInterval": 180000, "staleSessionPurgeInterval": 60000, - "staleSessionActivityEvents": "mousemove click keydown" + "staleSessionActivityEvents": "mousemove click keydown", + "usePwstrength": false, + "useZxcvbn": false } } \ No newline at end of file From 4e986c83955679e8677d5d48cd273f0ba3d9e2a4 Mon Sep 17 00:00:00 2001 From: Aysel Afsar Date: Wed, 3 Feb 2016 14:41:51 -0500 Subject: [PATCH 06/13] Added testing issues for password validation --- .../components/entrySignIn/entrySignIn.js | 4 ++-- .../components/entrySignUp/entrySignUp.js | 22 ++++--------------- Packages/active-entry/lib/ActiveEntry.js | 12 +++++----- .../tests/gagarin/activeEntryTests.js | 10 ++++----- 4 files changed, 16 insertions(+), 32 deletions(-) diff --git a/Packages/active-entry/components/entrySignIn/entrySignIn.js b/Packages/active-entry/components/entrySignIn/entrySignIn.js index 9f1b109c4..2cb922923 100755 --- a/Packages/active-entry/components/entrySignIn/entrySignIn.js +++ b/Packages/active-entry/components/entrySignIn/entrySignIn.js @@ -48,7 +48,7 @@ Template.entrySignIn.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"; @@ -59,7 +59,7 @@ Template.entrySignIn.helpers({ return "border: 1px solid #a94442"; } else if (ActiveEntry.errorMessages.equals('password', "Password must have at least 8 characters. It must contain at least 1 uppercase, 1 lowercase, 1 number and 1 special character.")) { 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"; diff --git a/Packages/active-entry/components/entrySignUp/entrySignUp.js b/Packages/active-entry/components/entrySignUp/entrySignUp.js index 4614546b8..a2c973046 100755 --- a/Packages/active-entry/components/entrySignUp/entrySignUp.js +++ b/Packages/active-entry/components/entrySignUp/entrySignUp.js @@ -29,7 +29,6 @@ Template.entrySignUp.helpers({ return Session.get('defaultSignInMessage'); } }, - entryErrorMessages: function () { var errorMessages = []; Object.keys(ActiveEntry.errorMessages.all()).forEach(function(key) { @@ -39,7 +38,6 @@ Template.entrySignUp.helpers({ }); return errorMessages; }, - getButtonText: function () { if (ActiveEntry.errorMessages.get('signInError')) { return ActiveEntry.errorMessages.get('signInError').message; @@ -127,30 +125,18 @@ Template.entrySignUp.events({ ActiveEntry.verifyFullName(fullName); ActiveEntry.errorMessages.set('signInError', null); }, - // TODO: this is outdated, and should be changed to match the signature/pattern in entrySignIn 'click #signUpPageJoinNowButton': function (event, template) { - event.preventDefault(); - - ActiveEntry.reset(); - var newUser = { - fullName: template.$('[name="fullName"]').val(), - email: template.$('[name="email"]').val(), - password: template.$('[name="password"]').val(), - confirm: template.$('[name="confirm"]').val() - }; - ActiveEntry.signUp( - newUser.email, - newUser.password, - newUser.confirm, - newUser.fullName + $('#signUpPageEmailInput').val(), + $('#signUpPagePasswordInput').val(), + $('#signUpPagePasswordConfirmInput').val(), + $('#signUpPageFullNameInput').val() ); } }); Template.entrySignUp.onRendered(function() { // Password strength meter for password inputs - console.log(passwordValidationSettings) if (passwordValidationSettings.usePwstrength) { this.$('#signUpPagePasswordInput').pwstrength(passwordValidationSettings.pwstrengthOptions); } diff --git a/Packages/active-entry/lib/ActiveEntry.js b/Packages/active-entry/lib/ActiveEntry.js index a013dacd3..567e1006c 100755 --- a/Packages/active-entry/lib/ActiveEntry.js +++ b/Packages/active-entry/lib/ActiveEntry.js @@ -33,19 +33,14 @@ if (Meteor.isClient) { // Success messages ActiveEntry.successMessages = new ReactiveDict('successMessages'); - } - ActiveEntry.configure = function (configObject) { if (Meteor.isClient) { Session.set('Photonic.ActiveEntry', configObject); } }; - - - ActiveEntry.verifyPassword = function (password) { if (password.length === 0) { ActiveEntry.errorMessages.set('password', 'Password is required'); @@ -60,6 +55,7 @@ ActiveEntry.verifyPassword = function (password) { } }; + ActiveEntry.verifyConfirmPassword = function (password, confirmPassword) { if (confirmPassword === password) { //ActiveEntry.errorMessages.set('confirm', 'Passwords match'); @@ -70,6 +66,7 @@ ActiveEntry.verifyConfirmPassword = function (password, confirmPassword) { ActiveEntry.successMessages.set('confirm', null); } }; + ActiveEntry.verifyEmail = function (email) { if (email.length === 0) { ActiveEntry.errorMessages.set('email', 'Email is required'); @@ -83,6 +80,7 @@ ActiveEntry.verifyEmail = function (email) { ActiveEntry.successMessages.set('email', 'Email present'); } }; + ActiveEntry.verifyFullName = function (fullName) { if (fullName.length === 0) { ActiveEntry.errorMessages.set('fullName', 'Name is required'); @@ -162,7 +160,6 @@ ActiveEntry.signUp = function (emailValue, passwordValue, confirmPassword, fullN // }); }; ActiveEntry.signOut = function (){ - ActiveEntry.reset(); Meteor.logout(); }; @@ -173,7 +170,8 @@ ActiveEntry.reset = function (){ ActiveEntry.errorMessages.set('confirm', false); ActiveEntry.errorMessages.set('password', false); }; + ActiveEntry.logoIsDisplayed = function (){ var ActiveEntryConfig = Session.get('Photonic.ActiveEntry'); return ActiveEntryConfig.logo.displayed; -} +}; diff --git a/Packages/active-entry/tests/gagarin/activeEntryTests.js b/Packages/active-entry/tests/gagarin/activeEntryTests.js index 8903f5863..5a514bcc6 100644 --- a/Packages/active-entry/tests/gagarin/activeEntryTests.js +++ b/Packages/active-entry/tests/gagarin/activeEntryTests.js @@ -72,7 +72,7 @@ describe('clinical:active-entry', function () { it('Password match validation confirms that two passwords are the same.', function () { return client.execute(function (a) { ActiveEntry.verifyConfirmPassword('K1tt#kittens', 'kittens'); - expect(ActiveEntry.successMessages.get('confirm')).to.equal("Passwords do not match"); + expect(ActiveEntry.errorMessages.get('confirm')).to.equal("Passwords do not match"); ActiveEntry.verifyConfirmPassword('K1tt#ns123', 'K1tt#ns123'); expect(ActiveEntry.successMessages.get('confirm')).to.equal("Passwords match"); @@ -97,8 +97,8 @@ describe('clinical:active-entry', function () { // ActiveEntry.signIn it('Newly created user record should have role, profile, and name set.', function () { return client.execute(function () { - ActiveEntry.signUp('janedoe@test.org', 'janedoe123', 'janedoe123', 'Jane Doe'); - expect(ActiveEntry.errorMessages.get('fullName')).to.equal("Name present"); + ActiveEntry.signUp('janedoe@test.org', 'Janed*e123', 'Janed*e123', 'Jane Doe'); + expect(ActiveEntry.successMessages.get('fullName')).to.equal("Name present"); }).then(function (){ return server.wait(300, 'until account is created on the server', function () { return Meteor.users.findOne({'emails.address': 'janedoe@test.org'}); @@ -133,7 +133,7 @@ describe('clinical:active-entry', function () { it("Newly created user can sign in to the application.", function () { return client.execute(function () { expect(Meteor.userId()).to.not.exist; - ActiveEntry.signIn('janedoe@test.org', 'janedoe123'); + ActiveEntry.signIn('janedoe@test.org', 'Janed*e123'); }).then(function (){ client.wait(3000, "for user to sign in", function (){ expect(Meteor.userId()).to.exist; @@ -143,7 +143,7 @@ describe('clinical:active-entry', function () { it("Newly created user can sign out of the application.", function () { return client.execute(function () { expect(Meteor.userId()).to.not.exist; - ActiveEntry.signIn('janedoe@test.org', 'janedoe123'); + ActiveEntry.signIn('janedoe@test.org', 'Janed*e123'); }).then(function (){ client.wait(3000, "for user to sign in", function (){ expect(Meteor.userId()).to.exist; From e659f0f4b3aa1485c979c91981fdab95f9b233f7 Mon Sep 17 00:00:00 2001 From: Aysel Afsar Date: Thu, 4 Feb 2016 17:53:01 -0500 Subject: [PATCH 07/13] - Renamed validatePassword method, usePwstrength and useZxcvbn, - Moved passwordOptions from settings.json to ActiveEntry configuration object - Used zxcvbn rule set if showPasswordStrengthIndicator is true, used regular expression rule set if showPasswordStrengthIndicator is false --- LesionTracker/.meteor/versions | 2 +- LesionTracker/activeEntry.js | 4 + Packages/active-entry/README.md | 7 +- .../components/entrySignIn/entrySignIn.js | 15 +++- .../components/entrySignUp/entrySignUp.html | 6 +- .../components/entrySignUp/entrySignUp.js | 40 +++++++-- .../components/entrySignUp/entrySignUp.less | 2 +- Packages/active-entry/lib/ActiveEntry.js | 11 ++- .../active-entry/lib/checkPasswordStrength.js | 46 +++++++++++ Packages/active-entry/lib/validatePassword.js | 24 ------ Packages/active-entry/package.js | 4 +- .../tests/gagarin/activeEntryTests.js | 2 +- .../walkthroughs/activeEntryWalkthrough.js | 82 ++++++++++--------- config/localhostOrthanc.json | 4 +- config/medicalConnections.json | 4 +- config/medkenOrthanc.json | 4 +- config/siimDCM4CHEE.json | 10 +-- 17 files changed, 165 insertions(+), 102 deletions(-) create mode 100644 Packages/active-entry/lib/checkPasswordStrength.js delete mode 100644 Packages/active-entry/lib/validatePassword.js diff --git a/LesionTracker/.meteor/versions b/LesionTracker/.meteor/versions index 785996d0e..375a94406 100644 --- a/LesionTracker/.meteor/versions +++ b/LesionTracker/.meteor/versions @@ -16,7 +16,7 @@ caching-compiler@1.0.0 caching-html-compiler@1.0.2 callback-hook@1.0.4 check@1.1.0 -clinical:active-entry@1.5.14 +clinical:active-entry@1.5.15 clinical:auto-resizing@0.1.2 clinical:error-pages@0.1.1 clinical:extended-api@2.2.2 diff --git a/LesionTracker/activeEntry.js b/LesionTracker/activeEntry.js index 595cd4890..51a196b62 100644 --- a/LesionTracker/activeEntry.js +++ b/LesionTracker/activeEntry.js @@ -13,6 +13,10 @@ if (Meteor.isClient){ }, themeColors: { primary: "" + }, + passwordOptions: { + requireStrongPasswords: true, + showPasswordStrengthIndicator: true } }); } diff --git a/Packages/active-entry/README.md b/Packages/active-entry/README.md index e55460600..5fe15f346 100755 --- a/Packages/active-entry/README.md +++ b/Packages/active-entry/README.md @@ -2,7 +2,7 @@ This package provides the SignIn, SignUp, and ForgotPassword pages. -[![Circle CI](https://circleci.com/gh/clinical-meteor/clinical-active-entry/tree/master.svg?style=svg)](https://circleci.com/gh/clinical-meteor/clinical-active-entry/tree/master) +[![Circle CI](https://circleci.com/gh/clinical-meteor/active-entry/tree/master.svg?style=svg)](https://circleci.com/gh/clinical-meteor/active-entry/tree/master) =============================== #### Installation @@ -16,7 +16,7 @@ meteor add clinical:active-entry The following diagram represents the entry workflow that is being implemented in this package. This package is under active development, and is about half completed. Pull requests which help implement the following workflow will be fast-tracked and accepted into the package. -![entry-workflow](https://raw.githubusercontent.com/clinical-meteor/clinical-active-entry/master/docs/Entry%20Workflow.png) +![entry-workflow](https://raw.githubusercontent.com/clinical-meteor/active-entry/master/docs/Entry%20Workflow.png) @@ -111,6 +111,9 @@ npm install -g starrynight # verification testing (a.k.a. package-level unit/integration testing) starrynight run-tests --type package-verification +#to run validation tests, you'll need an ``.initializeUsers()`` function +meteor add clinical:accounts-housemd + #validation testing (a.k.a. application acceptance/end-to-end testing) starrynight autoscan starrynight run-tests --type validation diff --git a/Packages/active-entry/components/entrySignIn/entrySignIn.js b/Packages/active-entry/components/entrySignIn/entrySignIn.js index 2cb922923..3320925fc 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 must have at least 8 characters. It must contain at least 1 uppercase, 1 lowercase, 1 number and 1 special character.")) { + } else if (ActiveEntry.errorMessages.equals('password', Session.get('passwordWarning'))) { return "border: 1px solid #f2dede"; } else if (ActiveEntry.successMessages.equals('password', "Password present")) { return "border: 1px solid green"; @@ -117,7 +117,6 @@ Template.entrySignIn.events({ // ActiveEntry.signIn(emailValue, passwordValue); // }, 'click #signInToAppButton': function (event, template){ - console.log('click #signInToAppButton'); ActiveEntry.reset(); // var emailValue = template.$('[name=email]').val(); // var passwordValue = template.$('[name=password]').val(); @@ -126,6 +125,18 @@ Template.entrySignIn.events({ ActiveEntry.signIn(emailValue, passwordValue); event.preventDefault(); + }, + 'keypress #entrySignIn': function(event, template) { + if(event.keyCode == 13) { + ActiveEntry.verifyEmail($("#signInPageEmailInput").val()); + ActiveEntry.verifyPassword($("#signInPagePasswordInput").val()); + + if (!ActiveEntry.errorMessages.get('signInError') && + ActiveEntry.successMessages.get('email') && + ActiveEntry.successMessages.get('password')) { + $("#signInToAppButton").click(); + } + } } }); diff --git a/Packages/active-entry/components/entrySignUp/entrySignUp.html b/Packages/active-entry/components/entrySignUp/entrySignUp.html index 9681464a9..b84c44103 100755 --- a/Packages/active-entry/components/entrySignUp/entrySignUp.html +++ b/Packages/active-entry/components/entrySignUp/entrySignUp.html @@ -10,7 +10,7 @@

    Join.

    {{getSignUpMessage}}
    - + {{#if entryErrorMessages}}
    diff --git a/Packages/active-entry/lib/ActiveEntry.js b/Packages/active-entry/lib/ActiveEntry.js index 83710f7d9..064061c70 100755 --- a/Packages/active-entry/lib/ActiveEntry.js +++ b/Packages/active-entry/lib/ActiveEntry.js @@ -28,6 +28,7 @@ if (Meteor.isClient) { requireRegexValidation: true //requireStrongPasswords: false } + }); } @@ -62,7 +63,6 @@ ActiveEntry.verifyPassword = function (password) { ActiveEntry.errorMessages.set('password', null); ActiveEntry.successMessages.set('password', 'Password present'); } - }; ActiveEntry.verifyConfirmPassword = function (password, confirmPassword) { @@ -88,7 +88,6 @@ ActiveEntry.verifyEmail = function (email) { 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', null); ActiveEntry.successMessages.set('email', 'Email present'); } @@ -111,17 +110,43 @@ ActiveEntry.verifyFullName = function (fullName) { ActiveEntry.signIn = function (emailValue, passwordValue){ ActiveEntry.verifyPassword(passwordValue); ActiveEntry.verifyEmail(emailValue); + var ActiveEntryConfig = Session.get('Photonic.ActiveEntry'); + var failedAttemptsLimit = ActiveEntryConfig && ActiveEntryConfig.passwordOptions && ActiveEntryConfig.passwordOptions.failedAttemptsLimit || 5; - Meteor.loginWithPassword({email: emailValue}, passwordValue, function (error, result) { + Meteor.call("getFailedAttemptsCount", emailValue, function(error, failedAttemptsCount) { if (error) { - ActiveEntry.errorMessages.set('signInError', error.message); + console.warn(error.message); } else { - console.log('result', result); - var ActiveEntryConfig = Session.get('Photonic.ActiveEntry'); - console.log('ActiveEntryConfig', JSON.stringify(ActiveEntryConfig)); - Router.go(ActiveEntryConfig.signIn.destination); + if (failedAttemptsCount != failedAttemptsLimit) { + Meteor.loginWithPassword({email: emailValue}, passwordValue, function (error, result) { + if (error) { + // Login failed + Meteor.call("updateFailedAttempts", [emailValue, failedAttemptsLimit], function(error, failedAttemptCount) { + if (error) { + console.warn(error); + } else { + if (failedAttemptCount == failedAttemptsLimit) { + ActiveEntry.errorMessages.set('signInError', "Too many failed login attempts. Your account has been locked."); + + } else { + ActiveEntry.errorMessages.set('signInError', (failedAttemptsLimit - failedAttemptCount) + " attempts remaining."); + + } + } + }); + } else { + console.log('result', result); + Meteor.call("resetFailedAttempts", emailValue); + Router.go(ActiveEntryConfig.signIn.destination); + } + }); + } else { + ActiveEntry.errorMessages.set('signInError', "Your account has been locked."); + } } + }); + }; ActiveEntry.signUp = function (emailValue, passwordValue, confirmPassword, fullName){ @@ -147,12 +172,18 @@ ActiveEntry.signUp = function (emailValue, passwordValue, confirmPassword, fullN password: passwordValue, profile: { fullName: fullName + }, + testCase: { + createdAt: new Date() } }, function (error, result) { if (error) { console.log(error); ActiveEntry.errorMessages.set('signInError', error.message); } else { + // Add password in previous password field + ActiveEntry.insertHashedPassword(passwordValue); + ActiveEntry.updatePasswordCreatedDate(); var ActiveEntryConfig = Session.get('Photonic.ActiveEntry'); Router.go(ActiveEntryConfig.signUp.destination); } @@ -172,6 +203,18 @@ ActiveEntry.signUp = function (emailValue, passwordValue, confirmPassword, fullN // Router.go(ActiveEntryConfig.signIn.destination); // }); }; + +// Insert hashed password in previousPasswords fields +ActiveEntry.insertHashedPassword = function(passwordValue) { + var ActiveEntryConfig = Session.get('Photonic.ActiveEntry'); + var passwordHistoryCount = ActiveEntryConfig && ActiveEntryConfig.passwordOptions && ActiveEntryConfig.passwordOptions.passwordHistoryCount || 6; + Meteor.call("insertHashedPassword", [new String(passwordValue).hashCode(),passwordHistoryCount]); +}; + +ActiveEntry.updatePasswordCreatedDate = function() { + Meteor.call("updatePasswordCreatedDate"); +}; + ActiveEntry.signOut = function (){ Meteor.logout(); }; diff --git a/Packages/active-entry/lib/hashCodeGenerator.js b/Packages/active-entry/lib/hashCodeGenerator.js new file mode 100644 index 000000000..764f46e58 --- /dev/null +++ b/Packages/active-entry/lib/hashCodeGenerator.js @@ -0,0 +1,10 @@ +String.prototype.hashCode = function() { + var hash = 0, i, chr, len; + if (this.length === 0) return hash; + for (i = 0, len = this.length; i < len; i++) { + chr = this.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; // Convert to 32bit integer + } + return hash; +}; \ No newline at end of file diff --git a/Packages/active-entry/package.js b/Packages/active-entry/package.js index a4e5244fb..d8382c70a 100755 --- a/Packages/active-entry/package.js +++ b/Packages/active-entry/package.js @@ -35,7 +35,8 @@ Package.onUse(function (api) { api.addFiles([ 'lib/jquery.pwstrength.bootstrap.js', - 'lib/checkPasswordStrength.js' + 'lib/checkPasswordStrength.js', + 'lib/hashCodeGenerator.js' ], ['client']); api.imply('accounts-base'); diff --git a/Packages/active-entry/server/methods.js b/Packages/active-entry/server/methods.js index 834dfde21..5b6f4c268 100644 --- a/Packages/active-entry/server/methods.js +++ b/Packages/active-entry/server/methods.js @@ -3,10 +3,74 @@ Meteor.methods({ console.log('Initializing Users', Meteor.users.find().fetch()); }, + dropEntryUsers: function (){ console.log('Drop Users', Meteor.users.find().fetch()); Meteor.users.find().forEach(function(user){ Meteor.users.remove({_id: user._id}); }); + }, + + insertHashedPassword: function(passwordParameters) { + var hashedPassword = passwordParameters[0]; + var passwordHistoryCount = passwordParameters[1]; + + var userId = Meteor.userId(); + var previousPasswords = Meteor.users.findOne({_id: userId}).previousPasswords; + if (previousPasswords) { + if (previousPasswords.length == passwordHistoryCount) { + // Remove oldest password + var ascSortedPasswords = _.sortBy(previousPasswords, function(previousPassword){ return previousPassword.createdAt; }); + ascSortedPasswords.splice(0, 1); + previousPasswords = ascSortedPasswords; + } + + previousPasswords.push({hashedPassword: hashedPassword, createdAt: new Date()}); + Meteor.users.update({_id: userId}, {$set: {previousPasswords: previousPasswords}}); + } else { + Meteor.users.update({_id: userId}, {$set: {previousPasswords: [{hashedPassword: hashedPassword, createdAt: new Date(), select: false}]}}); + } + }, + + checkPasswordExistence: function(hashedPassword) { + var previousPasswords = Meteor.users.find({_id: Meteor.userId()}).fetch()[0].previousPasswords; + for(var i=0; i< previousPasswords.length; i++) { + var recordedHashedPassword = previousPasswords[i].hashedPassword; + if (recordedHashedPassword == hashedPassword) { + return true; + } + } + return false; + + }, + + getFailedAttemptsCount: function(emailAddress) { + return Meteor.users.findOne({"emails.address": emailAddress}).failedPasswordAttempts || 0; + }, + + updateFailedAttempts: function(failedAttemptsParameters) { + var emailAddress = failedAttemptsParameters[0]; + var failedAttemptsLimit = failedAttemptsParameters[1]; + var failedAttemptCount = Meteor.users.findOne({"emails.address": emailAddress}).failedPasswordAttempts || 0; + if (failedAttemptCount == failedAttemptsLimit) { + return failedAttemptCount; + } else { + if (failedAttemptCount == 4) { + // Locked user account + Meteor.users.update({"emails.address": emailAddress}, {$set: {"profile.isLocked": true, failedPasswordAttempts: failedAttemptCount + 1}}); + } else if (failedAttemptCount < 4) { + Meteor.users.update({"emails.address": emailAddress}, {$set: {failedPasswordAttempts: failedAttemptCount + 1}}); + } + } + + return failedAttemptCount + 1; + }, + + resetFailedAttempts: function(emailAddress) { + Meteor.users.update({"emails.address": emailAddress}, {$set: {failedPasswordAttempts: 0}}); + }, + + updatePasswordCreatedDate: function() { + Meteor.users.update({_id: Meteor.userId()}, {$set: {"services.password.createdAt": new Date()}}); } }); diff --git a/Packages/meteor-stale-session/client.js b/Packages/meteor-stale-session/client.js index 34594fd7b..bb16b7f00 100644 --- a/Packages/meteor-stale-session/client.js +++ b/Packages/meteor-stale-session/client.js @@ -41,19 +41,13 @@ Meteor.startup(function() { overdueTimestamp = overdueTimestamp - (overdueTimestamp % 1000); console.log(overdueTimestamp); var startTime = inactivityTimeout - dialogTimeout; - if (overdueTimestamp <= inactivityTimeout) { - var nextIntervalTime = overdueTimestamp + countdownHeartbeatInterval; - if (nextIntervalTime <= inactivityTimeout && nextIntervalTime >= startTime) { - if (Math.abs(startTime - overdueTimestamp) <= Math.abs(nextIntervalTime - startTime) && !dialogIsOpen) { - // Open dialog - var leftTime = Math.round((inactivityTimeout - overdueTimestamp) / 1000); - $.event.trigger('TriggerOpenTimeoutCountdownDialog', leftTime); - dialogIsOpen = true; - } - } else { - // Event to close dialog - $.event.trigger('TriggerCloseTimeoutCountdownDialog'); - dialogIsOpen = false; + var nextIntervalTime = overdueTimestamp + countdownHeartbeatInterval; + if (overdueTimestamp <= inactivityTimeout && nextIntervalTime <= inactivityTimeout && nextIntervalTime >= startTime) { + if (Math.abs(startTime - overdueTimestamp) <= Math.abs(nextIntervalTime - startTime) && !dialogIsOpen) { + // Open dialog + var leftTime = Math.round((inactivityTimeout - overdueTimestamp) / 1000); + $.event.trigger('TriggerOpenTimeoutCountdownDialog', leftTime); + dialogIsOpen = true; } } else {