diff --git a/bower.json b/bower.json index e36409780a..71a6b51671 100644 --- a/bower.json +++ b/bower.json @@ -12,7 +12,8 @@ "angular-ui-utils": "bower", "angular-ui-router": "~0.2", "angular-file-upload": "1.1.5", - "angular-messages": "1.3.17" + "angular-messages": "1.3.17", + "owasp-password-strength-test": "~1.3.0" }, "resolutions": { "angular": "~1.3" diff --git a/config/assets/default.js b/config/assets/default.js index 85917d595d..c64fdb7ab8 100644 --- a/config/assets/default.js +++ b/config/assets/default.js @@ -15,7 +15,8 @@ module.exports = { 'public/lib/angular-ui-router/release/angular-ui-router.js', 'public/lib/angular-ui-utils/ui-utils.js', 'public/lib/angular-bootstrap/ui-bootstrap-tpls.js', - 'public/lib/angular-file-upload/angular-file-upload.js' + 'public/lib/angular-file-upload/angular-file-upload.js', + 'public/lib/owasp-password-strength-test/owasp-password-strength-test.js' ], tests: ['public/lib/angular-mocks/angular-mocks.js'] }, diff --git a/modules/articles/tests/server/article.server.model.tests.js b/modules/articles/tests/server/article.server.model.tests.js index f45d154c79..aa45237c13 100644 --- a/modules/articles/tests/server/article.server.model.tests.js +++ b/modules/articles/tests/server/article.server.model.tests.js @@ -24,7 +24,7 @@ describe('Article Model Unit Tests:', function () { displayName: 'Full Name', email: 'test@test.com', username: 'username', - password: 'password' + password: 'M3@n.jsI$Aw3$0m3' }); user.save(function () { diff --git a/modules/articles/tests/server/article.server.routes.tests.js b/modules/articles/tests/server/article.server.routes.tests.js index ef0f1ac781..87854620ad 100644 --- a/modules/articles/tests/server/article.server.routes.tests.js +++ b/modules/articles/tests/server/article.server.routes.tests.js @@ -29,7 +29,7 @@ describe('Article CRUD tests', function () { // Create user credentials credentials = { username: 'username', - password: 'password' + password: 'M3@n.jsI$Aw3$0m3' }; // Create a new user diff --git a/modules/users/client/controllers/authentication.client.controller.js b/modules/users/client/controllers/authentication.client.controller.js index f5f256e6c6..47dd234491 100644 --- a/modules/users/client/controllers/authentication.client.controller.js +++ b/modules/users/client/controllers/authentication.client.controller.js @@ -1,8 +1,9 @@ 'use strict'; -angular.module('users').controller('AuthenticationController', ['$scope', '$state', '$http', '$location', '$window', 'Authentication', - function ($scope, $state, $http, $location, $window, Authentication) { +angular.module('users').controller('AuthenticationController', ['$scope', '$state', '$http', '$location', '$window', 'Authentication', 'PasswordValidator', + function ($scope, $state, $http, $location, $window, Authentication, PasswordValidator) { $scope.authentication = Authentication; + $scope.popoverMsg = PasswordValidator.getPopoverMsg(); // Get an eventual error defined in the URL query string: $scope.error = $location.search().err; diff --git a/modules/users/client/controllers/password.client.controller.js b/modules/users/client/controllers/password.client.controller.js index e24c556296..35b4aae18a 100644 --- a/modules/users/client/controllers/password.client.controller.js +++ b/modules/users/client/controllers/password.client.controller.js @@ -1,8 +1,9 @@ 'use strict'; -angular.module('users').controller('PasswordController', ['$scope', '$stateParams', '$http', '$location', 'Authentication', - function ($scope, $stateParams, $http, $location, Authentication) { +angular.module('users').controller('PasswordController', ['$scope', '$stateParams', '$http', '$location', 'Authentication', 'PasswordValidator', + function ($scope, $stateParams, $http, $location, Authentication, PasswordValidator) { $scope.authentication = Authentication; + $scope.popoverMsg = PasswordValidator.getPopoverMsg(); //If user is signed in then redirect back home if ($scope.authentication.user) { @@ -10,9 +11,15 @@ angular.module('users').controller('PasswordController', ['$scope', '$stateParam } // Submit forgotten password account id - $scope.askForPasswordReset = function () { + $scope.askForPasswordReset = function (isValid) { $scope.success = $scope.error = null; + if (!isValid) { + $scope.$broadcast('show-errors-check-validity', 'forgotPasswordForm'); + + return false; + } + $http.post('/api/auth/forgot', $scope.credentials).success(function (response) { // Show user success message and clear form $scope.credentials = null; @@ -26,9 +33,15 @@ angular.module('users').controller('PasswordController', ['$scope', '$stateParam }; // Change user password - $scope.resetUserPassword = function () { + $scope.resetUserPassword = function (isValid) { $scope.success = $scope.error = null; + if (!isValid) { + $scope.$broadcast('show-errors-check-validity', 'resetPasswordForm'); + + return false; + } + $http.post('/api/auth/reset/' + $stateParams.token, $scope.passwordDetails).success(function (response) { // If successful show success message and clear form $scope.passwordDetails = null; diff --git a/modules/users/client/controllers/settings/change-password.client.controller.js b/modules/users/client/controllers/settings/change-password.client.controller.js index 5e32e11d9b..ff8c4179b1 100644 --- a/modules/users/client/controllers/settings/change-password.client.controller.js +++ b/modules/users/client/controllers/settings/change-password.client.controller.js @@ -1,8 +1,9 @@ 'use strict'; -angular.module('users').controller('ChangePasswordController', ['$scope', '$http', 'Authentication', - function ($scope, $http, Authentication) { +angular.module('users').controller('ChangePasswordController', ['$scope', '$http', 'Authentication', 'PasswordValidator', + function ($scope, $http, Authentication, PasswordValidator) { $scope.user = Authentication.user; + $scope.popoverMsg = PasswordValidator.getPopoverMsg(); // Change user password $scope.changeUserPassword = function (isValid) { diff --git a/modules/users/client/directives/password-validator.client.directive.js b/modules/users/client/directives/password-validator.client.directive.js new file mode 100644 index 0000000000..da9fe08370 --- /dev/null +++ b/modules/users/client/directives/password-validator.client.directive.js @@ -0,0 +1,42 @@ +'use strict'; + +angular.module('users') + .directive('passwordValidator', ['PasswordValidator', function(PasswordValidator) { + return { + require: 'ngModel', + link: function(scope, element, attrs, modelCtrl) { + modelCtrl.$parsers.unshift(function (password) { + var result = PasswordValidator.getResult(password); + var strengthIdx = 0; + + // Strength Meter - visual indicator for users + var strengthMeter = [ + { color: "danger", progress: "20" }, + { color: "warning", progress: "40"}, + { color: "info", progress: "60"}, + { color: "primary", progress: "80"}, + { color: "success", progress: "100"} + ]; + var strengthMax = strengthMeter.length; + + if (result.errors.length < strengthMeter.length) { + strengthIdx = strengthMeter.length - result.errors.length - 1; + } + + scope.strengthColor = strengthMeter[strengthIdx].color; + scope.strengthProgress = strengthMeter[strengthIdx].progress; + + if (result.errors.length) { + scope.popoverMsg = PasswordValidator.getPopoverMsg(); + scope.passwordErrors = result.errors; + modelCtrl.$setValidity('strength', false); + return undefined; + } else { + scope.popoverMsg = ''; + modelCtrl.$setValidity('strength', true); + return password; + } + }); + } + }; +}]); diff --git a/modules/users/client/directives/password-verify.client.directive.js b/modules/users/client/directives/password-verify.client.directive.js new file mode 100644 index 0000000000..63e6c13a84 --- /dev/null +++ b/modules/users/client/directives/password-verify.client.directive.js @@ -0,0 +1,33 @@ +'use strict'; + +angular.module('users') + .directive("passwordVerify", function() { + return { + require: "ngModel", + scope: { + passwordVerify: '=' + }, + link: function(scope, element, attrs, modelCtrl) { + scope.$watch(function() { + var combined; + if (scope.passwordVerify || modelCtrl.$viewValue) { + combined = scope.passwordVerify + '_' + modelCtrl.$viewValue; + } + return combined; + }, function(value) { + if (value) { + modelCtrl.$parsers.unshift(function(viewValue) { + var origin = scope.passwordVerify; + if (origin !== viewValue) { + modelCtrl.$setValidity("passwordVerify", false); + return undefined; + } else { + modelCtrl.$setValidity("passwordVerify", true); + return viewValue; + } + }); + } + }); + } + }; +}); diff --git a/modules/users/client/services/password-validator.client.service.js b/modules/users/client/services/password-validator.client.service.js new file mode 100644 index 0000000000..0c10f554e2 --- /dev/null +++ b/modules/users/client/services/password-validator.client.service.js @@ -0,0 +1,19 @@ +'use strict'; + +// PasswordValidator service used for testing the password strength +angular.module('users').factory('PasswordValidator', ['$window', + function ($window) { + var owaspPasswordStrengthTest = $window.owaspPasswordStrengthTest; + + return { + getResult: function (password) { + var result = owaspPasswordStrengthTest.test(password); + return result; + }, + getPopoverMsg: function () { + var popoverMsg = "Please enter a passphrase or password with greater than 10 characters, numbers, lowercase, upppercase, and special characters."; + return popoverMsg; + } + }; + } +]); diff --git a/modules/users/client/views/authentication/signup.client.view.html b/modules/users/client/views/authentication/signup.client.view.html index 60c42b9ee3..65fbb4bb5c 100644 --- a/modules/users/client/views/authentication/signup.client.view.html +++ b/modules/users/client/views/authentication/signup.client.view.html @@ -34,14 +34,20 @@
Password is required.
-Password is too short.
+{{passwordError}}
+Enter your account username.