pyc-website

main website for pyc inc.

git clone https://9o.is/git/pyc-website.git

commit a506de9746b1d473909b15d9aabdc717d367a630
parent a6790e6a6d93bf02eb3fd934d5074a449c2d9d93
Author: Jul <jul@9o.is>
Date:   Sat,  3 May 2014 15:04:48 -0400

converted password change form to angular

Diffstat:
Msrc/main/scala/inc/pyc/snippet/UserScreens.scala | 44--------------------------------------------
Msrc/main/scala/inc/pyc/snippet/UserSnip.scala | 36++++++++++++++++++++++++++++++++++++
Msrc/main/webapp/app/App.js | 39+++++++++++++++++++++++++++++++++++++++
Msrc/main/webapp/app/App.spec.js | 8++++++++
Msrc/main/webapp/settings/password.html | 2+-
Asrc/main/webapp/templates-hidden/parts/change-password-form.html | 25+++++++++++++++++++++++++
Asrc/test/scala/inc/pyc/snippet/UserSnipSpec.scala | 20++++++++++++++++++++
7 files changed, 129 insertions(+), 45 deletions(-)

diff --git a/src/main/scala/inc/pyc/snippet/UserScreens.scala b/src/main/scala/inc/pyc/snippet/UserScreens.scala @@ -26,50 +26,6 @@ sealed trait BaseCurrentUserScreen extends BaseScreen { } /* -object AccountScreen extends BaseCurrentUserScreen { - addFields(() => userVar.is.accountScreenFields) - - def finish() { - userVar.is.save - S.notice("Account settings saved") - } -} -*/ - -sealed trait BasePasswordScreen { - this: LiftScreen => - - def pwdName: String = "Password" - def pwdMinLength: Int = 8 - def pwdMaxLength: Int = 32 - - val passwordField = password(pwdName, "", trim, - valMinLen(pwdMinLength, "Password must be at least "+pwdMinLength+" characters"), - valMaxLen(pwdMaxLength, "Password must be "+pwdMaxLength+" characters or less"), - "tabindex" -> "1" - ) - val confirmPasswordField = password("Confirm Password", "", trim, "tabindex" -> "1") - - def passwordsMustMatch(): Errors = { - if (passwordField.is != confirmPasswordField.is) - List(FieldError(confirmPasswordField, "Passwords must match")) - else Nil - } -} - - -object PasswordScreen extends BaseCurrentUserScreen with BasePasswordScreen { - override def pwdName = "New Password" - override def validations = passwordsMustMatch _ :: super.validations - - def finish() { - userVar.is.password(passwordField.is) - userVar.is.password.hashIt - userVar.is.save() - } -} - -/* * Use for editing the currently logged in user only. */ /* diff --git a/src/main/scala/inc/pyc/snippet/UserSnip.scala b/src/main/scala/inc/pyc/snippet/UserSnip.scala @@ -50,6 +50,42 @@ class UserLogin extends AngularSnippet { } } +class PasswordChange extends AngularSnippet { + + /** + * Passwords must match this pattern. + * - makes sure there are no white-space characters + * - minimum length of 8 + * - at least one non-alpha character + */ + def passwordPattern: String = "^(?=.*[^a-zA-Z])\\S{8,}$" + + def roundTrips: List[RoundTripInfo] = List("submit" -> submit _) + + def submit(model: JValue): JValue = + (for(user <- User.currentUser) yield { + (for(JString(password) <- model \ "password") yield { + + if(password matches passwordPattern) { + + user.password(password) + user.password.hashIt + user.save() + + NgAlert.success( + <i class="fa-fw fa fa-thumbs-o-up"></i> ++ + <strong>Your password is now updated.</strong>) + } else { + NgAlert.danger( + <i class="fa-fw fa fa-thumbs-o-down"></i> ++ + <strong>Your password must contain</strong> ++ + <ul><li>no white spaces</li><li>at least 8 characters</li><li>at least one non-alpha character</li></ul>, Nil) + } + + }).headOption.getOrElse(JNull) + }).openOr(JNull) +} + class PasswordRecovery extends AngularSnippet { def roundTrips: List[RoundTripInfo] = List("submit" -> submit _) diff --git a/src/main/webapp/app/App.js b/src/main/webapp/app/App.js @@ -1,6 +1,7 @@ var app = angular.module("app", ['google-maps', 'ui.bootstrap', 'ui.router', 'ui.mask']); var ZIP_CODE_REGEXP = /^(\d{5}(-\d{4})?|[A-Z]\d[A-Z] *\d[A-Z]\d)$/; +var PASSWORD_REGEXP = /^(?=.*[^a-zA-Z])\S{8,}$/; var UNSAFE_URL_REGEXP = /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/; app.run(["$rootScope", "$window", "$state", "$stateParams", function ($rootScope, $window, $state, $stateParams) { @@ -37,6 +38,23 @@ app.directive('disabler', ['$compile', function($compile) { }; }]); +app.directive('match', function () { + return { + require: 'ngModel', + restrict: 'A', + scope: { + match: '=' + }, + link: function(scope, elem, attrs, ctrl) { + scope.$watch(function() { + return (ctrl.$pristine && angular.isUndefined(ctrl.$modelValue)) || scope.match === ctrl.$modelValue; + }, function(currentValue) { + ctrl.$setValidity('match', currentValue); + }); + } + }; +}); + app.controller('AlertCtrl', ['$scope', function($scope) { $scope.alerts = []; @@ -192,6 +210,27 @@ app.controller('FindAtmCtrl', ['$scope', '$state', '$rootScope', function($scope }; }]); +app.controller('PasswordChangeCtrl', ['$scope', '$controller', '$rootScope', function($scope, $controller, $rootScope) { + $controller('FormCtrl', {$scope: $scope}); + + $scope.password_regex = PASSWORD_REGEXP; + + $scope.save = function() { + $scope.loading = true; + window.PasswordChange.submit($scope.model).then(function(alert) { + $scope.$apply(function() { + $scope.loading = false; + if(alert.msg_type === "success") { + $rootScope.$broadcast('alertDialog', alert); + $scope.reset(); + } else { + $rootScope.$broadcast('alertDialog', alert); + } + }); + }); + }; +}]); + app.controller('GMapCtrl', ['$scope', function($scope) { $scope.lat = 40.778202; $scope.long = -74.122381; diff --git a/src/main/webapp/app/App.spec.js b/src/main/webapp/app/App.spec.js @@ -6,6 +6,7 @@ describe("App", function() { AtmApplicationCtrl, UserRegistrationCtrl, PasswordRecoveryCtrl, + PasswordChangeCtrl, UserLoginCtrl, FindAtmCtrl, GMapCtrl; @@ -20,6 +21,7 @@ describe("App", function() { AtmApplicationCtrl = $controller('AtmApplicationCtrl', {$scope: scope}); UserRegistrationCtrl = $controller('UserRegistrationCtrl', {$scope: scope}); PasswordRecoveryCtrl = $controller('PasswordRecoveryCtrl', {$scope: scope}); + PasswordChangeCtrl = $controller('PasswordChangeCtrl', {$scope: scope}); UserLoginCtrl = $controller('UserLoginCtrl', {$scope: scope}); FindAtmCtrl = $controller('FindAtmCtrl', {$scope: scope}); GMapCtrl = $controller('GMapCtrl', {$scope: scope}); @@ -50,6 +52,12 @@ describe("App", function() { }); }); + describe("PasswordChangeCtrl", function() { + it("should exist", function() { + expect(PasswordChangeCtrl).toBeDefined(); + }); + }); + describe("UserLoginCtrl", function() { it("should exist", function() { expect(UserLoginCtrl).toBeDefined(); diff --git a/src/main/webapp/settings/password.html b/src/main/webapp/settings/password.html @@ -2,7 +2,7 @@ <div class="row margin-top-10"> <div class="col-xs-12 col-sm-8 col-md-6 col-sm-offset-2 col-md-offset-0"> <div class="well"> - <div data-lift="PasswordScreen?ajax=true"></div> + <span data-lift="embed?what=/templates-hidden/parts/change-password-form"></span> </div> </div> <div class="col-xs-12 col-sm-8 col-md-6 col-sm-offset-2 col-md-offset-0"> diff --git a/src/main/webapp/templates-hidden/parts/change-password-form.html b/src/main/webapp/templates-hidden/parts/change-password-form.html @@ -0,0 +1,24 @@ +<div data-lift="PasswordChange" ng-controller="PasswordChangeCtrl" ng-cloak> + <form name="form" class="smart-form client-form" ng-submit="save()" novalidate> + <fieldset> + <section> + <label>New Password</label> + <label class="input"> + <i class="icon-append fa fa-key"></i> + <input name="password" ng-model="model.password" type="password" ng-pattern="password_regex" required> + </label> + </section> + <section> + <label>Confirm Password</label> + <label class="input"> + <i class="icon-append fa fa-key"></i> + <input name="passwordconfirm" ng-model="model.passwordconfirm" type="password" data-match="model.password" required> + </label> + </section> + </fieldset> + + <footer> + <button type="submit" class="btn btn-primary" ng-disabled="form.$invalid" disabler ng-model="loading">Change Password</button> + </footer> + </form> +</div> +\ No newline at end of file diff --git a/src/test/scala/inc/pyc/snippet/UserSnipSpec.scala b/src/test/scala/inc/pyc/snippet/UserSnipSpec.scala @@ -0,0 +1,19 @@ +package inc.pyc +package snippet + +class UserSnipSpec extends BaseWordSpec { + + "Password" should { + "have minimum requirements" in { + + val pattern: String = new PasswordChange().passwordPattern + + val valid = List("helloworld1", "hel?lo$(world", "&Helloworld") + + val invalid = List("helloworld","hello1W", "hello world2") + + valid.map(_.matches(pattern) should equal (true)) + invalid.map(_.matches(pattern) should equal (false)) + } + } +} +\ No newline at end of file