From 793ba76415498399ad75e7474894a848b960d603 Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Fri, 4 Mar 2016 22:22:54 -0500 Subject: [PATCH] Finalize basic client portal password support --- .../Controllers/Auth/ClientAuthController.php | 127 ------------- .../Controllers/ClientAuth/AuthController.php | 62 +++++++ .../ClientAuth/PasswordController.php | 168 ++++++++++++++++++ app/Http/Middleware/Authenticate.php | 88 ++++++--- app/Http/routes.php | 15 +- app/Providers/AuthServiceProvider.php | 46 ----- config/auth.php | 27 ++- resources/views/client.blade.php | 3 + resources/views/clientauth/login.blade.php | 116 ++++++++++++ resources/views/clientauth/password.blade.php | 101 +++++++++++ resources/views/clientauth/reset.blade.php | 104 +++++++++++ resources/views/clients/edit.blade.php | 2 +- .../views/emails/client_password.blade.php | 26 +++ resources/views/invoices/edit.blade.php | 5 +- 14 files changed, 680 insertions(+), 210 deletions(-) delete mode 100644 app/Http/Controllers/Auth/ClientAuthController.php create mode 100644 app/Http/Controllers/ClientAuth/AuthController.php create mode 100644 app/Http/Controllers/ClientAuth/PasswordController.php delete mode 100644 app/Providers/AuthServiceProvider.php create mode 100644 resources/views/clientauth/login.blade.php create mode 100644 resources/views/clientauth/password.blade.php create mode 100644 resources/views/clientauth/reset.blade.php create mode 100644 resources/views/emails/client_password.blade.php diff --git a/app/Http/Controllers/Auth/ClientAuthController.php b/app/Http/Controllers/Auth/ClientAuthController.php deleted file mode 100644 index 263dd8e4f786..000000000000 --- a/app/Http/Controllers/Auth/ClientAuthController.php +++ /dev/null @@ -1,127 +0,0 @@ -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; - } -} diff --git a/app/Http/Controllers/ClientAuth/AuthController.php b/app/Http/Controllers/ClientAuth/AuthController.php new file mode 100644 index 000000000000..079b95984eac --- /dev/null +++ b/app/Http/Controllers/ClientAuth/AuthController.php @@ -0,0 +1,62 @@ +only('password'); + $credentials['id'] = null; + + $invitation_key = session('invitation_key'); + if($invitation_key){ + $invitation = Invitation::where('invitation_key', '=', $invitation_key)->first(); + if ($invitation && !$invitation->is_deleted) { + $credentials['id'] = $invitation->contact_id; + } + } + + return $credentials; + } + + /** + * Validate the user login request. + * + * @param \Illuminate\Http\Request $request + * @return void + */ + protected function validateLogin(Request $request) + { + $this->validate($request, [ + 'password' => 'required', + ]); + } +} diff --git a/app/Http/Controllers/ClientAuth/PasswordController.php b/app/Http/Controllers/ClientAuth/PasswordController.php new file mode 100644 index 000000000000..1c67f41ffd7c --- /dev/null +++ b/app/Http/Controllers/ClientAuth/PasswordController.php @@ -0,0 +1,168 @@ +middleware('guest'); + Config::set("auth.defaults.passwords","client"); + } + + public function showLinkRequestForm() + { + return view('clientauth.password'); + } + + /** + * Send a reset link to the given user. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function sendResetLinkEmail(Request $request) + { + $broker = $this->getBroker(); + + $contact_id = null; + $invitation_key = session('invitation_key'); + if($invitation_key){ + $invitation = Invitation::where('invitation_key', '=', $invitation_key)->first(); + if ($invitation && !$invitation->is_deleted) { + $contact_id = $invitation->contact_id; + } + } + + $response = Password::broker($broker)->sendResetLink(array('id'=>$contact_id), function (Message $message) { + $message->subject($this->getEmailSubject()); + }); + + switch ($response) { + case Password::RESET_LINK_SENT: + return $this->getSendResetLinkEmailSuccessResponse($response); + + case Password::INVALID_USER: + default: + return $this->getSendResetLinkEmailFailureResponse($response); + } + } + + /** + * Display the password reset view for the given token. + * + * If no token is present, display the link request form. + * + * @param \Illuminate\Http\Request $request + * @param string|null $invitation_key + * @param string|null $token + * @return \Illuminate\Http\Response + */ + public function showResetForm(Request $request, $invitation_key = null, $token = null) + { + if (is_null($token)) { + return $this->getEmail(); + } + + return view('clientauth.reset')->with(compact('token', 'invitation_key')); + } + + + + /** + * Display the password reset view for the given token. + * + * If no token is present, display the link request form. + * + * @param \Illuminate\Http\Request $request + * @param string|null $invitation_key + * @param string|null $token + * @return \Illuminate\Http\Response + */ + public function getReset(Request $request, $invitation_key = null, $token = null) + { + return $this->showResetForm($request, $invitation_key, $token); + } + + /** + * Reset the given user's password. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function reset(Request $request) + { + $this->validate($request, $this->getResetValidationRules()); + + $credentials = $request->only( + 'password', 'password_confirmation', 'token' + ); + + $credentials['id'] = null; + + $invitation_key = $request->input('invitation_key'); + if($invitation_key){ + $invitation = Invitation::where('invitation_key', '=', $invitation_key)->first(); + if ($invitation && !$invitation->is_deleted) { + $credentials['id'] = $invitation->contact_id; + } + } + + $broker = $this->getBroker(); + + $response = Password::broker($broker)->reset($credentials, function ($user, $password) { + $this->resetPassword($user, $password); + }); + + switch ($response) { + case Password::PASSWORD_RESET: + return $this->getResetSuccessResponse($response); + + default: + return $this->getResetFailureResponse($request, $response); + } + } + + /** + * Get the password reset validation rules. + * + * @return array + */ + protected function getResetValidationRules() + { + return [ + 'token' => 'required', + 'password' => 'required|confirmed|min:6', + ]; + } +} diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 1780b299f005..08097d6a9b80 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -1,28 +1,13 @@ auth = $auth; - } - /** * Handle an incoming request. * @@ -30,9 +15,45 @@ class Authenticate { * @param \Closure $next * @return mixed */ - public function handle($request, Closure $next) + public function handle($request, Closure $next, $guard = 'user') { - if ($this->auth->guest()) + $authenticated = Auth::guard($guard)->check(); + + if($guard == 'client' && !empty($request->invitation_key)){ + $old_key = session('invitation_key'); + if($old_key && $old_key != $request->invitation_key){ + if($this->getInvitationContactId($old_key) != $this->getInvitationContactId($request->invitation_key)){ + // This is a different client; reauthenticate + $authenticated = false; + Auth::guard($guard)->logout(); + } + } + Session::put('invitation_key', $request->invitation_key); + } + + if($guard=='client'){ + + if(Auth::guard('user')->check()){ + // This is an admin; let them pretend to be a client + $authenticated = true; + } + + // Does this account require portal passwords? + $invitation_key = session('invitation_key'); + $account = Account::whereId($this->getInvitationAccountId($invitation_key))->first(); + if(!$account->enable_portal_password || !$account->isPro()){ + $authenticated = true; + } + + if(!$authenticated){ + $contact = Contact::whereId($this->getInvitationContactId($invitation_key))->first(); + if($contact && !$contact->password){ + $authenticated = true; + } + } + } + + if (!$authenticated) { if ($request->ajax()) { @@ -40,11 +61,30 @@ class Authenticate { } else { - return redirect()->guest('/login'); + return redirect()->guest($guard=='client'?'/client/login':'/login'); } } return $next($request); } - + + protected function getInvitation($key){ + $invitation = Invitation::where('invitation_key', '=', $key)->first(); + if ($invitation && !$invitation->is_deleted) { + return $invitation; + } + else return null; + } + + protected function getInvitationContactId($key){ + $invitation = $this->getInvitation($key); + + return $invitation?$invitation->contact_id:null; + } + + protected function getInvitationAccountId($key){ + $invitation = $this->getInvitation($key); + + return $invitation?$invitation->account_id:null; + } } diff --git a/app/Http/routes.php b/app/Http/routes.php index 1ec65eb34d08..3e784670aeb8 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -35,7 +35,7 @@ Route::get('/keep_alive', 'HomeController@keepAlive'); Route::post('/get_started', 'AccountController@getStarted'); // Client visible pages -Route::group(['middleware' => 'auth'], function() { +Route::group(['middleware' => 'auth:client'], function() { Route::get('view/{invitation_key}', 'PublicClientController@view'); Route::get('download/{invitation_key}', 'PublicClientController@download'); Route::get('view', 'HomeController@viewLogo'); @@ -78,6 +78,15 @@ Route::get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\Pa Route::post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset')); Route::get('/user/confirm/{code}', 'UserController@confirm'); +// Client auth +Route::get('/client/login', array('as' => 'login', 'uses' => 'ClientAuth\AuthController@getLogin')); +Route::post('/client/login', array('as' => 'login', 'uses' => 'ClientAuth\AuthController@postLogin')); +Route::get('/client/logout', array('as' => 'logout', 'uses' => 'ClientAuth\AuthController@getLogout')); +Route::get('/client/forgot', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getEmail')); +Route::post('/client/forgot', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postEmail')); +Route::get('/client/password/reset/{invitation_key}/{token}', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getReset')); +Route::post('/client/password/reset', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postReset')); + if (Utils::isNinja()) { Route::post('/signup/register', 'AccountController@doRegister'); @@ -89,7 +98,7 @@ if (Utils::isReseller()) { Route::post('/reseller_stats', 'AppController@stats'); } -Route::group(['middleware' => 'auth'], function() { +Route::group(['middleware' => 'auth:user'], function() { Route::get('dashboard', 'DashboardController@index'); Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible'); Route::get('hide_message', 'HomeController@hideMessage'); @@ -685,4 +694,4 @@ if (Utils::isNinjaDev()) //ini_set('memory_limit','1024M'); //Auth::loginUsingId(1); } -*/ \ No newline at end of file +*/ diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php deleted file mode 100644 index 569762b38938..000000000000 --- a/app/Providers/AuthServiceProvider.php +++ /dev/null @@ -1,46 +0,0 @@ -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(); - }); - }); - } -} \ No newline at end of file diff --git a/config/auth.php b/config/auth.php index dd7606cc5326..7b08fd473147 100644 --- a/config/auth.php +++ b/config/auth.php @@ -13,7 +13,7 @@ return [ */ 'defaults' => [ - 'guard' => 'web', + 'guard' => 'user', 'passwords' => 'users', ], @@ -35,10 +35,15 @@ return [ */ 'guards' => [ - 'web' => [ + 'user' => [ 'driver' => 'session', 'provider' => 'users', ], + + 'client' => [ + 'driver' => 'session', + 'provider' => 'client', + ], 'api' => [ 'driver' => 'token', @@ -68,11 +73,11 @@ return [ 'driver' => 'eloquent', 'model' => App\Models\User::class, ], - - // 'users' => [ - // 'driver' => 'database', - // 'table' => 'users', - // ], + + 'client' => [ + 'driver' => 'eloquent', + 'model' => App\Models\Contact::class, + ] ], /* @@ -97,7 +102,13 @@ return [ 'passwords' => [ 'users' => [ 'provider' => 'users', - 'email' => 'emails.password', //auth.emails.password + 'email' => 'emails.password', + 'table' => 'password_resets', + 'expire' => 60, + ], + 'client' => [ + 'provider' => 'client', + 'email' => 'emails.client_password', 'table' => 'password_resets', 'expire' => 60, ], diff --git a/resources/views/client.blade.php b/resources/views/client.blade.php index 2dd313456b74..dcf72234634d 100644 --- a/resources/views/client.blade.php +++ b/resources/views/client.blade.php @@ -32,6 +32,9 @@ {!! Former::text('last_name')->data_bind("value: last_name, valueUpdate: 'afterkeydown'") !!} {!! Former::text('email')->data_bind("value: email, valueUpdate: 'afterkeydown'") !!} {!! Former::text('phone')->data_bind("value: phone, valueUpdate: 'afterkeydown'") !!} + @if ($account->isPro() && $account->enable_portal_password) + {!! Former::password('password')->data_bind("value: password()?'-%unchanged%-':'', valueUpdate: 'afterkeydown'") !!} + @endif
diff --git a/resources/views/clientauth/login.blade.php b/resources/views/clientauth/login.blade.php new file mode 100644 index 000000000000..d21165eda28f --- /dev/null +++ b/resources/views/clientauth/login.blade.php @@ -0,0 +1,116 @@ +@extends('master') + +@section('head') + + + + + + +@endsection + +@section('body') +
+ + @include('partials.warn_session', ['redirectTo' => '/client/login']) + + {!! Former::open('client/login') + ->rules(['password' => 'required']) + ->addClass('form-signin') !!} + {{ Former::populateField('remember', 'true') }} + + +
+

+ {!! Former::password('password')->placeholder(trans('texts.password'))->raw() !!} + {!! Former::hidden('remember')->raw() !!} +

+ +

{!! Button::success(trans('texts.login')) + ->withAttributes(['id' => 'loginButton']) + ->large()->submit()->block() !!}

+ + + + + @if (count($errors->all())) +
+ @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
    + @endif + + @if (Session::has('warning')) +
    {{ Session::get('warning') }}
    + @endif + + @if (Session::has('message')) +
    {{ Session::get('message') }}
    + @endif + + @if (Session::has('error')) +
  • {{ Session::get('error') }}
  • + @endif + +
    + + {!! Former::close() !!} +
    +@endsection \ No newline at end of file diff --git a/resources/views/clientauth/password.blade.php b/resources/views/clientauth/password.blade.php new file mode 100644 index 000000000000..a4f6396757fc --- /dev/null +++ b/resources/views/clientauth/password.blade.php @@ -0,0 +1,101 @@ +@extends('master') + +@section('head') + + + + + + +@stop + +@section('body') +
    + +{!! Former::open('client/forgot')->addClass('form-signin') !!} + +
    + +

    {!! Button::success(trans('texts.send_email'))->large()->submit()->block() !!}

    + + @if (count($errors->all())) +
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
    + @endif + + @if (session('status')) +
    + {{ session('status') }} +
    + @endif + + + @if (Session::has('warning')) +
    {{ Session::get('warning') }}
    + @endif + + @if (Session::has('message')) +
    {{ Session::get('message') }}
    + @endif + + @if (Session::has('error')) +
    {{ Session::get('error') }}
    + @endif + + {!! Former::close() !!} + +
    +
    + + + +@stop \ No newline at end of file diff --git a/resources/views/clientauth/reset.blade.php b/resources/views/clientauth/reset.blade.php new file mode 100644 index 000000000000..93dd6d7a69c0 --- /dev/null +++ b/resources/views/clientauth/reset.blade.php @@ -0,0 +1,104 @@ +@extends('master') + +@section('head') + + + + + + +@stop + +@section('body') +
    + + {!! Former::open('/client/password/reset')->addClass('form-signin')->rules(array( + 'password' => 'required', + 'password_confirmation' => 'required', + )) !!} + + +
    + + + + +

    + {!! Former::password('password')->placeholder(trans('texts.password'))->raw() !!} + {!! Former::password('password_confirmation')->placeholder(trans('texts.confirm_password'))->raw() !!} + +

    + +

    {!! Button::success(trans('texts.save'))->large()->submit()->block() !!}

    + + + @if (count($errors->all())) +
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
    + @endif + + + @if (Session::has('warning')) +
    {{ Session::get('warning') }}
    + @endif + + @if (Session::has('message')) +
    {{ Session::get('message') }}
    + @endif + + @if (Session::has('error')) +
    {{ Session::get('error') }}
    + @endif + + + {!! Former::close() !!} +
    + +
    + +@stop \ No newline at end of file diff --git a/resources/views/clients/edit.blade.php b/resources/views/clients/edit.blade.php index 4419ca45d0fe..dd4b28273430 100644 --- a/resources/views/clients/edit.blade.php +++ b/resources/views/clients/edit.blade.php @@ -93,7 +93,7 @@ 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) + @if ($account->isPro() && $account->enable_portal_password) {!! Former::password('password')->data_bind("value: password()?'-%unchanged%-':'', valueUpdate: 'afterkeydown', attr: {name: 'contacts[' + \$index() + '][password]'}") !!} @endif diff --git a/resources/views/emails/client_password.blade.php b/resources/views/emails/client_password.blade.php new file mode 100644 index 000000000000..f4b5d0d3b3c5 --- /dev/null +++ b/resources/views/emails/client_password.blade.php @@ -0,0 +1,26 @@ +@extends('emails.master_user') + +@section('body') +
    + {{ trans('texts.reset_password') }} +
    +   +
    +
    + @include('partials.email_button', [ + 'link' => URL::to("client/password/reset/".session('invitation_key')."/{$token}"), + 'field' => 'reset', + 'color' => '#36c157', + ]) +
    +
    +   +
    + {{ trans('texts.email_signature') }}
    + {{ trans('texts.email_from') }} +
    +   +
    + {{ trans('texts.reset_password_footer') }} +
    +@stop \ No newline at end of file diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 652a0d365fda..9a4c7f0bb388 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -543,7 +543,10 @@ ->addClass('client-email') !!} {!! Former::text('phone')->data_bind("value: phone, valueUpdate: 'afterkeydown', attr: {name: 'client[contacts][' + \$index() + '][phone]'}") !!} -f + @if ($account->isPro() && $account->enable_portal_password) + {!! Former::password('password')->data_bind("value: (typeof password=='function'?password():null)?'-%unchanged%-':'', valueUpdate: 'afterkeydown', + attr: {name: 'client[contacts][' + \$index() + '][password]'}") !!} + @endif