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

Commit

Permalink
Merge pull request #53 from DarkGhostHunter/master
Browse files Browse the repository at this point in the history
Laravel 8.0 and PHP 8.0 support.
  • Loading branch information
DarkGhostHunter authored Mar 17, 2021
2 parents 0c3ed4e + bc0cdf5 commit c1073d0
Show file tree
Hide file tree
Showing 19 changed files with 316 additions and 189 deletions.
20 changes: 9 additions & 11 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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') }}
Expand All @@ -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
24 changes: 14 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
<p align="center">
<img src="laraguardlogo.png">
</p>


# Laraguard

Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down
21 changes: 12 additions & 9 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -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"

},
Expand Down
6 changes: 3 additions & 3 deletions config/laraguard.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
|
*/

Expand Down
121 changes: 81 additions & 40 deletions database/factories/TwoFactorAuthenticationFactory.php
Original file line number Diff line number Diff line change
@@ -1,61 +1,102 @@
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */

namespace Database\Factories\DarkGhostHunter\Laraguard\Eloquent;

use Faker\Generator as Faker;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Collection;
use DarkGhostHunter\Laraguard\Eloquent\TwoFactorAuthentication;

$factory->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,
]);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Binary file added laraguardlogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 24 additions & 31 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,33 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
verbose="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
<logging>
<log type="junit" target="build/report.junit.xml"/>
<log type="coverage-html" target="build/coverage"/>
<log type="coverage-text" target="build/coverage.txt"/>
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_KEY" value="AckfSECXIvnK5r28GVIWUAxmbBSjTsmF"/>
<env name="DB_CONNECTION" value="testing"/>
</php>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" backupGlobals="false" backupStaticAttributes="false" colors="true" verbose="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage>
<include>
<directory suffix=".php">src/</directory>
</include>
<report>
<clover outputFile="build/logs/clover.xml"/>
<html outputDirectory="build/coverage"/>
<text outputFile="build/coverage.txt"/>
</report>
</coverage>
<testsuites>
<testsuite name="Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<logging>
<junit outputFile="build/report.junit.xml"/>
</logging>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_KEY" value="AckfSECXIvnK5r28GVIWUAxmbBSjTsmF"/>
<env name="DB_CONNECTION" value="testing"/>
</php>
</phpunit>
7 changes: 7 additions & 0 deletions resources/views/auth.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ class="@if($error) is-invalid @endif form-control form-control-lg"
@endif
</div>
<div class="w-100"></div>
<div class="col-sm-8 col-8 mb-3">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="safe_device">
<label class="custom-control-label" for="safe_device">Don't ask for codes in this device.</label>
</div>
</div>
<div class="w-100"></div>
<div class="col-auto mb-3">
<button type="submit" class="btn btn-primary btn-lg">
{{ trans('laraguard::messages.confirm') }}
Expand Down
3 changes: 2 additions & 1 deletion src/Eloquent/HandlesCodes.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use DateTime;
use Illuminate\Support\Carbon;
use ParagonIE\ConstantTime\Base32;

trait HandlesCodes
{
Expand Down Expand Up @@ -141,7 +142,7 @@ protected function timestampToBinary(int $timestamp)
*/
protected function getBinarySecret()
{
return $this->attributes['shared_secret'];
return Base32::decodeUpper($this->attributes['shared_secret']);
}

/**
Expand Down
Loading

0 comments on commit c1073d0

Please sign in to comment.