[haiku-depot-web] [haiku-depot-web-app] push by haiku.li...@xxxxxxxxx - + implemented a change password function... on 2014-01-16 08:38 GMT

  • From: haiku-depot-web-app@xxxxxxxxxxxxxx
  • To: haiku-depot-web@xxxxxxxxxxxxx
  • Date: Thu, 16 Jan 2014 08:38:17 +0000

Revision: 4b9ea5e0d960
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Thu Jan 16 08:37:44 2014 UTC
Log:      + implemented a change password function
+ implemented basic validation to check the complexity of a supplied password

http://code.google.com/p/haiku-depot-web-app/source/detail?r=4b9ea5e0d960

Added:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/ChangePasswordRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/ChangePasswordResult.java /haikudepotserver-webapp/src/main/webapp/js/app/controller/changepassword.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/changepasswordcontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/userlabeldirective.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/validpassworddirective.js /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js
Modified:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthenticationService.java
 /haikudepotserver-webapp/src/main/resources/messages.properties
/haikudepotserver-webapp/src/main/resources/spring/webresourcegroup-context.xml
 /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css
/haikudepotserver-webapp/src/main/webapp/js/app/controller/authenticateuser.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/authenticateusercontroller.js
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/createuser.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/createusercontroller.js
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgicon.html
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewuser.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/viewusercontroller.js
 /haikudepotserver-webapp/src/main/webapp/js/app/routes.js

