forked from miguel456/rbrecruiter
Add two factor authentication
This commit is contained in:
parent
5f1f92a9ce
commit
d392c0593f
|
@ -44,7 +44,7 @@ class LoginController extends Controller
|
|||
|
||||
// We can't customise the error message, since that would imply overriding the login method, which is large.
|
||||
// Also, the user should never know that they're banned.
|
||||
public function attemptLogin(Request $request)
|
||||
public function attemptLogin(Request $request)
|
||||
{
|
||||
$user = User::where('email', $request->email)->first();
|
||||
|
||||
|
@ -60,9 +60,9 @@ class LoginController extends Controller
|
|||
return $this->originalAttemptLogin($request);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $this->originalAttemptLogin($request);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Traits\AuthenticatesTwoFactor;
|
||||
|
||||
class TwofaController extends Controller
|
||||
{
|
||||
use AuthenticatesTwoFactor;
|
||||
|
||||
|
||||
protected $redirectTo = '/dashboard';
|
||||
|
||||
}
|
|
@ -8,6 +8,8 @@ use App\Http\Requests\FlushSessionsRequest;
|
|||
use App\Http\Requests\DeleteUserRequest;
|
||||
use App\Http\Requests\SearchPlayerRequest;
|
||||
use App\Http\Requests\UpdateUserRequest;
|
||||
use App\Http\Requests\Add2FASecretRequest;
|
||||
use App\Http\Requests\Remove2FASecretRequest;
|
||||
|
||||
use App\User;
|
||||
use App\Ban;
|
||||
|
@ -21,6 +23,8 @@ use App\Notifications\EmailChanged;
|
|||
use App\Notifications\ChangedPassword;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
use Google2FA;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
|
||||
|
@ -112,10 +116,32 @@ class UserController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
public function showAccount()
|
||||
public function showAccount(Request $request)
|
||||
{
|
||||
$QRCode = null;
|
||||
|
||||
if (!$request->user()->has2FA())
|
||||
{
|
||||
if ($request->session()->has('twofaAttemptFailed'))
|
||||
{
|
||||
$twoFactorSecret = $request->session()->get('current2FA');
|
||||
}
|
||||
else
|
||||
{
|
||||
$twoFactorSecret = Google2FA::generateSecretKey(32, '');
|
||||
$request->session()->put('current2FA', $twoFactorSecret);
|
||||
}
|
||||
|
||||
$QRCode = Google2FA::getQRCodeInline(
|
||||
config('app.name'),
|
||||
$request->user()->email,
|
||||
$twoFactorSecret
|
||||
);
|
||||
}
|
||||
|
||||
return view('dashboard.user.profile.useraccount')
|
||||
->with('ip', request()->ip());
|
||||
->with('ip', request()->ip())
|
||||
->with('twofaQRCode', $QRCode);
|
||||
}
|
||||
|
||||
|
||||
|
@ -247,10 +273,67 @@ class UserController extends Controller
|
|||
|
||||
}
|
||||
|
||||
public function add2FASecret(Add2FASecretRequest $request)
|
||||
{
|
||||
$currentSecret = $request->session()->get('current2FA');
|
||||
$isValid = Google2FA::verifyKey($currentSecret, $request->otp);
|
||||
|
||||
if ($isValid)
|
||||
{
|
||||
$request->user()->twofa_secret = $currentSecret;
|
||||
$request->user()->save();
|
||||
|
||||
Log::warning('SECURITY: User activated two-factor authentication', [
|
||||
'initiator' => $request->user()->email,
|
||||
'ip' => $request->ip()
|
||||
]);
|
||||
|
||||
Google2FA::login();
|
||||
|
||||
Log::warning('SECURITY: Started two factor session automatically', [
|
||||
'initiator' => $request->user()->email,
|
||||
'ip' => $request->ip()
|
||||
]);
|
||||
|
||||
$request->session()->forget('current2FA');
|
||||
|
||||
if ($request->session()->has('twofaAttemptFailed'))
|
||||
$request->session()->forget('twofaAttemptFailed');
|
||||
|
||||
|
||||
$request->session()->flash('success', '2FA succesfully enabled! You\'ll now be prompted for an OTP each time you log in.');
|
||||
}
|
||||
else
|
||||
{
|
||||
$request->session()->flash('error', 'Incorrect code. Please reopen the 2FA settings panel and try again.');
|
||||
$request->session()->put('twofaAttemptFailed', true);
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function remove2FASecret(Remove2FASecretRequest $request)
|
||||
{
|
||||
Log::warning('SECURITY: Disabling two factor authentication (user initiated)', [
|
||||
'initiator' => $request->user()->email,
|
||||
'ip' => $request->ip()
|
||||
]);
|
||||
|
||||
$request->user()->twofa_secret = null;
|
||||
$request->user()->save();
|
||||
|
||||
$request->session()->flash('success', 'Two-factor authentication disabled.');
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public function terminate(Request $request, User $user)
|
||||
{
|
||||
$this->authorize('terminate', User::class);
|
||||
|
||||
// TODO: move logic to policy
|
||||
if (!$user->isStaffMember() || $user->is(Auth::user()))
|
||||
{
|
||||
$request->session()->flash('error', 'You cannot terminate this user.');
|
||||
|
|
|
@ -64,6 +64,7 @@ class Kernel extends HttpKernel
|
|||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
'eligibility' => \App\Http\Middleware\ApplicationEligibility::class,
|
||||
'usernameUUID' => \App\Http\Middleware\UsernameUUID::class,
|
||||
'forcelogout' => \App\Http\Middleware\ForceLogoutMiddleware::class
|
||||
'forcelogout' => \App\Http\Middleware\ForceLogoutMiddleware::class,
|
||||
'2fa' => \PragmaRX\Google2FALaravel\Middleware::class,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class Add2FASecretRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
// current logic only updates currently authenticated user
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'otp' => 'required|string|min:6|max:6'
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class Remove2FASecretRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'currentPassword' => 'required|password',
|
||||
'consent' => 'required|accepted'
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use Google2FA;
|
||||
use App\Http\Requests\Add2FASecretRequest;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
|
||||
trait AuthenticatesTwoFactor
|
||||
{
|
||||
|
||||
public function verify2FA(Add2FASecretRequest $request)
|
||||
{
|
||||
$isValid = Google2FA::verifyKey($request->user()->twofa_secret, $request->otp);
|
||||
|
||||
if ($isValid)
|
||||
{
|
||||
Google2FA::login();
|
||||
|
||||
Log::info('SECURITY (postauth): One-time password verification succeeded', [
|
||||
'initiator' => $request->user()->email,
|
||||
'ip' => $request->ip()
|
||||
]);
|
||||
|
||||
return redirect()->to($this->redirectTo);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::warning('SECURITY (preauth): One-time password verification failed', [
|
||||
'initiator' => $request->user()->email,
|
||||
'ip' => $request->ip()
|
||||
]);
|
||||
|
||||
$request->session()->flash('error', 'Your one time password is invalid.');
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -68,18 +68,24 @@ class User extends Authenticatable
|
|||
}
|
||||
|
||||
|
||||
|
||||
public function isBanned()
|
||||
{
|
||||
return !$this->bans()->get()->isEmpty();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public function isStaffMember()
|
||||
{
|
||||
return $this->hasAnyRole('reviewer', 'admin', 'hiringManager');
|
||||
}
|
||||
|
||||
public function has2FA()
|
||||
{
|
||||
return !is_null($this->twofa_secret);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function routeNotificationForSlack($notification)
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"laravel/slack-notification-channel": "^2.0",
|
||||
"laravel/tinker": "^2.0",
|
||||
"laravel/ui": "^2.0",
|
||||
"pragmarx/google2fa-laravel": "^1.3",
|
||||
"sentry/sentry-laravel": "1.7.1",
|
||||
"spatie/laravel-permission": "^3.13"
|
||||
},
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "51429857899e8134bbe6b4fa61145cc3",
|
||||
"content-hash": "7bd6f57124bb77e0b496e2200f729267",
|
||||
"packages": [
|
||||
{
|
||||
"name": "almasaeed2010/adminlte",
|
||||
|
@ -221,6 +221,55 @@
|
|||
],
|
||||
"time": "2019-12-24T22:41:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
"version": "2.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Bacon/BaconQrCode.git",
|
||||
"reference": "6e53ced3d2499cee4a0ef23a7c4d6449607ac7da"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/6e53ced3d2499cee4a0ef23a7c4d6449607ac7da",
|
||||
"reference": "6e53ced3d2499cee4a0ef23a7c4d6449607ac7da",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"dasprid/enum": "^1.0",
|
||||
"ext-iconv": "*",
|
||||
"php": "^7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phly/keep-a-changelog": "^1.4",
|
||||
"phpunit/phpunit": "^6.4",
|
||||
"squizlabs/php_codesniffer": "^3.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-imagick": "to generate QR code images"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"BaconQrCode\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ben Scholzen 'DASPRiD'",
|
||||
"email": "mail@dasprids.de",
|
||||
"homepage": "http://www.dasprids.de",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "BaconQrCode is a QR code generator for PHP.",
|
||||
"homepage": "https://github.com/Bacon/BaconQrCode",
|
||||
"time": "2020-07-14T11:04:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brick/math",
|
||||
"version": "0.8.15",
|
||||
|
@ -374,6 +423,48 @@
|
|||
"description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
|
||||
"time": "2020-04-23T11:49:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dasprid/enum",
|
||||
"version": "1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/DASPRiD/Enum.git",
|
||||
"reference": "631ef6e638e9494b0310837fa531bedd908fc22b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/DASPRiD/Enum/zipball/631ef6e638e9494b0310837fa531bedd908fc22b",
|
||||
"reference": "631ef6e638e9494b0310837fa531bedd908fc22b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.4",
|
||||
"squizlabs/php_codesniffer": "^3.1"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"DASPRiD\\Enum\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ben Scholzen 'DASPRiD'",
|
||||
"email": "mail@dasprids.de",
|
||||
"homepage": "https://dasprids.de/"
|
||||
}
|
||||
],
|
||||
"description": "PHP 7.1 enum implementation",
|
||||
"keywords": [
|
||||
"enum",
|
||||
"map"
|
||||
],
|
||||
"time": "2017-10-25T22:45:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dnoegel/php-xdg-base-dir",
|
||||
"version": "v0.1.1",
|
||||
|
@ -2260,6 +2351,68 @@
|
|||
],
|
||||
"time": "2020-05-25T09:32:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/constant_time_encoding",
|
||||
"version": "v2.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/constant_time_encoding.git",
|
||||
"reference": "47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2",
|
||||
"reference": "47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7|^8"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6|^7",
|
||||
"vimeo/psalm": "^1|^2|^3"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ParagonIE\\ConstantTime\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paragon Initiative Enterprises",
|
||||
"email": "security@paragonie.com",
|
||||
"homepage": "https://paragonie.com",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Steve 'Sc00bz' Thomas",
|
||||
"email": "steve@tobtu.com",
|
||||
"homepage": "https://www.tobtu.com",
|
||||
"role": "Original Developer"
|
||||
}
|
||||
],
|
||||
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
|
||||
"keywords": [
|
||||
"base16",
|
||||
"base32",
|
||||
"base32_decode",
|
||||
"base32_encode",
|
||||
"base64",
|
||||
"base64_decode",
|
||||
"base64_encode",
|
||||
"bin2hex",
|
||||
"encoding",
|
||||
"hex",
|
||||
"hex2bin",
|
||||
"rfc4648"
|
||||
],
|
||||
"time": "2019-11-06T19:20:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v9.99.99",
|
||||
|
@ -2783,6 +2936,183 @@
|
|||
],
|
||||
"time": "2020-03-21T18:07:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pragmarx/google2fa",
|
||||
"version": "8.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/antonioribeiro/google2fa.git",
|
||||
"reference": "26c4c5cf30a2844ba121760fd7301f8ad240100b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/26c4c5cf30a2844ba121760fd7301f8ad240100b",
|
||||
"reference": "26c4c5cf30a2844ba121760fd7301f8ad240100b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"paragonie/constant_time_encoding": "^1.0|^2.0",
|
||||
"php": "^7.1|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^0.12.18",
|
||||
"phpunit/phpunit": "^7.5.15|^8.5|^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PragmaRX\\Google2FA\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Antonio Carlos Ribeiro",
|
||||
"email": "acr@antoniocarlosribeiro.com",
|
||||
"role": "Creator & Designer"
|
||||
}
|
||||
],
|
||||
"description": "A One Time Password Authentication package, compatible with Google Authenticator.",
|
||||
"keywords": [
|
||||
"2fa",
|
||||
"Authentication",
|
||||
"Two Factor Authentication",
|
||||
"google2fa"
|
||||
],
|
||||
"time": "2020-04-05T10:47:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pragmarx/google2fa-laravel",
|
||||
"version": "v1.3.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/antonioribeiro/google2fa-laravel.git",
|
||||
"reference": "ed6e0a9ea1519550688ffb5afb4919204e46ecea"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/antonioribeiro/google2fa-laravel/zipball/ed6e0a9ea1519550688ffb5afb4919204e46ecea",
|
||||
"reference": "ed6e0a9ea1519550688ffb5afb4919204e46ecea",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"laravel/framework": ">=5.4.36",
|
||||
"php": ">=7.0",
|
||||
"pragmarx/google2fa-qrcode": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"orchestra/testbench": "3.4.*|3.5.*|3.6.*|3.7.*|4.*",
|
||||
"phpunit/phpunit": "~5|~6|~7|~8"
|
||||
},
|
||||
"suggest": {
|
||||
"bacon/bacon-qr-code": "Required to generate inline QR Codes.",
|
||||
"pragmarx/recovery": "Generate recovery codes."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"component": "package",
|
||||
"frameworks": [
|
||||
"Laravel"
|
||||
],
|
||||
"branch-alias": {
|
||||
"dev-master": "0.2-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"PragmaRX\\Google2FALaravel\\ServiceProvider"
|
||||
],
|
||||
"aliases": {
|
||||
"Google2FA": "PragmaRX\\Google2FALaravel\\Facade"
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PragmaRX\\Google2FALaravel\\": "src/",
|
||||
"PragmaRX\\Google2FALaravel\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Antonio Carlos Ribeiro",
|
||||
"email": "acr@antoniocarlosribeiro.com",
|
||||
"role": "Creator & Designer"
|
||||
}
|
||||
],
|
||||
"description": "A One Time Password Authentication package, compatible with Google Authenticator.",
|
||||
"keywords": [
|
||||
"Authentication",
|
||||
"Two Factor Authentication",
|
||||
"google2fa",
|
||||
"laravel"
|
||||
],
|
||||
"time": "2020-04-05T17:39:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pragmarx/google2fa-qrcode",
|
||||
"version": "v1.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/antonioribeiro/google2fa-qrcode.git",
|
||||
"reference": "fd5ff0531a48b193a659309cc5fb882c14dbd03f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/antonioribeiro/google2fa-qrcode/zipball/fd5ff0531a48b193a659309cc5fb882c14dbd03f",
|
||||
"reference": "fd5ff0531a48b193a659309cc5fb882c14dbd03f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"bacon/bacon-qr-code": "~1.0|~2.0",
|
||||
"php": ">=5.4",
|
||||
"pragmarx/google2fa": ">=4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"khanamiryan/qrcode-detector-decoder": "^1.0",
|
||||
"phpunit/phpunit": "~4|~5|~6|~7"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"component": "package",
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PragmaRX\\Google2FAQRCode\\": "src/",
|
||||
"PragmaRX\\Google2FAQRCode\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Antonio Carlos Ribeiro",
|
||||
"email": "acr@antoniocarlosribeiro.com",
|
||||
"role": "Creator & Designer"
|
||||
}
|
||||
],
|
||||
"description": "QR Code package for Google2FA",
|
||||
"keywords": [
|
||||
"2fa",
|
||||
"Authentication",
|
||||
"Two Factor Authentication",
|
||||
"google2fa",
|
||||
"qr code",
|
||||
"qrcode"
|
||||
],
|
||||
"time": "2019-03-20T16:42:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
"version": "1.0.0",
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
* Enable / disable Google2FA.
|
||||
*/
|
||||
'enabled' => env('OTP_ENABLED', true),
|
||||
|
||||
/*
|
||||
* Lifetime in minutes.
|
||||
*
|
||||
* In case you need your users to be asked for a new one time passwords from time to time.
|
||||
*/
|
||||
'lifetime' => env('OTP_LIFETIME', 0), // 0 = eternal
|
||||
|
||||
/*
|
||||
* Renew lifetime at every new request.
|
||||
*/
|
||||
'keep_alive' => env('OTP_KEEP_ALIVE', true),
|
||||
|
||||
/*
|
||||
* Auth container binding.
|
||||
*/
|
||||
'auth' => 'auth',
|
||||
|
||||
/*
|
||||
* 2FA verified session var.
|
||||
*/
|
||||
|
||||
'session_var' => 'google2fa',
|
||||
|
||||
/*
|
||||
* One Time Password request input name.
|
||||
*/
|
||||
'otp_input' => 'otp',
|
||||
|
||||
/*
|
||||
* One Time Password Window.
|
||||
*/
|
||||
'window' => 1,
|
||||
|
||||
/*
|
||||
* Forbid user to reuse One Time Passwords.
|
||||
*/
|
||||
'forbid_old_passwords' => false,
|
||||
|
||||
/*
|
||||
* User's table column for google2fa secret.
|
||||
*/
|
||||
'otp_secret_column' => 'twofa_secret',
|
||||
|
||||
/*
|
||||
* One Time Password View.
|
||||
*/
|
||||
'view' => 'auth.2fa',
|
||||
|
||||
/*
|
||||
* One Time Password error message.
|
||||
*/
|
||||
'error_messages' => [
|
||||
'wrong_otp' => "Your one time code was incorrect.",
|
||||
'cannot_be_empty' => 'The one time code cannot be empty.',
|
||||
'unknown' => 'An unknown error has occurred. Please try again.',
|
||||
],
|
||||
|
||||
/*
|
||||
* Throw exceptions or just fire events?
|
||||
*/
|
||||
'throw_exceptions' => env('OTP_THROW_EXCEPTION', true),
|
||||
|
||||
/*
|
||||
* Which image backend to use for generating QR codes?
|
||||
*
|
||||
* Supports imagemagick, svg and eps
|
||||
*/
|
||||
'qrcode_image_backend' => \PragmaRX\Google2FALaravel\Support\Constants::QRCODE_IMAGE_BACKEND_IMAGEMAGICK,
|
||||
|
||||
];
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddTwofaSecretToUsers extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('twofa_secret')->nullable()->after('password');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('twofa_secret');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
/* overrides customizations for the AdminLTE auth pages */
|
||||
|
||||
.login-page, .register-page {
|
||||
background-image: url('/img/authbg.jpg') !important;
|
||||
background-size: cover !important;
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
body {
|
||||
font-family: "Karla", sans-serif;
|
||||
background-color: #d0d0ce;
|
||||
min-height: 100vh; }
|
||||
|
||||
.brand-wrapper {
|
||||
margin-bottom: 19px; }
|
||||
.brand-wrapper .logo {
|
||||
height: 37px; }
|
||||
|
||||
.login-card {
|
||||
border: 0;
|
||||
border-radius: 27.5px;
|
||||
box-shadow: 0 10px 30px 0 rgba(172, 168, 168, 0.43);
|
||||
overflow: hidden; }
|
||||
.login-card-img {
|
||||
border-radius: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
-o-object-fit: cover;
|
||||
object-fit: cover; }
|
||||
.login-card .card-body {
|
||||
padding: 85px 60px 60px; }
|
||||
@media (max-width: 422px) {
|
||||
.login-card .card-body {
|
||||
padding: 35px 24px; } }
|
||||
.login-card-description {
|
||||
font-size: 25px;
|
||||
color: #000;
|
||||
font-weight: normal;
|
||||
margin-bottom: 23px; }
|
||||
.login-card form {
|
||||
max-width: 326px; }
|
||||
.login-card .form-control {
|
||||
border: 1px solid #d5dae2;
|
||||
padding: 15px 25px;
|
||||
margin-bottom: 20px;
|
||||
min-height: 45px;
|
||||
font-size: 13px;
|
||||
line-height: 15;
|
||||
font-weight: normal; }
|
||||
.login-card .form-control::-webkit-input-placeholder {
|
||||
color: #919aa3; }
|
||||
.login-card .form-control::-moz-placeholder {
|
||||
color: #919aa3; }
|
||||
.login-card .form-control:-ms-input-placeholder {
|
||||
color: #919aa3; }
|
||||
.login-card .form-control::-ms-input-placeholder {
|
||||
color: #919aa3; }
|
||||
.login-card .form-control::placeholder {
|
||||
color: #919aa3; }
|
||||
.login-card .login-btn {
|
||||
padding: 13px 20px 12px;
|
||||
background-color: #000;
|
||||
border-radius: 4px;
|
||||
font-size: 17px;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
color: #fff;
|
||||
margin-bottom: 24px; }
|
||||
.login-card .login-btn:hover {
|
||||
border: 1px solid #000;
|
||||
background-color: transparent;
|
||||
color: #000; }
|
||||
.login-card .forgot-password-link {
|
||||
font-size: 14px;
|
||||
color: #919aa3;
|
||||
margin-bottom: 12px; }
|
||||
.login-card-footer-text {
|
||||
font-size: 16px;
|
||||
color: #0d2366;
|
||||
margin-bottom: 60px; }
|
||||
@media (max-width: 767px) {
|
||||
.login-card-footer-text {
|
||||
margin-bottom: 24px; } }
|
||||
.login-card-footer-nav a {
|
||||
font-size: 14px;
|
||||
color: #919aa3; }
|
||||
|
||||
/*# sourceMappingURL=login.css.map */
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"sources":["login.scss"],"names":[],"mappings":"AAAA;EACI,gCAAgC;EAChC,yBAAyB;EACzB,iBAAiB,EAAA;;AAGrB;EACI,mBAAmB,EAAA;EADvB;IAIQ,YAAY,EAAA;;AAIpB;EACI,SAAS;EACT,qBAAqB;EACrB,mDAAmD;EACnD,gBAAgB,EAAA;EAGhB;IACI,gBAAgB;IAChB,kBAAkB;IAClB,WAAW;IACX,YAAY;IACZ,oBAAiB;OAAjB,iBAAiB,EAAA;EAZzB;IAeQ,uBAAuB,EAAA;IAEvB;MAjBR;QAkBY,kBAAkB,EAAA,EAEzB;EAED;IACI,eAAe;IACf,WAAW;IACX,mBAAmB;IACnB,mBAAmB,EAAA;EA1B3B;IA8BQ,gBAAgB,EAAA;EA9BxB;IAkCQ,yBAAyB;IACzB,kBAAkB;IAClB,mBAAmB;IACnB,gBAAgB;IAChB,eAAe;IACf,eAAe;IACf,mBAAmB,EAAA;IAxC3B;MA2CY,cAAc,EAAA;IA3C1B;MA2CY,cAAc,EAAA;IA3C1B;MA2CY,cAAc,EAAA;IA3C1B;MA2CY,cAAc,EAAA;IA3C1B;MA2CY,cAAc,EAAA;EA3C1B;IAgDQ,uBAAuB;IACvB,sBAAsB;IACtB,kBAAkB;IAClB,eAAe;IACf,iBAAiB;IACjB,iBAAiB;IACjB,WAAW;IACX,mBAAmB,EAAA;IAvD3B;MA0DY,sBAAsB;MACtB,6BAA6B;MAC7B,WAAW,EAAA;EA5DvB;IAiEQ,eAAe;IACf,cAAc;IACd,mBAAmB,EAAA;EAGvB;IACI,eAAe;IACf,cAAc;IACd,mBAAmB,EAAA;IAEnB;MALJ;QAMQ,mBAAmB,EAAA,EAE1B;EAEA;IAEO,eAAe;IACf,cAAc,EAAA","file":"login.css","sourcesContent":["body {\n font-family: \"Karla\", sans-serif;\n background-color: #d0d0ce;\n min-height: 100vh;\n}\n\n.brand-wrapper {\n margin-bottom: 19px;\n\n .logo {\n height: 37px;\n }\n}\n\n.login-card {\n border: 0;\n border-radius: 27.5px;\n box-shadow: 0 10px 30px 0 rgba(172, 168, 168, 0.43);\n overflow: hidden;\n\n\n &-img {\n border-radius: 0;\n position: absolute;\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n .card-body {\n padding: 85px 60px 60px;\n\n @media (max-width: 422px) {\n padding: 35px 24px;\n }\n }\n\n &-description {\n font-size: 25px;\n color: #000;\n font-weight: normal;\n margin-bottom: 23px;\n }\n\n form {\n max-width: 326px;\n }\n\n .form-control {\n border: 1px solid #d5dae2;\n padding: 15px 25px;\n margin-bottom: 20px;\n min-height: 45px;\n font-size: 13px;\n line-height: 15;\n font-weight: normal;\n\n &::placeholder {\n color: #919aa3;\n }\n }\n\n .login-btn {\n padding: 13px 20px 12px;\n background-color: #000;\n border-radius: 4px;\n font-size: 17px;\n font-weight: bold;\n line-height: 20px;\n color: #fff;\n margin-bottom: 24px;\n\n &:hover {\n border: 1px solid #000;\n background-color: transparent;\n color: #000;\n }\n }\n\n .forgot-password-link {\n font-size: 14px;\n color: #919aa3;\n margin-bottom: 12px;\n }\n\n &-footer-text {\n font-size: 16px;\n color: #0d2366;\n margin-bottom: 60px;\n\n @media (max-width: 767px) {\n margin-bottom: 24px;\n }\n }\n\n &-footer-nav {\n a {\n font-size: 14px;\n color: #919aa3;\n }\n }\n}\n"]}
|
Binary file not shown.
After Width: | Height: | Size: 436 KiB |
|
@ -0,0 +1,36 @@
|
|||
@extends('breadcrumbs.auth.main')
|
||||
|
||||
@section('authpage')
|
||||
|
||||
<div class="container">
|
||||
<div class="card login-card">
|
||||
<div class="row no-gutters">
|
||||
<div class="col-md-5">
|
||||
<img src="/img/login.jpg" alt="login" class="login-card-img">
|
||||
</div>
|
||||
<div class="col-md-7">
|
||||
<div class="card-body">
|
||||
<div class="brand-wrapper">
|
||||
<img src="{{ config('adminlte.logo_img') }}" alt="logo" class="logo">{{ config('adminlte.logo') }}
|
||||
</div>
|
||||
<p class="login-card-description">Two-factor Authentication</p>
|
||||
<form action="{{ route('verify2FA') }}" method="POST" id="verify">
|
||||
@csrf
|
||||
<div class="form-group">
|
||||
<label for="name" class="sr-only">Two-factor secret code (You can find this on Google Authenticator)</label>
|
||||
<input type="text" name="otp" id="name" class="form-control" placeholder="2FA Code (e.g. 543324)">
|
||||
</div>
|
||||
<input name="register" id="register" class="btn btn-block login-btn mb-4" type="submit" value="Send 2FA Code">
|
||||
</form>
|
||||
<p class="login-card-footer-text">Don't know the code? <a href="{{ route('logout') }}" class="text-reset">Cancel login (logout)</a></p>
|
||||
<nav class="login-card-footer-nav">
|
||||
<a href="#!">Terms of use</a>
|
||||
<a href="#!">Privacy policy</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@stop
|
|
@ -1 +1,44 @@
|
|||
@extends('adminlte::auth.login')
|
||||
@extends('breadcrumbs.auth.main')
|
||||
|
||||
@section('authpage')
|
||||
|
||||
<div class="container">
|
||||
<div class="card login-card">
|
||||
<div class="row no-gutters">
|
||||
<div class="col-md-5">
|
||||
<img src="/img/login.jpg" alt="login" class="login-card-img">
|
||||
</div>
|
||||
<div class="col-md-7">
|
||||
<div class="card-body">
|
||||
<div class="brand-wrapper">
|
||||
<img src="{{ config('adminlte.logo_img') }}" alt="logo" class="logo">{{ config('adminlte.logo') }}
|
||||
</div>
|
||||
<p class="login-card-description">Sign into your account</p>
|
||||
<form action="{{ route('login') }}" method="POST" id="loginForm">
|
||||
@csrf
|
||||
<div class="form-group">
|
||||
<label for="email" class="sr-only">Email</label>
|
||||
<input type="email" name="email" id="email" class="form-control" placeholder="Email address">
|
||||
</div>
|
||||
<div class="form-group mb-4">
|
||||
<label for="password" class="sr-only">Password</label>
|
||||
<input type="password" name="password" id="password" class="form-control" placeholder="***********">
|
||||
</div>
|
||||
<div class="form-group mb-4">
|
||||
<label for="remember">Remember me</label>
|
||||
<input type="checkbox" name="remember" id="remember" />
|
||||
</div>
|
||||
<input name="login" id="login" class="btn btn-block login-btn mb-4" type="submit" value="Sign-in">
|
||||
</form>
|
||||
<a href="{{ route('password.request') }}" class="forgot-password-link">Forgot password?</a>
|
||||
<p class="login-card-footer-text">Don't have an account? <a href="{{ route('register') }}" class="text-reset">Register here</a></p>
|
||||
<nav class="login-card-footer-nav">
|
||||
<a href="#!">Terms of use</a>
|
||||
<a href="#!">Privacy policy</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@stop
|
||||
|
|
|
@ -1 +1,53 @@
|
|||
@extends('adminlte::auth.register')
|
||||
@extends('breadcrumbs.auth.main')
|
||||
|
||||
@section('authpage')
|
||||
|
||||
<div class="container">
|
||||
<div class="card login-card">
|
||||
<div class="row no-gutters">
|
||||
<div class="col-md-5">
|
||||
<img src="/img/login.jpg" alt="login" class="login-card-img">
|
||||
</div>
|
||||
<div class="col-md-7">
|
||||
<div class="card-body">
|
||||
<div class="brand-wrapper">
|
||||
<img src="{{ config('adminlte.logo_img') }}" alt="logo" class="logo">{{ config('adminlte.logo') }}
|
||||
</div>
|
||||
<p class="login-card-description">Register a new account</p>
|
||||
<form action="{{ route('register') }}" method="POST" id="registerForm">
|
||||
@csrf
|
||||
<div class="form-group">
|
||||
<label for="name" class="sr-only">Name</label>
|
||||
<input type="text" name="name" id="name" class="form-control" placeholder="Name (e.g. John Smith)">
|
||||
</div>
|
||||
<div class="form-group mb-4">
|
||||
<label for="email" class="sr-only">Email address</label>
|
||||
<input type="email" name="email" id="email" class="form-control" placeholder="Email Address">
|
||||
</div>
|
||||
<div class="form-group mb-4">
|
||||
<label for="password" class="sr-only">Password</label>
|
||||
<input type="password" name="password" id="password" class="form-control" placeholder="Password"
|
||||
</div>
|
||||
<div class="form-group mb-4">
|
||||
<label for="passwordc" class="sr-only">Confirm password</label>
|
||||
<input type="password" id="passwordc" name="password_confirmation" class="form-control" placeholder="Confirm password" />
|
||||
</div>
|
||||
|
||||
<div class="form-group mt-5">
|
||||
<label for="mcusername" class="sr-only">Minecraft Username (Premium)</label>
|
||||
<input type="text" name="uuid" class="form-control" id="mcusername" placeholder="Premium Minecraft Username (e.g. Notch)" />
|
||||
</div>
|
||||
<input name="register" id="register" class="btn btn-block login-btn mb-4" type="submit" value="Register">
|
||||
</form>
|
||||
<p class="login-card-footer-text">Have an account with us? <a href="{{ route('login') }}" class="text-reset">Login here</a></p>
|
||||
<nav class="login-card-footer-nav">
|
||||
<a href="#!">Terms of use</a>
|
||||
<a href="#!">Privacy policy</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@stop
|
||||
|
|
|
@ -1 +1 @@
|
|||
@extends('adminlte::auth.verify')
|
||||
@extends('adminlte::auth.verify')
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
@yield('content')
|
||||
|
||||
@include('breadcrumbs.footer')
|
||||
@include('breadcrumbs.footer')
|
||||
|
||||
</body>
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>{{ config('app.name') . '| ID' }}</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Karla:400,700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.materialdesignicons.com/4.8.95/css/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css" />
|
||||
<link rel="stylesheet" href="/css/login.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<main class="d-flex align-items-center min-vh-100 py-3 py-md-0">
|
||||
@yield('authpage')
|
||||
</main>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/toastr@2.1.4/toastr.min.js"></script>
|
||||
|
||||
<x-global-errors></x-global-errors>
|
||||
|
||||
</body>
|
|
@ -19,6 +19,92 @@
|
|||
@stop
|
||||
|
||||
@section('content')
|
||||
|
||||
@if (!Auth::user()->has2FA())
|
||||
|
||||
|
||||
<x-modal id="twoFactorAuthModal" modal-label="2faLabel" modal-title="Two-factor Authentication" include-close-button="true">
|
||||
|
||||
<h3><i class="fas fa-user-shield"></i> We're glad you decided to increase your account's security!</h3>
|
||||
|
||||
<p><b>Supported apps you can install:</b></p>
|
||||
<ul>
|
||||
<li><a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en"><i class="fab fa-google-play"></i> Google Authenticator</a></li>
|
||||
</ul>
|
||||
|
||||
<p>Scan the <i>QR code</i> below with your preferred app, and then copy the code here.</p>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-3 offset-3">
|
||||
<div class="qr-code-container text-center">
|
||||
|
||||
<img src="{{ $twofaQRCode }}" alt="2FA Security key" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col">
|
||||
|
||||
<form method="POST" action="{{ route('enable2FA') }}" id="enable2Fa">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<label for="otp">One-time code</label>
|
||||
<input type="text" id="otp" name="otp" class="form-control" />
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<x-slot name="modalFooter">
|
||||
|
||||
<button type="button" class="btn btn-success" onclick="$('#enable2Fa').submit()"><i class="fas fa-key"></i> Enable 2FA</button>
|
||||
|
||||
</x-slot>
|
||||
|
||||
</x-modal>
|
||||
|
||||
@endif
|
||||
|
||||
@if (Auth::user()->has2FA())
|
||||
|
||||
<x-modal id="remove2FA" modal-label="remove2FALabel" modal-title="Remove Two-Factor Authentication" include-close-button="true">
|
||||
|
||||
<p><i class="fas fa-exclamation-triangle"></i> <b>Are you sure?</b> Removing two-factor authentication will reduce the security of your account.</p>
|
||||
|
||||
<form action="{{ route('disable2FA') }}" method="POST" id="disable2FA">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<label for="currentPassword">Confirm your password to continue</label>
|
||||
<input id="currentPassword" type="password" name="currentPassword" class="form-control" required />
|
||||
<p class="text-sm text-muted">To prevent unauthorized changes, a password is always required for sensitive operations.</p>
|
||||
|
||||
<div class="form-group mt-2">
|
||||
|
||||
<label for="consent">"I understand the possible consequences of disabling two factor authentication"</label>
|
||||
<span><i>Click to confirm </i> </span><input type="checkbox" name="consent" id="consent" required />
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<x-slot name="modalFooter">
|
||||
|
||||
<button type="button" class="btn btn-danger" onclick="$('#disable2FA').submit()"><i class="fa fa-trash"></i> Remove 2FA</button>
|
||||
|
||||
</x-slot>
|
||||
|
||||
</x-modal>
|
||||
|
||||
@endif
|
||||
|
||||
<div class="modal fade" tabindex="-1" id="authenticationForm" role="dialog" aria-labelledby="authenticationFormLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
|
@ -116,8 +202,16 @@
|
|||
</div>
|
||||
<div class="tab-pane fade p-3" id="twofa" role="tabpanel" aria-labelledby="twofaTab">
|
||||
<h5 class="card-title">Two-factor Authentication</h5>
|
||||
<p class="card-text"><b>This feature is not yet available.</b> Support for Google Authenticator, Authy, Microsoft Authenticator and other compatible apps is coming soon, as well as fingerprint login for android devices.</p>
|
||||
<button type="button" class="btn btn-primary" disabled>Enable 2FA</button>
|
||||
<br />
|
||||
@if (Auth::user()->has2FA())
|
||||
<p><b>Hooray!</b> 2FA is setup correctly for your account. A code will be asked each time you login.</p>
|
||||
<button type="button" class="btn btn-danger" onclick="$('#remove2FA').modal('show')"><i class="fa fa-ban"></i> Disable 2FA (not recommended)</button>
|
||||
@else
|
||||
<p class="card-text"><b>Two-factor auth is available for your account.</b> Enabling this security option greatly increases your account's security in case your password ever gets stolen.</p>
|
||||
<button type="button" class="btn btn-primary" onclick="$('#twoFactorAuthModal').modal('show')">Enable 2FA</button>
|
||||
|
||||
@endif
|
||||
|
||||
</div>
|
||||
<div class="tab-pane fade p-3" id="sessions" role="tabpanel" aria-labelledby="sessionsTab">
|
||||
<h5 class="card-title">Session Manager</h5>
|
||||
|
|
|
@ -16,6 +16,9 @@ Route::group(['prefix' => 'auth', 'middleware' => ['usernameUUID']], function ()
|
|||
|
||||
Auth::routes();
|
||||
|
||||
Route::post('/twofa/authenticate', 'Auth\TwofaController@verify2FA')
|
||||
->name('verify2FA');
|
||||
|
||||
});
|
||||
|
||||
Route::get('/','HomeController@index')
|
||||
|
@ -25,7 +28,7 @@ Route::post('/form/contact', 'ContactController@create')
|
|||
->name('sendSubmission');
|
||||
|
||||
|
||||
Route::group(['middleware' => ['auth', 'forcelogout']], function(){
|
||||
Route::group(['middleware' => ['auth', 'forcelogout', '2fa']], function(){
|
||||
|
||||
Route::get('/dashboard', 'DashboardController@index')
|
||||
->name('dashboard')
|
||||
|
@ -129,6 +132,12 @@ Route::group(['middleware' => ['auth', 'forcelogout']], function(){
|
|||
Route::post('/settings/account/flush-sessions', 'UserController@flushSessions')
|
||||
->name('flushSessions');
|
||||
|
||||
Route::patch('/settings/account/twofa/enable', 'UserController@add2FASecret')
|
||||
->name('enable2FA');
|
||||
|
||||
Route::patch('/settings/account/twofa/disable', 'UserController@remove2FASecret')
|
||||
->name('disable2FA');
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue