1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-08 12:12:48 +01:00

Added password strength check

This commit is contained in:
Hillel Coren 2018-04-10 18:07:10 +03:00
parent a09ba934cf
commit d0d510331a
12 changed files with 215 additions and 172 deletions

View File

@ -58,8 +58,9 @@ class ResetPasswordController extends Controller
public function showResetForm(Request $request, $token = null) public function showResetForm(Request $request, $token = null)
{ {
return view('auth.passwords.reset')->with( return view('auth.passwords.reset')->with([
['token' => $token] 'token' => $token,
); 'url' => '/password/reset'
]);
} }
} }

View File

@ -54,9 +54,10 @@ class ResetPasswordController extends Controller
public function showResetForm(Request $request, $token = null) public function showResetForm(Request $request, $token = null)
{ {
return view('clientauth.passwords.reset')->with( return view('auth.passwords.reset')->with([
['token' => $token] 'token' => $token,
); 'url' => '/client/password/reset'
]);
} }
} }

View File

@ -103,7 +103,7 @@ class Authenticate
} else { } else {
if ($guard == 'client') { if ($guard == 'client') {
$url = '/client/login'; $url = '/client/login';
if (Utils::isNinja()) { if (Utils::isNinjaProd()) {
if ($account && Utils::getSubdomain() == 'app') { if ($account && Utils::getSubdomain() == 'app') {
$url .= '?account_key=' . $account->account_key; $url .= '?account_key=' . $account->account_key;
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1265,3 +1265,33 @@ function openUrlOnClick(url, event) {
window.location = url; window.location = url;
} }
} }
// https://stackoverflow.com/a/11268104/497368
function scorePassword(pass) {
var score = 0;
if (!pass)
return score;
// award every unique letter until 5 repetitions
var letters = new Object();
for (var i=0; i<pass.length; i++) {
letters[pass[i]] = (letters[pass[i]] || 0) + 1;
score += 5.0 / letters[pass[i]];
}
// bonus points for mixing it up
var variations = {
digits: /\d/.test(pass),
lower: /[a-z]/.test(pass),
upper: /[A-Z]/.test(pass),
nonWords: /\W/.test(pass),
}
variationCount = 0;
for (var check in variations) {
variationCount += (variations[check] == true) ? 1 : 0;
}
score += (variationCount - 1) * 10;
return parseInt(score);
}

View File

@ -2825,6 +2825,10 @@ $LANG = array(
'unapproved_proposal' => 'Unapproved Proposal', 'unapproved_proposal' => 'Unapproved Proposal',
'autofills_city_state' => 'Auto-fills city/state', 'autofills_city_state' => 'Auto-fills city/state',
'no_match_found' => 'No match found', 'no_match_found' => 'No match found',
'password_strength' => 'Password Strength',
'strength_weak' => 'Weak',
'strength_good' => 'Good',
'strength_strong' => 'Strong',
); );

View File

@ -129,7 +129,7 @@
{!! Former::password('current_password')->style('width:300px') !!} {!! Former::password('current_password')->style('width:300px') !!}
{!! Former::password('newer_password')->style('width:300px')->label(trans('texts.new_password')) !!} {!! Former::password('newer_password')->style('width:300px')->label(trans('texts.new_password')) !!}
{!! Former::password('confirm_password')->style('width:300px') !!} {!! Former::password('confirm_password')->style('width:300px')->help('<span id="passwordStrength">&nbsp;</span>') !!}
&nbsp; &nbsp;
<br/> <br/>
@ -205,7 +205,7 @@
var isValid = val; var isValid = val;
if (field != 'current_password') { if (field != 'current_password') {
isValid = val.length >= 6; isValid = val.length >= 8;
} }
if (isValid && field == 'confirm_password') { if (isValid && field == 'confirm_password') {
@ -221,6 +221,15 @@
$input.closest('div.form-group').addClass('has-error'); $input.closest('div.form-group').addClass('has-error');
} }
} }
if (field == 'newer_password') {
var score = scorePassword(val);
if (isValid) {
isValid = score > 50;
}
showPasswordStrength(val, score);
}
}); });
$('#changePasswordButton').prop('disabled', !isFormValid); $('#changePasswordButton').prop('disabled', !isFormValid);

View File

