Skip to content

Commit 05d898a

Browse files
committed
2fa: Initial setup
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
1 parent 6c0113c commit 05d898a

File tree

17 files changed

+1010
-12
lines changed

17 files changed

+1010
-12
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace HMS\Auth;
4+
5+
use PragmaRX\Google2FALaravel\Support\Authenticator;
6+
7+
class Google2FAAuthenticator extends Authenticator
8+
{
9+
/**
10+
* Check if it is already logged in or passable without checking for an OTP.
11+
*
12+
* @return bool
13+
*/
14+
protected function canPassWithoutCheckingOTP()
15+
{
16+
return ! $this->getUser()->isGoogle2faEnable() ||
17+
! $this->isEnabled() ||
18+
$this->noUserIsAuthenticated() ||
19+
$this->twoFactorAuthStillValid();
20+
}
21+
22+
/**
23+
* Get the user Google2FA secret.
24+
*
25+
* @throws InvalidSecretKey
26+
*
27+
* @return mixed
28+
*/
29+
protected function getGoogle2FASecretKey()
30+
{
31+
$secret = $this->getUser()->getGoogle2faSecret();
32+
33+
if (is_null($secret) || empty($secret)) {
34+
throw new InvalidSecretKey('Secret key cannot be empty.');
35+
}
36+
37+
return $secret;
38+
}
39+
}

app/HMS/Entities/User.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@ class User implements
9090
*/
9191
protected $emails;
9292

93+
/**
94+
* @var bool
95+
*/
96+
protected $google2faEnable;
97+
98+
/**
99+
* @var string|null
100+
*/
101+
protected $google2faSecret;
102+
93103
/**
94104
* User constructor.
95105
*
@@ -340,4 +350,44 @@ public function getEmails()
340350
{
341351
return $this->emails;
342352
}
353+
354+
/**
355+
* @return bool
356+
*/
357+
public function isGoogle2faEnable()
358+
{
359+
return $this->google2faEnable;
360+
}
361+
362+
/**
363+
* @param bool $google2faEnable
364+
*
365+
* @return self
366+
*/
367+
public function setGoogle2faEnable(bool $google2faEnable)
368+
{
369+
$this->google2faEnable = $google2faEnable;
370+
371+
return $this;
372+
}
373+
374+
/**
375+
* @return string|null
376+
*/
377+
public function getGoogle2faSecret()
378+
{
379+
return $this->google2faSecret;
380+
}
381+
382+
/**
383+
* @param string|null $google2faSecret
384+
*
385+
* @return self
386+
*/
387+
public function setGoogle2faSecret($google2faSecret)
388+
{
389+
$this->google2faSecret = $google2faSecret;
390+
391+
return $this;
392+
}
343393
}

app/HMS/Mappings/HMS.Entities.User.dcm.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ HMS\Entities\User:
3838
column: email_verified_at
3939
type: datetime
4040
nullable: true
41+
google2faEnable:
42+
type: boolean
43+
options:
44+
default: 0
45+
google2faSecret:
46+
type: string
47+
nullable: true
4148
deletedAt:
4249
type: datetime
4350
nullable: true

