- 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
This commit is contained in:
Aysel Afsar 2016-02-04 17:53:01 -05:00
parent 4e986c8395
commit e659f0f4b3
17 changed files with 165 additions and 102 deletions

View File

@ -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

View File

@ -13,6 +13,10 @@ if (Meteor.isClient){
},
themeColors: {
primary: ""
},
passwordOptions: {
requireStrongPasswords: true,
showPasswordStrengthIndicator: true
}
});
}

View File

@ -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

View File

@ -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();
}
}
}
});

View File

@ -10,7 +10,7 @@
<h1 id="signUpPageTitle" class="title-auth">Join.</h1>
<div id="signUpPageMessage" class="subtitle-auth" style="{{getSignUpMessageColor}}">{{getSignUpMessage}}</div>
<form>
<form id="entrySignUpForm">
<!--{{#if errorMessages}}
<div id="errorMessages" class="list-errors">
{{#each errorMessages}}
@ -22,9 +22,9 @@
{{#if entryErrorMessages}}
<div id="errorMessages" class="list-errors">
{{#each entryErrorMessages}}
<div class="alert alert-danger list-item">{{this}}</div>
<div class="alert alert-danger list-item">{{this}}</div>
{{/each}}
</div>
</div>
{{/if}}
<div class="input-symbol ">

View File

@ -17,9 +17,9 @@ Router.route('/sign-up', {
Template.entrySignUp.helpers({
getSignUpMessageColor: function (){
if (ActiveEntry.errorMessages.get('signInError')) {
return "color: #a94442; background-color: #f2dede; border-color: #ebccd1;"
return "color: #a94442; background-color: #f2dede; border-color: #ebccd1;";
} else {
return "color: black;"
return "color: black;";
}
},
getSignUpMessage: function (){
@ -59,7 +59,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 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";
@ -93,8 +93,8 @@ Template.entrySignUp.helpers({
Template.entrySignUp.events({
"click #signUpPageSignInButton": function (event) {
ActiveEntry.reset();
event.preventDefault();
ActiveEntry.reset();
Router.go('/entrySignIn');
},
'change, keyup #signUpPageEmailInput': function (event, template) {
@ -127,17 +127,39 @@ Template.entrySignUp.events({
},
'click #signUpPageJoinNowButton': function (event, template) {
ActiveEntry.signUp(
$('#signUpPageEmailInput').val(),
$('#signUpPagePasswordInput').val(),
$('#signUpPagePasswordConfirmInput').val(),
$('#signUpPageFullNameInput').val()
$('#signUpPageEmailInput').val(),
$('#signUpPagePasswordInput').val(),
$('#signUpPagePasswordConfirmInput').val(),
$('#signUpPageFullNameInput').val()
);
},
'keypress #entrySignUp': function(event, template) {
if(event.keyCode == 13) {
ActiveEntry.verifyFullName($("#signUpPageFullNameInput").val());
ActiveEntry.verifyEmail($("#signUpPageEmailInput").val());
ActiveEntry.verifyPassword($("#signUpPagePasswordInput").val());
ActiveEntry.verifyConfirmPassword($("#signUpPagePasswordInput").val(), $("#signUpPagePasswordConfirmInput").val());
if (!ActiveEntry.errorMessages.get('signInError') &&
ActiveEntry.successMessages.get('fullName') &&
ActiveEntry.successMessages.get('email') &&
ActiveEntry.successMessages.get('password') &&
ActiveEntry.successMessages.get('confirm')) {
$("#signUpPageJoinNowButton").click();
}
}
}
});
Template.entrySignUp.onRendered(function() {
// Password strength meter for password inputs
if (passwordValidationSettings.usePwstrength) {
if (passwordValidationSettings.requireStrongPasswords) {
this.$('#signUpPagePasswordInput').pwstrength(passwordValidationSettings.pwstrengthOptions);
}
// Update password warning message if zxcvbn is active
if(passwordValidationSettings.showPasswordStrengthIndicator) {
Session.set('passwordWarning', 'Password is weak');
}
});

View File

@ -9,4 +9,4 @@
#entrySignUp .progress{
height: 5px;
margin-bottom: 1px
}
}

View File

@ -22,6 +22,10 @@ if (Meteor.isClient) {
},
themeColors: {
primary: ""
},
passwordOptions: {
requireStrongPasswords: false,
showPasswordStrengthIndicator: false
}
});
}
@ -33,6 +37,9 @@ if (Meteor.isClient) {
// Success messages
ActiveEntry.successMessages = new ReactiveDict('successMessages');
// Change password warning message according to whether zxcvbn is turned on
Session.set('passwordWarning', 'Password must have at least 8 characters. It must contain at least 1 uppercase, 1 lowercase, 1 number and 1 special character.');
}
ActiveEntry.configure = function (configObject) {
@ -45,8 +52,8 @@ ActiveEntry.verifyPassword = function (password) {
if (password.length === 0) {
ActiveEntry.errorMessages.set('password', 'Password is required');
ActiveEntry.successMessages.set('password', null);
} else if (!validatePassword(password)) {
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.');
} else if (!checkPasswordStrength(password)) {
ActiveEntry.errorMessages.set('password', Session.get('passwordWarning'));
ActiveEntry.successMessages.set('password', null);
} else {
//ActiveEntry.errorMessages.set('password', 'Password present');

View File

@ -0,0 +1,46 @@
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 = {
common: {
minChar: 8,
zxcvbn: showPasswordStrengthIndicator
},
ui: {
showVerdictsInsideProgressBar: true,
showStatus: true
},
rules: {
activated: {
wordNotEmail: true,
wordTwoCharacterClasses: true,
wordRepetitions: true
}
}
};
});
// 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) {
var iszxcvbnActive = (ActiveEntryConfiguration && ActiveEntryConfiguration.passwordOptions && ActiveEntryConfiguration.passwordOptions.showPasswordStrengthIndicator || false);
if (iszxcvbnActive) {
// Check zxcvbn rule
var zxcvbnResult = zxcvbn(password);
if (zxcvbnResult.score > 2) {
return true;
}
} else{
// Apply validation rule
var result = password.search(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*])[0-9a-zA-Z!@#$%^&*]{8,}$/i);
if (result > -1) {
return true;
}
}
return false;
};

View File

@ -1,24 +0,0 @@
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) {
var result = password.search(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*])[0-9a-zA-Z!@#$%^&*]{8,}$/i);
if (result > -1) {
return true;
}
return false;
};

View File

@ -1,6 +1,6 @@
Package.describe({
name: 'clinical:active-entry',
version: '1.5.14',
version: '1.5.15',
summary: 'SignIn, SignUp, and ForgotPassword pages for Clinical Framework.',
git: 'https://github.com/clinical-meteor/clinical-active-entry',
documentation: 'README.md'
@ -37,7 +37,7 @@ Package.onUse(function (api) {
api.addFiles([
'lib/jquery.pwstrength.bootstrap.js',
'lib/validatePassword.js'
'lib/checkPasswordStrength.js'
], ['client']);
api.imply('accounts-base');

View File

@ -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 must have at least 8 characters. It must contain at least 1 uppercase, 1 lowercase, 1 number and 1 special character.");
expect(ActiveEntry.errorMessages.get('password')).to.equal(Session.get('passwordWarning'));
ActiveEntry.verifyPassword('K1tt#ns123');
expect(ActiveEntry.successMessages.get('password')).to.equal("Password present");

View File

@ -21,13 +21,13 @@
module.exports = {
tags: ['users', 'entry'],
before: function(client){
before: function (client) {
client
.url("http://localhost:3000/entrySignUp")
.initializeUsers()
.resizeWindow(1024, 768)
.resizeWindow(1600, 1200);
},
"new user should be able to register on desktop" : function (client) {
"new user should be able to register on desktop": function (client) {
client
.verify.elementPresent("#entrySignUp")
.verify.elementPresent("#signUpPageTitle")
@ -35,21 +35,21 @@ module.exports = {
.verify.elementPresent("#signUpPageEmailInput")
.verify.elementPresent("#signUpPagePasswordInput")
.verify.elementPresent("#signUpPageJoinNowButton")
.verify.elementPresent("#signUpPageSignInButton")
.verify.elementPresent("#signUpPageSignInButton");
},
"company logo should display on sign-in page" : function (client) {
"company logo should display on sign-in page": function (client) {
client
.verify.elementPresent("#entrySignUp")
.verify.elementPresent("#entryAppLogo")
.verify.elementPresent("#entryAppLogo");
},
"user should be able to request be able to create new account" : function (client) {
"user should be able to request be able to create new account": function (client) {
client.verify.elementPresent("#signUpPageEmailInput")
.verify.elementPresent("#signUpPagePasswordInput")
.verify.elementPresent("#signUpPagePasswordConfirmInput")
.verify.elementPresent("#signUpPageJoinNowButton")
.verify.elementPresent("#signUpPagePasswordInput")
.verify.elementPresent("#signUpPageJoinNowButton");
},
"guest should be notified if password is insecure" : function (client) {
"guest should be notified if password is insecure": function (client) {
client
.clearValue("input")
.verify.elementPresent("#signUpPagePasswordInput")
@ -59,14 +59,15 @@ module.exports = {
.setValue("#signUpPagePasswordInput", "iceD*e123")
.verify.cssProperty('#signUpPagePasswordInput', 'border', '1px solid green')
.verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid gray')
.verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid gray')
.setValue("#signUpPagePasswordConfirmInput", "ja")
.verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid rgb(242, 222, 222)')
.verify.cssProperty('#signUpPagePasswordConfirmInput', 'border',
'1px solid rgb(242, 222, 222)')
.clearValue("#signUpPagePasswordConfirmInput")
.setValue("#signUpPagePasswordConfirmInput", "Janiced*e123")
.verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid green')
.verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid green');
},
"guest should be notified if passwords do not match" : function (client) {
"guest should be notified if passwords do not match": function (client) {
client
.clearValue("#signUpPagePasswordConfirmInput")
.clearValue("#signUpPagePasswordInput")
@ -79,9 +80,9 @@ module.exports = {
.verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid gray')
.setValue("#signUpPagePasswordConfirmInput", "Janiced*e123")
.verify.cssProperty('#signUpPagePasswordInput', 'border', '1px solid green')
.verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid green')
.verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid green');
},
"guest should be notified if email is not correctly formatted" : function (client) {
"guest should be notified if email is not correctly formatted": function (client) {
client
.clearValue("#signUpPageEmailInput")
.resetEntry()
@ -90,43 +91,43 @@ module.exports = {
.setValue("#signUpPageEmailInput", "janicedoe")
.verify.cssProperty('#signUpPageEmailInput', 'border', '1px solid rgb(242, 222, 222)')
.setValue("#signUpPageEmailInput", "@symptomatic.io")
.verify.cssProperty('#signUpPageEmailInput', 'border', '1px solid green')
.verify.cssProperty('#signUpPageEmailInput', 'border', '1px solid green');
},
"when new user fills out form and registers, new user should get created" : function (client) {
"when new user fills out form and registers, new user should get created": function (client) {
client
.verify.elementPresent("#entrySignUp")
.clearValue("#signUpPagePasswordConfirmInput")
.clearValue("#signUpPagePasswordConfirmInput")
.clearValue("#signUpPagePasswordInput")
.clearValue("#signUpPageFullNameInput")
.clearValue("#signUpPageEmailInput")
.resetEntry()
.setValue("#signUpPageFullNameInput", "Janice Doe")
.setValue("#signUpPageFullNameInput", "Janice Doe")
.setValue("#signUpPageEmailInput", "janicedoe@symptomatic.io")
.setValue("#signUpPagePasswordInput", "Janiced*e123")
.setValue("#signUpPagePasswordConfirmInput", "Janiced*e123")
.click("#signUpPageJoinNowButton").pause(1000)
.click("#signUpPageJoinNowButton").pause(1000)
.verify.containsText("#usernameLink", "janicedoe@symptomatic.io")
.verify.containsText("#usernameLink", "janicedoe@symptomatic.io");
},
"user should be able to signout" : function (client) {
"user should be able to signout": function (client) {
client
.verify.elementPresent("#logoutButton")
.click("#logoutButton").pause(300)
.verify.containsText("#usernameLink", "Sign In")
.verify.containsText("#usernameLink", "Sign In");
},
"user should be able to request reset password email" : function (client) {
client
"user should be able to request reset password email": function (client) {
client
.url("http://localhost:3000/entrySignIn")
.verify.elementPresent("#forgotPasswordButton")
.click("#forgotPasswordButton")
.verify.elementPresent("#forgotPassword")
.verify.elementPresent("#signInPageEmailInput")
.verify.elementPresent("#sendReminderButton")
.verify.elementPresent("#sendReminderButton");
},
"existing user should be able to sign in on desktop" : function (client) {
"existing user should be able to sign in on desktop": function (client) {
client
.url("http://localhost:3000/entrySignIn")
.resizeWindow(1600, 1200)
@ -134,9 +135,9 @@ module.exports = {
.signIn("janicedoe@symptomatic.io", "Janiced*e123").pause(500)
.verify.containsText("#usernameLink", "janicedoe@symptomatic.io")
.click("#logoutButton").pause(200)
.verify.containsText("#usernameLink", "Sign In")
.verify.containsText("#usernameLink", "Sign In");
},
"existing user should be able to sign in on tablet" : function (client) {
"existing user should be able to sign in on tablet": function (client) {
client
.url("http://localhost:3000/entrySignIn")
.resizeWindow(1024, 768)
@ -144,20 +145,21 @@ module.exports = {
.signIn("janicedoe@symptomatic.io", "Janiced*e123").pause(500)
.verify.containsText("#usernameLink", "janicedoe@symptomatic.io")
.click("#logoutButton").pause(200)
.verify.containsText("#usernameLink", "Sign In")
.verify.containsText("#usernameLink", "Sign In");
},
"existing user should be able to sign in on phone" : function (client) {
"existing user should be able to sign in on phone": function (client) {
client
.url("http://localhost:3000/entrySignIn")
.resizeWindow(320, 960)
// .verify.containsText("#usernameLink", "Sign In")
.signIn("janicedoe@symptomatic.io", "Janiced*e123").pause(500)
.click("#sidebarToggle").pause(300)
.click("#navbarHeader").pause(300)
.verify.containsText("#usernameLink", "janicedoe@symptomatic.io")
.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 (client) {
"if anonymous user tries to log in with non-existing account, a message is shown": function (
client) {
client
.url("http://localhost:3000/entrySignIn")
.resizeWindow(1024, 768)
@ -165,18 +167,18 @@ module.exports = {
.verify.containsText("#signInPageMessage", "User not found [403]")
.verify.cssProperty("#signInPageMessage", "color", "rgba(169, 68, 66, 1)")
.verify.cssProperty("#signInPageMessage", "background-color", "rgba(242, 222, 222, 1)")
.verify.cssProperty("#signInPageMessage", "border-color", "rgb(235, 204, 209)")
.verify.cssProperty("#signInPageMessage", "border-color", "rgb(235, 204, 209)");
},
"anonymous guest should be notified if email already exists" : function (client) {
"anonymous guest should be notified if email already exists": function (client) {
client
.url("http://localhost:3000/entrySignUp")
.resizeWindow(1024, 768)
.signUp("janicedoe@symptomatic.io", "Janiced*e123").pause(500)
.click("#signUpPageEmailInput").pause(500)
.click("#signUpPageJoinNowButton").pause(1000)
.verify.elementPresent("#signUpPageMessage")
.verify.containsText("#signUpPageMessage", "Email already exists. [403]")
.verify.containsText("#signUpPageMessage", "Email already exists. [403]");
},
after: function(client){
after: function (client) {
client
.dropEntryUsers()
.end();

View File

@ -22,8 +22,6 @@
"staleSessionInactivityTimeout": 1800000,
"staleSessionHeartbeatInterval": 180000,
"staleSessionPurgeInterval": 60000,
"staleSessionActivityEvents": "mousemove click keydown",
"usePwstrength": false,
"useZxcvbn": false
"staleSessionActivityEvents": "mousemove click keydown"
}
}

View File

@ -20,8 +20,6 @@
"staleSessionInactivityTimeout": 1800000,
"staleSessionHeartbeatInterval": 180000,
"staleSessionPurgeInterval": 60000,
"staleSessionActivityEvents": "mousemove click keydown",
"usePwstrength": false,
"useZxcvbn": false
"staleSessionActivityEvents": "mousemove click keydown"
}
}

View File

@ -21,8 +21,6 @@
"staleSessionInactivityTimeout": 1800000,
"staleSessionHeartbeatInterval": 180000,
"staleSessionPurgeInterval": 60000,
"staleSessionActivityEvents": "mousemove click keydown",
"usePwstrength": false,
"useZxcvbn": false
"staleSessionActivityEvents": "mousemove click keydown"
}
}

View File

@ -17,11 +17,9 @@
]
},
"public": {
"staleSessionInactivityTimeout": 1800000,
"staleSessionHeartbeatInterval": 180000,
"staleSessionPurgeInterval": 60000,
"staleSessionActivityEvents": "mousemove click keydown",
"usePwstrength": false,
"useZxcvbn": false
"staleSessionInactivityTimeout": 30000,
"staleSessionHeartbeatInterval": 3000,
"staleSessionPurgeInterval": 1000,
"staleSessionActivityEvents": "mousemove click keydown"
}
}