=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/ChangePasswordRequest.java Thu Jan 16 08:37:44 2014 UTC
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+/**
+ * <p>This is the request model object for changing a password. For details on the captcha token, see the documentation + * supplied on {@link org.haikuos.haikudepotserver.api1.model.user.CreateUserRequest}.</p>
+ */
+
+public class ChangePasswordRequest {
+
+    public String nickname;
+    public String oldPasswordClear;
+    public String newPasswordClear;
+    public String captchaToken;
+    public String captchaResponse;
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/ChangePasswordResult.java Thu Jan 16 08:37:44 2014 UTC
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class ChangePasswordResult {
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/changepassword.html Thu Jan 16 08:37:44 2014 UTC
@@ -0,0 +1,78 @@
+<breadcrumbs items="breadcrumbItems"></breadcrumbs>
+
+<div class="content-container">
+    <form name="changePasswordForm" novalidate="novalidate">
+
+        <label>Nickname</label>
+        <div class="form-control-group">
+            <div class="form-control-group-static">
+                <user-label user="user"></user-label>
+            </div>
+        </div>
+
+        <label for="old-password-clear">Existing Password</label>
+ <div class="form-control-group" ng-class="deriveFormControlsContainerClasses('oldPasswordClear')">
+            <input
+                    id="old-password-clear"
+                    type="password"
+                    name="oldPasswordClear"
+                    ng-change="oldPasswordChanged()"
+                    ng-required="true"
+                    ng-model="changePasswordData.oldPasswordClear"></input>
+ <error-messages key-prefix="changePassword.oldPasswordClear" error="changePasswordForm.oldPasswordClear.$error"></error-messages>
+        </div>
+
+        <label for="new-password-clear">New Password</label>
+ <div class="form-control-group" ng-class="deriveFormControlsContainerClasses('newPasswordClear')">
+            <input
+                    id="new-password-clear"
+                    type="password"
+                    name="newPasswordClear"
+                    valid-password=""
+                    ng-change="newPasswordsChanged()"
+                    ng-required="true"
+                    ng-model="changePasswordData.newPasswordClear"></input>
+ <error-messages key-prefix="changePassword.newPasswordClear" error="changePasswordForm.newPasswordClear.$error"></error-messages>
+        </div>
+
+ <label for="new-password-clear-repeated">New Password Repeated</label> + <div class="form-control-group" ng-class="deriveFormControlsContainerClasses('newPasswordClearRepeated')">
+            <input
+                    id="new-password-clear-repeated"
+                    type="password"
+                    name="newPasswordClearRepeated"
+                    ng-change="newPasswordsChanged()"
+                    ng-required="true"
+ ng-model="changePasswordData.newPasswordClearRepeated"></input> + <error-messages key-prefix="changePassword.newPasswordClearRepeated" error="changePasswordForm.newPasswordClearRepeated.$error"></error-messages>
+        </div>
+
+        <label for="captcha-response-input">Check for a Person</label>
+ <div class="form-control-group" ng-class="deriveFormControlsContainerClasses('captchaResponse')"> + <img style="vertical-align:middle;" src="{{captchaImageUrl}}"></img>
+            =
+            <input
+                    id="captcha-response-input"
+                    size="3"
+                    type="text"
+                    name="captchaResponse"
+                    ng-required="true"
+                    ng-change="captchaResponseDidChange()"
+                    ng-model="changePasswordData.captchaResponse"></input>
+ <error-messages key-prefix="changePassword.captchaResponse" error="changePasswordForm.captchaResponse.$error"></error-messages>
+        </div>
+
+        <div class="form-action-container">
+            <button
+                    ng-disabled="changePasswordForm.$invalid"
+                    ng-click="goChangePassword()"
+                    type="submit"
+                    class="main-action">Change Password</button>
+        </div>
+
+    </form>
+</div>
+
+<div class="footer"></div>
+<spinner spin="shouldSpin()"></spinner>
+
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/changepasswordcontroller.js Thu Jan 16 08:37:44 2014 UTC
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+angular.module('haikudepotserver').controller(
+    'ChangePasswordController',
+    [
+        '$scope','$log','$location','$routeParams',
+        'jsonRpc','constants','breadcrumbs','userState',
+        function(
+            $scope,$log,$location,$routeParams,
+            jsonRpc,constants,breadcrumbs,userState) {
+
+            $scope.breadcrumbItems = undefined;
+            $scope.user = undefined;
+            $scope.captchaToken = undefined;
+            $scope.captchaImageUrl = undefined;
+            $scope.changePasswordData = {
+                captchaResponse : undefined,
+                oldPasswordClear : undefined,
+                newPasswordClear : undefined,
+                newPasswordClearRepeated : undefined
+            };
+
+            var amChangingPassword = false;
+
+            $scope.shouldSpin = function() {
+                return undefined == $scope.user || amChangingPassword;
+            }
+
+            $scope.deriveFormControlsContainerClasses = function(name) {
+ return $scope.changePasswordForm[name].$invalid ? ['form-control-group-error'] : [];
+            }
+
+            refreshUser();
+            regenerateCaptcha();
+
+            function refreshBreadcrumbItems() {
+                $scope.breadcrumbItems = [
+                    breadcrumbs.createViewUser($scope.user),
+                    breadcrumbs.createChangePassword($scope.user)
+                ];
+            }
+
+            function refreshUser() {
+                jsonRpc.call(
+                        constants.ENDPOINT_API_V1_USER,
+                        "getUser",
+                        [{ nickname : $routeParams.nickname }]
+                    ).then(
+                    function(result) {
+                        $scope.user = result;
+                        refreshBreadcrumbItems();
+                        $log.info('fetched user; '+result.nickname);
+                    },
+                    function(err) {
+ constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                    }
+                );
+            };
+
+            function regenerateCaptcha() {
+
+                $scope.captchaToken = undefined;
+                $scope.captchaImageUrl = undefined;
+                $scope.changePasswordData.captchaResponse = undefined;
+
+                jsonRpc.call(
+                        constants.ENDPOINT_API_V1_CAPTCHA,
+                        "generateCaptcha",
+                        [{}]
+                    ).then(
+                    function(result) {
+                        $scope.captchaToken = result.token;
+ $scope.captchaImageUrl = 'data:image/png;base64,'+result.pngImageDataBase64;
+                        refreshBreadcrumbItems();
+                    },
+                    function(err) {
+ constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                    }
+                );
+            }
+
+ // When you go to action, if the user types the wrong captcha response then they will get an error message + // letting them know this, but there is no natural mechanism for this invalid state to get unset. For + // this reason, any change to the response text field will be taken to trigger this error state to be
+            // removed.
+
+            $scope.captchaResponseDidChange = function() {
+ $scope.changePasswordForm.captchaResponse.$setValidity('badresponse',true);
+            }
+
+            $scope.newPasswordsChanged = function() {
+ $scope.changePasswordForm.newPasswordClearRepeated.$setValidity(
+                    'repeat',
+                    !$scope.changePasswordData.newPasswordClear
+ | | !$scope.changePasswordData.newPasswordClearRepeated + || $scope.changePasswordData.newPasswordClear == $scope.changePasswordData.newPasswordClearRepeated);
+            }
+
+            $scope.oldPasswordChanged = function() {
+ $scope.changePasswordForm.oldPasswordClear.$setValidity('mismatched',true);
+            }
+
+            $scope.goChangePassword = function() {
+
+                if($scope.changePasswordForm.$invalid) {
+ throw 'expected the change password of a user only to be possible if the form is valid';
+                }
+
+                $scope.amChangingPassword = true;
+
+                jsonRpc.call(
+                        constants.ENDPOINT_API_V1_USER,
+                        "changePassword",
+                        [{
+                            nickname : $scope.user.nickname,
+ oldPasswordClear : $scope.changePasswordData.oldPasswordClear, + newPasswordClear : $scope.changePasswordData.newPasswordClear,
+                            captchaToken : $scope.captchaToken,
+ captchaResponse : $scope.changePasswordData.captchaResponse
+                        }]
+                    ).then(
+                    function(result) {
+ $log.info('did change password for user; '+$scope.user.nickname);
+                        userState.user(null); // logout
+                        $location.path('/authenticateuser').search({
+                            nickname : $scope.user.nickname,
+                            didChangePassword : 'true'
+                        });
+                    },
+                    function(err) {
+                        regenerateCaptcha();
+                        $scope.amSaving = false;
+
+                        switch(err.code) {
+
+ // should not be any validation failures that we need to deal with here.
+
+
+                            case jsonRpc.errorCodes.VALIDATION:
+
+ // actually there shouldn't really be any validation problems except that the oldPasswordClear + // not match to the user for which the change password operation is being performed.
+
+ if(err.data && err.data.validationfailures) { + _.each(err.data.validationfailures, function(vf) { + var model = $scope.changePasswordForm[vf.property];
+
+                                        if(model) {
+ model.$setValidity(vf.message, false);
+                                        }
+                                        else {
+ $log.error('other validation failures exist; will invoke default handling'); + constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                                        }
+                                    })
+                                }
+
+                                break;
+
+                            case jsonRpc.errorCodes.CAPTCHABADRESPONSE:
+ $log.error('the user has mis-interpreted the captcha; will lodge an error into the form and then populate a new one for them'); + $scope.changePasswordForm.captchaResponse.$setValidity('badresponse',false);
+                                break;
+
+                            default:
+ constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                                break;
+                        }
+                    }
+                );
+
+            }
+
+        }
+    ]
+);
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/userlabeldirective.js Thu Jan 16 08:37:44 2014 UTC
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+/**
+ * <p>This directive renders a small piece of text and maybe a hyperlink to show a user.</p>
+ */
+
+angular.module('haikudepotserver').directive('userLabel',function() {
+    return {
+        restrict: 'E',
+        template:'<span>{{user.nickname}}</span>',
+        replace: true,
+        scope: {
+            user: '='
+        },
+        controller:
+            ['$scope',
+                function($scope) {
+                }
+            ]
+    };
+});
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/validpassworddirective.js Thu Jan 16 08:37:44 2014 UTC
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+/**
+ * <p>This directive will check to make sure that the password entered in is in the correct format, is long enough
+ * and is complex enough.
+ */
+
+angular.module('haikudepotserver').directive('validPassword',function() {
+    return {
+        restrict: 'A',
+        require: 'ngModel',
+        link: function(scope,elem, attr, ctrl) { // ctrl = ngModel
+
+            ctrl.$parsers.unshift(function(value) {
+
+                var valid = (value.length >= 8)
+                    && value.replace(/[^0-9]/g,'').length >= 2
+                    && value.replace(/[^A-Z]/g,'').length >= 1;
+
+                ctrl.$setValidity('validPassword', valid);
+                return valid ? value : undefined;
+            });
+
+        }
+    };
+});
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Thu Jan 16 08:37:44 2014 UTC
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+/**
+ * <p>This service helps in the creation of breadcrumb items.</p>
+ */
+
+angular.module('haikudepotserver').factory('breadcrumbs',
+    [
+        function() {
+
+            var BreadcrumbsService = {
+
+                createViewUser : function(user) {
+                    return {
+                        title : user.nickname,
+                        path : '/viewuser/' + user.nickname
+                    };
+                },
+
+                createChangePassword : function(user) {
+                    return {
+                        title : 'Change Password',
+                        path : '/changepassword/' + user.nickname
+                    };
+                }
+            };
+
+            return BreadcrumbsService;
+
+        }
+    ]
+);
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java Wed Nov 20 10:28:55 2013 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java Thu Jan 16 08:37:44 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -9,6 +9,7 @@
 import org.haikuos.haikudepotserver.api1.model.user.*;
import org.haikuos.haikudepotserver.api1.support.AuthorizationFailureException;
 import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;
+import org.haikuos.haikudepotserver.api1.support.ValidationException;

 /**
* <p>This interface defines operations that can be undertaken around users.</p>
@@ -40,4 +41,10 @@

AuthenticateUserResult authenticateUser(AuthenticateUserRequest authenticateUserRequest);

+    /**
+ * <p>This method will allow the client to modify the password of a user.</p>
+     */
+
+ ChangePasswordResult changePassword(ChangePasswordRequest changePasswordRequest) throws ObjectNotFoundException, AuthorizationFailureException, ValidationException;
+
 }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java Thu Jan 16 08:37:44 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014 Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -8,14 +8,11 @@
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
-import com.google.common.hash.Hashing;
 import com.google.common.util.concurrent.Uninterruptibles;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.haikuos.haikudepotserver.api1.model.user.*;
-import org.haikuos.haikudepotserver.api1.support.AuthorizationFailureException; -import org.haikuos.haikudepotserver.api1.support.CaptchaBadResponseException;
-import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;
+import org.haikuos.haikudepotserver.api1.support.*;
 import org.haikuos.haikudepotserver.captcha.CaptchaService;
 import org.haikuos.haikudepotserver.dataobjects.User;
 import org.haikuos.haikudepotserver.security.AuthenticationService;
@@ -44,8 +41,14 @@
public CreateUserResult createUser(CreateUserRequest createUserRequest) {

         Preconditions.checkNotNull(createUserRequest);
-        Preconditions.checkNotNull(createUserRequest.captchaToken);
-        Preconditions.checkNotNull(createUserRequest.captchaResponse);
+ Preconditions.checkNotNull(!Strings.isNullOrEmpty(createUserRequest.nickname)); + Preconditions.checkNotNull(!Strings.isNullOrEmpty(createUserRequest.passwordClear)); + Preconditions.checkNotNull(!Strings.isNullOrEmpty(createUserRequest.captchaToken)); + Preconditions.checkNotNull(!Strings.isNullOrEmpty(createUserRequest.captchaResponse));
+
+ if(!authenticationService.validatePassword(createUserRequest.passwordClear)) { + throw new ValidationException(new ValidationFailure("passwordClear", "invalid"));
+        }

         // check the supplied catcha matches the token.

@@ -77,7 +80,7 @@
         User user = context.newObject(User.class);
         user.setNickname(createUserRequest.nickname);
         user.setPasswordSalt(); // random
- user.setPasswordHash(Hashing.sha256().hashUnencodedChars(user.getPasswordSalt() + createUserRequest.passwordClear).toString()); + user.setPasswordHash(authenticationService.hashPassword(user, createUserRequest.passwordClear));
         context.commitChanges();

         logger.info("data create user; {}",user.getNickname());
@@ -125,8 +128,8 @@
         }

authenticateUserResult.authenticated = authenticationService.authenticate(
-                    authenticateUserRequest.nickname,
-                    authenticateUserRequest.passwordClear).isPresent();
+                authenticateUserRequest.nickname,
+                authenticateUserRequest.passwordClear).isPresent();

         // if the authentication has failed then best to sleep for a moment
         // to make brute forcing a bit more tricky.
