LT-97: - Remove zxcvbn package because of version conflict,

- Add passwordOptions in ActiveEntry.configure
- Toggle showPasswordStrengthIndicator to show password strength meter ui, toggle requireRegexValidation to set password regex for validation or requireStrongPasswords to create strong passwords by using zxcvbn.
- Sign in/ sing up when enter key is pressed
This commit is contained in:
Aysel Afsar 2016-02-11 08:48:11 -05:00
parent 5ec3cace94
commit 24e615da1e
15 changed files with 997 additions and 733 deletions

View File

@ -16,7 +16,7 @@ caching-compiler@1.0.0
caching-html-compiler@1.0.2 caching-html-compiler@1.0.2
callback-hook@1.0.4 callback-hook@1.0.4
check@1.1.0 check@1.1.0
clinical:active-entry@1.5.15 clinical:active-entry@1.5.16
clinical:auto-resizing@0.1.2 clinical:auto-resizing@0.1.2
clinical:error-pages@0.1.1 clinical:error-pages@0.1.1
clinical:extended-api@2.2.2 clinical:extended-api@2.2.2
@ -28,7 +28,6 @@ clinical:router-location@2.0.14
clinical:router-middleware-stack@2.0.13 clinical:router-middleware-stack@2.0.13
clinical:router-url@2.0.15 clinical:router-url@2.0.15
clinical:theming@0.4.7 clinical:theming@0.4.7
codetheweb:zxcvbn@4.0.1
coffeescript@1.0.11 coffeescript@1.0.11
cornerstone@0.0.1 cornerstone@0.0.1
ddp@1.2.2 ddp@1.2.2

View File

@ -15,8 +15,9 @@ if (Meteor.isClient){
primary: "" primary: ""
}, },
passwordOptions: { passwordOptions: {
requireStrongPasswords: true, showPasswordStrengthIndicator: true,
showPasswordStrengthIndicator: true requireRegexValidation: true
//requireStrongPasswords: false
} }
}); });
} }

View File

