-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Password reset controller and password strength directive
- Loading branch information
1 parent
42cd1c3
commit e06608d
Showing
3 changed files
with
243 additions
and
0 deletions.
There are no files selected for viewing
149 changes: 149 additions & 0 deletions
149
src/app/components/mno-password-strength/mno-password-strength.coffee
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
angular.module 'mnoEnterpriseAngular' | ||
.directive 'mnoPasswordStrength', (MnoErrorsHandler) -> | ||
require: "ngModel" | ||
restrict: 'A' | ||
scope: | ||
passwordScore: '=' #outject score | ||
|
||
link: (scope, element, attrs, ctrl) -> | ||
measureStrength = (p) -> | ||
matches = | ||
pos: {} | ||
neg: {} | ||
|
||
counts = | ||
pos: {} | ||
neg: | ||
seqLetter: 0 | ||
seqNumber: 0 | ||
seqSymbol: 0 | ||
|
||
tmp = undefined | ||
strength = 0 | ||
letters = "abcdefghijklmnopqrstuvwxyz" | ||
numbers = "01234567890" | ||
symbols = "\\!@#$%&/()=?¿" | ||
back = undefined | ||
forth = undefined | ||
|
||
if p | ||
# Benefits | ||
matches.pos.lower = p.match(/[a-z]/g) | ||
matches.pos.upper = p.match(/[A-Z]/g) | ||
matches.pos.numbers = p.match(/\d/g) | ||
matches.pos.symbols = p.match(/[$-/:-?{-~!^_`\[\]]/g) | ||
matches.pos.middleNumber = p.slice(1, -1).match(/\d/g) | ||
matches.pos.middleSymbol = p.slice(1, -1).match(/[$-/:-?{-~!^_`\[\]]/g) | ||
counts.pos.lower = (if matches.pos.lower then matches.pos.lower.length else 0) | ||
counts.pos.upper = (if matches.pos.upper then matches.pos.upper.length else 0) | ||
counts.pos.numbers = (if matches.pos.numbers then matches.pos.numbers.length else 0) | ||
counts.pos.symbols = (if matches.pos.symbols then matches.pos.symbols.length else 0) | ||
tmp = _.reduce(counts.pos, (memo, val) -> | ||
# if has count will add 1 | ||
memo + Math.min(1, val) | ||
, 0) | ||
counts.pos.numChars = p.length | ||
tmp += (if (counts.pos.numChars >= 8) then 1 else 0) | ||
counts.pos.requirements = (if (tmp >= 3) then tmp else 0) | ||
counts.pos.middleNumber = (if matches.pos.middleNumber then matches.pos.middleNumber.length else 0) | ||
counts.pos.middleSymbol = (if matches.pos.middleSymbol then matches.pos.middleSymbol.length else 0) | ||
|
||
# Deductions | ||
matches.neg.consecLower = p.match(/(?=([a-z]{2}))/g) | ||
matches.neg.consecUpper = p.match(/(?=([A-Z]{2}))/g) | ||
matches.neg.consecNumbers = p.match(/(?=(\d{2}))/g) | ||
matches.neg.onlyNumbers = p.match(/^[0-9]*$/g) | ||
matches.neg.onlyLetters = p.match(/^([a-z]|[A-Z])*$/g) | ||
counts.neg.consecLower = (if matches.neg.consecLower then matches.neg.consecLower.length else 0) | ||
counts.neg.consecUpper = (if matches.neg.consecUpper then matches.neg.consecUpper.length else 0) | ||
counts.neg.consecNumbers = (if matches.neg.consecNumbers then matches.neg.consecNumbers.length else 0) | ||
|
||
# sequential letters (back and forth) | ||
i = 0 | ||
while i < letters.length - 2 | ||
p2 = p.toLowerCase() | ||
forth = letters.substring(i, parseInt(i + 3)) | ||
back = _(forth).split("").reverse() | ||
counts.neg.seqLetter++ if p2.indexOf(forth) isnt -1 or p2.indexOf(back) isnt -1 | ||
i++ | ||
|
||
# sequential numbers (back and forth) | ||
i = 0 | ||
while i < numbers.length - 2 | ||
forth = numbers.substring(i, parseInt(i + 3)) | ||
back = _(forth).split("").reverse() | ||
counts.neg.seqNumber++ if p.indexOf(forth) isnt -1 or p.toLowerCase().indexOf(back) isnt -1 | ||
i++ | ||
|
||
# sequential symbols (back and forth) | ||
i = 0 | ||
while i < symbols.length - 2 | ||
forth = symbols.substring(i, parseInt(i + 3)) | ||
back = _(forth).split("").reverse() | ||
counts.neg.seqSymbol++ if p.indexOf(forth) isnt -1 or p.toLowerCase().indexOf(back) isnt -1 | ||
i++ | ||
|
||
# repeated chars | ||
counts.neg.repeated = _.chain(p.toLowerCase().split("")).countBy((val) -> | ||
val | ||
).reject((val) -> | ||
val is 1 | ||
).reduce((memo, val) -> | ||
memo + val | ||
, 0).value() | ||
|
||
# Calculations | ||
strength += counts.pos.numChars * 4 | ||
strength += (counts.pos.numChars - counts.pos.upper) * 2 if counts.pos.upper | ||
strength += (counts.pos.numChars - counts.pos.lower) * 2 if counts.pos.lower | ||
strength += counts.pos.numbers * 4 if counts.pos.upper or counts.pos.lower | ||
strength += counts.pos.symbols * 6 | ||
strength += (counts.pos.middleSymbol + counts.pos.middleNumber) * 2 | ||
strength += counts.pos.requirements * 2 | ||
strength -= counts.neg.consecLower * 2 | ||
strength -= counts.neg.consecUpper * 2 | ||
strength -= counts.neg.consecNumbers * 2 | ||
strength -= counts.neg.seqNumber * 3 | ||
strength -= counts.neg.seqLetter * 3 | ||
strength -= counts.neg.seqSymbol * 3 | ||
strength -= counts.pos.numChars if matches.neg.onlyNumbers | ||
strength -= counts.pos.numChars if matches.neg.onlyLetters | ||
strength -= (counts.neg.repeated / counts.pos.numChars) * 10 if counts.neg.repeated | ||
Math.max 0, Math.min(100, Math.round(strength)) | ||
|
||
getPwStrength = (s) -> | ||
switch Math.round(s / 20) | ||
when 0, 1 | ||
"weak" | ||
when 2,3 | ||
"good" | ||
when 4,5 | ||
"secure" | ||
|
||
getClass = (s) -> | ||
switch getPwStrength(s) | ||
when 'weak' | ||
"danger" | ||
when 'good' | ||
"warning" | ||
when 'secure' | ||
"success" | ||
|
||
isPwStrong = (s) -> | ||
switch getPwStrength(s) | ||
when 'weak' | ||
false | ||
else | ||
true | ||
|
||
scope.$watch (-> ctrl.$modelValue), -> | ||
scope.value = measureStrength(ctrl.$modelValue) | ||
scope.pwStrength = getPwStrength(scope.value) | ||
ctrl.$setValidity('password-strength', isPwStrong(scope.value)) | ||
if scope.passwordScore? | ||
scope.passwordScore.value = scope.pwStrength | ||
scope.passwordScore.class = getClass(scope.value) | ||
scope.passwordScore.showTip = (ctrl.$modelValue? && ctrl.$modelValue != '' && !isPwStrong(scope.value)) | ||
|
||
|
||
return |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
angular.module('mnoEnterpriseAngular') | ||
.controller 'PasswordResetCtrl', ($state, $location, Auth, toastr, MnoErrorsHandler) -> | ||
'ngInject' | ||
|
||
vm = this | ||
vm.resetConfirmed = false | ||
vm.user = { | ||
$pwdScore: {} | ||
} | ||
|
||
vm.password_reset = -> | ||
if vm.form.$invalid && !MnoErrorsHandler.onlyServerError(vm.form) | ||
return | ||
else if vm.user.password != vm.user.password_confirmation | ||
return | ||
|
||
MnoErrorsHandler.resetErrors(vm.form) | ||
|
||
vm.user.reset_password_token = $location.search().reset_password_token | ||
Auth.resetPassword(vm.user).then( | ||
-> | ||
toastr.info('devise.passwords.updated', { | ||
timeOut: 0, | ||
closeButton: true, | ||
extendedTimeOut: 0 | ||
}) | ||
Auth.login(vm.user).then( | ||
-> | ||
$state.go('home.impac') | ||
).finally( -> vm.resetConfirmed = true) | ||
(error) -> | ||
MnoErrorsHandler.processServerError(error, vm.form) | ||
).finally( -> vm.hasClicked = false) | ||
|
||
return true | ||
|
||
return |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
<div class='registration'> | ||
<div class='container'> | ||
<div class='row'> | ||
<div class='login-box-wrapper'> | ||
<div class='login-box-title'> | ||
<h2>{{ 'mno_enterprise.auth.passwords.edit.title' | translate }}</h2> | ||
</div> | ||
<div class='login-box'> | ||
<div class='brand-logo'></div> | ||
<form class="form-horizontal autofill-detect" id="new_user" name="vm.form" ng-submit="vm.password_reset()" validate> | ||
<div class='row'> | ||
<div class='col-sm-12'> | ||
<input mno-password-strength password-score="vm.user.$pwdScore" type="{{isShown ? 'text' : 'password'}}" placeholder="*Password" ng-model="vm.user.password" class="form-control" name="password" id="user_password" autocomplete="off" autofocus required/> | ||
<span class="input-group-addon pw-strength-indicator" ng-class='vm.user.$pwdScore.class' style="display: inline;"> | ||
{{ vm.user.$pwdScore.value }} | ||
</span> | ||
</div> | ||
</div> | ||
<br> | ||
<div class='row'> | ||
<div class='col-sm-12'> | ||
<input type="{{isShown ? 'text' : 'password'}}" placeholder="*Confirm Password" ng-model="vm.user.password_confirmation" class="form-control" name="password_confirmation" id="user_password_confirmation" ng-change="vm.resetConfirmed = false" autocomplete="off" autofocus required /> | ||
</div> | ||
<div class="col-md-3 text-center" style="padding: 0px 2px;"> | ||
<label>Show</label> | ||
<br /> | ||
<input type="checkbox" ng-model="isShown" /> | ||
</div> | ||
</div> | ||
<br> | ||
<div class='row'> | ||
<div class='col-sm-12'> | ||
<div class='text-center'> | ||
<button class='btn btn-warning' ng-click='vm.hasClicked = true' ng-disabled='vm.form.$invalid' ng-hide='vm.hasClicked' type='submit'>{{ 'mno_enterprise.auth.passwords.edit.change' | translate }}</button> | ||
<img class="loader" ng-show='vm.hasClicked'> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
|
||
|
||
<hr> | ||
<a ui-sref="login">Log in</a><br /> | ||
|
||
<a ui-sref="signup">Sign up</a><br /> | ||
|
||
<a href="/mnoe/auth/users/confirmation/new">Didn't receive confirmation instructions?</a><br /> | ||
|
||
<a href="/mnoe/auth/users/unlock/new">Didn't receive unlock instructions?</a><br /> | ||
|
||
<!-- <a href="/mnoe/users/auth/intuit">Sign in with Intuit</a><br /> --> | ||
</form> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> --> |