Skip to content
This repository has been archived by the owner on Feb 17, 2022. It is now read-only.

Commit

Permalink
Merge 5c79f2a into f1fc62d
Browse files Browse the repository at this point in the history
  • Loading branch information
DarkGhostHunter committed Apr 8, 2020
2 parents f1fc62d + 5c79f2a commit 29a00e0
Show file tree
Hide file tree
Showing 18 changed files with 166 additions and 60 deletions.
8 changes: 0 additions & 8 deletions .github/workflows/composer-coveralls.json

This file was deleted.

14 changes: 5 additions & 9 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ jobs:
- laravel: 7.*
testbench: 5.*
- laravel: 6.*
testbench: 4.*
testbench: ^4.1

name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - ${{ matrix.dependency-version }}

steps:
- name: Checkout
Expand All @@ -33,14 +33,10 @@ jobs:
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 }}
path: ~/.composer/cache/files
key: ${{ runner.os }}-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
restore-keys: ${{ runner.os }}-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-

Expand All @@ -49,10 +45,10 @@ jobs:
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-progress --no-update
composer update --${{ matrix.dependency-version }} --prefer-dist --no-progress --no-suggest
- name: Run test suite
- name: Run Tests
run: composer run-script test

- name: Upload coverage results to Coveralls
- name: Upload Coverage to Coveralls
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_SERVICE_NAME: github
Expand Down
42 changes: 34 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ This package _silently_ enables authentication using 6 digits codes, without Int

## Requirements