@@ -137,6 +140,89 @@

         return authenticateUserResult;
     }
+
+    @Override
+    public ChangePasswordResult changePassword(
+            ChangePasswordRequest changePasswordRequest)
+ throws ObjectNotFoundException, AuthorizationFailureException, ValidationException {
+
+        Preconditions.checkNotNull(changePasswordRequest);
+ Preconditions.checkState(!Strings.isNullOrEmpty(changePasswordRequest.nickname)); + Preconditions.checkState(!Strings.isNullOrEmpty(changePasswordRequest.newPasswordClear));
+
+ if(!authenticationService.validatePassword(changePasswordRequest.newPasswordClear)) { + throw new ValidationException(new ValidationFailure("passwordClear", "invalid"));
+        }
+
+        final ObjectContext context = serverRuntime.getContext();
+
+        User authUser = obtainAuthenticatedUser(context);
+
+ // if the logged in user is non-root then we need to make sure that the captcha
+        // is valid.
+
+        if(!authUser.getIsRoot()) {
+
+            if(Strings.isNullOrEmpty(changePasswordRequest.captchaToken)) {
+ throw new IllegalStateException("the captcha token must be supplied to change the password");
+            }
+
+ if(Strings.isNullOrEmpty(changePasswordRequest.captchaResponse)) { + throw new IllegalStateException("the captcha response must be supplied to change the password");
+            }
+
+ if(!captchaService.verify(changePasswordRequest.captchaToken, changePasswordRequest.captchaResponse)) {
+                throw new CaptchaBadResponseException();
+            }
+        }
+
+ // if the logged in user is not root then only the user who has authenticated can change their password.
+
+ if(!authUser.getNickname().equals(changePasswordRequest.nickname)) {
+            if(authUser.getIsRoot()) {
+                logger.info("allowing change password for root user");
+            }
+            else {
+ logger.info("the logged in user {} is not allowed to change the password of another user {}",authUser.getNickname(),changePasswordRequest.nickname);
+                throw new AuthorizationFailureException();
+            }
+        }
+
+ // if the logged in user is non-root then we need to make sure that the old and new passwords
+        // match-up.
+
+        if(!authUser.getIsRoot()) {
+
+ if(Strings.isNullOrEmpty(changePasswordRequest.oldPasswordClear)) { + throw new IllegalStateException("the old password clear is required to change the password of a user unless the logged in user is root.");
+            }
+
+            if(!authenticationService.authenticate(
+                    changePasswordRequest.nickname,
+                    changePasswordRequest.oldPasswordClear).isPresent()) {
+
+ // if the old password does not match to the user then we should present this + // as a validation failure rather than an authorization failure.
+
+ logger.info("the supplied old password is invalid for the user {}", changePasswordRequest.nickname);
+
+ throw new ValidationException(new ValidationFailure("oldPasswordClear","mismatched"));
+            }
+        }
+
+ Optional<User> userOptional = User.getByNickname(context, changePasswordRequest.nickname);
+
+        if(!userOptional.isPresent()) {
+ throw new ObjectNotFoundException(User.class.getSimpleName(), changePasswordRequest.nickname);
+        }
+
+        User user = userOptional.get();
+ user.setPasswordHash(authenticationService.hashPassword(user, changePasswordRequest.newPasswordClear));
+        context.commitChanges();
+ logger.info("did change password for user {}", changePasswordRequest.nickname);
+
+        return new ChangePasswordResult();
+    }


 }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthenticationService.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthenticationService.java Thu Jan 16 08:37:44 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -100,7 +100,7 @@

             if(userOptional.isPresent()) {
                 User user = userOptional.get();
- String hash = Hashing.sha256().hashUnencodedChars(user.getPasswordSalt() + passwordClear).toString();
+                String hash = hashPassword(user, passwordClear);

                 if(hash.equals(user.getPasswordHash())) {
result = Optional.fromNullable(userOptional.get().getObjectId());
@@ -119,5 +119,57 @@

         return result;
     }
+
+    /**
+ * <p>This method will hash the password in a consistent manner across the whole system.</p>
+     */
+
+    public String hashPassword(User user, String passwordClear) {
+ return Hashing.sha256().hashUnencodedChars(user.getPasswordSalt() + passwordClear).toString();
+    }
+
+    private int countMatches(String s, CharToBooleanFunction fn) {
+        int length = s.length();
+        int count = 0;
+        for(int i=0;i<length;i++) {
+            char c = s.charAt(i);
+            if(fn.test(c)) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+ * <p>Passwords should be hard to guess and so there needs to be a certain level of complexity to + * them. They should of a certain length and should contain some mix of letters and digits as well
+     * as at least one upper case letter.</p>
+     *
+     * <p>This method will check the password for suitability.</p>
+     */
+
+    public boolean validatePassword(String passwordClear) {
+        Preconditions.checkNotNull(passwordClear);
+
+        if(passwordClear.length() < 8) {
+            return false;
+        }
+
+        // get a count of digits - should be at least two.
+ if(countMatches(passwordClear, new CharToBooleanFunction() { public boolean test(char c) { return c >= 48 && c <= 57; } }) < 2) {
+            return false;
+        }
+
+        // get a count of upper case letters - should be at least one.
+ if(countMatches(passwordClear, new CharToBooleanFunction() { public boolean test(char c) { return c >= 65 && c <= 90; } }) < 1) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private interface CharToBooleanFunction {
+        boolean test(char c);
+    }

 }
=======================================
--- /haikudepotserver-webapp/src/main/resources/messages.properties Mon Jan 13 10:50:43 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages.properties Thu Jan 16 08:37:44 2014 UTC
@@ -4,9 +4,19 @@
createUser.nickname.pattern=The nickname can consist of latin characters and digits only and must be between 4 and 16 characters in length. createUser.nickname.notunique=The nickname is already in-use; nominate an alternative nickname.
 createUser.passwordClear.required=The password of the new user is required.
-createUser.passwordClear.minlength=The password must be at least 5 characters long. -createUserForm.captchaResponse.required=The response to the question in the image is required to ensure that the registration is from a human operator. -createUserForm.captchaResponse.badresponse=The response supplied does not match the question in the image or the question has expired; a new image has been provided. +createUser.passwordClear.validPassword=The password should be at least 8 characters long and contain two digits and one uppercase latin letter. +createUser.passwordClearRepeated.required=The password must be repeated to ensure it was entered correctly. +createUser.passwordClearRepeated.repeat=The password has not been repeated correctly. +createUser.captchaResponse.required=The response to the question in the image is required to ensure that the registration is from a human operator. +createUser.captchaResponse.badresponse=The response supplied does not match the question in the image or the question has expired; a new image has been provided.
+
+changePassword.oldPasswordClear.required=The existing password for this user is required to prove the user's identity. +changePassword.newPasswordClear.required=The new password is required to set the password. +changePassword.newPasswordClear.validPassword=The password should be at least 8 characters long and contain two digits and one uppercase latin letter. +changePassword.newPasswordClearRepeated.required=The new password must be repeated in order to ensure that it has been supplied correctly. +changePassword.newPasswordClearRepeated.repeat=The password has not been repeated correctly. +changePassword.captchaResponse.required=The result of this simple question must be supplied to prove that the change password is from a human operator. +changePassword.oldPasswordClear.mismatched=Authentication problem; try to enter the existing password again.

 authenticateUser.nickname.required=The nickname is required to login.
 authenticateUser.passwordClear.required=The password is required to login.
=======================================
--- /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup-context.xml Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup-context.xml Thu Jan 16 08:37:44 2014 UTC
@@ -55,6 +55,8 @@
<value>/js/app/directive/pkgicondirective.js</value> <value>/js/app/directive/pkglabeldirective.js</value> <value>/js/app/directive/filesupplydirective.js</value> + <value>/js/app/directive/validpassworddirective.js</value> + <value>/js/app/directive/userlabeldirective.js</value>

<value>/js/app/controller/viewpkgcontroller.js</value> <value>/js/app/controller/homecontroller.js</value>
@@ -63,12 +65,14 @@
<value>/js/app/controller/viewusercontroller.js</value> <value>/js/app/controller/authenticateusercontroller.js</value> <value>/js/app/controller/editpkgiconcontroller.js</value> + <value>/js/app/controller/changepasswordcontroller.js</value>

<value>/js/app/service/jsonrpcservice.js</value> <value>/js/app/service/messagesourceservice.js</value> <value>/js/app/service/userstateservice.js</value> <value>/js/app/service/referencedataservice.js</value> <value>/js/app/service/pkgiconservice.js</value> + <value>/js/app/service/breadcrumbsservice.js</value>

                         </list>
                     </property>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css Thu Jan 16 08:37:44 2014 UTC
@@ -140,7 +140,7 @@
 */

 .form-control-group .form-control-group-static {
-    padding: 8px;
+    padding: 3px;
 }

 .form-control-group.form-control-group-error input {
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/authenticateuser.html Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/authenticateuser.html Thu Jan 16 08:37:44 2014 UTC
@@ -2,6 +2,10 @@

 <div class="content-container">

+    <div class="form-info-container" ng-show="didChangePassword">
+ The password was changed; you can now re-authenticate with the new password.
+    </div>
+
     <div class="form-info-container" ng-show="didCreate">
         The new account was created; you can now login and use it.
     </div>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/authenticateusercontroller.js Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/authenticateusercontroller.js Thu Jan 16 08:37:44 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -19,6 +19,7 @@
             $scope.didFailAuthentication = false;
             $scope.amAuthenticating = false;
             $scope.didCreate = !!$location.search()['didCreate'];
+ $scope.didChangePassword = !!$location.search()['didChangePassword'];
             $scope.authenticationDetails = {
                 nickname : undefined,
                 passwordClear : undefined
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/createuser.html Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/createuser.html Thu Jan 16 08:37:44 2014 UTC
@@ -24,24 +24,37 @@
                     type="password"
                     name="passwordClear"
                     ng-required="true"
-                    ng-minlength="5"
+                    ng-change="passwordsChanged()"
+                    valid-password=""
                     ng-model="newUser.passwordClear"></input>
<error-messages key-prefix="createUser.passwordClear" error="createUserForm.passwordClear.$error"></error-messages>
         </div>

- <label for="create-user-captcha-response-input">Check for a Person</label>
+        <label for="password-clear-repeated">Password Repeated</label>
+ <div class="form-control-group" ng-class="deriveFormControlsContainerClasses('passwordClearRepeated')">
+            <input
+                    id="password-clear-repeated"
+                    type="password"
+                    name="passwordClearRepeated"
+                    ng-change="passwordsChanged()"
+                    ng-required="true"
+                    ng-model="newUser.passwordClearRepeated"></input>
+ <error-messages key-prefix="createUser.passwordClearRepeated" error="createUserForm.passwordClearRepeated.$error"></error-messages>
+        </div>
+
+        <label for="captcha-response-input">Check for a Person</label>
<div class="form-control-group" ng-class="deriveFormControlsContainerClasses('captchaResponse')"> <img style="vertical-align:middle;" src="{{captchaImageUrl}}"></img>
             =
             <input
-                    id="create-user-captcha-response-input"
+                    id="captcha-response-input"
                     size="3"
                     type="text"
                     name="captchaResponse"
                     ng-required="true"
                     ng-change="captchaResponseDidChange()"
                     ng-model="newUser.captchaResponse"></input>
- <error-messages key-prefix="createUserForm.captchaResponse" error="createUserForm.captchaResponse.$error"></error-messages> + <error-messages key-prefix="createUser.captchaResponse" error="createUserForm.captchaResponse.$error"></error-messages>
         </div>

         <div class="form-action-container">
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/createusercontroller.js Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/createusercontroller.js Thu Jan 16 08:37:44 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -19,6 +19,7 @@
             $scope.newUser = {
                 nickname : undefined,
                 passwordClear : undefined,
+                passwordClearRepeated : undefined,
                 captchaResponse : undefined
             };

@@ -73,6 +74,14 @@
             $scope.nicknameDidChange = function() {
$scope.createUserForm.nickname.$setValidity('notunique',true);
             }
+
+            $scope.passwordsChanged = function() {
+                $scope.createUserForm.passwordClearRepeated.$setValidity(
+                    'repeat',
+                    !$scope.newUser.passwordClear
+                    || !$scope.newUser.passwordClearRepeated
+ || $scope.newUser.passwordClear == $scope.newUser.passwordClearRepeated);
+            }

// This function will take the data from the form and will create the user from this data.

@@ -115,19 +124,17 @@
                                 // default handler.

if(err.data && err.data.validationfailures) { - var nicknameFailure = _.find(err.data.validationfailures, function(e) { return e.property == 'nickname'; }); + _.each(err.data.validationfailures, function(vf) { + var model = $scope.createUserForm[vf.property];

-                                    if(nicknameFailure) {
- $scope.createUserForm.nickname.$setValidity(nicknameFailure.message,false);
-                                    }
-                                }
-
-                                if(
-                                    !err.data
-                                        || !err.data.validationfailures
- || 0!=_.filter(err.data.validationfailures, function(e) { return e.property != 'nickname'; }).length) { - $log.error('other validation failures exist; will invoke default handling'); - constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                                        if(model) {
+ model.$setValidity(vf.message, false);
+                                        }
+                                        else {
+ $log.error('other validation failures exist; will invoke default handling'); + constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                                        }
+                                    })
                                 }

                                 break;
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgicon.html Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgicon.html Thu Jan 16 08:37:44 2014 UTC
@@ -7,8 +7,8 @@
         <label>Package</label>
         <div class="form-control-group">
             <div class="form-control-group-static">
-            <pkg-label pkg="pkg"></pkg-label>
-                </div>
+                <pkg-label pkg="pkg"></pkg-label>
+            </div>
         </div>

         <label for="icon-32-file">Icon 32x32px</label>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewuser.html Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewuser.html Thu Jan 16 08:37:44 2014 UTC
@@ -6,6 +6,12 @@
         {{user.nickname}}
     </h1>

+    <h2>Actions</h2>
+
+    <ul>
+ <li><a href="" ng-click="goChangePassword()">Change password</a></li>
+    </ul>
+
 </div>

 <div class="footer"></div>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewusercontroller.js Fri Nov 15 08:51:45 2013 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewusercontroller.js Thu Jan 16 08:37:44 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -47,6 +47,10 @@
                 );
             };

+            $scope.goChangePassword = function() {
+                $location.path('/changepassword/' + $scope.user.nickname);
+            }
+
         }
     ]
 );
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Thu Jan 16 08:37:44 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -10,6 +10,7 @@
             $routeProvider
.when('/authenticateuser',{controller:'AuthenticateUserController', templateUrl:'/js/app/controller/authenticateuser.html'}) .when('/createuser',{controller:'CreateUserController', templateUrl:'/js/app/controller/createuser.html'}) + .when('/changepassword/:nickname',{controller:'ChangePasswordController', templateUrl:'/js/app/controller/changepassword.html'}) .when('/viewuser/:nickname',{controller:'ViewUserController', templateUrl:'/js/app/controller/viewuser.html'}) .when('/viewpkg/:name/:version/:architectureCode',{controller:'ViewPkgController', templateUrl:'/js/app/controller/viewpkg.html'}) .when('/editpkgicon/:name',{controller:'EditPkgIconController', templateUrl:'/js/app/controller/editpkgicon.html'})

Other related posts:

  • » [haiku-depot-web] [haiku-depot-web-app] push by haiku.li...@xxxxxxxxx - + implemented a change password function... on 2014-01-16 08:38 GMT - haiku-depot-web-app