diff --git a/Packages/active-entry/components/changePassword/changePassword.js b/Packages/active-entry/components/changePassword/changePassword.js index edbc30897..9289111b9 100755 --- a/Packages/active-entry/components/changePassword/changePassword.js +++ b/Packages/active-entry/components/changePassword/changePassword.js @@ -90,40 +90,11 @@ Template.changePassword.events({ ActiveEntry.errorMessages.set('changePasswordError', null); - if (ActiveEntry.successMessages.get('password') && ActiveEntry.successMessages.get('confirm') && oldPassword) { - Meteor.call("checkPasswordExistence", new String(password).hashCode(), function(error, result) { - if (error) { - console.warn(error.message); - ActiveEntry.errorMessages.set('changePasswordError', error.message); - - } else { - if (result) { - ActiveEntry.errorMessages.set('changePasswordError', 'Password is used before. Please change your new password.'); - } else { - ActiveEntry.errorMessages.set('changePasswordError', null); - - // If password is not found in password history, change the password - Accounts.changePassword(oldPassword, confirmPassword, function(error) { - if (error) { - console.warn(error); - ActiveEntry.errorMessages.set('changePasswordError', error.message); - } else { - // Save the new password - ActiveEntry.insertHashedPassword(confirmPassword); - - // Update password expiration date - ActiveEntry.updatePasswordSetDate(); - - // Logout - ActiveEntry.signOut(); - // Go to signIn page for new entry - Router.go('/entrySignIn'); - } - }); - } - } - }); + if (ActiveEntry.errorMessages.get('password') || ActiveEntry.errorMessages.get('confirm') || !oldPassword) { + return; } + ActiveEntry.changePassword(oldPassword, password); + } }); diff --git a/Packages/active-entry/components/forgotPassword/forgotPassword.html b/Packages/active-entry/components/forgotPassword/forgotPassword.html index 66096207b..478058839 100755 --- a/Packages/active-entry/components/forgotPassword/forgotPassword.html +++ b/Packages/active-entry/components/forgotPassword/forgotPassword.html @@ -28,45 +28,3 @@ - - diff --git a/Packages/active-entry/components/forgotPassword/forgotPassword.js b/Packages/active-entry/components/forgotPassword/forgotPassword.js index 41846f206..36fbf7a08 100755 --- a/Packages/active-entry/components/forgotPassword/forgotPassword.js +++ b/Packages/active-entry/components/forgotPassword/forgotPassword.js @@ -5,16 +5,6 @@ Router.route('/forgotPassword', { template: "forgotPassword" }); -Router.route('/resetPassword/:token', { - template: 'resetPassword', - name: 'resetPassword', - onBeforeAction: function() { - var token = this.params.token; - Session.set('_resetPasswordToken', token); - this.next(); - } -}); - Template.forgotPassword.helpers({ getForgotPasswordMessageColor: function (){ if (ActiveEntry.errorMessages.get('forgotPassword')) { @@ -37,48 +27,10 @@ Template.forgotPassword.helpers({ Template.forgotPassword.events({ "submit": function (event, template) { event.preventDefault(); - console.log('send reminder!'); + ActiveEntry.successMessages.set("forgotPassword", "Your password reset email is sending..."); var emailAddress = $('#signInPageEmailInput').val(); ActiveEntry.forgotPassword(emailAddress); } }); -// Reset password template -Template.resetPassword.helpers({ - resetPassword: function(){ - return Session.get('_resetPasswordToken'); - }, - resetPasswordErrorMessages: function() { - if (ActiveEntry.errorMessages.get("password")) { - return [ActiveEntry.errorMessages.get("password")]; - } - if (ActiveEntry.errorMessages.get("confirm")) { - return [ActiveEntry.errorMessages.get("confirm")]; - } - - return; - } -}); - -Template.resetPassword.events({ - 'keyup #resetPasswordInput': function (event, template) { - var password = $('#resetPasswordInput').val(); - ActiveEntry.verifyPassword(password); - ActiveEntry.errorMessages.set('forgotPassword', null); - }, - 'keyup #resetPasswordConfirmInput': function (event, template) { - - var password = $('#resetPasswordInput').val(); - var confirmPassword = $('#resetPasswordConfirmInput').val(); - - ActiveEntry.verifyConfirmPassword(password, confirmPassword); - ActiveEntry.errorMessages.set('forgotPassword', null); - }, - 'click #resetPasswordButton': function(e, template) { - e.preventDefault(); - var password = $('#resetPasswordInput').val(); - var passwordConfirm = $('#resetPasswordConfirmInput').val(); - ActiveEntry.resetPassword(password, passwordConfirm); - } -}); diff --git a/Packages/active-entry/components/resetPassword/resetPassword.html b/Packages/active-entry/components/resetPassword/resetPassword.html new file mode 100644 index 000000000..5aea5d08c --- /dev/null +++ b/Packages/active-entry/components/resetPassword/resetPassword.html @@ -0,0 +1,42 @@ + + \ No newline at end of file diff --git a/Packages/active-entry/components/resetPassword/resetPassword.js b/Packages/active-entry/components/resetPassword/resetPassword.js new file mode 100644 index 000000000..ab5986c72 --- /dev/null +++ b/Packages/active-entry/components/resetPassword/resetPassword.js @@ -0,0 +1,67 @@ + +Router.route('/resetPassword/:token', { + template: 'resetPassword', + name: 'resetPassword', + onBeforeAction: function() { + var token = this.params.token; + Session.set('_resetPasswordToken', token); + this.next(); + } +}); + +// Reset password template +Template.resetPassword.helpers({ + getResetPasswordInMessageColor: function (){ + if (ActiveEntry.errorMessages.get('resetPasswordError')) { + return "color: #a94442; background-color: #f2dede; border-color: #ebccd1;" + } else { + return "color: black;" + } + }, + getResetPasswordMessage: function (){ + if (ActiveEntry.errorMessages.get('resetPasswordError')) { + return ActiveEntry.errorMessages.get('resetPasswordError'); + } else { + return Session.get('defaultSignInMessage'); + } + }, + resetPassword: function(){ + return Session.get('_resetPasswordToken'); + }, + resetPasswordErrorMessages: function() { + if (ActiveEntry.errorMessages.get("password")) { + return [ActiveEntry.errorMessages.get("password")]; + } + if (ActiveEntry.errorMessages.get("confirm")) { + return [ActiveEntry.errorMessages.get("confirm")]; + } + + return; + } +}); + +Template.resetPassword.events({ + 'keyup #resetPasswordInput': function (event, template) { + var password = $('#resetPasswordInput').val(); + ActiveEntry.verifyPassword(password); + ActiveEntry.errorMessages.set('resetPasswordError', null); + }, + 'keyup #resetPasswordConfirmInput': function (event, template) { + + var password = $('#resetPasswordInput').val(); + var confirmPassword = $('#resetPasswordConfirmInput').val(); + + ActiveEntry.verifyConfirmPassword(password, confirmPassword); + ActiveEntry.errorMessages.set('resetPasswordError', null); + }, + 'click #resetPasswordButton': function(e, template) { + e.preventDefault(); + var password = $('#resetPasswordInput').val(); + var passwordConfirm = $('#resetPasswordConfirmInput').val(); + + if (ActiveEntry.errorMessages.get('password') || ActiveEntry.errorMessages.get('confirm')) { + return; + } + ActiveEntry.resetPassword(password, passwordConfirm); + } +}); \ No newline at end of file diff --git a/Packages/active-entry/lib/ActiveEntry.js b/Packages/active-entry/lib/ActiveEntry.js index dd78f8fc4..c306088d7 100755 --- a/Packages/active-entry/lib/ActiveEntry.js +++ b/Packages/active-entry/lib/ActiveEntry.js @@ -30,7 +30,8 @@ if (Meteor.isClient) { passwordHistoryCount: 6, failedAttemptsLimit: 5, passwordExpirationDays: 90, - inactivityPeriodDays: 180 + inactivityPeriodDays: 180, + expireTimeInMinute: 30 } }); @@ -43,6 +44,7 @@ if (Meteor.isClient) { // 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.'); + // Activate LDAP if ldap url and port is set in settings.json Meteor.call('isLDAPSet', function(error, isSet) { Session.set('isLDAPSet', isSet); }); @@ -65,7 +67,8 @@ ActiveEntry.configure = function (configObject) { passwordHistoryCount: 6, failedAttemptsLimit: 5, passwordExpirationDays: 90, - inactivityPeriodDays: 180 + inactivityPeriodDays: 180, + expireTimeInMinute: 30 } } Session.set('Photonic.ActiveEntry', configObject); @@ -151,96 +154,152 @@ ActiveEntry.signIn = function (emailValue, passwordValue){ ActiveEntry.verifyPassword(passwordValue); ActiveEntry.verifyEmail(emailValue); - // TODO: Find a solution nested calling + + var signInArgs = {email: emailValue, password: passwordValue}; var ActiveEntryConfig = Session.get('Photonic.ActiveEntry'); - var failedAttemptsLimit = ActiveEntryConfig && ActiveEntryConfig.passwordOptions && ActiveEntryConfig.passwordOptions.failedAttemptsLimit || 5; - var passwordExpirationDays = ActiveEntryConfig && ActiveEntryConfig.passwordOptions && ActiveEntryConfig.passwordOptions.passwordExpirationDays || 90; - var inactivityPeriodDays = ActiveEntryConfig && ActiveEntryConfig.passwordOptions && ActiveEntryConfig.passwordOptions.inactivityPeriodDays || 180; + var passwordOptions = ActiveEntryConfig && ActiveEntryConfig.passwordOptions; - Meteor.call("isAccountInactive",[emailValue,inactivityPeriodDays], function(error, isAccountInactive) { + Meteor.call("isAccountInactive",emailValue, passwordOptions.inactivityPeriodDays, function(error, isAccountInactive) { if (error) { - console.warn(error); + console.warn(error.message); + return; + } + + if (isAccountInactive) { + // Lock account + ActiveEntry.lockAccount(signInArgs); } else { - if (isAccountInactive) { - // Lock account - Meteor.call("lockAccount", emailValue); - ActiveEntry.errorMessages.set('signInError', "Your account has been locked due to inactivity."); - return; - } else { - // Check account is locked - Meteor.call("isAccountLocked", function (error, isAccountLocked) { - if (error) { - console.warn(error); - } else { - - if (isAccountLocked) { - ActiveEntry.errorMessages.set('signInError', "Your account has been locked."); - return; - } - - Meteor.call("getFailedAttemptsCount", emailValue, function(error, failedAttemptsCount) { - if (error) { - console.warn(error.message); - } else { - if (failedAttemptsCount != failedAttemptsLimit) { - Meteor.loginWithPassword({email: emailValue}, passwordValue, function (loginError, result) { - if (loginError) { - // 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 if (failedAttemptCount < failedAttemptsLimit) { - ActiveEntry.errorMessages.set('signInError', loginError.message + "
" +(failedAttemptsLimit - failedAttemptCount) + " attempts remaining."); - } else { - ActiveEntry.errorMessages.set('signInError', loginError.message); - } - } - }); - } else { - // Reset failed attempts - Meteor.call("resetFailedAttempts", emailValue); - - // Check password expiration - // if password expired, route to changePassword page - Meteor.call("isPasswordExpired", passwordExpirationDays, function(error, isPasswordExpired) { - if (error) { - console.warn(error); - } else { - // Update last login time - Meteor.call("updateLastLoginDate"); - - if (isPasswordExpired) { - ActiveEntry.errorMessages.set('changePasswordError', 'Your password expired. Please change your password.'); - Router.go('/changePassword'); - } else { - Router.go(ActiveEntryConfig.signIn.destination); - } - } - }); - - } - }); - } else { - ActiveEntry.errorMessages.set('signInError', "Your account has been locked."); - } - } - - }); - - } - - }); - } + // Check account is locked + ActiveEntry.isAccountLocked(signInArgs, passwordOptions); } }); }; +ActiveEntry.lockAccount = function(signInArgs) { + var emailValue = signInArgs && signInArgs.email; + if (!emailValue) { + return; + } + Meteor.call("lockAccount", emailValue); + ActiveEntry.errorMessages.set('signInError', "Your account has been locked due to inactivity."); +}; + +ActiveEntry.isAccountLocked = function(signInArgs, passwordOptions) { + var emailValue = signInArgs && signInArgs.email; + if (!emailValue) { + return; + } + + Meteor.call("isAccountLocked", emailValue, function (error, isAccountLocked) { + if (error) { + console.warn(error.message); + return; + } + + if (isAccountLocked) { + ActiveEntry.errorMessages.set('signInError', "Your account has been locked."); + return; + } + + // Get failed attempts count + ActiveEntry.getFailedAttemptsCount(signInArgs, passwordOptions); + }); + +}; + +ActiveEntry.getFailedAttemptsCount = function(signInArgs, passwordOptions) { + var emailValue = signInArgs && signInArgs.email; + if (!emailValue) { + return; + } + Meteor.call("getFailedAttemptsCount", emailValue, function(error, failedAttemptsCount) { + if (error) { + console.warn(error.message); + return; + } + + if (failedAttemptsCount != passwordOptions.failedAttemptsLimit) { + // Login with password + ActiveEntry.loginWithPassword(signInArgs, passwordOptions); + } else { + ActiveEntry.errorMessages.set('signInError', "Your account has been locked."); + } + + }); +}; + +ActiveEntry.loginWithPassword = function(signInArgs, passwordOptions) { + var emailValue = signInArgs && signInArgs.email; + var password = signInArgs && signInArgs.password; + + if (!emailValue || !password) { + return; + } + + Meteor.loginWithPassword(emailValue, password, function (loginError, result) { + if (loginError) { + // Login failed + if (loginError.error == 403) { + ActiveEntry.updateFailedAttempts(signInArgs, passwordOptions, loginError); + } + return; + } + + // Reset failed attempts + Meteor.call("resetFailedAttempts", emailValue); + + // Check password expiration + ActiveEntry.isPasswordExpired(passwordOptions); + + }); +}; + +ActiveEntry.isPasswordExpired = function(passwordOptions) { + // if password expired, route to changePassword page + Meteor.call("isPasswordExpired", passwordOptions.passwordExpirationDays, function(error, isPasswordExpired) { + if (error) { + console.warn(error.message); + return; + } + + // Update last login time + Meteor.call("updateLastLoginDate"); + + if (isPasswordExpired) { + ActiveEntry.errorMessages.set('changePasswordError', 'Your password expired. Please change your password.'); + Router.go('/changePassword'); + } else { + var ActiveEntryConfig = Session.get('Photonic.ActiveEntry'); + Router.go(ActiveEntryConfig.signIn.destination); + } + }); +}; + +ActiveEntry.updateFailedAttempts = function(signInArgs, passwordOptions, loginError) { + var emailValue = signInArgs && signInArgs.email; + + if (!emailValue) { + return; + } + Meteor.call("updateFailedAttempts", emailValue, passwordOptions.failedAttemptsLimit, function(error, failedAttemptCount) { + if (error) { + console.warn(error.message); + return; + } + + if (failedAttemptCount == passwordOptions.failedAttemptsLimit) { + ActiveEntry.errorMessages.set('signInError', "Too many failed login attempts. Your account has been locked."); + } else if (failedAttemptCount < passwordOptions.failedAttemptsLimit) { + ActiveEntry.errorMessages.set('signInError', loginError.message + "
" +(passwordOptions.failedAttemptsLimit - failedAttemptCount) + " attempts remaining."); + } else { + ActiveEntry.errorMessages.set('signInError', loginError.message); + } + }); +}; + + ActiveEntry.loginWithLDAP = function(username, password) { ActiveEntry.verifyLDAPUsername(username); ActiveEntry.verifyLDAPPassword(password); @@ -315,19 +374,56 @@ ActiveEntry.signUp = function (emailValue, passwordValue, confirmPassword, fullN }, function (error, result) { if (error) { ActiveEntry.errorMessages.set('signInError', error.message); - } else { - // Add password in previousPasswords field - ActiveEntry.insertHashedPassword(passwordValue); - - // Update password set date - ActiveEntry.updatePasswordSetDate(); - - // Update last login time - Meteor.call("updateLastLoginDate"); - - var ActiveEntryConfig = Session.get('Photonic.ActiveEntry'); - Router.go(ActiveEntryConfig.signUp.destination); + return; } + // Add password in previousPasswords field + ActiveEntry.insertHashedPassword(passwordValue); + + // Update password set date + ActiveEntry.updatePasswordSetDate(); + + // Update last login time + Meteor.call("updateLastLoginDate"); + + var ActiveEntryConfig = Session.get('Photonic.ActiveEntry'); + Router.go(ActiveEntryConfig.signUp.destination); + + }); +}; +ActiveEntry.changePassword = function(oldPassword, password) { + Meteor.call("checkPasswordExistence", new String(password).hashCode(), function(error, isPasswordExisted) { + if (error) { + console.warn(error.message); + ActiveEntry.errorMessages.set('changePasswordError', error.message); + return; + } + + if (isPasswordExisted) { + ActiveEntry.errorMessages.set('changePasswordError', 'Password is used before. Please change your new password.'); + } else { + ActiveEntry.errorMessages.set('changePasswordError', null); + + // If password is not found in password history, change the password + Accounts.changePassword(oldPassword, password, function(error) { + if (error) { + console.warn(error); + ActiveEntry.errorMessages.set('changePasswordError', error.message); + return; + } + + // Save the new password + ActiveEntry.insertHashedPassword(password); + + // Update password expiration date + ActiveEntry.updatePasswordSetDate(); + + // Logout + ActiveEntry.signOut(); + // Go to signIn page for new entry + Router.go('/entrySignIn'); + }); + } + }); }; @@ -344,8 +440,10 @@ ActiveEntry.forgotPassword = function(emailAddress) { if (error) { console.warn(error.message); ActiveEntry.errorMessages.set("forgotPassword", error.message); + ActiveEntry.successMessages.set("forgotPassword", null); return; } + // Show email sent notification ActiveEntry.successMessages.set("forgotPassword", "Your password reset email is sent to "+emailAddress+""); }); @@ -361,8 +459,11 @@ ActiveEntry.resetPassword = function(passwordValue, confirmPassword) { return; } + var ActiveEntryConfig = Session.get('Photonic.ActiveEntry'); + var passwordOptions = ActiveEntryConfig && ActiveEntryConfig.passwordOptions; + // Check token is expired - Meteor.call('checkResetTokenIsExpired',Session.get('_resetPasswordToken'), function(error, isTokenExpired) { + Meteor.call('checkResetTokenIsExpired', Session.get('_resetPasswordToken'), passwordOptions.expireTimeInMinute, function(error, isTokenExpired) { if (error) { console.log(error.message); return; @@ -376,16 +477,39 @@ ActiveEntry.resetPassword = function(passwordValue, confirmPassword) { return; } - Accounts.resetPassword(Session.get('_resetPasswordToken'), passwordValue, function(error) { + // Check password history + Meteor.call("checkResetPasswordExistence", new String(passwordValue).hashCode(), Session.get('_resetPasswordToken'), function(error, isPasswordExisted) { if (error) { - ActiveEntry.errorMessages.set("resetPassword", error.message); + console.warn(error.message); + ActiveEntry.errorMessages.set('resetPasswordError', error.message); return; } - Session.set('_resetPasswordToken', null); - // Update last login time - Meteor.call("updateLastLoginDate"); - var ActiveEntryConfig = Session.get('Photonic.ActiveEntry'); - Router.go(ActiveEntryConfig.signIn.destination); + + if (isPasswordExisted) { + + ActiveEntry.errorMessages.set('resetPasswordError', 'Password is used before. Please change your new password.'); + } else { + + ActiveEntry.errorMessages.set('resetPasswordError', null); + Accounts.resetPassword(Session.get('_resetPasswordToken'), passwordValue, function(error) { + if (error) { + ActiveEntry.errorMessages.set("resetPassword", error.message); + return; + } + Session.set('_resetPasswordToken', null); + // Save the new password + ActiveEntry.insertHashedPassword(passwordValue); + + // Update password expiration date + ActiveEntry.updatePasswordSetDate(); + + // Update last login time + Meteor.call("updateLastLoginDate"); + + var ActiveEntryConfig = Session.get('Photonic.ActiveEntry'); + Router.go(ActiveEntryConfig.signIn.destination); + }); + } }); }); diff --git a/Packages/active-entry/package.js b/Packages/active-entry/package.js index 9ff29b8dc..1c549ad7c 100755 --- a/Packages/active-entry/package.js +++ b/Packages/active-entry/package.js @@ -61,7 +61,10 @@ Package.onUse(function (api) { 'components/changePassword/changePassword.html', 'components/changePassword/changePassword.js', - 'components/changePassword/changePassword.less' + 'components/changePassword/changePassword.less', + + 'components/resetPassword/resetPassword.html', + 'components/resetPassword/resetPassword.js' ], ['client']); diff --git a/Packages/active-entry/server/methods.js b/Packages/active-entry/server/methods.js index 5a0df52c7..64974e2ef 100644 --- a/Packages/active-entry/server/methods.js +++ b/Packages/active-entry/server/methods.js @@ -41,7 +41,6 @@ Meteor.methods({ } } return false; - }, getFailedAttemptsCount: function(emailAddress) { @@ -54,9 +53,7 @@ Meteor.methods({ return currentUser.failedPasswordAttempts || 0; }, - updateFailedAttempts: function(failedAttemptsParameters) { - var emailAddress = failedAttemptsParameters[0]; - var failedAttemptsLimit = failedAttemptsParameters[1]; + updateFailedAttempts: function(emailAddress, failedAttemptsLimit) { // Check if the user actually exists, and if not, stop here var currentUser = Meteor.users.findOne({"emails.address": emailAddress}); @@ -135,10 +132,7 @@ Meteor.methods({ Meteor.users.update({_id: Meteor.userId()}, {$set: {priorLoginDate: priorLoginDate, lastLoginDate: new Date()}}); }, - isAccountInactive: function (inactivityParameters) { - var emailAddress = inactivityParameters[0]; - var inactivityPeriodDays = inactivityParameters[1]; - + isAccountInactive: function (emailAddress, inactivityPeriodDays) { // Check if the user actually exists, and if not, stop here var currentUser = Meteor.users.findOne({"emails.address": emailAddress}); if (!currentUser) { @@ -167,7 +161,7 @@ Meteor.methods({ return false; }, - checkResetTokenIsExpired: function(token) { + checkResetTokenIsExpired: function(token, expireTimeInMinute) { var user = Meteor.users.findOne({"services.password.reset.token": token}); if (!user) { return; @@ -177,7 +171,7 @@ Meteor.methods({ return; } // Token will be expired if created time is over 30 min as default - tokenCreatedTime.setTime(tokenCreatedTime.getTime() + 30*60000); + tokenCreatedTime.setTime(tokenCreatedTime.getTime() + expireTimeInMinute*60000); if (tokenCreatedTime < new Date()) { // Remove reset token Meteor.users.update({_id: user._id}, {$unset: {'services.password.reset': 1}}); @@ -185,6 +179,24 @@ Meteor.methods({ } return false; - } + }, + + checkResetPasswordExistence: function(hashedPassword, token) { + var user = Meteor.users.findOne({"services.password.reset.token": token}); + if (!user) { + return; + } + var previousPasswords = user.previousPasswords; + if (!previousPasswords) { + return; + } + for(var i=0; i< previousPasswords.length; i++) { + var recordedHashedPassword = previousPasswords[i].hashedPassword; + if (recordedHashedPassword == hashedPassword) { + return true; + } + } + return false; + }, });