Skip to content

Commit

Permalink
2fa: Initial setup
Browse files Browse the repository at this point in the history
TODO:
 * update views (inc text view of generated key for manual input)
 * allow secrete regeneration
 * allow recovery
 * allow admin to force disable on a users account (for recovery)
 * tie into roles
  • Loading branch information
dpslwk committed May 20, 2019
1 parent 6c0113c commit c225e03
Show file tree
Hide file tree
Showing 14 changed files with 1,002 additions and 2 deletions.
39 changes: 39 additions & 0 deletions app/HMS/Auth/Google2FAAuthenticator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace HMS\Auth;

use PragmaRX\Google2FALaravel\Support\Authenticator;

class Google2FAAuthenticator extends Authenticator
{
/**
* Check if it is already logged in or passable without checking for an OTP.
*
* @return bool
*/
protected function canPassWithoutCheckingOTP()
{
return ! $this->getUser()->isGoogle2faEnable() ||
! $this->isEnabled() ||
$this->noUserIsAuthenticated() ||
$this->twoFactorAuthStillValid();
}

/**
* Get the user Google2FA secret.
*
* @throws InvalidSecretKey
*
* @return mixed
*/
protected function getGoogle2FASecretKey()
{
$secret = $this->getUser()->getGoogle2faSecret();

if (is_null($secret) || empty($secret)) {
throw new InvalidSecretKey('Secret key cannot be empty.');
}

return $secret;
}
}
50 changes: 50 additions & 0 deletions app/HMS/Entities/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ class User implements
*/
protected $emails;

/**
* @var bool
*/
protected $google2faEnable;

/**
* @var string|null
*/
protected $google2faSecret;

/**
* User constructor.
*
Expand Down Expand Up @@ -340,4 +350,44 @@ public function getEmails()
{
return $this->emails;
}

/**
* @return bool
*/
public function isGoogle2faEnable()
{
return $this->google2faEnable;
}

/**
* @param bool $google2faEnable
*
* @return self
*/
public function setGoogle2faEnable(bool $google2faEnable)
{
$this->google2faEnable = $google2faEnable;

return $this;
}

/**
* @return string|null
*/
public function getGoogle2faSecret()
{
return $this->google2faSecret;
}

/**
* @param string|null $google2faSecret
*
* @return self
*/
public function setGoogle2faSecret($google2faSecret)
{
$this->google2faSecret = $google2faSecret;

return $this;
}
}
7 changes: 7 additions & 0 deletions app/HMS/Mappings/HMS.Entities.User.dcm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ HMS\Entities\User:
column: email_verified_at
type: datetime
nullable: true
google2faEnable:
type: boolean
options:
default: 0
google2faSecret:
type: string
nullable: true
deletedAt:
type: datetime
nullable: true
Expand Down
148 changes: 148 additions & 0 deletions app/Http/Controllers/Auth/TwoFactorAuthenticationController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?php

namespace App\Http\Controllers\Auth;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use HMS\Repositories\UserRepository;
use Illuminate\Support\Facades\Auth;
use PragmaRX\Google2FALaravel\Google2FA;