* Laravel [6.15](https://blog.laravel.com/laravel-v6-15-0-released) or later.
* Laravel [6.15](https://blog.laravel.com/laravel-v6-15-0-released) or Laravel 7.
* PHP 7.2+

## Table of Contents
Expand All @@ -30,6 +30,7 @@ This package _silently_ enables authentication using 6 digits codes, without Int
* [Protecting the Login](#protecting-the-login)
* [Configuration](#configuration)
+ [Listener](#listener)
+ [Eloquent Model](#eloquent-model)
+ [Input name](#input-name)
+ [Cache Store](#cache-store)
+ [Recovery](#recovery)
Expand All @@ -52,13 +53,21 @@ That's it.

This packages adds a **Contract** to detect in a **per-user basis** if it should use Two Factor Authentication. It includes a custom **view** and a **listener** to handle the Two Factor authentication itself during login attempts.

It is not invasive, but you can go full manual if you want.
This package was made to be the less invasive possible, but you can go full manual if you want.

## Usage

First, run the migrations. This will create a table to handle the Two Factor Authentication information for each model you set.
First, publish the migration with:

php artisan migrate:run
php artisan vendor:publish --provider="DarkGhostHunter\Laraguard\LaraguardServiceProvider" --tag="migrations"

> The default migration assumes you are using integers for your user model IDs. If you are using UUIDs, or some other format, adjust the format of the morphs `authenticatable` fields in the published migration before continuing.
After publishing the migration, you can create the `two_factor_authentications` table by running the migration:

php artisan migrate

This will create a table to handle the Two Factor Authentication information for each model you set.

Add the `TwoFactorAuthenticatable` _contract_ and the `TwoFactorAuthentication` trait to the User model, or any other model you want to make Two Factor Authentication available.

Expand Down Expand Up @@ -243,7 +252,8 @@ You will receive the authentication view in `resources/views/vendor/laraguard/au

```php
return [
'listener' => true,
'listener' => \DarkGhostHunter\Laraguard\Listeners\EnforceTwoFactorAuth::class,
'model' => \DarkGhostHunter\Laraguard\Eloquent\TwoFactorAuthentication::class,
'input' => '2fa_code',
'cache' => [
'store' => null,
Expand Down Expand Up @@ -273,13 +283,29 @@ return [

```php
return [
'listener' => true,
'listener' => \DarkGhostHunter\Laraguard\Listeners\EnforceTwoFactorAuth::class,
];
```

This package works out-of-the-box by hooking up the `ForcesTwoFactorAuth` listener to the `Attempting` and `Validated` events, which is in charge of checking if the user login needs a 2FA code or not.

This may work wonders, but if you want tighter control on how and when prompt for Two Factor Authentication, you can use another listener, or disable it. For example, to create your own 2FA Guard or greatly modify the Login Controller.

> If you change it for your own Listener, ensure it implements the `TwoFactorAuthListener` contract.
### Eloquent Model

```php
return [
'model' => \DarkGhostHunter\Laraguard\Eloquent\TwoFactorAuthentication::class,
];
```

This package works by hooking up the `ForcesTwoFactorAuth` listener to the `Attempting` and `Validated` events as a fallback.
This is the model where the the Two Factor Authentication data, like the shared secret and recovery codes, are saved and associated to the User model.

You can change this model for your own if you wish.

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.
> If you change it for your own Model, ensure it implements the `TwoFactorTotp` contract.
### Input name

Expand Down
8 changes: 5 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"2fa"
],
"homepage": "https://github.com/darkghosthunter/laraguard",
"minimum-stability": "dev",
"prefer-stable": true,
"license": "MIT",
"type": "library",
"authors": [
Expand All @@ -21,14 +23,14 @@
"require": {
"php": "^7.2.15",
"ext-json": "*",
"bacon/bacon-qr-code": "2.*",
"paragonie/constant_time_encoding": "2.*",
"bacon/bacon-qr-code": "^2.0",
"paragonie/constant_time_encoding": "^2.0",
"illuminate/support": "^6.15||^7.0",
"illuminate/auth": "^6.15||^7.0"
},
"require-dev": {
"orchestra/testbench": "^4.0||^5.0",
"orchestra/canvas": "^4.0||5.0",
"orchestra/canvas": "^4.0||^5.0",
"phpunit/phpunit": "^8.0"
},
"autoload": {
Expand Down
27 changes: 20 additions & 7 deletions config/laraguard.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,26 @@
| Listener hook
|--------------------------------------------------------------------------
|
| If the Listener is enabled, Laraguard will automatically hook into the
| "Attempting" event and magically ask for Two Factor Authentication if
| is necessary. Disable this to use your own 2FA authentication logic.
| If a Listener class is present, Laraguard will hook into the Attempting
| and Validated events and check if it needs Two Factor Authentication.
| Set this to false or null to use your own 2FA logic without events.
|
*/

'listener' => true,
'listener' => \DarkGhostHunter\Laraguard\Listeners\EnforceTwoFactorAuth::class,

/*
|--------------------------------------------------------------------------
| TwoFactorAuthentication Model
|--------------------------------------------------------------------------
|
| When using the "TwoFactorAuthentication" trait from this package, we need
| to know which Eloquent model should be used to retrieve your two factor
| authentication records. You can use your own for more advanced logic.
|
*/

'model' => \DarkGhostHunter\Laraguard\Eloquent\TwoFactorAuthentication::class,

/*
|--------------------------------------------------------------------------
Expand Down Expand Up @@ -83,9 +96,9 @@
| Secret Length
|--------------------------------------------------------------------------
|
| Using a shared secret with a length of 160-bit (as recommended per RFC
| 4226) is recommended, but you may want to tighten or loose the secret
| length. The RFC 4226 standard allows down to 128-bit shared secrets.
| The package uses a shared secret length of 160-bit, as recommended by the
| RFC 4226. This makes it compatible with most 2FA apps. You can change it
| freely but consider the standard allows shared secrets down to 128-bit.
|
*/

Expand Down
1 change: 0 additions & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
</whitelist>
</filter>
<logging>
<log type="tap" target="build/report.tap"/>
<log type="junit" target="build/report.junit.xml"/>
<log type="coverage-html" target="build/coverage"/>
<log type="coverage-text" target="build/coverage.txt"/>
Expand Down
25 changes: 25 additions & 0 deletions src/Contracts/TwoFactorListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace DarkGhostHunter\Laraguard\Contracts;

use Illuminate\Auth\Events\Validated;
use Illuminate\Auth\Events\Attempting;

interface TwoFactorListener
{
/**
* Saves the credentials temporarily into the class instance.
*
* @param \Illuminate\Auth\Events\Attempting $event
* @return void
*/
public function saveCredentials(Attempting $event);

/**
* Checks if the user should use Two Factor Auth.
*
* @param \Illuminate\Auth\Events\Validated $event
* @return void
*/
public function checkTwoFactor(Validated $event);
}
53 changes: 36 additions & 17 deletions src/LaraguardServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public function register()
*
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Illuminate\Routing\Router $router
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
* @return void
*/
public function boot(Repository $config, Router $router, Dispatcher $dispatcher)
Expand All @@ -34,17 +35,10 @@ public function boot(Repository $config, Router $router, Dispatcher $dispatcher)
$this->registerMiddleware($router);

$this->loadViewsFrom(__DIR__ . '/../resources/views', 'laraguard');
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
$this->loadFactoriesFrom(__DIR__ . '/../database/factories');

if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__ . '/../config/laraguard.php' => config_path('laraguard.php'),
], 'config');

$this->publishes([
__DIR__ . '/../resources/views' => resource_path('views/vendor/laraguard'),
], 'views');
$this->publishFiles();
}
}

Expand All @@ -66,18 +60,43 @@ protected function registerMiddleware(Router $router)
*/
protected function registerListener(Repository $config, Dispatcher $dispatcher)
{
if (! $config['laraguard.listener']) {
if (! $listener = $config['laraguard.listener']) {
return;
}

$this->app->singleton(Listeners\EnforceTwoFactorAuth::class, function ($app) {
return new Listeners\EnforceTwoFactorAuth($app['config'], $app['request']);
$this->app->singleton(Contracts\TwoFactorListener::class, function ($app) use ($listener) {
return new $listener($app['config'], $app['request']);
});
$dispatcher->listen(Attempting::class,
'DarkGhostHunter\Laraguard\Listeners\EnforceTwoFactorAuth@saveCredentials'
);
$dispatcher->listen(Validated::class,
'DarkGhostHunter\Laraguard\Listeners\EnforceTwoFactorAuth@checkTwoFactor'
);

$dispatcher->listen(Attempting::class, Contracts\TwoFactorListener::class . '@saveCredentials');
$dispatcher->listen(Validated::class, Contracts\TwoFactorListener::class . '@checkTwoFactor');
}

/**
* Publish config, view and migrations files.
*
* @return void
*/
protected function publishFiles()
{
$this->publishes([
__DIR__ . '/../config/laraguard.php' => config_path('laraguard.php'),
], 'config');

$this->publishes([
__DIR__ . '/../resources/views' => resource_path('views/vendor/laraguard'),
], 'views');

// We will allow the publishing for the Two Factor Authentication migration that
// holds the TOTP data, only if it wasn't published before, avoiding multiple
// copies for the same migration, which can throw errors when re-migrating.
if (! class_exists('CreateTwoFactorAuthenticationsTable')) {
$timestamp = now()->format('Y_m_d_His');

$this->publishes([
__DIR__ .
'/../database/migrations/2020_04_02_000000_create_two_factor_authentications_table.php' => database_path("/migrations/{$timestamp}_create_two_factor_authentications_table.php"),
], 'migrations');
}
}
}
5 changes: 3 additions & 2 deletions src/Listeners/EnforceTwoFactorAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
use Illuminate\Auth\Events\Validated;
use Illuminate\Auth\Events\Attempting;
use Illuminate\Contracts\Config\Repository;
use DarkGhostHunter\Laraguard\Contracts\TwoFactorListener;
use DarkGhostHunter\Laraguard\Contracts\TwoFactorAuthenticatable;

class EnforceTwoFactorAuth
class EnforceTwoFactorAuth implements TwoFactorListener
{
use ChecksTwoFactorCode;

Expand Down Expand Up @@ -61,7 +62,7 @@ public function __construct(Repository $config, Request $request)
}

/**
* Saves the Credentials for the User.
* Saves the credentials temporarily into the class instance.
*
* @param \Illuminate\Auth\Events\Attempting $event
* @return void
Expand Down
6 changes: 3 additions & 3 deletions src/TwoFactorAuthentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public function initializeTwoFactorAuthentication()
*/
public function twoFactorAuth()
{
return $this->morphOne(Eloquent\TwoFactorAuthentication::class, 'authenticatable')
return $this->morphOne(config('laraguard.model'), 'authenticatable')
->withDefault(config('laraguard.totp'));
}

Expand Down Expand Up @@ -184,7 +184,7 @@ public function generateRecoveryCodes() : Collection
{
[$enabled, $amount, $length] = array_values(config('laraguard.recovery'));

$this->twoFactorAuth->recovery_codes = Eloquent\TwoFactorAuthentication::generateRecoveryCodes($amount, $length);
$this->twoFactorAuth->recovery_codes = config('laraguard.model')::generateRecoveryCodes($amount, $length);
$this->twoFactorAuth->recovery_codes_generated_at = now();
$this->twoFactorAuth->save();

Expand Down Expand Up @@ -248,7 +248,7 @@ public function addSafeDevice(Request $request) : string
*/
protected function generateTwoFactorRemember()
{
return Eloquent\TwoFactorAuthentication::generateDefaultTwoFactorRemember();
return config('laraguard.model')::generateDefaultTwoFactorRemember();
}

/**
Expand Down
3 changes: 3 additions & 0 deletions tests/Eloquent/TwoFactorAuthenticationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Tests\Eloquent;

use Carbon\Carbon;
use Tests\RunsPublishableMigrations;
use Tests\RegistersPackage;
use Orchestra\Testbench\TestCase;
use ParagonIE\ConstantTime\Base32;
Expand All @@ -16,12 +17,14 @@ class TwoFactorAuthenticationTest extends TestCase
{
use RegistersPackage;
use DatabaseMigrations;
use RunsPublishableMigrations;

protected $tfa;

protected function setUp() : void
{
$this->afterApplicationCreated([$this, 'loadLaravelMigrations']);
$this->afterApplicationCreated([$this, 'runPublishableMigration']);
parent::setUp();
}

Expand Down

0 comments on commit 29a00e0

Please sign in to comment.