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)
{
return view('auth.passwords.reset')->with(
['token' => $token]
);
return view('auth.passwords.reset')->with([
'token' => $token,
'url' => '/password/reset'
]);
}
}

View File

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

View File

@ -103,7 +103,7 @@ class Authenticate
} else {
if ($guard == 'client') {
$url = '/client/login';
if (Utils::isNinja()) {
if (Utils::isNinjaProd()) {
if ($account && Utils::getSubdomain() == 'app') {
$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;
}
}
// 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',
'autofills_city_state' => 'Auto-fills city/state',
'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('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;
<br/>
@ -205,7 +205,7 @@
var isValid = val;
if (field != 'current_password') {
isValid = val.length >= 6;
isValid = val.length >= 8;
}
if (isValid && field == 'confirm_password') {
@ -221,6 +221,15 @@
$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);

View File

@ -3,10 +3,11 @@
@section('form')
<div class="container">
{!! Former::open('/password/reset')
{!! Former::open($url)
->addClass('form-signin')
->autocomplete('off')
->rules(array(
'email' => 'required|email',
'password' => 'required',
'password_confirmation' => 'required',
)) !!}
@ -39,13 +40,17 @@
<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::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>
<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() !!}
@ -53,7 +58,32 @@
<script type="text/javascript">
$(function() {
$('#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>
@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 */
$.extend(true, $.fn.dataTable.defaults, {
"bSortClasses": false,

View File

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