@ -3,10 +3,11 @@
@section('form') @section('form')
<div class="container"> <div class="container">
{!! Former::open('/password/reset') {!! Former::open($url)
->addClass('form-signin') ->addClass('form-signin')
->autocomplete('off') ->autocomplete('off')
->rules(array( ->rules(array(
'email' => 'required|email',
'password' => 'required', 'password' => 'required',
'password_confirmation' => 'required', 'password_confirmation' => 'required',
)) !!} )) !!}
@ -39,13 +40,17 @@
<input type="hidden" name="token" value="{{{ $token }}}"> <input type="hidden" name="token" value="{{{ $token }}}">
<div> <div onkeyup="validateForm()" onclick="validateForm()" onkeydown="validateForm(event)">
{!! Former::text('email')->placeholder(trans('texts.email'))->raw() !!} {!! Former::text('email')->placeholder(trans('texts.email'))->raw() !!}
{!! Former::password('password')->placeholder(trans('texts.password'))->autocomplete('new-password')->raw() !!} {!! Former::password('password')->placeholder(trans('texts.password'))->autocomplete('new-password')->raw() !!}
{!! Former::password('password_confirmation')->placeholder(trans('texts.confirm_password'))->autocomplete('new-password')->raw() !!} {!! Former::password('password_confirmation')->placeholder(trans('texts.confirm_password'))->autocomplete('new-password')->raw() !!}
</div> </div>
<p>{!! Button::success(trans('texts.save'))->large()->submit()->withAttributes(['class' => 'green'])->block() !!}</p> <div id="passwordStrength" style="font-weight:normal;padding:16px">
&nbsp;
</div>
<p>{!! Button::success(trans('texts.save'))->large()->submit()->withAttributes(['class' => 'green', 'id' => 'saveButton', 'disabled' => true])->block() !!}</p>
{!! Former::close() !!} {!! Former::close() !!}
@ -53,7 +58,32 @@
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {
$('#password').focus(); $('#password').focus();
validateForm();
}) })
function validateForm() {
var isValid = true;
if (! $('#email').val()) {
isValid = false;
}
var password = $('#password').val();
var confirm = $('#password_confirmation').val();
if (! password || password != confirm || password.length < 8) {
isValid = false;
}
var score = scorePassword(password);
if (score < 50) {
isValid = false;
}
showPasswordStrength(password, score);
$('#saveButton').prop('disabled', ! isValid);
}
</script> </script>
@endsection @endsection

View File

@ -1,57 +0,0 @@
@extends('login')
@section('form')
<div class="container">
{!! Former::open('/client/password/reset')
->addClass('form-signin')
->autocomplete('false')
->rules(array(
'password' => 'required',
'password_confirmation' => 'required',
)) !!}
@include('partials.autocomplete_fix')
<h2 class="form-signin-heading">{{ trans('texts.set_password') }}</h2>
<hr class="green">
@if (count($errors->all()))
<div class="alert alert-danger">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</div>
@endif
<!-- if there are login errors, show them here -->
@if (Session::has('warning'))
<div class="alert alert-warning">{{ Session::get('warning') }}</div>
@endif
@if (Session::has('message'))
<div class="alert alert-info">{{ Session::get('message') }}</div>
@endif
@if (Session::has('error'))
<div class="alert alert-danger">{{ Session::get('error') }}</div>
@endif
<input type="hidden" name="token" value="{{{ $token }}}">
<div>
{!! Former::text('email')->placeholder(trans('texts.email'))->raw() !!}
{!! Former::password('password')->placeholder(trans('texts.password'))->autocomplete('new-password')->raw() !!}
{!! Former::password('password_confirmation')->placeholder(trans('texts.confirm_password'))->autocomplete('new-password')->raw() !!}
</div>
<p>{!! Button::success(trans('texts.save'))->large()->submit()->withAttributes(['class' => 'green'])->block() !!}</p>
{!! Former::close() !!}
</div>
<script type="text/javascript">
$(function () {
$('#password').focus();
})
</script>
@endsection

View File

