diff --git a/README.md b/README.md index 81b9899..7034012 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ This package _silently_ enables authentication using 6 digits codes, without Int + [Safe devices](#safe-devices) + [Secret length](#secret-length) + [TOTP configuration](#totp-configuration) + + [QR Code Configuration](#qr-code-configuration) + [Custom view](#custom-view) * [Security](#security) * [License](#license) @@ -270,13 +271,17 @@ return [ 'expiration_days' => 14, ], 'secret_length' => 20, - 'issuer' => env('APP_NAME', 'Laravel'), + 'issuer' => env('OTP_TOTP_ISSUER'), 'totp' => [ 'digits' => 6, 'seconds' => 30, 'window' => 1, 'algorithm' => 'sha1', ], + 'qr_code' => [ + 'size' => 400, + 'margin' => 4 + ], ]; ``` @@ -302,7 +307,7 @@ return [ ]; ``` -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. +This is the model where the data for Two Factor Authentication is saved, like the shared secret and recovery codes, and associated to the User model. You can change this model for your own if you wish. @@ -331,7 +336,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 the 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. 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. @@ -363,7 +368,7 @@ return [ ]; ``` -Enabling this option will allow the application to "remember" a device using a cookie, allowing it to bypass Two Factor Authentication once a code is verified in that device. When the User logs in again in that device, it won't be prompted for a Code. +Enabling this option will allow the application to "remember" a device using a cookie, allowing it to bypass Two Factor Authentication once a code is verified in that device. When the User logs in again in that device, it won't be prompted for a 2FA Code again. There is a limit of devices that can be saved. New devices will displace the oldest devices registered. Devices are considered no longer "safe" until a set amount of days. @@ -387,7 +392,7 @@ It's recommended to use 128-bit or 160-bit because some Authenticator apps may h ```php return [ - 'issuer' => env('APP_NAME', 'Laravel'), + 'issuer' => env('OTP_TOTP_ISSUER'), 'totp' => [ 'digits' => 6, 'seconds' => 30, @@ -409,10 +414,23 @@ This configuration values are always passed down to the authentication app as UR 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. +These values are printed to each 2FA data record inside the application. Changes will only take effect for new activations. > It's not recommended to edit these parameters if you plan to use publicly available Authenticator apps, since some of them **may not support non-standard configuration**, like more digits, different period of seconds or other algorithms. +### QR Code Configuration + +```php +return [ + 'qr_code' => [ + 'size' => 400, + 'margin' => 4 + ], +]; +``` + +This controls the size and margin used to create the QR Code, which are created as SVG. + ### Custom view resources/views/vendor/laraguard/auth.blade.php @@ -420,14 +438,14 @@ These values are printed to each 2FA data inside the application. Changes will o You can override the view, which handles the Two Factor Code verification for the User. It receives this data: * `$action`: The full URL where the form should send the login credentials. -* `$credentials`: An array containing the User credentials used for the login. +* `$credentials`: An `array|null` containing the User credentials used for the login. * `$user`: The User instance trying to authenticate. * `$error`: If the Two Factor Code is invalid. * `$remember`: If the "remember" checkbox has been filled. 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 form is bypassed if the User doesn't uses Two Factor Authentication, making the log in transparent and non-invasive. +This view and its form is bypassed if the User doesn't uses Two Factor Authentication, making the login transparent and non-invasive. ## Security diff --git a/config/laraguard.php b/config/laraguard.php index 0d5c9f5..b3f4c6f 100644 --- a/config/laraguard.php +++ b/config/laraguard.php @@ -115,7 +115,7 @@ | */ - 'issuer' => env('APP_NAME', 'Laravel'), + 'issuer' => env('OTP_TOTP_ISSUER'), 'totp' => [ 'digits' => 6, @@ -123,4 +123,20 @@ 'window' => 1, 'algorithm' => 'sha1', ], + + /* + |-------------------------------------------------------------------------- + | QR Code Config + |-------------------------------------------------------------------------- + | + | This package uses the BaconQrCode generator package to generate QR codes + | as SVG. These size and image margin values are used to create them. You + | can always your own code to create personalized QR Codes from the URI. + | + */ + + 'qr_code' => [ + 'size' => 400, + 'margin' => 4 + ], ]; diff --git a/resources/views/auth.blade.php b/resources/views/auth.blade.php index 0c1ed17..d7473f5 100644 --- a/resources/views/auth.blade.php +++ b/resources/views/auth.blade.php @@ -3,7 +3,7 @@ @section('card-body')
@csrf - @foreach($credentials as $name => $value) + @foreach((array)$credentials as $name => $value) @endforeach @if($remember) diff --git a/src/Eloquent/SerializesSharedSecret.php b/src/Eloquent/SerializesSharedSecret.php index b5bfe6e..df1fdc5 100644 --- a/src/Eloquent/SerializesSharedSecret.php +++ b/src/Eloquent/SerializesSharedSecret.php @@ -34,8 +34,10 @@ public function toUri() : string */ public function toQr() : string { + [$size, $margin] = array_values(config('laraguard.qr_code')); + return ( - new Writer((new ImageRenderer(new RendererStyle(400), new SvgImageBackEnd()))) + new Writer((new ImageRenderer(new RendererStyle($size, $margin), new SvgImageBackEnd()))) )->writeString($this->toUri()); } diff --git a/tests/Eloquent/TwoFactorAuthenticationTest.php b/tests/Eloquent/TwoFactorAuthenticationTest.php index fc6202a..5d73783 100644 --- a/tests/Eloquent/TwoFactorAuthenticationTest.php +++ b/tests/Eloquent/TwoFactorAuthenticationTest.php @@ -298,6 +298,25 @@ public function test_serializes_to_qr_and_renders_to_qr() $this->assertStringEqualsFile(__DIR__ . '/../Stubs/QrStub.svg', $tfa->render()); } + public function test_serializes_to_qr_and_renders_to_qr_with_custom_values() + { + config(['laraguard.issuer' => 'quz']); + config(['laraguard.qr_code' => [ + 'size' => 600, + 'margin' => 10 + ]]); + + $tfa = factory(TwoFactorAuthentication::class)->states('with recovery', 'with safe devices')->make([ + 'label' => 'test@foo.com', + 'shared_secret' => 'KS72XBTN5PEBGX2IWBMVW44LXHPAQ7L3', + 'algorithm' => 'sHa256', + 'digits' => 14, + ]); + + $this->assertStringEqualsFile(__DIR__ . '/../Stubs/CustomQrStub.svg', $tfa->toQr()); + $this->assertStringEqualsFile(__DIR__ . '/../Stubs/CustomQrStub.svg', $tfa->render()); + } + public function test_serializes_uri_to_json() { config(['laraguard.issuer' => 'quz']); diff --git a/tests/Listeners/ForcesTwoFactorAuthTest.php b/tests/Listeners/ForcesTwoFactorAuthTest.php index 68c06f2..f513816 100644 --- a/tests/Listeners/ForcesTwoFactorAuthTest.php +++ b/tests/Listeners/ForcesTwoFactorAuthTest.php @@ -54,6 +54,22 @@ public function test_form_contains_action_credentials_remember_and_user() $this->assertStringContainsString(__('The Code is invalid or has expired.'), $view); } + public function test_form_doesnt_contains_credentials() + { + $view = view('laraguard::auth')->with([ + 'action' => 'qux', + 'user' => $this->user, + 'credentials' => null, + 'remember' => 'on', + 'error' => true, + ])->render(); + + $this->assertStringContainsString('action="qux"', $view); + $this->assertStringNotContainsString('', $view); + $this->assertStringContainsString('', $view); + $this->assertStringContainsString(__('The Code is invalid or has expired.'), $view); + } + public function test_login_with_no_valid_credentials_no_2fa_fails() { $this->app['config']->set('auth.providers.users.model', UserStub::class); diff --git a/tests/Stubs/CustomQrStub.svg b/tests/Stubs/CustomQrStub.svg new file mode 100644 index 0000000..3a6a180 --- /dev/null +++ b/tests/Stubs/CustomQrStub.svg @@ -0,0 +1,2 @@ + + diff --git a/tests/Stubs/UserStub.php b/tests/Stubs/UserStub.php index 80879ed..3a6f265 100644 --- a/tests/Stubs/UserStub.php +++ b/tests/Stubs/UserStub.php @@ -4,8 +4,6 @@ use Illuminate\Auth\Authenticatable; use Illuminate\Database\Eloquent\Model; -use DarkGhostHunter\Laraguard\TwoFactorAuthentication; -use DarkGhostHunter\Laraguard\Contracts\TwoFactorAuthenticatable; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; class UserStub extends Model implements AuthenticatableContract