class TwoFactorAuthenticationController extends Controller
{
/**
* @var Google2FA
*/
protected $google2fa;

/**
* @var UserRepository
*/
protected $userRepository;

/**
* Create a new controller instance.
*
* @param Google2FA $google2fa
*
* @return void
*/
public function __construct(Google2FA $google2fa, UserRepository $userRepository)
{
$this->google2fa = $google2fa;
$this->userRepository = $userRepository;

$this->middleware('auth');
}

/**
* Show the 2fa enable/disable form.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\Response
*/
public function show2faForm(Request $request)
{
$user = Auth::user();

$google2faUrl = '';
if (! empty($user->getGoogle2faSecret())) {
$google2faUrl = $this->google2fa->getQRCodeInline(
'Nottingham Hackspace HMS',
$user->getEmail(),
$user->getGoogle2faSecret()
);
}

return view('auth.2fa')
->with('user', $user)
->with('google2faUrl', $google2faUrl);
}

/**
* Generate new 2fa secret for user.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\Response
*/
public function generate2faSecret(Request $request)
{
$user = Auth::user();

// Add the secret key to the registration data
$user->setGoogle2faEnable(false);
$user->setGoogle2faSecret($this->google2fa->generateSecretKey(32));
$this->userRepository->save($user);

return redirect('2fa')->with('success', 'Secret Key is generated, Please verify Code to Enable 2FA');
}

/**
* Enable 2fa for USer.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\Response
*/
public function enable2fa(Request $request)
{
$user = Auth::user();
// $google2fa = app('pragmarx.google2fa');
$secret = $request->input('verify-code');

$valid = $this->google2fa->verifyKey($user->getGoogle2faSecret(), $secret);

if ($valid) {
$user->setGoogle2faEnable(true);
$this->userRepository->save($user);

return redirect('2fa')->with('success', '2FA is Enabled Successfully.');
} else {
return redirect('2fa')->with('error', 'Invalid Verification Code, Please try again.');
}
}

/**
* Disable 2fa for User.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\Response
*/
public function disable2fa(Request $request)
{
$validatedData = $request->validate([
'current-password' => 'required',
]);

$user = Auth::user();
$credentials = [
$user->getAuthIdentifierName() => $user->getAuthIdentifier(),
'password' => $validatedData['current-password'],
];
if (! Auth::attempt($credentials)) {
return redirect()
->back()
->with('error', 'Your password does not matches with your account password. Please try again.');
}

$user = Auth::user();
$user->setGoogle2faEnable(false);
$user->setGoogle2faSecret(null);
$this->userRepository->save($user);

return redirect('2fa')->with('success', '2FA is now Disabled.');
}

/**
* Google2FAMiddleware verify redirect.
*
* @return \Illuminate\Http\Response
*/
public function verify()
{
return redirect(request()->session()->get('_previous')['url']);
}
}
1 change: 1 addition & 0 deletions app/Http/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class Kernel extends HttpKernel
'entity.bindings' => \LaravelDoctrine\ORM\Middleware\SubstituteBindings::class,
'json.request' => \App\Http\Middleware\JsonRequestMiddleware::class,
'client' => \Laravel\Passport\Http\Middleware\CheckClientCredentials::class,
'2fa' => \App\Http\Middleware\Google2FAMiddleware::class,
];

/**
Expand Down
43 changes: 43 additions & 0 deletions app/Http/Middleware/Google2FAMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace App\Http\Middleware;

use Closure;
use HMS\Auth\Google2FAAuthenticator;

class Google2FAMiddleware
{
/**
* @var Google2FAAuthenticator
*/
protected $authenticator;

/**
* Construct Middleware.
*
* @param Google2FAAuthenticator $authenticator
*/
public function __construct(Google2FAAuthenticator $authenticator)
{
$this->authenticator = $authenticator;
}

/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @return mixed
*/
public function handle($request, Closure $next)
{
$this->authenticator->boot($request);

if ($this->authenticator->isAuthenticated()) {
return $next($request);
}

return $this->authenticator->makeRequestOneTimePasswordResponse();
}
}
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"type": "project",
"require": {
"php": "^7.1.3",
"bacon/bacon-qr-code": "^2.0",
"brainrepo/carbon-normalizer": "dev-master",
"fideloper/proxy": "^4.0",
"garygreen/pretty-routes": "^1.0",
Expand All @@ -26,6 +27,7 @@
"maxbrokman/safe-queue": "^0.3.0",
"orphans/git-deploy-laravel": "dev-master",
"pda/pheanstalk": "~4.0",
"pragmarx/google2fa-laravel": "^1.0",
"predis/predis": "^1.1",
"spatie/laravel-cookie-consent": "^2.0",
"tremby/laravel-git-version": "^1.1"
Expand Down
Loading

0 comments on commit c225e03

Please sign in to comment.