From e8093ea02f858b5082aea3b575e45d988eabd021 Mon Sep 17 00:00:00 2001 From: DarkGhostHunter Date: Tue, 11 Feb 2020 12:22:24 -0300 Subject: [PATCH 1/9] Minor fixes into the README.md to reflect the current status of the package version. --- README.md | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6ec0be1..db1f921 100644 --- a/README.md +++ b/README.md @@ -191,11 +191,18 @@ Route::get('system/settings') ->middleware('2fa'); ``` -This middleware works much like the `verified` middleware: if the User has not enabled Two Factor Authentication, it will be redirected to a route name containing the warning, which is `2fa.notice` by default. +This middleware works much like the `verified` middleware: if the User has not enabled Two Factor Authentication, it will be redirected to a route name containing the warning, which is `2fa.notice` by default. + +You can implement this easily using this package: + +```php +Route::view('2fa-required', 'laraguard::notice') + ->name('2fa.notice'); +``` ## Protecting the Login -Two Factor Authentication can be victim of brute-force attacks. The attacker will need between 16.000~34.000 requests each second to get the correct codes. +Two Factor Authentication can be victim of brute-force attacks. The attacker will need between 16.000~34.000 requests each second to get the correct code, or less depending on the lifetime of the code. Since the listener throws a response before the default Login throttler increments its failed tries, its recommended to use a try-catch in the `attemptLogin()` method to keep the throttler working. @@ -233,6 +240,10 @@ You will receive the authentication view in `resources/views/vendor/laraguard/au return [ 'listener' => true, 'input' => '2fa_code', + 'cache' => [ + 'store' => null, + 'prefix' => '2fa.code' + ], 'recovery' => [ 'enabled' => true, 'codes' => 10, @@ -280,7 +291,7 @@ This allows to seamlessly intercept the log in attempt and proceed with Two Fact ### Cache Store ```php -return [ +return [ 'cache' => [ 'store' => null, 'prefix' => '2fa.code' @@ -288,7 +299,7 @@ return [ ]; ``` -[RFC 6238](https://tools.ietf.org/html/rfc6238#section-5) states that one-time passwords shouldn't be able to be usable again, even if inside the time window. For this, we need to use the Cache to save code for a given period of time. +[RFC 6238](https://tools.ietf.org/html/rfc6238#section-5) states that one-time passwords shouldn't be able to be usable again, even if inside the time window. For this, we need to use the Cache to save the code for a given period of time. You can change the store to use, which it's the default used by your application, and the prefix to use as cache keys, in case of collisions. @@ -362,7 +373,7 @@ This controls TOTP code generation and verification mechanisms: This configuration values are always passed down to the authentication app as URI parameters: - otpauth://totp/Laravel:taylor@laravel.com?secret=THISISMYSECRETPLEASEDONOTSHAREIT&issuer=Laravel&algorithm=SHA1&digits=6&period=30 + otpauth://totp/Laravel:taylor@laravel.com?secret=THISISMYSECRETPLEASEDONOTSHAREIT&issuer=Laravel&label=taylor%40laravel.com&algorithm=SHA1&digits=6&period=30 These values are printed to each 2FA data inside the application. Changes will only take effect for new activations. @@ -382,7 +393,7 @@ You can override the view, which handles the Two Factor Code verification for th The way it works is very simple: it will hold the User credentials in a hidden input while it asks for the Two Factor Code. The User will send everything again along with the Code, the application will ensure its correct, and complete the log in. -This view and its controller is bypassed if the User doesn't uses Two Factor Authentication, making the log in transparent and non-invasive. +This view and its controller is bypassed if the User doesn't uses Two Factor Authentication, making the log in transparent and non-invasive. ## Security From a21ff4b562110c8e75a2eaa2bc440e0e2325764a Mon Sep 17 00:00:00 2001 From: DarkGhostHunter Date: Tue, 11 Feb 2020 12:23:16 -0300 Subject: [PATCH 2/9] Fixed too long index for morphs #4 --- ...01_01_000000_create_two_factor_authentications_table.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/database/migrations/2020_01_01_000000_create_two_factor_authentications_table.php b/database/migrations/2020_01_01_000000_create_two_factor_authentications_table.php index 6d87bc9..9fe33b3 100644 --- a/database/migrations/2020_01_01_000000_create_two_factor_authentications_table.php +++ b/database/migrations/2020_01_01_000000_create_two_factor_authentications_table.php @@ -1,8 +1,8 @@ bigIncrements('id'); - $table->morphs('authenticatable'); + $table->morphs('authenticatable', '2fa_auth_type_auth_id_index'); $table->binary('shared_secret'); $table->timestampTz('enabled_at')->nullable(); $table->string('label'); From 163650f537bcfd94e4f773713b1d55811a756bde Mon Sep 17 00:00:00 2001 From: DarkGhostHunter Date: Tue, 11 Feb 2020 12:34:29 -0300 Subject: [PATCH 3/9] Testing why Github Actions fails and everywhere else it runs fine. --- ...020_01_01_000000_create_two_factor_authentications_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2020_01_01_000000_create_two_factor_authentications_table.php b/database/migrations/2020_01_01_000000_create_two_factor_authentications_table.php index 9fe33b3..99df5c8 100644 --- a/database/migrations/2020_01_01_000000_create_two_factor_authentications_table.php +++ b/database/migrations/2020_01_01_000000_create_two_factor_authentications_table.php @@ -15,7 +15,7 @@ public function up() { Schema::create('two_factor_authentications', function (Blueprint $table) { $table->bigIncrements('id'); - $table->morphs('authenticatable', '2fa_auth_type_auth_id_index'); + $table->morphs('authenticatable'); $table->binary('shared_secret'); $table->timestampTz('enabled_at')->nullable(); $table->string('label'); From 07f6d81f245739a30ba317d840f8e0ad01847cb2 Mon Sep 17 00:00:00 2001 From: DarkGhostHunter Date: Tue, 11 Feb 2020 13:09:56 -0300 Subject: [PATCH 4/9] Disabled composer Cache. --- .github/workflows/php.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index a7c5065..d885f84 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -21,17 +21,6 @@ jobs: - name: Install dependencies run: composer install --prefer-dist --no-progress --no-suggest - - name: Get Composer Cache Directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Cache dependencies - uses: actions/cache@v1 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - name: Run test suite run: composer run-script test From 499b0e033f6a4398025645c668981a09991f908c Mon Sep 17 00:00:00 2001 From: DarkGhostHunter Date: Tue, 11 Feb 2020 13:46:31 -0300 Subject: [PATCH 5/9] Fixed some variables on workflow. --- .github/workflows/php.yml | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index d885f84..5757f39 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -15,9 +15,20 @@ jobs: uses: shivammathur/setup-php@v1 with: php-version: '7.4' - extension-csv: mbstring, intl + extensions: mbstring, intl coverage: xdebug + - name: Get Composer Cache Directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + - name: Install dependencies run: composer install --prefer-dist --no-progress --no-suggest @@ -26,8 +37,8 @@ jobs: - name: Upload coverage results to Coveralls env: - COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_SERVICE_NAME: github + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_SERVICE_NAME: github run: | - composer require cedx/coveralls - vendor/bin/coveralls build/logs/clover.xml + composer require cedx/coveralls + vendor/bin/coveralls build/logs/clover.xml From ae60611d7128eafac6435de2d218231c27204a31 Mon Sep 17 00:00:00 2001 From: DarkGhostHunter Date: Tue, 11 Feb 2020 13:54:33 -0300 Subject: [PATCH 6/9] Fixed some hash file for cache. --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 5757f39..f42decd 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -26,7 +26,7 @@ jobs: uses: actions/cache@v1 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies From e6742dbfad51e8236afd66a1e7472e321a4f599f Mon Sep 17 00:00:00 2001 From: DarkGhostHunter Date: Tue, 11 Feb 2020 14:30:43 -0300 Subject: [PATCH 7/9] Readded shorter index on morphs. --- ...020_01_01_000000_create_two_factor_authentications_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2020_01_01_000000_create_two_factor_authentications_table.php b/database/migrations/2020_01_01_000000_create_two_factor_authentications_table.php index 99df5c8..9fe33b3 100644 --- a/database/migrations/2020_01_01_000000_create_two_factor_authentications_table.php +++ b/database/migrations/2020_01_01_000000_create_two_factor_authentications_table.php @@ -15,7 +15,7 @@ public function up() { Schema::create('two_factor_authentications', function (Blueprint $table) { $table->bigIncrements('id'); - $table->morphs('authenticatable'); + $table->morphs('authenticatable', '2fa_auth_type_auth_id_index'); $table->binary('shared_secret'); $table->timestampTz('enabled_at')->nullable(); $table->string('label'); From 6cc8066473ec9ce9285a2fd5ff17bed24cf13b40 Mon Sep 17 00:00:00 2001 From: DarkGhostHunter Date: Tue, 11 Feb 2020 14:39:14 -0300 Subject: [PATCH 8/9] Added dumping error to the route testing. --- phpunit.xml.dist | 2 +- tests/RegistersLoginRoute.php | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index fd88042..a6abd70 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -22,7 +22,7 @@ - + diff --git a/tests/RegistersLoginRoute.php b/tests/RegistersLoginRoute.php index caca893..a3892b5 100644 --- a/tests/RegistersLoginRoute.php +++ b/tests/RegistersLoginRoute.php @@ -11,9 +11,14 @@ trait RegistersLoginRoute protected function registerLoginRoute() { Route::post('login', function (Request $request) { - return Auth::guard('web')->attempt($request->only('email', 'password'), $request->filled('remember')) - ? 'authenticated' - : 'unauthenticated'; + try { + return Auth::guard('web')->attempt($request->only('email', 'password'), $request->filled('remember')) + ? 'authenticated' + : 'unauthenticated'; + } catch (\Throwable $exception) { + var_dump([get_class($exception), $exception->getMessage()]); + throw $exception; + } })->middleware('web'); } } From f83c39d410697f7aae832d0e22db2494f4423c85 Mon Sep 17 00:00:00 2001 From: DarkGhostHunter Date: Tue, 11 Feb 2020 18:39:02 -0300 Subject: [PATCH 9/9] Reworked all the package. Laravel <6.15 support dropped for sanity. --- README.md | 11 +- composer.json | 4 +- src/LaraguardServiceProvider.php | 31 ++-- src/Listeners/ChecksTwoFactorCode.php | 75 ++++++++ src/Listeners/EnforceTwoFactorAuth.php | 112 ++++++++++++ src/Listeners/ForcesTwoFactorAuth.php | 185 -------------------- tests/Listeners/ForcesTwoFactorAuthTest.php | 11 +- tests/RegistersLoginRoute.php | 5 +- 8 files changed, 224 insertions(+), 210 deletions(-) create mode 100644 src/Listeners/ChecksTwoFactorCode.php create mode 100644 src/Listeners/EnforceTwoFactorAuth.php delete mode 100644 src/Listeners/ForcesTwoFactorAuth.php diff --git a/README.md b/README.md index db1f921..3c3a950 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,11 @@ Two Factor Authentication via TOTP for all your Users out-of-the-box. This package _silently_ enables authentication using 6 digits codes, without Internet or external providers. +## Requirements + +* Laravel [6.15](https://blog.laravel.com/laravel-v6-15-0-released) or later. +* PHP 7.2+ + ## Table of Contents * [Installation](#installation) @@ -29,7 +34,7 @@ This package _silently_ enables authentication using 6 digits codes, without Int + [Cache Store](#cache-store) + [Recovery](#recovery) + [Safe devices](#safe-devices) - + [Secret length](#secret-bytes) + + [Secret length](#secret-length) + [TOTP configuration](#totp-configuration) + [Custom view](#custom-view) * [Security](#security) @@ -146,7 +151,7 @@ public function showRecoveryCodes(Request $request) ### Logging in -This package hooks into the `Validated` event (or `Attempting` if it doesn't exists) to check the User's Two Factor Authentication configuration preemptively. +This package hooks into the `Attempting` and `Validated` events to check the User's Two Factor Authentication configuration preemptively. 1. If the User has set up Two Factor Authentication, it will be prompted for a 2FA Code, otherwise authentication will proceed as normal. 2. If the Login attempt contains a `2fa_code` with the 2FA Code inside the Request, it will be used to check if its valid and proceed as normal. @@ -272,7 +277,7 @@ return [ ]; ``` -This package works by hooking up the `ForcesTwoFactorAuth` listener to the `Validated`, or `Attempting` event as a fallback. +This package works by hooking up the `ForcesTwoFactorAuth` listener to the `Attempting` and `Validated` events as a fallback. This may work wonders out-of-the-box, but if you want tighter control on how and when prompt for Two Factor Authentication, you can disable it. For example, to create your own 2FA Guard or greatly modify the Login Controller. diff --git a/composer.json b/composer.json index 636816a..097c227 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,8 @@ "ext-json": "*", "bacon/bacon-qr-code": "2.*", "paragonie/constant_time_encoding": "2.*", - "illuminate/support": "^6.13", - "illuminate/auth": "^6.13" + "illuminate/support": "^6.15", + "illuminate/auth": "^6.15" }, "require-dev": { "orchestra/testbench": "^4.0", diff --git a/src/LaraguardServiceProvider.php b/src/LaraguardServiceProvider.php index dc3c77a..2840cc8 100644 --- a/src/LaraguardServiceProvider.php +++ b/src/LaraguardServiceProvider.php @@ -3,7 +3,10 @@ namespace DarkGhostHunter\Laraguard; use Illuminate\Routing\Router; +use Illuminate\Auth\Events\Validated; +use Illuminate\Auth\Events\Attempting; use Illuminate\Support\ServiceProvider; +use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Config\Repository; class LaraguardServiceProvider extends ServiceProvider @@ -25,9 +28,9 @@ public function register() * @param \Illuminate\Routing\Router $router * @return void */ - public function boot(Repository $config, Router $router) + public function boot(Repository $config, Router $router, Dispatcher $dispatcher) { - $this->registerListener($config); + $this->registerListener($config, $dispatcher); $this->registerMiddleware($router); $this->loadViewsFrom(__DIR__ . '/../resources/views', 'laraguard'); @@ -59,28 +62,20 @@ protected function registerMiddleware(Router $router) * Register a listeners to tackle authentication. * * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher */ - protected function registerListener(Repository $config) + protected function registerListener(Repository $config, Dispatcher $dispatcher) { if (! $config['laraguard.listener']) { return; } - // We will check if the "Validated" auth event exists. If it is, this will allow our - // listener to not retrieve the user beforehand, since it'll be already retrieved. - // If not, we listen to the "Attempting" to retrieve and validate it ourselves. - $this->app['events']->listen( - $this->getEventName(), Listeners\ForcesTwoFactorAuth::class + $this->app->singleton(Listeners\EnforceTwoFactorAuth::class); + $dispatcher->listen(Attempting::class, + 'DarkGhostHunter\Laraguard\Listeners\EnforceTwoFactorAuth@saveCredentials' + ); + $dispatcher->listen(Validated::class, + 'DarkGhostHunter\Laraguard\Listeners\EnforceTwoFactorAuth@checkTwoFactor' ); - } - - /** - * Checks if the "Validated" event exists, otherwise fallback to "Attempting". - * - * @return string - */ - protected function getEventName() - { - return class_exists('Illuminate\Auth\Events\Validated') ? 'Illuminate\Auth\Events\Validated' : 'Illuminate\Auth\Events\Attempting'; } } diff --git a/src/Listeners/ChecksTwoFactorCode.php b/src/Listeners/ChecksTwoFactorCode.php new file mode 100644 index 0000000..9a32f6b --- /dev/null +++ b/src/Listeners/ChecksTwoFactorCode.php @@ -0,0 +1,75 @@ +hasTwoFactorEnabled(); + + if ($this->config['laraguard.safe_devices.enabled']) { + return $shouldUse && ! $user->isSafeDevice($this->request); + } + + return $shouldUse; + } + + /** + * Returns if the Request is from a Safe Device. + * + * @param \DarkGhostHunter\Laraguard\Contracts\TwoFactorAuthenticatable $user + * @return bool + */ + protected function isSafeDevice(TwoFactorAuthenticatable $user) + { + return $this->config['laraguard.safe_devices.enabled'] && $user->isSafeDevice($this->request); + } + + /** + * Returns if the Request has the Two Factor Code. + * + * @return bool + */ + protected function hasCode() + { + return $this->request->has($this->input); + } + + /** + * Checks if the Request has a Two Factor Code and is correct (even if is invalid). + * + * @param \DarkGhostHunter\Laraguard\Contracts\TwoFactorAuthenticatable $user + * @return bool + */ + protected function hasValidCode(TwoFactorAuthenticatable $user) + { + return ! validator($this->request->only($this->input), [$this->input => 'alphanum'])->fails() + && $user->validateTwoFactorCode($this->request->input($this->input)); + } + + /** + * Adds a safe device to Two Factor Authentication data. + * + * @param \DarkGhostHunter\Laraguard\Contracts\TwoFactorAuthenticatable $user + * @return void + */ + protected function addSafeDevice(TwoFactorAuthenticatable $user) + { + if ($this->config['laraguard.safe_devices.enabled']) { + $user->addSafeDevice($this->request); + } + } +} diff --git a/src/Listeners/EnforceTwoFactorAuth.php b/src/Listeners/EnforceTwoFactorAuth.php new file mode 100644 index 0000000..12916f2 --- /dev/null +++ b/src/Listeners/EnforceTwoFactorAuth.php @@ -0,0 +1,112 @@ +config = $config; + $this->request = $request; + $this->input = $config['laraguard.input']; + } + + /** + * Saves the Credentials for the User. + * + * @param \Illuminate\Auth\Events\Attempting $event + * @return void + */ + public function saveCredentials(Attempting $event) + { + $this->credentials = $event->credentials; + $this->remember = $event->remember; + } + + /** + * Checks if the user should use Two Factor Auth. + * + * @param \Illuminate\Auth\Events\Validated $event + * @return void + */ + public function checkTwoFactor(Validated $event) + { + if ($this->shouldUseTwoFactorAuth($event->user)) { + + if ($this->isSafeDevice($event->user) || ($this->hasCode() && $invalid = $this->hasValidCode($event->user))) { + return $this->addSafeDevice($event->user); + } + + $this->throwResponse($event->user, isset($invalid)); + } + } + + /** + * Creates a response containing the Two Factor Authentication view. + * + * @param \DarkGhostHunter\Laraguard\Contracts\TwoFactorAuthenticatable $user + * @param bool $error + * @return void + */ + protected function throwResponse(TwoFactorAuthenticatable $user, bool $error = false) + { + $view = view('laraguard::auth', [ + 'action' => request()->fullUrl(), + 'credentials' => $this->credentials, + 'user' => $user, + 'error' => $error, + 'remember' => $this->remember, + ]); + + return response($view, $error ? 422 : 403)->throwResponse(); + } +} diff --git a/src/Listeners/ForcesTwoFactorAuth.php b/src/Listeners/ForcesTwoFactorAuth.php deleted file mode 100644 index f0ebaf9..0000000 --- a/src/Listeners/ForcesTwoFactorAuth.php +++ /dev/null @@ -1,185 +0,0 @@ -config = $config; - $this->request = $request; - $this->input = $config->get('laraguard.input'); - } - - /** - * Handle the event. - * - * @param \Illuminate\Auth\Events\Attempting|\Illuminate\Auth\Events\Validated $event - * @return void - */ - public function handle($event) - { - if ($this->shouldUseTwoFactorAuth($user = $this->retrieveUser($event))) { - - if ($this->isSafeDevice($user) || ($this->hasCode() && $invalid = $this->hasValidCode($user))) { - return $this->addSafeDevice($user); - } - - $this->throwResponse($event, $user, isset($invalid)); - } - } - - /** - * Retrieve the User from the event. - * - * @param \Illuminate\Auth\Events\Attempting|\Illuminate\Auth\Events\Validated $event - * @return null|\DarkGhostHunter\Laraguard\Contracts\TwoFactorAuthenticatable|\Illuminate\Contracts\Auth\Authenticatable - */ - protected function retrieveUser($event) - { - return $event instanceof Attempting - ? $this->getUserFromProvider($event->guard, $event->credentials) - : $event->user; - } - - /** - * Returns the User from the User Provider used by the Guard. - * - * @param string $guard - * @param array $credentials - * @return null|\DarkGhostHunter\Laraguard\Contracts\TwoFactorAuthenticatable|\Illuminate\Contracts\Auth\Authenticatable - */ - protected function getUserFromProvider(string $guard, array $credentials = []) - { - // Since we only have the credentials from the event, we will try to retrieve the currently - // used User Provider from the application configuration. For that, we will call the auth - // manager and just ask to get the provider being used for the currently active guard. - $provider = app(AuthManager::class)->createUserProvider($this->config["auth.guards.$guard.provider"]); - - $user = $provider->retrieveByCredentials($credentials); - - return $user && $provider->validateCredentials($user, $credentials) ? $user : null; - } - - /** - * Returns if the login attempt should enforce Two Factor Authentication. - * - * @param null|\DarkGhostHunter\Laraguard\Contracts\TwoFactorAuthenticatable|\Illuminate\Contracts\Auth\Authenticatable $user - * @return bool - */ - protected function shouldUseTwoFactorAuth($user = null) - { - if (! $user instanceof TwoFactorAuthenticatable) { - return false; - } - - $shouldUse = $user->hasTwoFactorEnabled(); - - if ($this->config['laraguard.safe_devices.enabled']) { - return $shouldUse && ! $user->isSafeDevice($this->request); - } - - return $shouldUse; - } - - /** - * Returns if the Request is from a Safe Device. - * - * @param \DarkGhostHunter\Laraguard\Contracts\TwoFactorAuthenticatable $user - * @return bool - */ - protected function isSafeDevice(TwoFactorAuthenticatable $user) - { - return $this->config['laraguard.safe_devices.enabled'] && $user->isSafeDevice($this->request); - } - - /** - * Returns if the Request has the Two Factor Code. - * - * @return bool - */ - protected function hasCode() - { - return $this->request->has($this->input); - } - - /** - * Checks if the Request has a Two Factor Code and is correct (even if is invalid). - * - * @param \DarkGhostHunter\Laraguard\Contracts\TwoFactorAuthenticatable $user - * @return bool - */ - protected function hasValidCode(TwoFactorAuthenticatable $user) - { - return ! validator($this->request->only($this->input), [$this->input => 'alphanum'])->fails() - && $user->validateTwoFactorCode($this->request->input($this->input)); - } - - /** - * Adds a safe device to Two Factor Authentication data. - * - * @param \DarkGhostHunter\Laraguard\Contracts\TwoFactorAuthenticatable $user - * @return void - */ - protected function addSafeDevice(TwoFactorAuthenticatable $user) - { - if ($this->config['laraguard.safe_devices.enabled']) { - $user->addSafeDevice($this->request); - } - } - - /** - * Creates a response containing the Two Factor Authentication view. - * - * @param \Illuminate\Auth\Events\Attempting $event - * @param \DarkGhostHunter\Laraguard\Contracts\TwoFactorAuthenticatable $user - * @param bool $error - * @return void - */ - protected function throwResponse(Attempting $event, TwoFactorAuthenticatable $user, bool $error = false) - { - $view = view('laraguard::auth', [ - 'action' => $this->request->fullUrl(), - 'credentials' => $event->credentials, - 'user' => $user, - 'error' => $error, - 'remember' => $event->remember, - ]); - - return response($view, $error ? 422 : 403)->throwResponse(); - } - -} diff --git a/tests/Listeners/ForcesTwoFactorAuthTest.php b/tests/Listeners/ForcesTwoFactorAuthTest.php index 3b2942a..5e1518e 100644 --- a/tests/Listeners/ForcesTwoFactorAuthTest.php +++ b/tests/Listeners/ForcesTwoFactorAuthTest.php @@ -12,6 +12,7 @@ use Tests\Stubs\UserTwoFactorStub; use Illuminate\Foundation\Testing\WithFaker; use Illuminate\Foundation\Testing\DatabaseMigrations; +use DarkGhostHunter\Laraguard\Listeners\EnforceTwoFactorAuth; class ForcesTwoFactorAuthTest extends TestCase { @@ -52,6 +53,8 @@ public function test_form_contains_action_credentials_remember_and_user() public function test_login_with_no_valid_credentials_no_2fa_fails() { + $this->app['config']->set('auth.providers.users.model', UserStub::class); + $user = UserStub::create([ 'name' => 'test', 'email' => 'bar@test.com', @@ -67,9 +70,11 @@ public function test_login_with_no_valid_credentials_no_2fa_fails() public function test_login_with_no_2fa_no_code_succeeds() { + $this->app['config']->set('auth.providers.users.model', UserStub::class); + $user = UserStub::create([ 'name' => 'test', - 'email' => 'bar@test.com', + 'email' => 'quz@test.com', 'password' => '$2y$10$EicEv29xyMt/AbuWc0AIkeWb8Ip0fdhAYqgiXUaoG8Klu43521jQW', ]); @@ -82,6 +87,8 @@ public function test_login_with_no_2fa_no_code_succeeds() public function test_login_with_no_2fa_with_code_succeeds() { + $this->app['config']->set('auth.providers.users.model', UserStub::class); + $user = UserStub::create([ 'name' => 'test', 'email' => 'bar@test.com', @@ -391,6 +398,8 @@ public function test_auth_requests_receives_code_and_saves_device() $this->assertCount(1, $this->user->twoFactorAuth->safe_devices); + $this->app->forgetInstance(EnforceTwoFactorAuth::class); + $code = $this->user->generateRecoveryCodes()->first()['code']; $this->post('login', [ diff --git a/tests/RegistersLoginRoute.php b/tests/RegistersLoginRoute.php index a3892b5..6fb537d 100644 --- a/tests/RegistersLoginRoute.php +++ b/tests/RegistersLoginRoute.php @@ -5,6 +5,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Route; +use Illuminate\Http\Exceptions\HttpResponseException; trait RegistersLoginRoute { @@ -16,7 +17,9 @@ protected function registerLoginRoute() ? 'authenticated' : 'unauthenticated'; } catch (\Throwable $exception) { - var_dump([get_class($exception), $exception->getMessage()]); + if (! $exception instanceof HttpResponseException) { + var_dump([get_class($exception), $exception->getMessage()]); + } throw $exception; } })->middleware('web');