@ -137,6 +137,22 @@
}); });
} }
function showPasswordStrength(password, score) {
if (password) {
var str = {!! json_encode(trans('texts.password_strength')) !!} + ': ';
if (password.length < 8 || score < 50) {
str += {!! json_encode(trans('texts.strength_weak')) !!};
} else if (score < 75) {
str += {!! json_encode(trans('texts.strength_good')) !!};
} else {
str += {!! json_encode(trans('texts.strength_strong')) !!};
}
$('#passwordStrength').html(str);
} else {
$('#passwordStrength').html('&nbsp;');
}
}
/* Set the defaults for DataTables initialisation */ /* Set the defaults for DataTables initialisation */
$.extend(true, $.fn.dataTable.defaults, { $.extend(true, $.fn.dataTable.defaults, {
"bSortClasses": false, "bSortClasses": false,

View File

@ -1,53 +1,53 @@
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {
validateSignUp(); validateSignUp();
$('#signUpModal').on('shown.bs.modal', function () { $('#signUpModal').on('shown.bs.modal', function () {
trackEvent('/account', '/view_sign_up'); trackEvent('/account', '/view_sign_up');
// change the type after page load to prevent errors in Chrome console // change the type after page load to prevent errors in Chrome console
$('#new_password').attr('type', 'password'); $('#new_password').attr('type', 'password');
$(['first_name','last_name','email','password']).each(function(i, field) { $(['first_name','last_name','email','password']).each(function(i, field) {
var $input = $('form.signUpForm #new_'+field); var $input = $('form.signUpForm #new_'+field);
if (!$input.val()) { if (!$input.val()) {
$input.focus(); $input.focus();
return false; return false;
} }
}); });
}) })
@if (Auth::check() && !Utils::isNinja() && ! Auth::user()->registered) @if (Auth::check() && !Utils::isNinja() && ! Auth::user()->registered)
$('#closeSignUpButton').hide(); $('#closeSignUpButton').hide();
showSignUp(); showSignUp();
@elseif(Session::get('sign_up') || Input::get('sign_up')) @elseif(Session::get('sign_up') || Input::get('sign_up'))
showSignUp(); showSignUp();
@endif @endif
// Ensure terms is checked for sign up form // Ensure terms is checked for sign up form
@if (Auth::check()) @if (Auth::check())
setSignupEnabled(false); setSignupEnabled(false);
$("#terms_checkbox").change(function() { $("#terms_checkbox").change(function() {
setSignupEnabled(this.checked); setSignupEnabled(this.checked);
}); });
@endif @endif
}); });
function showSignUp() { function showSignUp() {
if (location.href.indexOf('/dashboard') == -1) { if (location.href.indexOf('/dashboard') == -1) {
location.href = "{{ url('/dashboard') }}?sign_up=true"; location.href = "{{ url('/dashboard') }}?sign_up=true";
} else { } else {
$('#signUpModal').modal('show'); $('#signUpModal').modal('show');
} }
} }
function hideSignUp() { function hideSignUp() {
$('#signUpModal').modal('hide'); $('#signUpModal').modal('hide');
} }
function setSignupEnabled(enabled) { function setSignupEnabled(enabled) {
$('.signup-form input[type=text]').prop('disabled', !enabled); $('.signup-form input[type=text]').prop('disabled', !enabled);
$('.signup-form input[type=password]').prop('disabled', !enabled); $('.signup-form input[type=password]').prop('disabled', !enabled);
if (enabled) { if (enabled) {
@ -55,100 +55,108 @@
} else { } else {
$('.signup-form a.btn').addClass('disabled'); $('.signup-form a.btn').addClass('disabled');
} }
} }
function validateSignUp(showError) function validateSignUp(showError) {
{
var isFormValid = true; var isFormValid = true;
$(['first_name','last_name','email','password']).each(function(i, field) { $(['first_name','last_name','email','password']).each(function(i, field) {
var $input = $('form.signUpForm #new_'+field), var $input = $('form.signUpForm #new_'+field),
val = $.trim($input.val()); val = $.trim($input.val());
var isValid = val && val.length >= (field == 'password' ? 6 : 1); var isValid = val && val.length >= (field == 'password' ? 8 : 1);
if (isValid && field == 'email') {
isValid = isValidEmailAddress(val); if (field == 'password') {
} var score = scorePassword(val);
if (isValid) { if (isValid) {
$input.closest('div.form-group').removeClass('has-error').addClass('has-success'); isValid = score > 50;
} else { }
isFormValid = false;
$input.closest('div.form-group').removeClass('has-success'); showPasswordStrength(val, score);
if (showError) { }
$input.closest('div.form-group').addClass('has-error');
if (isValid && field == 'email') {
isValid = isValidEmailAddress(val);
}
if (isValid) {
$input.closest('div.form-group').removeClass('has-error').addClass('has-success');
} else {
isFormValid = false;
$input.closest('div.form-group').removeClass('has-success');
if (showError) {
$input.closest('div.form-group').addClass('has-error');
}
} }
}
}); });
if (!$('#terms_checkbox').is(':checked')) { if (!$('#terms_checkbox').is(':checked')) {
isFormValid = false; isFormValid = false;
} }
$('#saveSignUpButton').prop('disabled', !isFormValid); $('#saveSignUpButton').prop('disabled', !isFormValid);
return isFormValid; return isFormValid;
} }
function validateServerSignUp() function validateServerSignUp() {
{
if (!validateSignUp(true)) { if (!validateSignUp(true)) {
return; return;
} }
$('#signUpDiv, #signUpFooter').hide(); $('#signUpDiv, #signUpFooter').hide();
$('#working').show(); $('#working').show();
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: '{{ URL::to('signup/validate') }}', url: '{{ URL::to('signup/validate') }}',
data: 'email=' + $('form.signUpForm #new_email').val(), data: 'email=' + $('form.signUpForm #new_email').val(),
success: function(result) { success: function(result) {
if (result == 'available') { if (result == 'available') {
submitSignUp(); submitSignUp();
} else { } else {
$('#errorTaken').show(); $('#errorTaken').show();
$('form.signUpForm #new_email').closest('div.form-group').removeClass('has-success').addClass('has-error'); $('form.signUpForm #new_email').closest('div.form-group').removeClass('has-success').addClass('has-error');
$('#signUpDiv, #signUpFooter').show(); $('#signUpDiv, #signUpFooter').show();
$('#working').hide(); $('#working').hide();
}
} }
}
}); });
} }
function submitSignUp() { function submitSignUp() {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: '{{ URL::to('signup/submit') }}', url: '{{ URL::to('signup/submit') }}',
data: 'new_email=' + encodeURIComponent($('form.signUpForm #new_email').val()) + data: 'new_email=' + encodeURIComponent($('form.signUpForm #new_email').val()) +
'&new_password=' + encodeURIComponent($('form.signUpForm #new_password').val()) + '&new_password=' + encodeURIComponent($('form.signUpForm #new_password').val()) +
'&new_first_name=' + encodeURIComponent($('form.signUpForm #new_first_name').val()) + '&new_first_name=' + encodeURIComponent($('form.signUpForm #new_first_name').val()) +
'&new_last_name=' + encodeURIComponent($('form.signUpForm #new_last_name').val()) + '&new_last_name=' + encodeURIComponent($('form.signUpForm #new_last_name').val()) +
'&go_pro=' + $('#go_pro').val(), '&go_pro=' + $('#go_pro').val(),
success: function(result) { success: function(result) {
if (result) { if (result) {
@if (Auth::user()->registered) @if (Auth::user()->registered)
hideSignUp(); hideSignUp();
NINJA.formIsChanged = false; NINJA.formIsChanged = false;
location.href = "{{ url('/dashboard') }}"; location.href = "{{ url('/dashboard') }}";
@else @else
handleSignedUp(); handleSignedUp();
NINJA.isRegistered = true; NINJA.isRegistered = true;
$('#gettingStartedIframe').attr('src', '{{ str_replace('watch?v=', 'embed/', config('ninja.video_urls.getting_started')) }}'); $('#gettingStartedIframe').attr('src', '{{ str_replace('watch?v=', 'embed/', config('ninja.video_urls.getting_started')) }}');
$('#signUpButton').hide(); $('#signUpButton').hide();
$('#myAccountButton').html(result); $('#myAccountButton').html(result);
$('#signUpSuccessDiv, #signUpFooter, #closeSignUpButton').show(); $('#signUpSuccessDiv, #signUpFooter, #closeSignUpButton').show();
$('#working, #saveSignUpButton').hide(); $('#working, #saveSignUpButton').hide();
@endif @endif
}
} }
}
}); });
} }
function handleSignedUp() { function handleSignedUp() {
if (isStorageSupported()) { if (isStorageSupported()) {
localStorage.setItem('guest_key', ''); localStorage.setItem('guest_key', '');
} }
fbq('track', 'CompleteRegistration'); fbq('track', 'CompleteRegistration');
trackEvent('/account', '/signed_up'); trackEvent('/account', '/signed_up');
} }
</script> </script>
@ -234,7 +242,8 @@
->placeholder(trans('texts.password')) ->placeholder(trans('texts.password'))
->autocomplete('new-password') ->autocomplete('new-password')
->data_lpignore('true') ->data_lpignore('true')
->label(' ') !!} ->label(' ')
->help('<span id="passwordStrength">&nbsp;</span>') !!}
{{ Former::setOption('TwitterBootstrap3.labelWidths.large', 4) }} {{ Former::setOption('TwitterBootstrap3.labelWidths.large', 4) }}
{{ Former::setOption('TwitterBootstrap3.labelWidths.small', 4) }} {{ Former::setOption('TwitterBootstrap3.labelWidths.small', 4) }}