mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-17 16:42:48 +01:00
Add option for client portal password
This commit is contained in:
parent
f26ab6fe5b
commit
6376302ed9
@ -150,7 +150,7 @@ class AccountController extends BaseController
|
||||
} elseif ($section == ACCOUNT_INVOICE_DESIGN || $section == ACCOUNT_CUSTOMIZE_DESIGN) {
|
||||
return self::showInvoiceDesign($section);
|
||||
} elseif ($section == ACCOUNT_CLIENT_PORTAL) {
|
||||
return self::showClientViewStyling();
|
||||
return self::showClientPortal();
|
||||
} elseif ($section === ACCOUNT_TEMPLATES_AND_REMINDERS) {
|
||||
return self::showTemplates();
|
||||
} elseif ($section === ACCOUNT_PRODUCTS) {
|
||||
@ -398,7 +398,7 @@ class AccountController extends BaseController
|
||||
return View::make("accounts.{$section}", $data);
|
||||
}
|
||||
|
||||
private function showClientViewStyling()
|
||||
private function showClientPortal()
|
||||
{
|
||||
$account = Auth::user()->account->load('country');
|
||||
$css = $account->client_view_css ? $account->client_view_css : '';
|
||||
@ -414,6 +414,9 @@ class AccountController extends BaseController
|
||||
|
||||
$data = [
|
||||
'client_view_css' => $css,
|
||||
'enable_portal_password' => $account->enable_portal_password,
|
||||
'fill_portal_password' => $account->fill_portal_password,
|
||||
'send_portal_password' => $account->send_portal_password,
|
||||
'title' => trans("texts.client_portal"),
|
||||
'section' => ACCOUNT_CLIENT_PORTAL,
|
||||
];
|
||||
@ -528,6 +531,9 @@ class AccountController extends BaseController
|
||||
|
||||
$account = Auth::user()->account;
|
||||
$account->client_view_css = $sanitized_css;
|
||||
$account->enable_portal_password = Input::get('enable_portal_password');
|
||||
$account->fill_portal_password = Input::get('fill_portal_password');
|
||||
$account->send_portal_password = Input::get('send_portal_password');
|
||||
$account->save();
|
||||
|
||||
Session::flash('message', trans('texts.updated_settings'));
|
||||
|
127
app/Http/Controllers/Auth/ClientAuthController.php
Normal file
127
app/Http/Controllers/Auth/ClientAuthController.php
Normal file
@ -0,0 +1,127 @@
|
||||
<?php namespace App\Http\Controllers\Auth;
|
||||
|
||||
use Auth;
|
||||
use Event;
|
||||
use Utils;
|
||||
use Session;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User;
|
||||
use App\Events\UserLoggedIn;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Ninja\Repositories\AccountRepository;
|
||||
use App\Services\AuthService;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
use Illuminate\Contracts\Auth\Registrar;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
|
||||
|
||||
class ClientAuthController extends Controller {
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Registration & Login Controller
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This controller handles the registration of new users, as well as the
|
||||
| authentication of existing users. By default, this controller uses
|
||||
| a simple trait to add these behaviors. Why don't you explore it?
|
||||
|
|
||||
*/
|
||||
|
||||
use AuthenticatesAndRegistersUsers;
|
||||
|
||||
protected $loginPath = '/client/login';
|
||||
protected $redirectTo = '/client/dashboard';
|
||||
protected $guard = 'contact';
|
||||
protected $authService;
|
||||
protected $accountRepo;
|
||||
|
||||
/**
|
||||
* Create a new authentication controller instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Guard $auth
|
||||
* @param \Illuminate\Contracts\Auth\Registrar $registrar
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Guard $auth, Registrar $registrar, AccountRepository $repo, AuthService $authService)
|
||||
{
|
||||
$this->auth = $auth;
|
||||
$this->registrar = $registrar;
|
||||
$this->accountRepo = $repo;
|
||||
$this->authService = $authService;
|
||||
|
||||
//$this->middleware('guest', ['except' => 'getLogout']);
|
||||
}
|
||||
|
||||
public function authLogin($provider, Request $request)
|
||||
{
|
||||
return $this->authService->execute($provider, $request->has('code'));
|
||||
}
|
||||
|
||||
public function authUnlink()
|
||||
{
|
||||
$this->accountRepo->unlinkUserFromOauth(Auth::user());
|
||||
|
||||
Session::flash('message', trans('texts.updated_settings'));
|
||||
return redirect()->to('/settings/' . ACCOUNT_USER_DETAILS);
|
||||
}
|
||||
|
||||
public function getLoginWrapper()
|
||||
{
|
||||
if (!Utils::isNinja() && !User::count()) {
|
||||
return redirect()->to('invoice_now');
|
||||
}
|
||||
|
||||
return self::getLogin();
|
||||
}
|
||||
|
||||
public function postLoginWrapper(Request $request)
|
||||
{
|
||||
|
||||
$userId = Auth::check() ? Auth::user()->id : null;
|
||||
$user = User::where('email', '=', $request->input('email'))->first();
|
||||
|
||||
if ($user && $user->failed_logins >= MAX_FAILED_LOGINS) {
|
||||
Session::flash('error', trans('texts.invalid_credentials'));
|
||||
return redirect()->to('login');
|
||||
}
|
||||
|
||||
$response = self::postLogin($request);
|
||||
|
||||
if (Auth::check()) {
|
||||
Event::fire(new UserLoggedIn());
|
||||
|
||||
$users = false;
|
||||
// we're linking a new account
|
||||
if ($request->link_accounts && $userId && Auth::user()->id != $userId) {
|
||||
$users = $this->accountRepo->associateAccounts($userId, Auth::user()->id);
|
||||
Session::flash('message', trans('texts.associated_accounts'));
|
||||
// check if other accounts are linked
|
||||
} else {
|
||||
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
|
||||
}
|
||||
Session::put(SESSION_USER_ACCOUNTS, $users);
|
||||
|
||||
} elseif ($user) {
|
||||
$user->failed_logins = $user->failed_logins + 1;
|
||||
$user->save();
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
public function getLogoutWrapper()
|
||||
{
|
||||
if (Auth::check() && !Auth::user()->registered) {
|
||||
$account = Auth::user()->account;
|
||||
$this->accountRepo->unlinkAccount($account);
|
||||
$account->forceDelete();
|
||||
}
|
||||
|
||||
$response = self::getLogout();
|
||||
|
||||
Session::flush();
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
@ -35,17 +35,19 @@ Route::get('/keep_alive', 'HomeController@keepAlive');
|
||||
Route::post('/get_started', 'AccountController@getStarted');
|
||||
|
||||
// Client visible pages
|
||||
Route::get('view/{invitation_key}', 'PublicClientController@view');
|
||||
Route::get('download/{invitation_key}', 'PublicClientController@download');
|
||||
Route::get('view', 'HomeController@viewLogo');
|
||||
Route::get('approve/{invitation_key}', 'QuoteController@approve');
|
||||
Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment');
|
||||
Route::post('payment/{invitation_key}', 'PaymentController@do_payment');
|
||||
Route::get('complete', 'PaymentController@offsite_payment');
|
||||
Route::get('client/quotes', 'PublicClientController@quoteIndex');
|
||||
Route::get('client/invoices', 'PublicClientController@invoiceIndex');
|
||||
Route::get('client/payments', 'PublicClientController@paymentIndex');
|
||||
Route::get('client/dashboard', 'PublicClientController@dashboard');
|
||||
Route::group(['middleware' => 'auth'], function() {
|
||||
Route::get('view/{invitation_key}', 'PublicClientController@view');
|
||||
Route::get('download/{invitation_key}', 'PublicClientController@download');
|
||||
Route::get('view', 'HomeController@viewLogo');
|
||||
Route::get('approve/{invitation_key}', 'QuoteController@approve');
|
||||
Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment');
|
||||
Route::post('payment/{invitation_key}', 'PaymentController@do_payment');
|
||||
Route::get('complete', 'PaymentController@offsite_payment');
|
||||
Route::get('client/quotes', 'PublicClientController@quoteIndex');
|
||||
Route::get('client/invoices', 'PublicClientController@invoiceIndex');
|
||||
Route::get('client/payments', 'PublicClientController@paymentIndex');
|
||||
Route::get('client/dashboard', 'PublicClientController@dashboard');
|
||||
});
|
||||
Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'PublicClientController@quoteDatatable'));
|
||||
Route::get('api/client.invoices', array('as'=>'api.client.invoices', 'uses'=>'PublicClientController@invoiceDatatable'));
|
||||
Route::get('api/client.payments', array('as'=>'api.client.payments', 'uses'=>'PublicClientController@paymentDatatable'));
|
||||
|
@ -154,7 +154,15 @@ class Client extends EntityModel
|
||||
$contact = Contact::createNew();
|
||||
$contact->send_invoice = true;
|
||||
}
|
||||
|
||||
|
||||
if (!Utils::isPro() || $this->account->enable_portal_password){
|
||||
if(!empty($data['password']) && $data['password']!='-%unchanged%-'){
|
||||
$contact->password = bcrypt($data['password']);
|
||||
} else if(empty($data['password'])){
|
||||
$contact->password = null;
|
||||
}
|
||||
}
|
||||
|
||||
$contact->fill($data);
|
||||
$contact->is_primary = $isPrimary;
|
||||
|
||||
|
@ -3,10 +3,14 @@
|
||||
use HTML;
|
||||
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Auth\Authenticatable;
|
||||
use Illuminate\Auth\Passwords\CanResetPassword;
|
||||
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
|
||||
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
|
||||
|
||||
class Contact extends EntityModel
|
||||
class Contact extends EntityModel implements AuthenticatableContract, CanResetPasswordContract
|
||||
{
|
||||
use SoftDeletes;
|
||||
use SoftDeletes, Authenticatable, CanResetPassword;
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
protected $fillable = [
|
||||
|
46
app/Providers/AuthServiceProvider.php
Normal file
46
app/Providers/AuthServiceProvider.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php namespace App\Auth;
|
||||
|
||||
use Illuminate\Auth\AuthServiceProvider;
|
||||
use App\Auth\CustomerAuthManager;
|
||||
use App\Auth\SiteGuard;
|
||||
|
||||
class CustomerAuthServiceProvider extends AuthServiceProvider
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
$this->app->alias('customerauth', 'App\Auth\CustomerAuthManager');
|
||||
$this->app->alias('customerauth.driver', 'App\Auth\SiteGuard');
|
||||
$this->app->alias('customerauth.driver', 'App\Contracts\Auth\SiteGuard');
|
||||
|
||||
parent::register();
|
||||
}
|
||||
|
||||
protected function registerAuthenticator()
|
||||
{
|
||||
$this->app->singleton('customerauth', function ($app) {
|
||||
$app['customerauth.loaded'] = true;
|
||||
|
||||
return new CustomerAuthManager($app);
|
||||
});
|
||||
|
||||
$this->app->singleton('customerauth.driver', function ($app) {
|
||||
return $app['customerauth']->driver();
|
||||
});
|
||||
}
|
||||
|
||||
protected function registerUserResolver()
|
||||
{
|
||||
$this->app->bind('Illuminate\Contracts\Auth\Authenticatable', function ($app) {
|
||||
return $app['customerauth']->user();
|
||||
});
|
||||
}
|
||||
|
||||
protected function registerRequestRebindHandler()
|
||||
{
|
||||
$this->app->rebinding('request', function ($app, $request) {
|
||||
$request->setUserResolver(function() use ($app) {
|
||||
return $app['customerauth']->user();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -1,67 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Authentication Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the authentication driver that will be utilized.
|
||||
| This driver manages the retrieval and authentication of the users
|
||||
| attempting to get access to protected areas of your application.
|
||||
|
|
||||
| Supported: "database", "eloquent"
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => 'eloquent',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Model
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the "Eloquent" authentication driver, we need to know which
|
||||
| Eloquent model should be used to retrieve your users. Of course, it
|
||||
| is often just the "User" model but you may use whatever you like.
|
||||
|
|
||||
*/
|
||||
|
||||
'model' => 'App\Models\User',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the "Database" authentication driver, we need to know which
|
||||
| table should be used to retrieve your users. We have chosen a basic
|
||||
| default value but you may easily change it to any table you like.
|
||||
|
|
||||
*/
|
||||
|
||||
'table' => 'users',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Reset Settings
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may set the options for resetting passwords including the view
|
||||
| that is your password reset e-mail. You can also set the name of the
|
||||
| table that maintains all of the reset tokens for your application.
|
||||
|
|
||||
| The expire time is the number of minutes that the reset token should be
|
||||
| considered valid. This security feature keeps tokens short-lived so
|
||||
| they have less time to be guessed. You may change this as needed.
|
||||
|
|
||||
*/
|
||||
|
||||
'password' => [
|
||||
'email' => 'emails.password',
|
||||
'table' => 'password_resets',
|
||||
'expire' => 60,
|
||||
],
|
||||
|
||||
]
|
||||
];
|
||||
|
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddClientPassword extends Migration {
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('accounts', function ($table) {
|
||||
$table->boolean('enable_portal_password')->default(0);
|
||||
$table->boolean('fill_portal_password')->default(0);
|
||||
$table->boolean('send_portal_password')->default(0);
|
||||
});
|
||||
|
||||
Schema::table('contacts', function ($table) {
|
||||
$table->string('password', 255)->nullable();
|
||||
$table->boolean('confirmation_code', 255)->nullable();
|
||||
$table->boolean('remember_token', 100)->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('accounts', function ($table) {
|
||||
$table->dropColumn('enable_portal_password');
|
||||
$table->dropColumn('fill_portal_password');
|
||||
$table->dropColumn('send_portal_password');
|
||||
});
|
||||
|
||||
Schema::table('contacts', function ($table) {
|
||||
$table->dropColumn('password');
|
||||
$table->dropColumn('confirmation_code');
|
||||
$table->dropColumn('remember_token');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -1028,6 +1028,12 @@ $LANG = array(
|
||||
'user_unconfirmed' => 'Please confirm your account to send emails',
|
||||
'invalid_contact_email' => 'Invalid contact email',
|
||||
],
|
||||
|
||||
// Client Passwords
|
||||
'client_portal_login_settings'=>'Login',
|
||||
'enable_portal_password'=>'Require a password',
|
||||
'send_portal_password'=>'Generate password automatically',
|
||||
'fill_portal_password'=>'Include password in invoice emails',
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
@ -13,6 +13,7 @@
|
||||
->addClass('warn-on-exit') !!}
|
||||
|
||||
{!! Former::populateField('client_view_css', $client_view_css) !!}
|
||||
{!! Former::populateField('enable_portal_password', $enable_portal_password) !!}
|
||||
|
||||
@if (!Utils::isNinja() && !Auth::user()->account->isWhiteLabel())
|
||||
<div class="alert alert-warning" style="font-size:larger;">
|
||||
@ -26,23 +27,38 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{!! trans('texts.custom_css') !!}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
{!! Former::textarea('client_view_css')
|
||||
->label(trans('texts.custom_css'))
|
||||
->rows(10)
|
||||
->raw()
|
||||
->autofocus()
|
||||
->maxlength(60000)
|
||||
->style("min-width:100%;max-width:100%;font-family:'Roboto Mono', 'Lucida Console', Monaco, monospace;font-size:14px;'") !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{!! trans('texts.client_portal_login_settings') !!}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{!! Former::checkbox('enable_portal_password')
|
||||
->text(trans('texts.enable_portal_password'))
|
||||
->label(' ') !!}
|
||||
{!! Former::checkbox('fill_portal_password')
|
||||
->text(trans('texts.fill_portal_password'))
|
||||
->label(' ') !!}
|
||||
{!! Former::checkbox('send_portal_password')
|
||||
->text(trans('texts.send_portal_password'))
|
||||
->label(' ') !!}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{!! trans('texts.custom_css') !!}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
{!! Former::textarea('client_view_css')
|
||||
->label(trans('texts.custom_css'))
|
||||
->rows(10)
|
||||
->raw()
|
||||
->autofocus()
|
||||
->maxlength(60000)
|
||||
->style("min-width:100%;max-width:100%;font-family:'Roboto Mono', 'Lucida Console', Monaco, monospace;font-size:14px;'") !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -51,5 +67,13 @@
|
||||
</center>
|
||||
|
||||
{!! Former::close() !!}
|
||||
|
||||
<script>
|
||||
$('#enable_portal_password').change(fixCheckboxes);
|
||||
function fixCheckboxes(){
|
||||
var checked = $('#enable_portal_password').is(':checked');
|
||||
$('#fill_portal_password').prop('disabled', !checked);
|
||||
$('#send_portal_password').prop('disabled', !checked);
|
||||
}
|
||||
fixCheckboxes();
|
||||
</script>
|
||||
@stop
|
@ -93,7 +93,10 @@
|
||||
attr: {name: 'contacts[' + \$index() + '][email]', id:'email'+\$index()}") !!}
|
||||
{!! Former::text('phone')->data_bind("value: phone, valueUpdate: 'afterkeydown',
|
||||
attr: {name: 'contacts[' + \$index() + '][phone]'}") !!}
|
||||
|
||||
@if (!Utils::isPro() || $account->enable_portal_password)
|
||||
{!! Former::password('password')->data_bind("value: password()?'-%unchanged%-':'', valueUpdate: 'afterkeydown',
|
||||
attr: {name: 'contacts[' + \$index() + '][password]'}") !!}
|
||||
@endif
|
||||
<div class="form-group">
|
||||
<div class="col-lg-8 col-lg-offset-4 bold">
|
||||
<span class="redlink bold" data-bind="visible: $parent.contacts().length > 1">
|
||||
|
@ -529,7 +529,7 @@
|
||||
->addClass('client-email') !!}
|
||||
{!! Former::text('phone')->data_bind("value: phone, valueUpdate: 'afterkeydown',
|
||||
attr: {name: 'client[contacts][' + \$index() + '][phone]'}") !!}
|
||||
|
||||
f
|
||||
<div class="form-group">
|
||||
<div class="col-lg-8 col-lg-offset-4">
|
||||
<span class="redlink bold" data-bind="visible: $parent.contacts().length > 1">
|
||||
|
Loading…
Reference in New Issue
Block a user