@ -1,5 +1,10 @@
## Contributing ## Contributing
**Submit your Pull Request on a Feature Branch**
- To ensure your pull-request has the greatest chance of getting merged in, please submit it on a feature branch rather than directly to master.
- Please see [A successful Git branching model](http://nvie.com/posts/a-successful-git-branching-model/) for more details.
**Quality Assurance** **Quality Assurance**
- Pull Requests will be generally accepted as long as the QA tests pass on [Circle CI](https://circleci.com/gh/clinical-meteor/clinical-active-entry). - Pull Requests will be generally accepted as long as the QA tests pass on [Circle CI](https://circleci.com/gh/clinical-meteor/clinical-active-entry).
- Begin a Pull Request by logging an Issue for discussion. - Begin a Pull Request by logging an Issue for discussion.

View File

@ -1,5 +1,7 @@
## Customize the test machine ## Customize the test machine
machine: machine:
node:
version: 0.10.33
# Timezone # Timezone
timezone: timezone:
@ -25,27 +27,48 @@ checkout:
## Customize dependencies ## Customize dependencies
dependencies: dependencies:
cache_directories: cache_directories:
- "~/.meteor" # relative to the user's home directory - ~/.meteor # relative to the user's home directory
- ~/nvm/v0.10.33/lib/node_modules/starrynight
- ~/nvm/v0.10.33/bin/starrynight
pre:
# Install Starrynight unless it is cached
- if [ ! -e ~/nvm/v0.10.33/bin/starrynight ]; then npm install -g starrynight; else echo "Starrynight seems to be cached"; fi;
# Install Meteor
- mkdir -p ${HOME}/.meteor
# If Meteor is already cached, do not need to build it again.
- if [ ! -e ${HOME}/.meteor/meteor ]; then curl https://install.meteor.com | /bin/sh; else echo "Meteor seems to be cached"; fi;
# Link the meteor executable into /usr/bin
- sudo ln -s $HOME/.meteor/meteor /usr/bin/meteor
# Check if the helloworld directory already exists, if it doesn't, create the helloworld app
# The following doesn't work, because it should be checking ${HOME}/active-entry/helloworld
# - if [ ! -e ${HOME}/helloworld ]; then meteor create --release METEOR@1.1.0.3 helloworld; else echo "helloworld app seems to be cached"; fi;
override: override:
- meteor || curl https://install.meteor.com | /bin/sh - cd ${HOME} && meteor create --release METEOR@1.1.0.3 helloworld
- npm install starrynight -g - cd ${HOME}/helloworld
- meteor create --release METEOR@1.1.0.3 helloworld - cd ${HOME}/helloworld && ls -la
- cd helloworld - cd ${HOME}/helloworld && rm helloworld.*
- cd helloworld && ls -la - cd ${HOME}/helloworld && mkdir packages && mkdir packages/active-entry
- cd helloworld && rm helloworld.* - cp -R * ${HOME}/helloworld/packages/active-entry
- cd helloworld && meteor add anti:gagarin@0.4.11 accounts-base accounts-password session meteor-platform clinical:active-entry clinical:user-model - cd ${HOME}/helloworld && meteor add anti:gagarin@0.4.11 accounts-base accounts-password session meteor-platform clinical:user-model clinical:active-entry
- cd helloworld && git clone http://github.com/clinical-meteor/clinical-active-entry packages/active-entry - cd ${HOME}/helloworld && starrynight autoconfig
- cd helloworld && starrynight autoconfig - cd ${HOME}/helloworld && meteor list
- cat tests/gagarin/activeEntryTests.js
- ls -la
- cd ~ && ls -la
- cd ${HOME} && pwd
- cd ${HOME} && ls -la
- cd ${HOME}/helloworld && ls -la
## Customize test commands ## Customize test commands
test: test:
pre: pre:
- cd helloworld && meteor: - cd helloworld && meteor:
background: true background: true
- sleep 80 - sleep 30
override: override:
- cd helloworld && starrynight run-tests --type package-verification - cd ${HOME}/helloworld && starrynight run-tests --type package-verification
## Customize deployment commands ## Customize deployment commands
#deployment: #deployment:

View File

@ -0,0 +1,40 @@
<template name="changePassword">
<div id="changePassword" class="page entryPage" style="{{getOpacityWithCorner}}">
<div class="content-scrollable">
<div class="wrapper-auth">
<div class="entryLogo" style="background-image: url('{{getLogoUrl}}')"></div>
<h1 id="changePasswordPageTitle" class="title-auth">Change Password</h1>
<div id="changePasswordPageMessage" class="subtitle-auth" style="{{getChangePasswordMessageColor}}">{{getChangePasswordMessage}}</div>
<form>
<div class="input-symbol">
<input id="changePasswordPageOldPasswordInput" type="password" name="oldPassword" placeholder="Old Password" style="{{getPasswordStyling}}" />
<span class="fa fa-lock" title="Password"></span>
</div>
<br>
<br>
<div class="input-symbol">
<input id="changePasswordPagePasswordInput" type="password" name="password" placeholder="Password" style="{{getPasswordStyling}}" />
<span class="fa fa-lock" title="Password"></span>
</div>
<br>
<br>
<div class="input-symbol">
<input id="changePasswordPagePasswordConfirmInput" type="password" name="confirm" placeholder="Confirm Password" style="{{getConfirmPasswordStyling}}" />
<span class="fa fa-lock" title="Confirm Password"></span>
</div>
<br><br>
<button id="changePasswordButton" type="submit" class="btn-gray btn-main btn-large" style="{{getButtonColor}}">Change Password</button>
<br><br>
</form>
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,84 @@
//==========================================
Router.route('/changePassword', {
name: "changePassword",
template: "changePassword"
});
Template.changePassword.helpers({
getChangePasswordMessageColor: function (){
if (ActiveEntry.errorMessages.get('changePasswordError')) {
return "color: #a94442; background-color: #f2dede; border-color: #ebccd1;"
} else {
return "color: black;"
}
},
getChangePasswordMessage: function (){
if (ActiveEntry.errorMessages.get('changePasswordError')) {
return ActiveEntry.errorMessages.get('changePasswordError');
} else {
return Session.get('defaultSignInMessage');
}
},
getPasswordStyling: function () {
if (ActiveEntry.errorMessages.equals('password', "Password is required")) {
return "border: 1px solid #a94442";
} else if (ActiveEntry.errorMessages.equals('password', "Password is weak")) {
return "border: 1px solid #f2dede";
} else if (ActiveEntry.errorMessages.equals('password', "Password present")) {
return "border: 1px solid green";
} else {
return "border: 1px solid gray";
}
},
getConfirmPasswordStyling: function () {
if (ActiveEntry.errorMessages.equals('confirm', "Password is required")) {
return "border: 1px solid #a94442";
} else if (ActiveEntry.errorMessages.equals('confirm', "Passwords do not match")) {
return "border: 1px solid #a94442";
} else if (ActiveEntry.errorMessages.equals('confirm', "Password is weak")) {
return "border: 1px solid #f2dede";
} else if (ActiveEntry.errorMessages.equals('confirm', "Passwords match")) {
return "border: 1px solid green";
} else {
return "border: 1px solid gray";
}
}
});
Template.changePassword.events({
'change, keyup #changePasswordPagePasswordInput': function (event, template) {
var password = $('[name="password"]').val();
var confirmPassword = $('[name="confirm"]').val();
ActiveEntry.verifyPassword(password);
ActiveEntry.errorMessages.set('changePasswordError', null);
},
'change, keyup #changePasswordPagePasswordConfirmInput': function (event, template) {
var password = $('[name="password"]').val();
var confirmPassword = $('[name="confirm"]').val();
ActiveEntry.verifyConfirmPassword(password, confirmPassword);
ActiveEntry.errorMessages.set('changePasswordError', null);
},
"submit": function (event, template) {
event.preventDefault();
var oldPassword = $('[name="oldPassword"]').val();
var password = $('[name="password"]').val();
var confirmPassword = $('[name="confirm"]').val();
ActiveEntry.verifyConfirmPassword(password, confirmPassword);
ActiveEntry.errorMessages.set('changePasswordError', null);
Accounts.changePassword(oldPassword, confirmPassword, function(error) {
if (error) {
console.warn(error);
return;
}
console.log('Password changed!');
});
}
});

View File

@ -0,0 +1,5 @@
#changePassword{
input{
padding-left: 40px;
}
}

View File

@ -126,14 +126,13 @@ Template.entrySignIn.events({
ActiveEntry.signIn(emailValue, passwordValue); ActiveEntry.signIn(emailValue, passwordValue);
event.preventDefault(); event.preventDefault();
}, },
'keypress #entrySignIn': function(event, template) { 'keyup #entrySignIn': function(event, template) {
if(event.keyCode == 13) { if(event.keyCode == 13) {
ActiveEntry.verifyEmail($("#signInPageEmailInput").val()); ActiveEntry.verifyEmail($("#signInPageEmailInput").val());
ActiveEntry.verifyPassword($("#signInPagePasswordInput").val());
if (!ActiveEntry.errorMessages.get('signInError') && if (!ActiveEntry.errorMessages.get('signInError') &&
ActiveEntry.successMessages.get('email') && ActiveEntry.successMessages.get('email') &&
ActiveEntry.successMessages.get('password')) { $("#signInPagePasswordInput").val()) {
$("#signInToAppButton").click(); $("#signInToAppButton").click();
} }
} }

View File

@ -71,6 +71,8 @@ Template.entrySignUp.helpers({
if (ActiveEntry.errorMessages.equals('confirm', "Password is required")) { if (ActiveEntry.errorMessages.equals('confirm', "Password is required")) {
return "border: 1px solid #a94442"; return "border: 1px solid #a94442";
} else if (ActiveEntry.errorMessages.equals('confirm', "Passwords do not match")) { } else if (ActiveEntry.errorMessages.equals('confirm', "Passwords do not match")) {
return "border: 1px solid #a94442";
} else if (ActiveEntry.errorMessages.equals('confirm', "Password is weak")) {
return "border: 1px solid #f2dede"; return "border: 1px solid #f2dede";
} else if (ActiveEntry.successMessages.equals('confirm', "Passwords match")) { } else if (ActiveEntry.successMessages.equals('confirm', "Passwords match")) {
return "border: 1px solid green"; return "border: 1px solid green";
@ -133,7 +135,7 @@ Template.entrySignUp.events({
$('#signUpPageFullNameInput').val() $('#signUpPageFullNameInput').val()
); );
}, },
'keypress #entrySignUp': function(event, template) { 'keyup #entrySignUp': function(event, template) {
if(event.keyCode == 13) { if(event.keyCode == 13) {
ActiveEntry.verifyFullName($("#signUpPageFullNameInput").val()); ActiveEntry.verifyFullName($("#signUpPageFullNameInput").val());
ActiveEntry.verifyEmail($("#signUpPageEmailInput").val()); ActiveEntry.verifyEmail($("#signUpPageEmailInput").val());
@ -153,13 +155,12 @@ Template.entrySignUp.events({
Template.entrySignUp.onRendered(function() { Template.entrySignUp.onRendered(function() {
// Password strength meter for password inputs // Password strength meter for password inputs
if (passwordValidationSettings.requireStrongPasswords) { if (passwordValidationSettings.showPasswordStrengthIndicator) {
this.$('#signUpPagePasswordInput').pwstrength(passwordValidationSettings.pwstrengthOptions); this.$('#signUpPagePasswordInput').pwstrength(passwordValidationSettings.pwstrengthOptions);
} }
// Update password warning message if zxcvbn is active // Update password warning message if zxcvbn is active and zxcvbn function is defined
if(passwordValidationSettings.showPasswordStrengthIndicator) { if(passwordValidationSettings.requireStrongPasswords) {
Session.set('passwordWarning', 'Password is weak'); Session.set('passwordWarning', 'Password is weak');
} }
}); });

View File

@ -24,12 +24,15 @@ if (Meteor.isClient) {
primary: "" primary: ""
}, },
passwordOptions: { passwordOptions: {
requireStrongPasswords: false, showPasswordStrengthIndicator: true,
showPasswordStrengthIndicator: false requireRegexValidation: true
//requireStrongPasswords: false
} }
}); });
} }
// requireRegexValidation toggles regex
// reqiureStrongPasswords toggles zxcvbn
if (Meteor.isClient) { if (Meteor.isClient) {
ActiveEntry.errorMessages = new ReactiveDict('errorMessages'); ActiveEntry.errorMessages = new ReactiveDict('errorMessages');
@ -56,7 +59,6 @@ ActiveEntry.verifyPassword = function (password) {
ActiveEntry.errorMessages.set('password', Session.get('passwordWarning')); ActiveEntry.errorMessages.set('password', Session.get('passwordWarning'));
ActiveEntry.successMessages.set('password', null); ActiveEntry.successMessages.set('password', null);
} else { } else {
//ActiveEntry.errorMessages.set('password', 'Password present');
ActiveEntry.errorMessages.set('password', null); ActiveEntry.errorMessages.set('password', null);
ActiveEntry.successMessages.set('password', 'Password present'); ActiveEntry.successMessages.set('password', 'Password present');
} }
@ -64,8 +66,12 @@ ActiveEntry.verifyPassword = function (password) {
}; };
ActiveEntry.verifyConfirmPassword = function (password, confirmPassword) { ActiveEntry.verifyConfirmPassword = function (password, confirmPassword) {
if (confirmPassword === password) { // we have two different logic checks happening in this function
//ActiveEntry.errorMessages.set('confirm', 'Passwords match'); // would be reasonable to separate them out into separate functions
if (confirmPassword === "") {
ActiveEntry.errorMessages.set('confirm', 'Password is required');
ActiveEntry.successMessages.set('confirm', null);
} else if (confirmPassword === password) {
ActiveEntry.errorMessages.set('confirm', null); ActiveEntry.errorMessages.set('confirm', null);
ActiveEntry.successMessages.set('confirm', 'Passwords match'); ActiveEntry.successMessages.set('confirm', 'Passwords match');
} else { } else {

View File

@ -1,46 +1,59 @@
passwordValidationSettings = {}; passwordValidationSettings = {};
var ActiveEntryConfiguration;
Meteor.startup(function() {
ActiveEntryConfiguration = Session.get('Photonic.ActiveEntry');
var showPasswordStrengthIndicator = (ActiveEntryConfiguration && ActiveEntryConfiguration.passwordOptions && ActiveEntryConfiguration.passwordOptions.showPasswordStrengthIndicator || false);
passwordValidationSettings.requireStrongPasswords = (ActiveEntryConfiguration && ActiveEntryConfiguration.passwordOptions && ActiveEntryConfiguration.passwordOptions.requireStrongPasswords || false);
passwordValidationSettings.showPasswordStrengthIndicator = showPasswordStrengthIndicator;
passwordValidationSettings.pwstrengthOptions = { function getPasswordValidationSettings () {
var ActiveEntryConfiguration = Session.get('Photonic.ActiveEntry');
var validationSettings = {};
validationSettings.showPasswordStrengthIndicator = ActiveEntryConfiguration && ActiveEntryConfiguration.passwordOptions && ActiveEntryConfiguration.passwordOptions.showPasswordStrengthIndicator || false;
validationSettings.requireRegexValidation = ActiveEntryConfiguration && ActiveEntryConfiguration.passwordOptions && ActiveEntryConfiguration.passwordOptions.requireRegexValidation || false;
if (validationSettings.showPasswordStrengthIndicator) {
// Set password strength meter options
validationSettings.pwstrengthOptions = {
common: { common: {
minChar: 8, minChar: 8
zxcvbn: showPasswordStrengthIndicator
}, },
ui: { ui: {
showVerdictsInsideProgressBar: true, showVerdictsInsideProgressBar: true,
showStatus: true showStatus: true
},
rules: {
activated: {
wordNotEmail: true,
wordTwoCharacterClasses: true,
wordRepetitions: true
}
} }
}; };
}
// Check if codetheweb:zxcvbn is defined
if (typeof(zxcvbn) === typeof(Function)) {
validationSettings.requireStrongPasswords = ActiveEntryConfiguration && ActiveEntryConfiguration.passwordOptions && ActiveEntryConfiguration.passwordOptions.requireStrongPasswords || false;
// Set zxcvbn in pw strength meter
if (validationSettings.showPasswordStrengthIndicator) {
validationSettings.pwstrengthOptions.common.zxcvbn = passwordValidationSettings.requireStrongPasswords;
}
}
return validationSettings;
}
Meteor.startup(function() {
passwordValidationSettings = getPasswordValidationSettings();
}); });
// Check Password Strength: at least 8 characters in length and contain at least 1 uppercase, 1 lowercase and 1 number and 1 special character
checkPasswordStrength = function(password) { checkPasswordStrength = function(password) {
var iszxcvbnActive = (ActiveEntryConfiguration && ActiveEntryConfiguration.passwordOptions && ActiveEntryConfiguration.passwordOptions.showPasswordStrengthIndicator || false); if (passwordValidationSettings.requireStrongPasswords) {
if (iszxcvbnActive) { // Check zxcvbn
// Check zxcvbn rule
var zxcvbnResult = zxcvbn(password); var zxcvbnResult = zxcvbn(password);
if (zxcvbnResult.score > 2) { if (zxcvbnResult && zxcvbnResult.score > 2) {
return true; return true;
} }
} else{
return false;
} else if (passwordValidationSettings.requireRegexValidation) {
// Apply validation rule // Apply validation rule
var result = password.search(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*])[0-9a-zA-Z!@#$%^&*]{8,}$/i); var result = password.search(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*])[0-9a-zA-Z!@#$%^&*]{8,}$/i);
if (result > -1) { if (result > -1) {
return true; return true;
} }
}
return false; return false;
}
return true;
}; };

View File

@ -11,7 +11,6 @@
var rulesEngine = {}; var rulesEngine = {};
try { try {
@ -32,7 +31,9 @@
]; ];
validation.wordNotEmail = function (options, word, score) { 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)) { 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 score;
} }
return 0; return 0;
@ -49,7 +50,8 @@
validation.wordSimilarToUsername = function (options, word, score) { validation.wordSimilarToUsername = function (options, word, score) {
var username = $(options.common.usernameField).val(); var username = $(options.common.usernameField).val();
if (username && word.toLowerCase().match(username.replace(/[\-\[\]\/\{\}\(\)\*\+\=\?\:\.\\\^\$\|\!\,]/g, "\\$&").toLowerCase())) { if (username && word.toLowerCase().match(username.replace(
/[\-\[\]\/\{\}\(\)\*\+\=\?\:\.\\\^\$\|\!\,]/g, "\\$&").toLowerCase())) {
return score; return score;
} }
return 0; return 0;
@ -65,7 +67,9 @@
}; };
validation.wordRepetitions = function (options, word, score) { validation.wordRepetitions = function (options, word, score) {
if (word.match(/(.)\1\1/)) { return score; } if (word.match(/(.)\1\1/)) {
return score;
}
return 0; return 0;
}; };
@ -74,7 +78,9 @@
j; j;
if (word.length > 2) { if (word.length > 2) {
$.each(rulesEngine.forbiddenSequences, function (idx, seq) { $.each(rulesEngine.forbiddenSequences, function (idx, seq) {
if (found) { return; } if (found) {
return;
}
var sequences = [seq, seq.split('').reverse().join('')]; var sequences = [seq, seq.split('').reverse().join('')];
$.each(sequences, function (idx, sequence) { $.each(sequences, function (idx, sequence) {
for (j = 0; j < (word.length - 2); j += 1) { // iterate the word trough a sliding window of size 3: for (j = 0; j < (word.length - 2); j += 1) { // iterate the word trough a sliding window of size 3:
@ -84,7 +90,9 @@
} }
}); });
}); });
if (found) { return score; } if (found) {
return score;
}
} }
return 0; return 0;
}; };
@ -122,7 +130,9 @@
}; };
validation.wordLetterNumberCharCombo = function (options, word, score) { validation.wordLetterNumberCharCombo = function (options, word, score) {
return word.match(/([a-zA-Z0-9].*[!,@,#,$,%,\^,&,*,?,_,~])|([!,@,#,$,%,\^,&,*,?,_,~].*[a-zA-Z0-9])/) && score; return word.match(
/([a-zA-Z0-9].*[!,@,#,$,%,\^,&,*,?,_,~])|([!,@,#,$,%,\^,&,*,?,_,~].*[a-zA-Z0-9])/) &&
score;
}; };
rulesEngine.validation = validation; rulesEngine.validation = validation;
@ -170,7 +180,6 @@
var defaultOptions = {}; var defaultOptions = {};
defaultOptions.common = {}; defaultOptions.common = {};
@ -236,7 +245,9 @@
defaultOptions.ui.spanError = function (options, key) { defaultOptions.ui.spanError = function (options, key) {
"use strict"; "use strict";
var text = options.ui.errorMessages[key]; var text = options.ui.errorMessages[key];
if (!text) { return ''; } if (!text) {
return '';
}
return '<span style="color: #d52929">' + text + '</span>'; return '<span style="color: #d52929">' + text + '</span>';
}; };
defaultOptions.ui.popoverError = function (errors) { defaultOptions.ui.popoverError = function (errors) {
@ -274,7 +285,6 @@
var ui = {}; var ui = {};
(function ($, ui) { (function ($, ui) {
@ -314,16 +324,19 @@
} else { } else {
selector = "div.progress"; selector = "div.progress";
} }
result.$progressbar = ui.findElement($container, options.ui.viewports.progress, selector); result.$progressbar = ui.findElement($container, options.ui.viewports.progress,
selector);
if (options.ui.showVerdictsInsideProgressBar) { if (options.ui.showVerdictsInsideProgressBar) {
result.$verdict = result.$progressbar.find("span.password-verdict"); result.$verdict = result.$progressbar.find("span.password-verdict");
} }
if (!options.ui.showPopover) { if (!options.ui.showPopover) {
if (!options.ui.showVerdictsInsideProgressBar) { if (!options.ui.showVerdictsInsideProgressBar) {
result.$verdict = ui.findElement($container, options.ui.viewports.verdict, "span.password-verdict"); result.$verdict = ui.findElement($container, options.ui.viewports.verdict,
"span.password-verdict");
} }
result.$errors = ui.findElement($container, options.ui.viewports.errors, "ul.error-list"); result.$errors = ui.findElement($container, options.ui.viewports.errors,
"ul.error-list");
} }
options.instances.viewports = result; options.instances.viewports = result;
@ -392,7 +405,9 @@
if (options.ui.showPopover) { if (options.ui.showPopover) {
ui.initPopover(options, $el); ui.initPopover(options, $el);
} else { } else {
if (options.ui.showErrors) { ui.initErrorList(options, $el); } if (options.ui.showErrors) {
ui.initErrorList(options, $el);
}
if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) { if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) {
ui.initVerdict(options, $el); ui.initVerdict(options, $el);
} }
@ -470,7 +485,9 @@
return; return;
} }
if (options.ui.bootstrap2) { popover = $el.data("popover"); } if (options.ui.bootstrap2) {
popover = $el.data("popover");
}
if (popover.$arrow && popover.$arrow.parents("body").length > 0) { if (popover.$arrow && popover.$arrow.parents("body").length > 0) {
$el.find("+ .popover .popover-content").html(html); $el.find("+ .popover .popover-content").html(html);
@ -486,12 +503,16 @@
$container = $el.parents(targetClass).first(); $container = $el.parents(targetClass).first();
$.each(statusClasses, function (idx, css) { $.each(statusClasses, function (idx, css) {
if (!options.ui.bootstrap2) { css = "has-" + css; } if (!options.ui.bootstrap2) {
css = "has-" + css;
}
$container.removeClass(css); $container.removeClass(css);
}); });
cssClass = statusClasses[cssClass]; cssClass = statusClasses[cssClass];
if (!options.ui.bootstrap2) { cssClass = "has-" + cssClass; } if (!options.ui.bootstrap2) {
cssClass = "has-" + cssClass;
}
$container.addClass(cssClass); $container.addClass(cssClass);
}; };
@ -571,7 +592,6 @@
var methods = {}; var methods = {};
(function ($, methods) { (function ($, methods) {
@ -587,7 +607,9 @@
verdictLevel, verdictLevel,
score; score;
if (options === undefined) { return; } if (options === undefined) {
return;
}
options.instances.errors = []; options.instances.errors = [];
if (word.length === 0) { if (word.length === 0) {
@ -595,9 +617,12 @@
} else { } else {
if (options.common.zxcvbn) { if (options.common.zxcvbn) {
userInputs = []; userInputs = [];
$.each(options.common.userInputs.concat([options.common.usernameField]), function (idx, selector) { $.each(options.common.userInputs.concat([options.common.usernameField]), function (
idx, selector) {
var value = $(selector).val(); var value = $(selector).val();
if (value) { userInputs.push(value); } if (value) {
userInputs.push(value);
}
}); });
userInputs = userInputs.concat(options.common.zxcvbnTerms); userInputs = userInputs.concat(options.common.zxcvbnTerms);
score = Math.log2(zxcvbn(word, userInputs).guesses); score = Math.log2(zxcvbn(word, userInputs).guesses);
@ -610,7 +635,9 @@
verdictLevel = verdictText[2]; verdictLevel = verdictText[2];
verdictText = verdictText[0]; verdictText = verdictText[0];
if (options.common.debug) { console.log(score + ' - ' + verdictText); } if (options.common.debug) {
console.log(score + ' - ' + verdictText);
}
if ($.isFunction(options.common.onKeyUp)) { if ($.isFunction(options.common.onKeyUp)) {
options.common.onKeyUp(event, { options.common.onKeyUp(event, {
@ -662,7 +689,9 @@
methods.forceUpdate = function () { methods.forceUpdate = function () {
this.each(function (idx, el) { this.each(function (idx, el) {
var event = { target: el }; var event = {
target: el
};
onKeyUp(event); onKeyUp(event);
}); });
}; };

View File

@ -1,6 +1,6 @@
Package.describe({ Package.describe({
name: 'clinical:active-entry', name: 'clinical:active-entry',
version: '1.5.15', version: '1.5.16',
summary: 'SignIn, SignUp, and ForgotPassword pages for Clinical Framework.', summary: 'SignIn, SignUp, and ForgotPassword pages for Clinical Framework.',
git: 'https://github.com/clinical-meteor/clinical-active-entry', git: 'https://github.com/clinical-meteor/clinical-active-entry',
documentation: 'README.md' documentation: 'README.md'
@ -16,15 +16,13 @@ Package.onUse(function (api) {
'grove:less@0.1.1', 'grove:less@0.1.1',
'session', 'session',
'reactive-dict', 'reactive-dict',
'accounts-base', //'codetheweb:zxcvbn'
'accounts-password',
'codetheweb:zxcvbn'
], ['client']); ], ['client']);
api.use([ api.use([
'accounts-base', 'accounts-base',
'accounts-password' 'accounts-password'
], ['server']); ]);
api.use([ api.use([
'zuuk:stale-session@1.0.8' 'zuuk:stale-session@1.0.8'
@ -59,6 +57,10 @@ Package.onUse(function (api) {
'components/forgotPassword/forgotPassword.js', 'components/forgotPassword/forgotPassword.js',
'components/forgotPassword/forgotPassword.less', 'components/forgotPassword/forgotPassword.less',
'components/changePassword/changePassword.html',
'components/changePassword/changePassword.js',
'components/changePassword/changePassword.less',
], ['client']); ], ['client']);

View File

@ -69,13 +69,20 @@ describe('clinical:active-entry', function () {
}); });
// ActiveEntry.verifyConfirmPassword // ActiveEntry.verifyConfirmPassword
it('Password match validation confirms that two passwords are the same.', function () { it('Password match confirms that two passwords are the same.', function () {
return client.execute(function (a) { return client.execute(function (a) {
ActiveEntry.verifyConfirmPassword('K1tt#kittens', 'kittens'); ActiveEntry.verifyConfirmPassword('K1tt#kittens', 'kittens');
expect(ActiveEntry.errorMessages.get('confirm')).to.equal("Passwords do not match"); expect(ActiveEntry.errorMessages.get('confirm')).to.equal("Passwords do not match");
ActiveEntry.verifyConfirmPassword('kittens123', 'kittens');
expect(ActiveEntry.errorMessages.get('confirm')).to.equal("Passwords do not match");
ActiveEntry.verifyConfirmPassword('kittens123', 'kittens123');
expect(ActiveEntry.errorMessages.get('confirm')).to.equal("Passwords match");
ActiveEntry.verifyConfirmPassword('K1tt#ns123', 'K1tt#ns123'); ActiveEntry.verifyConfirmPassword('K1tt#ns123', 'K1tt#ns123');
expect(ActiveEntry.successMessages.get('confirm')).to.equal("Passwords match"); expect(ActiveEntry.successMessages.get('confirm')).to.equal("Passwords match");
}); });
}); });
@ -94,13 +101,28 @@ 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', 'Janed*e123', 'Janed*e123', 'Jane Doe');
// expect(ActiveEntry.successMessages.get('fullName')).to.equal("Name present");
// }).then(function (){
// return server.wait(500, 'until account is created on the server', function () {
// return Meteor.users.findOne({'emails.address': 'janedoe@test.org'});
// }).then(function (user){
// expect(user.role).to.equal('user');
// expect(user.profile.fullName).to.equal('Jane Doe');
// });
// });
// });
// ActiveEntry.signIn // ActiveEntry.signIn
it('Newly created user record should have role, profile, and name set.', function () { it('Newly created user record should have role, profile, and name set.', function () {
return client.execute(function () { return client.execute(function () {
ActiveEntry.signUp('janedoe@test.org', 'Janed*e123', 'Janed*e123', 'Jane Doe'); // ActiveEntry.signUp('janedoe@test.org', 'Janed*e123', 'Janed*e123', 'Jane Doe');
ActiveEntry.signUp('janedoe@test.org', 'Janedoe123', 'Janedoe123', 'Jane Doe');
expect(ActiveEntry.successMessages.get('fullName')).to.equal("Name present"); expect(ActiveEntry.successMessages.get('fullName')).to.equal("Name present");
}).then(function (){ }).then(function (){
return server.wait(300, 'until account is created on the server', function () { return server.wait(500, 'until account is created on the server', function () {
return Meteor.users.findOne({'emails.address': 'janedoe@test.org'}); return Meteor.users.findOne({'emails.address': 'janedoe@test.org'});
}).then(function (user){ }).then(function (user){
expect(user.role).to.equal('user'); expect(user.role).to.equal('user');
@ -110,7 +132,6 @@ describe('clinical:active-entry', function () {
}); });
it("Newly created user should have fullName(), preferredName(), and familyName() methods.", function () { it("Newly created user should have fullName(), preferredName(), and familyName() methods.", function () {
return server.execute(function () { return server.execute(function () {
var user = Meteor.users.findOne({'emails.address': 'janedoe@test.org'}); var user = Meteor.users.findOne({'emails.address': 'janedoe@test.org'});

View File

@ -15,6 +15,7 @@
// existing user should be able to sign in on desktop // existing user should be able to sign in on desktop
// existing user should be able to sign in on tablet // existing user should be able to sign in on tablet
// existing user should be able to sign in on phone // existing user should be able to sign in on phone
// existing user should be able to change their password
// company logo should display on sign//in page // company logo should display on sign//in page
@ -48,7 +49,6 @@ module.exports = {
.verify.elementPresent("#signUpPagePasswordInput") .verify.elementPresent("#signUpPagePasswordInput")
.verify.elementPresent("#signUpPageJoinNowButton"); .verify.elementPresent("#signUpPageJoinNowButton");
}, },
"guest should be notified if password is insecure": function (client) { "guest should be notified if password is insecure": function (client) {
client client
.clearValue("input") .clearValue("input")
@ -158,8 +158,44 @@ module.exports = {
.click("#logoutButton").pause(200) .click("#logoutButton").pause(200)
.verify.containsText("#usernameLink", "Sign In"); .verify.containsText("#usernameLink", "Sign In");
}, },
"if anonymous user tries to log in with non-existing account, a message is shown": function ( "existing user should be able to change their password" : function (client) {
client) { client
.url("http://localhost:3000/entrySignIn")
.resizeWindow(1600, 1200)
.verify.containsText("#usernameLink", "Sign In")
.signIn("janicedoe@symptomatic.io", "janicedoe123").pause(500)
.verify.containsText("#usernameLink", "janicedoe@symptomatic.io")
.url("http://localhost:3000/changePassword")
.verify.elementPresent("#changePasswordPageOldPasswordInput")
.verify.elementPresent("#changePasswordPagePasswordInput")
.verify.elementPresent("#changePasswordPagePasswordConfirmInput")
.verify.elementPresent("#changePasswordButton")
},
"existing user should be notified if desired new password is insecure" : function (client) {
client
.url("http://localhost:3000/entrySignIn")
.resizeWindow(1600, 1200)
.verify.containsText("#usernameLink", "Sign In")
.signIn("janicedoe@symptomatic.io", "janicedoe123").pause(500)
.verify.containsText("#usernameLink", "janicedoe@symptomatic.io")
.url("http://localhost:3000/changePassword")
.verify.elementPresent("#changePasswordPageOldPasswordInput")
.verify.elementPresent("#changePasswordPagePasswordInput")
.verify.elementPresent("#changePasswordPagePasswordConfirmInput")
.verify.elementPresent("#changePasswordButton")
.verify.cssProperty('#changePasswordPagePasswordInput', 'border', '1px solid gray')
.setValue("#changePasswordPagePasswordInput", "jan")
.verify.cssProperty('#changePasswordPagePasswordInput', 'border', '1px solid rgb(242, 222, 222)')
.setValue("#changePasswordPagePasswordInput", "icedoe123")
.verify.cssProperty('#changePasswordPagePasswordInput', 'border', '1px solid green')
.verify.cssProperty('#changePasswordPagePasswordConfirmInput', 'border', '1px solid gray')
.setValue("#changePasswordPagePasswordConfirmInput", "ja")
.verify.cssProperty('#changePasswordPagePasswordConfirmInput', 'border', '1px solid rgb(242, 222, 222)')
.clearValue("#changePasswordPagePasswordConfirmInput")
.setValue("#changePasswordPagePasswordConfirmInput", "janicedoe123")
.verify.cssProperty('#changePasswordPagePasswordConfirmInput', 'border', '1px solid green')
},
"if anonymous user tries to log in with non-existing account, a message is shown" : function (client) {
client client
.url("http://localhost:3000/entrySignIn") .url("http://localhost:3000/entrySignIn")
.resizeWindow(1024, 768) .resizeWindow(1024, 768)