diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index bd85c6807..62b754724 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -159,12 +159,12 @@ class RegisterController extends Controller $confirmPassword = $request->input('password_confirmation'); $email = $request->input('email'); - // Get the default user role. - $userDefault = Role::query()->where('isdefault', '=', 1)->first(); + // Get the default user role. + $userDefault = Role::query()->where('isdefault', '=', 1)->first(); - if (! empty($error)) { - return $this->showRegistrationForm($request, $error); - } + if (! empty($error)) { + return $this->showRegistrationForm($request, $error); + } if (Invite::isAllowed($inviteCode, $email) || Settings::settingValue('..registerstatus') !== Settings::REGISTER_STATUS_INVITE) { $user = $this->create( diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 28773a8ec..a7960b69b 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -84,18 +84,18 @@ class ResetPasswordController extends Controller $content = app('smarty.view')->fetch($theme.'/forgottenpassword.tpl'); app('smarty.view')->assign( - [ - 'content' => $content, - 'title' => $title, - 'meta_title' => $meta_title, - 'meta_keywords' => $meta_keywords, - 'meta_description' => $meta_description, - 'email' => $ret['email'] ?? '', - 'confirmed' => $confirmed, - 'error' => $error, - 'notice' => $onscreen, - ] - ); + [ + 'content' => $content, + 'title' => $title, + 'meta_title' => $meta_title, + 'meta_keywords' => $meta_keywords, + 'meta_description' => $meta_description, + 'email' => $ret['email'] ?? '', + 'confirmed' => $confirmed, + 'error' => $error, + 'notice' => $onscreen, + ] + ); app('smarty.view')->display($theme.'/basepage.tpl'); } } diff --git a/app/Http/Controllers/BasePageController.php b/app/Http/Controllers/BasePageController.php index 89c58e6e1..46f398680 100644 --- a/app/Http/Controllers/BasePageController.php +++ b/app/Http/Controllers/BasePageController.php @@ -81,7 +81,7 @@ class BasePageController extends Controller */ public function __construct() { - $this->middleware(['auth', 'web'])->except('api', 'contact', 'showContactForm', 'callback', 'getNzb', 'terms', 'capabilities', 'movie', 'apiSearch', 'tv', 'details', 'failed', 'showRssDesc', 'fullFeedRss', 'categoryFeedRss', 'cartRss', 'myMoviesRss', 'myShowsRss'); + $this->middleware(['auth', 'web', '2fa'])->except('api', 'contact', 'showContactForm', 'callback', 'getNzb', 'terms', 'capabilities', 'movie', 'apiSearch', 'tv', 'details', 'failed', 'showRssDesc', 'fullFeedRss', 'categoryFeedRss', 'cartRss', 'myMoviesRss', 'myShowsRss'); // Buffer settings/DB connection. $this->settings = new Settings(); $this->smarty = app('smarty.view'); diff --git a/app/Http/Controllers/PasswordSecurityController.php b/app/Http/Controllers/PasswordSecurityController.php new file mode 100644 index 000000000..6974c4116 --- /dev/null +++ b/app/Http/Controllers/PasswordSecurityController.php @@ -0,0 +1,101 @@ +passwordSecurity()->exists()) { + $google2fa_url = \Google2FA::getQRCodeInline( + config('app.name'), + $user->email, + $user->passwordSecurity->google2fa_secret + ); + } + $data = [ + 'user' => $user, + 'google2fa_url' => $google2fa_url, + ]; + + return view('auth.2fa')->with('data', $data); + } + + /** + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException + * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException + * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException + */ + public function generate2faSecret(Request $request) + { + $user = Auth::user(); + + // Add the secret key to the registration data + PasswordSecurity::create( + [ + 'user_id' => $user->id, + 'google2fa_enable' => 0, + 'google2fa_secret' => \Google2FA::generateSecretKey(), + ] + ); + + return redirect('2fa')->with('success', 'Secret Key is generated, Please verify Code to Enable 2FA'); + } + + /** + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException + * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException + * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException + */ + public function enable2fa(Request $request) + { + $user = Auth::user(); + $secret = $request->input('verify-code'); + $valid = \Google2FA::verifyKey($user->passwordSecurity->google2fa_secret, $secret); + if ($valid) { + $user->passwordSecurity->google2fa_enable = 1; + $user->passwordSecurity->save(); + + return redirect('2fa')->with('success', '2FA is Enabled Successfully.'); + } + + return redirect('2fa')->with('error', 'Invalid Verification Code, Please try again.'); + } + + /** + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function disable2fa(Request $request) + { + if (! (Hash::check($request->get('current-password'), Auth::user()->password))) { + // The passwords matches + return redirect()->back()->with('error', 'Your password does not match with your account password. Please try again.'); + } + + $validatedData = $request->validate([ + 'current-password' => 'required', + ]); + $user = Auth::user(); + $user->passwordSecurity->google2fa_enable = 0; + $user->passwordSecurity->save(); + + return redirect('2fa')->with('success', '2FA is now Disabled.'); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 3aa4275b7..fad3d28dd 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -63,5 +63,6 @@ class Kernel extends HttpKernel 'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class, 'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class, 'clearance' => \App\Http\Middleware\ClearanceMiddleware::class, + '2fa' => \App\Http\Middleware\Google2FAMiddleware::class, ]; } diff --git a/app/Http/Middleware/Google2FAMiddleware.php b/app/Http/Middleware/Google2FAMiddleware.php new file mode 100644 index 000000000..1bb273cd4 --- /dev/null +++ b/app/Http/Middleware/Google2FAMiddleware.php @@ -0,0 +1,27 @@ +boot($request); + + if ($authenticator->isAuthenticated()) { + return $next($request); + } + + return $authenticator->makeRequestOneTimePasswordResponse(); + } +} diff --git a/app/Models/PasswordSecurity.php b/app/Models/PasswordSecurity.php new file mode 100644 index 000000000..ea1b9c8ab --- /dev/null +++ b/app/Models/PasswordSecurity.php @@ -0,0 +1,25 @@ +belongsTo(User::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index bdcfc16a2..c1aecd116 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -946,4 +946,12 @@ class User extends Authenticatable { static::whereVerified(0)->where('created_at', '<', now()->subDays(3))->delete(); } + + /** + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function passwordSecurity(): \Illuminate\Database\Eloquent\Relations\HasOne + { + return $this->hasOne(PasswordSecurity::class); + } } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 03d21a5b6..68c30279c 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -16,6 +16,8 @@ class RouteServiceProvider extends ServiceProvider */ protected $namespace = 'App\Http\Controllers'; + public const HOME = '/'; + /** * Define your route model bindings, pattern filters, etc. * diff --git a/app/Support/Google2FAAuthenticator.php b/app/Support/Google2FAAuthenticator.php new file mode 100644 index 000000000..29bcb2963 --- /dev/null +++ b/app/Support/Google2FAAuthenticator.php @@ -0,0 +1,40 @@ +getUser()->passwordSecurity) { + return true; + } + + return + ! $this->getUser()->passwordSecurity->google2fa_enable || + ! $this->isEnabled() || + $this->noUserIsAuthenticated() || + $this->twoFactorAuthStillValid(); + } + + /** + * @return mixed + * @throws \PragmaRX\Google2FALaravel\Exceptions\InvalidSecretKey + */ + protected function getGoogle2FASecretKey() + { + $secret = $this->getUser()->passwordSecurity->{$this->config('otp_secret_column')}; + + if (is_null($secret) || empty($secret)) { + throw new InvalidSecretKey('Secret key cannot be empty.'); + } + + return $secret; + } +} diff --git a/composer.json b/composer.json index c4ae05915..cc07e32e6 100755 --- a/composer.json +++ b/composer.json @@ -134,6 +134,7 @@ "php-ffmpeg/php-ffmpeg": "^0.14", "php-http/guzzle6-adapter": "^1.1", "php-http/message": "^1.6", + "pragmarx/google2fa-laravel": "^1.4", "predis/predis": "^1.1", "propaganistas/laravel-disposable-email": "^2.0", "ramsey/uuid": "^4.0", diff --git a/composer.lock b/composer.lock index bd77369b0..65d35b1f2 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "6f13adce07c83b25e82e85fd0f403ef9", + "content-hash": "f507d2a8906f891af68933e1d18c169f", "packages": [ { "name": "aharen/omdbapi", @@ -208,6 +208,55 @@ "description": "PHP Wrapper for Accessing the Steam Storefront API", "time": "2017-08-27T15:14:06+00:00" }, + { + "name": "bacon/bacon-qr-code", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "3e9d791b67d0a2912922b7b7c7312f4b37af41e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/3e9d791b67d0a2912922b7b7c7312f4b37af41e4", + "reference": "3e9d791b67d0a2912922b7b7c7312f4b37af41e4", + "shasum": "" + }, + "require": { + "dasprid/enum": "^1.0.3", + "ext-iconv": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phly/keep-a-changelog": "^1.4", + "phpunit/phpunit": "^7 | ^8 | ^9", + "squizlabs/php_codesniffer": "^3.4" + }, + "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": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "time": "2020-10-30T02:02:47+00:00" + }, { "name": "bhuvidya/laravel-countries", "version": "v1.0.7", @@ -1253,6 +1302,49 @@ ], "time": "2020-09-22T11:38:01+00:00" }, + { + "name": "dasprid/enum", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/5abf82f213618696dda8e3bf6f64dd042d8542b2", + "reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "^7 | ^8 | ^9", + "squizlabs/php_codesniffer": "^3.4" + }, + "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/", + "role": "Developer" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "time": "2020-10-02T16:03:48+00:00" + }, { "name": "dborsatto/php-giantbomb", "version": "v2.1.0", @@ -4916,6 +5008,68 @@ ], "time": "2020-11-07T02:01:34+00:00" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c", + "reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c", + "shasum": "" + }, + "require": { + "php": "^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^6|^7|^8|^9", + "vimeo/psalm": "^1|^2|^3|^4" + }, + "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": "2020-12-06T15:14:20+00:00" + }, { "name": "pear/archive_tar", "version": "1.4.11", @@ -5770,6 +5924,183 @@ ], "time": "2020-07-20T17:29:33+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.4.1", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa-laravel.git", + "reference": "f9014fd7ea36a1f7fffa233109cf59b209469647" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa-laravel/zipball/f9014fd7ea36a1f7fffa233109cf59b209469647", + "reference": "f9014fd7ea36a1f7fffa233109cf59b209469647", + "shasum": "" + }, + "require": { + "laravel/framework": ">=5.4.36|^8.0", + "php": ">=7.0", + "pragmarx/google2fa-qrcode": "^1.0" + }, + "require-dev": { + "orchestra/testbench": "3.4.*|3.5.*|3.6.*|3.7.*|4.*|5.*|6.*", + "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-09-20T21:01:48+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": "predis/predis", "version": "v1.1.6", diff --git a/database/migrations/2020_12_27_214949_create_password_securities_table.php b/database/migrations/2020_12_27_214949_create_password_securities_table.php new file mode 100644 index 000000000..0208130ff --- /dev/null +++ b/database/migrations/2020_12_27_214949_create_password_securities_table.php @@ -0,0 +1,34 @@ +id(); + $table->integer('user_id'); + $table->boolean('google2fa_enable')->default(false); + $table->string('google2fa_secret')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('password_securities'); + } +} diff --git a/resources/views/auth/2fa.blade.php b/resources/views/auth/2fa.blade.php new file mode 100644 index 000000000..eb44a3bdc --- /dev/null +++ b/resources/views/auth/2fa.blade.php @@ -0,0 +1,105 @@ +@extends('layouts.app') + +@section('content') +
Two factor authentication (2FA) strengthens access security by requiring two methods (also referred to as factors) to verify your identity. Two factor authentication protects against phishing, social engineering and password brute force attacks and secures your logins from attackers exploiting weak or stolen credentials.
+To Enable Two Factor Authentication on your Account, you need to do following steps
+ +If you are looking to disable Two Factor Authentication. Please confirm your password and Click Disable 2FA Button.
+ + @endif + +Two factor authentication (2FA) strengthens access security by requiring two methods (also referred to as factors) to verify your identity. Two factor authentication protects against phishing, social engineering and password brute force attacks and secures your logins from attackers exploiting weak or stolen credentials.
+ + @if (session('error')) +| Enable or disable 2FA: + Here | +