app/Http/Controllers/Api/SearchController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public function users(string $searchQuery = null, Request $request)
5454
'addressPostcode' => $user->getProfile() ? $user->getProfile()->getAddressPostcode() : null,
5555
'accountId' => $user->getAccount() ? $user->getAccount()->getId() : null,
5656
'paymentRef' => $user->getAccount() ? $user->getAccount()->getPaymentRef() : null,
57+
'google2fa' => $user->isGoogle2faEnable(),
5758
];
5859
}));
5960

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Auth;
4+
5+
use Illuminate\Http\Request;
6+
use App\Http\Controllers\Controller;
7+
use HMS\Repositories\UserRepository;
8+
use Illuminate\Support\Facades\Auth;
9+
use PragmaRX\Google2FALaravel\Google2FA;
10+
11+
class TwoFactorAuthenticationController extends Controller
12+
{
13+
/**
14+
* @var Google2FA
15+
*/
16+
protected $google2fa;
17+
18+
/**
19+
* @var UserRepository
20+
*/
21+
protected $userRepository;
22+
23+
/**
24+
* Create a new controller instance.
25+
*
26+
* @param Google2FA $google2fa
27+
*
28+
* @return void
29+
*/
30+
public function __construct(Google2FA $google2fa, UserRepository $userRepository)
31+
{
32+
$this->google2fa = $google2fa;
33+
$this->userRepository = $userRepository;
34+
35+
$this->middleware('auth');
36+
}
37+
38+
/**
39+
* Show the 2fa enable/disable form.
40+
*
41+
* @param \Illuminate\Http\Request $request
42+
*
43+
* @return \Illuminate\Http\Response
44+
*/
45+
public function show2faForm(Request $request)
46+
{
47+
$user = Auth::user();
48+
49+
$google2faUrl = '';
50+
if (! empty($user->getGoogle2faSecret())) {
51+
$google2faUrl = $this->google2fa->getQRCodeInline(
52+
'Nottingham Hackspace HMS',
53+
$user->getEmail(),
54+
$user->getGoogle2faSecret()
55+
);
56+
}
57+
58+
return view('auth.2fa')
59+
->with('user', $user)
60+
->with('google2faUrl', $google2faUrl);
61+
}
62+
63+
/**
64+
* Generate new 2fa secret for user.
65+
*
66+
* @param \Illuminate\Http\Request $request
67+
*
68+
* @return \Illuminate\Http\Response
69+
*/
70+
public function generate2faSecret(Request $request)
71+
{
72+
$user = Auth::user();
73+
74+
// Add the secret key to the registration data
75+
$user->setGoogle2faEnable(false);
76+
$user->setGoogle2faSecret($this->google2fa->generateSecretKey(32));
77+
$this->userRepository->save($user);
78+
79+
return redirect('2fa')->with('success', 'Secret Key is generated, Please verify Code to Enable 2FA');
80+
}
81+
82+
/**
83+
* Enable 2fa for USer.
84+
*
85+
* @param \Illuminate\Http\Request $request
86+
*
87+
* @return \Illuminate\Http\Response
88+
*/
89+
public function enable2fa(Request $request)
90+
{
91+
$user = Auth::user();
92+
// $google2fa = app('pragmarx.google2fa');
93+
$secret = $request->input('verify-code');
94+
95+
$valid = $this->google2fa->verifyKey($user->getGoogle2faSecret(), $secret);
96+
97+
if ($valid) {
98+
$user->setGoogle2faEnable(true);
99+
$this->userRepository->save($user);
100+
101+
return redirect('2fa')->with('success', '2FA is Enabled Successfully.');
102+
} else {
103+
return redirect('2fa')->with('error', 'Invalid Verification Code, Please try again.');
104+
}
105+
}
106+
107+
/**
108+
* Disable 2fa for User.
109+
*
110+
* @param \Illuminate\Http\Request $request
111+
*
112+
* @return \Illuminate\Http\Response
113+
*/
114+
public function disable2fa(Request $request)
115+
{
116+
$validatedData = $request->validate([
117+
'current-password' => 'required',
118+
]);
119+
120+
$user = Auth::user();
121+
$credentials = [
122+
$user->getAuthIdentifierName() => $user->getAuthIdentifier(),
123+
'password' => $validatedData['current-password'],
124+
];
125+
if (! Auth::attempt($credentials)) {
126+
return redirect()
127+
->back()
128+
->with('error', 'Your password does not matches with your account password. Please try again.');
129+
}
130+
131+
$user = Auth::user();
132+
$user->setGoogle2faEnable(false);
133+
$user->setGoogle2faSecret(null);
134+
$this->userRepository->save($user);
135+
136+
return redirect('2fa')->with('success', '2FA is now Disabled.');
137+
}
138+
139+
/**
140+
* Google2FAMiddleware verify redirect.
141+
*
142+
* @return \Illuminate\Http\Response
143+
*/
144+
public function verify()
145+
{
146+
return redirect(request()->session()->get('_previous')['url']);
147+
}
148+
}

app/Http/Kernel.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class Kernel extends HttpKernel
6767
'entity.bindings' => \LaravelDoctrine\ORM\Middleware\SubstituteBindings::class,
6868
'json.request' => \App\Http\Middleware\JsonRequestMiddleware::class,
6969
'client' => \Laravel\Passport\Http\Middleware\CheckClientCredentials::class,
70+
'2fa' => \App\Http\Middleware\Google2FAMiddleware::class,
7071
];
7172

7273
/**
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace App\Http\Middleware;
4+
5+
use Closure;
6+
use HMS\Auth\Google2FAAuthenticator;
7+
8+
class Google2FAMiddleware
9+
{
10+
/**
11+
* @var Google2FAAuthenticator
12+
*/
13+
protected $authenticator;
14+
15+
/**
16+
* Construct Middleware.
17+
*
18+
* @param Google2FAAuthenticator $authenticator
19+
*/
20+
public function __construct(Google2FAAuthenticator $authenticator)
21+
{
22+
$this->authenticator = $authenticator;
23+
}
24+
25+
/**
26+
* Handle an incoming request.
27+
*
28+
* @param \Illuminate\Http\Request $request
29+
* @param \Closure $next
30+
*
31+
* @return mixed
32+
*/
33+
public function handle($request, Closure $next)
34+
{
35+
$this->authenticator->boot($request);
36+
37+
if ($this->authenticator->isAuthenticated()) {
38+
return $next($request);
39+
}
40+
41+
return $this->authenticator->makeRequestOneTimePasswordResponse();
42+
}
43+
}

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"type": "project",
77
"require": {
88
"php": "^7.1.3",
9+
"bacon/bacon-qr-code": "^2.0",
910
"brainrepo/carbon-normalizer": "dev-master",
1011
"fideloper/proxy": "^4.0",
1112
"garygreen/pretty-routes": "^1.0",
@@ -26,6 +27,7 @@
2627
"maxbrokman/safe-queue": "^0.3.0",
2728
"orphans/git-deploy-laravel": "dev-master",
2829
"pda/pheanstalk": "~4.0",
30+
"pragmarx/google2fa-laravel": "^1.0",
2931
"predis/predis": "^1.1",
3032
"spatie/laravel-cookie-consent": "^2.0",
3133
"tremby/laravel-git-version": "^1.1"

0 commit comments

Comments
 (0)