diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 373ece8..5704a18 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -11,30 +11,28 @@ jobs: strategy: fail-fast: true matrix: - php: [7.4, 7.3, 7.2.15] - laravel: [7.*, 6.*] + php: [8.0, 7.4] + laravel: [8.*] dependency-version: [prefer-lowest, prefer-stable] include: - - laravel: 7.* - testbench: 5.* - - laravel: 6.* - testbench: ^4.1 + - laravel: 8.* + testbench: 6.* name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - ${{ matrix.dependency-version }} steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v2 - name: Setup PHP - uses: shivammathur/setup-php@v1 + uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: mbstring, intl coverage: xdebug - name: Cache dependencies - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ~/.composer/cache/files key: ${{ runner.os }}-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} @@ -54,5 +52,5 @@ jobs: COVERALLS_SERVICE_NAME: github run: | rm -rf composer.* vendor/ - composer require cedx/coveralls - vendor/bin/coveralls build/logs/clover.xml + composer require php-coveralls/php-coveralls + vendor/bin/php-coveralls diff --git a/README.md b/README.md index b0345c9..e683df4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -![Zachary Lisko - Unsplash (UL) #JEBeXUHm1c4](https://images.unsplash.com/flagged/photo-1570343271132-8949dd284a04?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1280&h=400&q=80) +

+ +

+ # Laraguard @@ -7,14 +10,16 @@ ![](https://github.com/DarkGhostHunter/Laraguard/workflows/PHP%20Composer/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/DarkGhostHunter/Laraguard/badge.svg?branch=master)](https://coveralls.io/github/DarkGhostHunter/Laraguard?branch=master) -Two Factor Authentication via TOTP for all your Users out-of-the-box. +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 Laravel 7. -* PHP 7.2+ +* Laravel 8.x +* PHP 7.4 or PHP 8.0 + +> For older versions support, consider helping by sponsoring or donating. ## Table of Contents @@ -55,22 +60,21 @@ That's it. ### How this works -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. +This package adds a **Contract** to detect in a **per-user basis** if, after the credentials are deemed valid, should use Two Factor Authentication as a second layer of authentication. + +It includes a custom **view** and a **listener** to handle the Two Factor authentication itself during login attempts. This package was made to be the less invasive possible, but you can go full manual if you want. ## Usage -First, create the `two_factor_authentications` table by running the migration: +First, create the `two_factor_authentications` table by publishing the migration and migrating: + php artisan vendor:publish --provider="DarkGhostHunter\Laraguard\LaraguardServiceProvider" --tag="migrations" php artisan migrate This will create a table to handle the Two Factor Authentication information for each model you want to attach to 2FA. -> If you need to modify the migration from this package, you can publish it to override whatever you need. -> -> php artisan vendor:publish --provider="DarkGhostHunter\Laraguard\LaraguardServiceProvider" --tag="migrations" - Add the `TwoFactorAuthenticatable` _contract_ and the `TwoFactorAuthentication` trait to the User model, or any other model you want to make Two Factor Authentication available. ```php diff --git a/composer.json b/composer.json index bdcb611..c3b1d41 100644 --- a/composer.json +++ b/composer.json @@ -21,21 +21,24 @@ } ], "require": { - "php": "^7.2.15", + "php": "^7.4||^8.0", "ext-json": "*", "bacon/bacon-qr-code": "^2.0", - "paragonie/constant_time_encoding": "^2.0", - "illuminate/support": "^6.15||^7.0", - "illuminate/auth": "^6.15||^7.0" + "paragonie/constant_time_encoding": "^2.4", + "illuminate/support": "^8.0", + "illuminate/http": "^8.20", + "illuminate/auth": "^8.0" }, "require-dev": { - "orchestra/testbench": "^4.0||^5.0", - "orchestra/canvas": "^4.0||^5.0", - "phpunit/phpunit": "^8.0" + "orchestra/testbench": "^6.0", + "orchestra/canvas": "^6.0", + "mockery/mockery":"^1.4", + "phpunit/phpunit": "^9.3" }, "autoload": { "psr-4": { - "DarkGhostHunter\\Laraguard\\": "src" + "DarkGhostHunter\\Laraguard\\": "src", + "Database\\Factories\\DarkGhostHunter\\Laraguard\\Eloquent\\": "database/factories" } }, "autoload-dev": { @@ -44,7 +47,7 @@ } }, "scripts": { - "test": "vendor/bin/phpunit", + "test": "vendor/bin/phpunit --coverage-clover build/logs/clover.xml", "test-coverage": "vendor/bin/phpunit --coverage-html coverage" }, diff --git a/config/laraguard.php b/config/laraguard.php index d855747..6cb632e 100644 --- a/config/laraguard.php +++ b/config/laraguard.php @@ -79,9 +79,9 @@ | Safe Devices |-------------------------------------------------------------------------- | - | Authenticating with Two Factor Codes can become very obnoxious if you do - | it every time, so for this reasons the Safe Devices can be enabled. It - | remembers the device with an long-lived cookie to bypass Two Factor. + | Authenticating with Two Factor Codes can become very obnoxious when the + | user does it every time. To "remember" a device where a 2FA code was + | validated to not ask again you can enable Safe Device to save it. | */ diff --git a/database/factories/TwoFactorAuthenticationFactory.php b/database/factories/TwoFactorAuthenticationFactory.php index 2c0fe1b..d2a735b 100644 --- a/database/factories/TwoFactorAuthenticationFactory.php +++ b/database/factories/TwoFactorAuthenticationFactory.php @@ -1,61 +1,102 @@ define(TwoFactorAuthentication::class, function (Faker $faker) { +class TwoFactorAuthenticationFactory extends Factory { + /** + * The name of the factory's corresponding model. + * + * @var string + */ + protected $model = TwoFactorAuthentication::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition() + { + $config = config('laraguard'); - $config = config('laraguard'); + $array = array_merge([ + 'shared_secret' => TwoFactorAuthentication::generateRandomSecret(), + 'enabled_at' => $this->faker->dateTimeBetween('-1 year'), + 'label' => $this->faker->freeEmail, + ], $config['totp']); - $array = array_merge([ - 'shared_secret' => TwoFactorAuthentication::generateRandomSecret(), - 'enabled_at' => $faker->dateTimeBetween('-1 year'), - 'label' => $faker->freeEmail, - ], $config['totp']); + [$enabled, $amount, $length] = array_values($config['recovery']); - [$enabled, $amount, $length] = array_values($config['recovery']); + if ($enabled) { + $array['recovery_codes'] = TwoFactorAuthentication::generateRecoveryCodes($amount, $length); + $array['recovery_codes_generated_at'] = $this->faker->dateTimeBetween('-1 years'); + } - if ($enabled) { - $array['recovery_codes'] = TwoFactorAuthentication::generateRecoveryCodes($amount, $length); - $array['recovery_codes_generated_at'] = $faker->dateTimeBetween('-1 years'); + return $array; } - return $array; -}); + /** + * Returns a model with unused recovery codes. + * + * @return TwoFactorAuthenticationFactory + */ + public function withRecovery() + { + return $this->state(function(array $attributes) { + [$enabled, $amount, $length] = array_values(config('laraguard.recovery')); -$factory->state(TwoFactorAuthentication::class, 'with recovery', function (Faker $faker) { - [$enabled, $amount, $length] = array_values(config('laraguard.recovery')); - return [ - 'recovery_codes' => TwoFactorAuthentication::generateRecoveryCodes($amount, $length), - 'recovery_codes_generated_at' => $faker->dateTimeBetween('-1 years'), - ]; -}); + return [ + 'recovery_codes' => TwoFactorAuthentication::generateRecoveryCodes($amount, $length), + 'recovery_codes_generated_at' => $this->faker->dateTimeBetween('-1 years'), + ]; + }); + } -// This state will create a full set of safe devices, but it will leave the last as purposefully expired. -$factory->state(TwoFactorAuthentication::class, 'with safe devices', function (Faker $faker) { + /** + * Returns an authentication with a list of safe devices. + * + * @return TwoFactorAuthenticationFactory + */ + public function withSafeDevices() + { + return $this->state(function (array $attributes) { + $max = config('laraguard.safe_devices.max_devices'); - $max = config('laraguard.safe_devices.max_devices'); + return [ + 'safe_devices' => Collection::times($max, function ($step) use ($max) { - return [ - 'safe_devices' => Collection::times($max, function ($step) use ($faker, $max) { + $expiration_days = config('laraguard.safe_devices.expiration_days'); - $expiration_days = config('laraguard.safe_devices.expiration_days'); + $added_at = $max !== $step + ? now() + : $this->faker->dateTimeBetween(now()->subDays($expiration_days * 2), now()->subDays($expiration_days)); - $added_at = $max !== $step - ? now() - : $faker->dateTimeBetween(now()->subDays($expiration_days * 2), now()->subDays($expiration_days)); + return [ + '2fa_remember' => TwoFactorAuthentication::generateDefaultTwoFactorRemember(), + 'ip' => $this->faker->ipv4, + 'added_at' => $added_at, + ]; + }), + ]; + }); + } + /** + * Returns an enabled authentication. + * + * @return TwoFactorAuthenticationFactory + */ + public function enabled() + { + return $this->state(function (array $attributes) { return [ - '2fa_remember' => TwoFactorAuthentication::generateDefaultTwoFactorRemember(), - 'ip' => $faker->ipv4, - 'added_at' => $added_at, + 'enabled_at' => null ]; - }), - ]; -}); - -$factory->state(TwoFactorAuthentication::class, 'disabled', [ - 'enabled_at' => null, -]); + }); + } +} diff --git a/database/migrations/2020_04_02_000000_create_two_factor_authentications_table.php b/database/migrations/2020_04_02_000000_create_two_factor_authentications_table.php index 9fe33b3..369187e 100644 --- a/database/migrations/2020_04_02_000000_create_two_factor_authentications_table.php +++ b/database/migrations/2020_04_02_000000_create_two_factor_authentications_table.php @@ -16,7 +16,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->binary('shared_secret'); + $table->string('shared_secret'); $table->timestampTz('enabled_at')->nullable(); $table->string('label'); $table->unsignedTinyInteger('digits')->default(6); diff --git a/laraguardlogo.png b/laraguardlogo.png new file mode 100644 index 0000000..ab2e4af Binary files /dev/null and b/laraguardlogo.png differ diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 987fb11..a78e079 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,33 +1,26 @@ - - - - tests - - - - - src/ - - - - - - - - - - - - - + + + + src/ + + + + + + + + + + tests + + + + + + + + + + diff --git a/resources/views/auth.blade.php b/resources/views/auth.blade.php index 3b1c467..4ffe140 100644 --- a/resources/views/auth.blade.php +++ b/resources/views/auth.blade.php @@ -24,6 +24,13 @@ class="@if($error) is-invalid @endif form-control form-control-lg" @endif
+
+
+ + +
+
+