Skip to content

Commit

Permalink
Merge pull request #7 from LifeOnScreen/development
Browse files Browse the repository at this point in the history
[Feature] Recovery codes are stored as hashes
  • Loading branch information
janicerar authored Nov 9, 2018
2 parents 2607acb + 673389f commit b3c9e83
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 7 deletions.
46 changes: 44 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,40 @@

This package enforces 2FA for Laravel Nova.

## Upgrade from 0.0.7 to 1.0.0

Upgrade guide is available [Here](docs/upgrade_to_1.0.0.md').

## Flow

### Activation

- User gets recovery codes.

![Recovery codes](docs/images/recovery-codes.png)

- User activates 2FA on his device.

![Activate 2FA](docs/images/register.png)

### Verification

- User verifies login with 2FA.

![Enter 2FA](docs/images/enter-code.png)

### Recovery

- If user enters invalid code, recovery button is shown.

![Enter 2FA](docs/images/invalid-code.png)

- User enters recovery code.

![Enter 2FA](docs/images/enter-recovery-code.png)

- User is redirected to activation process.

## Installation

Install via composer
Expand Down Expand Up @@ -60,13 +94,13 @@ return [

'models' => [
/**
* Change this variable to your User model.
* Change this variable to path to user model.
*/
'user' => 'App\User',
],
'tables' => [
/**
* Table in witch users are stored.
* Table in which users are stored.
*/
'user' => 'users',
],
Expand All @@ -86,6 +120,14 @@ return [
* Number of characters in each block in recovery code.
*/
'chars_in_block' => 16,

/**
* The following algorithms are currently supported:
* - PASSWORD_DEFAULT
* - PASSWORD_BCRYPT
* - PASSWORD_ARGON2I // available from php 7.2
*/
'hashing_algorithm' => PASSWORD_BCRYPT,
],
];
```
Expand Down
8 changes: 8 additions & 0 deletions config/lifeonscreen2fa.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,13 @@
* Number of characters in each block in recovery code.
*/
'chars_in_block' => 16,

/**
* The following algorithms are currently supported:
* - PASSWORD_DEFAULT
* - PASSWORD_BCRYPT
* - PASSWORD_ARGON2I // available from php 7.2
*/
'hashing_algorithm' => PASSWORD_BCRYPT,
],
];
Binary file added docs/images/enter-code.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/enter-recovery-code.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/invalid-code.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/recovery-codes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/register.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 46 additions & 0 deletions docs/upgrade_to_1.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Upgrade from 0.0.7 to 1.0.0

In version 1.0.0 recovery codes are hashed in database.

Copy code bellow to migrations folder and run migrations:

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Lifeonscreen\Google2fa\Models\User2fa;

class HashGoogle2faRecoveryCodes extends Migration
{
/**
* Run the migrations.
*
* @return void
* @throws Exception
*/
public function up()
{
DB::beginTransaction();
$users2fa = User2fa::all();
foreach ($users2fa as $user2fa) {
$recoveryCodes = json_decode($user2fa->recovery);
array_walk($recoveryCodes, function (&$value) {
$value = password_hash($value, config('lifeonscreen2fa.recovery_codes.hashing_algorithm'));
});
$user2fa->recovery = json_encode($recoveryCodes);
$user2fa->save();
}
DB::commit();
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}
```

6 changes: 3 additions & 3 deletions src/Google2FAAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Lifeonscreen\Google2fa;

use App\Exceptions\ValidationException;
use Exception;
use PragmaRX\Google2FALaravel\Support\Authenticator;

/**
Expand All @@ -21,13 +21,13 @@ protected function canPassWithoutCheckingOTP()

/**
* @return mixed
* @throws ValidationException
* @throws Exception
*/
protected function getGoogle2FASecretKey()
{
$secret = $this->getUser()->user2fa->{$this->config('otp_secret_column')};
if (is_null($secret) || empty($secret)) {
throw new ValidationException('Secret key cannot be empty.');
throw new Exception('Secret key cannot be empty.');
}

return $secret;
Expand Down
20 changes: 18 additions & 2 deletions src/Google2fa.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,24 @@ public function register()

}

private function isRecoveryValid($recover, $recoveryHashes)
{
foreach ($recoveryHashes as $recoveryHash) {
if (password_verify($recover, $recoveryHash)) {
return true;
}
}

return false;
}

/**
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\View\View
*/
public function authenticate()
{
if ($recover = Request::get('recover')) {
if (in_array($recover, json_decode(auth()->user()->user2fa->recovery, true)) === false) {
if ($this->isRecoveryValid($recover, json_decode(auth()->user()->user2fa->recovery, true)) === false) {
$data['error'] = 'Recovery key is invalid.';

return view('google2fa::authenticate', $data);
Expand All @@ -107,12 +118,17 @@ public function authenticate()
->setChars(config('lifeonscreen2fa.recovery_codes.chars_in_block'))
->toArray();

$recoveryHashes = $data['recovery'];
array_walk($recoveryHashes, function (&$value) {
$value = password_hash($value, config('lifeonscreen2fa.recovery_codes.hashing_algorithm'));
});

User2fa::where('user_id', auth()->user()->id)->delete();

$user2fa = new User2fa();
$user2fa->user_id = auth()->user()->id;
$user2fa->google2fa_secret = $secretKey;
$user2fa->recovery = json_encode($data['recovery']);
$user2fa->recovery = json_encode($recoveryHashes);
$user2fa->save();

return response(view('google2fa::recovery', $data));
Expand Down

0 comments on commit b3c9e83

Please sign in to comment.