diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index cfd9549d4e..7175e6064c 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -209,26 +209,33 @@ class LoginController extends Controller public function getTwoFactorEnroll() { + // Make sure the user is logged in if (!Auth::check()) { - return redirect()->route('login')->with('error', 'You must be logged in.'); + return redirect()->route('login')->with('error', trans('auth/general.login_prompt')); } + + $settings = Setting::getSettings(); $user = Auth::user(); - $google2fa = app()->make('PragmaRX\Google2FA\Contracts\Google2FA'); - if ($user->two_factor_secret=='') { - $user->two_factor_secret = $google2fa->generateSecretKey(32); - $user->save(); + // We wouldn't normally see this page if 2FA isn't enforced via the + // \App\Http\Middleware\CheckForTwoFactor middleware AND if a device isn't enrolled, + // but let's check check anyway in case there's a browser history or back button thing. + // While you can access this page directly, enrolling a device when 2FA isn't enforced + // won't cause any harm. + + if (($user->two_factor_secret!='') && ($user->two_factor_enrolled==1)) { + return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.already_enrolled')); } + $google2fa = new Google2FA(); + $secret = $google2fa->generateSecretKey(); + $user->two_factor_secret = $secret; + $user->save(); - $google2fa_url = $google2fa->getQRCodeGoogleUrl( - urlencode(Setting::getSettings()->site_name), - urlencode($user->username), - $user->two_factor_secret - ); - - return view('auth.two_factor_enroll')->with('google2fa_url', $google2fa_url); + $barcode = new \Com\Tecnick\Barcode\Barcode(); + $barcode_obj = $barcode->getBarcodeObj('QRCODE', 'otpauth://totp/'.urlencode($settings->site_name).':'.urlencode($user->username).'?secret='.urlencode($secret).'&issuer=Snipe-IT&period=30', 300, 300, 'black', array(-2, -2, -2, -2)); + return view('auth.two_factor_enroll')->with('barcode_obj', $barcode_obj); } @@ -240,6 +247,20 @@ class LoginController extends Controller */ public function getTwoFactorAuth() { + // Check that the user is logged in + if (!Auth::check()) { + return redirect()->route('login')->with('error', trans('auth/general.login_prompt')); + } + + $user = Auth::user(); + + // Check whether there is a device enrolled. + // This *should* be handled via the \App\Http\Middleware\CheckForTwoFactor middleware + // but we're just making sure (in case someone edited the database directly, etc) + if (($user->two_factor_secret=='') || ($user->two_factor_enrolled!=1)) { + return redirect()->route('two-factor-enroll'); + } + return view('auth.two_factor'); } @@ -252,22 +273,25 @@ class LoginController extends Controller { if (!Auth::check()) { - return redirect()->route('login')->with('error', 'You must be logged in.'); + return redirect()->route('login')->with('error', trans('auth/general.login_prompt')); + } + + if (!$request->has('two_factor_secret')) { + return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.code_required')); } $user = Auth::user(); - $secret = $request->get('two_factor_secret'); - $google2fa = app()->make('PragmaRX\Google2FA\Contracts\Google2FA'); - $valid = $google2fa->verifyKey($user->two_factor_secret, $secret); + $google2fa = new Google2FA(); + $secret = $request->input('two_factor_secret'); - if ($valid) { + if ($google2fa->verifyKey($user->two_factor_secret, $secret)) { $user->two_factor_enrolled = 1; $user->save(); $request->session()->put('2fa_authed', 'true'); return redirect()->route('home')->with('success', 'You are logged in!'); } - return redirect()->route('two-factor')->with('error', 'Invalid two-factor code'); + return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.invalid_code')); } @@ -290,7 +314,7 @@ class LoginController extends Controller return redirect()->away($customLogoutUrl); } - return redirect()->route('login')->with('success', 'You have successfully logged out!'); + return redirect()->route('login')->with('success', trans('auth/general.logout.success')); } @@ -315,11 +339,11 @@ class LoginController extends Controller } /** - * Redirect the user after determining they are locked out. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse - */ + * Redirect the user after determining they are locked out. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ protected function sendLockoutResponse(Request $request) { $seconds = $this->limiter()->availableIn( @@ -330,18 +354,18 @@ class LoginController extends Controller $message = \Lang::get('auth/message.throttle', ['minutes' => $minutes]); - return redirect()->back() + return redirect()->back() ->withInput($request->only($this->username(), 'remember')) ->withErrors([$this->username() => $message]); } /** - * Override the lockout time and duration - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse - */ + * Override the lockout time and duration + * + * @param \Illuminate\Http\Request $request + * @return bool + */ protected function hasTooManyLoginAttempts(Request $request) { $lockoutTime = config('auth.throttle.lockout_duration'); diff --git a/composer.json b/composer.json index 6822043467..33a5a21d67 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,8 @@ "patchwork/utf8": "~1.2", "phpdocumentor/reflection-docblock": "3.2.2", "phpspec/prophecy": "1.6.2", - "pragmarx/google2fa": "^1.0", + "pragmarx/google2fa": "^5.0", + "pragmarx/google2fa-laravel": "^0.3.0", "predis/predis": "^1.1", "rollbar/rollbar-laravel": "2.4.1", "schuppo/password-strength": "~1.5", diff --git a/composer.lock b/composer.lock index e42a3de8d3..8d971265a8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "f44697f67c1de6fd46cb9a7cb8bc20a6", + "content-hash": "a723d895823e1569b8fc1449f47bee53", "packages": [ { "name": "barryvdh/laravel-debugbar", @@ -55,60 +55,6 @@ ], "time": "2017-07-21T11:56:48+00:00" }, - { - "name": "christian-riesen/base32", - "version": "1.3.1", - "source": { - "type": "git", - "url": "https://github.com/ChristianRiesen/base32.git", - "reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa", - "reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "4.*", - "satooshi/php-coveralls": "0.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Base32\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Riesen", - "email": "chris.riesen@gmail.com", - "homepage": "http://christianriesen.com", - "role": "Developer" - } - ], - "description": "Base32 encoder/decoder according to RFC 4648", - "homepage": "https://github.com/ChristianRiesen/base32", - "keywords": [ - "base32", - "decode", - "encode", - "rfc4648" - ], - "time": "2016-05-05T11:49:03+00:00" - }, { "name": "defuse/php-encryption", "version": "v2.2.1", @@ -2306,6 +2252,69 @@ ], "time": "2018-02-28T20:30:58+00:00" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "2132f0f293d856026d7d11bd81b9f4a23a1dc1f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/2132f0f293d856026d7d11bd81b9f4a23a1dc1f6", + "reference": "2132f0f293d856026d7d11bd81b9f4a23a1dc1f6", + "shasum": "" + }, + "require": { + "php": "^5.3|^7" + }, + "require-dev": { + "paragonie/random_compat": "^1.4|^2", + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^0.3|^1" + }, + "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": "2018-04-30T17:57:16+00:00" + }, { "name": "paragonie/random_compat", "version": "v2.0.17", @@ -2717,29 +2726,86 @@ }, { "name": "pragmarx/google2fa", - "version": "v1.0.1", + "version": "v5.0.0", "source": { "type": "git", "url": "https://github.com/antonioribeiro/google2fa.git", - "reference": "b346dc138339b745c5831405d00cff7c1351aa0d" + "reference": "17c969c82f427dd916afe4be50bafc6299aef1b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/b346dc138339b745c5831405d00cff7c1351aa0d", - "reference": "b346dc138339b745c5831405d00cff7c1351aa0d", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/17c969c82f427dd916afe4be50bafc6299aef1b4", + "reference": "17c969c82f427dd916afe4be50bafc6299aef1b4", "shasum": "" }, "require": { - "christian-riesen/base32": "~1.3", - "paragonie/random_compat": "~1.4|~2.0", + "paragonie/constant_time_encoding": "~1.0|~2.0", + "paragonie/random_compat": ">=1", "php": ">=5.4", "symfony/polyfill-php56": "~1.2" }, "require-dev": { - "phpspec/phpspec": "~2.1" + "phpunit/phpunit": "~4|~5|~6" + }, + "type": "library", + "extra": { + "component": "package", + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Google2FA\\": "src/", + "PragmaRX\\Google2FA\\Tests\\": "tests/" + } + }, + "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": "2019-03-19T22:44:16+00:00" + }, + { + "name": "pragmarx/google2fa-laravel", + "version": "v0.3.0", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa-laravel.git", + "reference": "048026cd55af7d4b019d18f83e5bbf92d4c8b071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa-laravel/zipball/048026cd55af7d4b019d18f83e5bbf92d4c8b071", + "reference": "048026cd55af7d4b019d18f83e5bbf92d4c8b071", + "shasum": "" + }, + "require": { + "laravel/framework": ">=5.2", + "php": ">=5.4", + "pragmarx/google2fa": ">=5.0" + }, + "require-dev": { + "orchestra/testbench-browser-kit": ">=3.4", + "phpunit/phpunit": ">=5.0" }, "suggest": { - "bacon/bacon-qr-code": "Required to generate inline QR Codes." + "bacon/bacon-qr-code": "Required to generate inline QR Codes.", + "pragmarx/recovery": "Generate recovery codes." }, "type": "library", "extra": { @@ -2748,12 +2814,21 @@ "Laravel" ], "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "0.2-dev" + }, + "laravel": { + "providers": [ + "PragmaRX\\Google2FALaravel\\ServiceProvider" + ], + "aliases": { + "Google2FA": "PragmaRX\\Google2FALaravel\\Facade" + } } }, "autoload": { "psr-4": { - "PragmaRX\\Google2FA\\": "src/" + "PragmaRX\\Google2FALaravel\\": "src/", + "PragmaRX\\Google2FALaravel\\Tests\\": "tests/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2774,7 +2849,7 @@ "google2fa", "laravel" ], - "time": "2016-07-18T20:25:04+00:00" + "time": "2019-03-19T23:20:01+00:00" }, { "name": "predis/predis", @@ -4145,7 +4220,7 @@ }, { "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "email": "backendtea@gmail.com" } ], "description": "Symfony polyfill for ctype functions", @@ -5293,6 +5368,60 @@ ], "time": "2016-10-30T11:50:56+00:00" }, + { + "name": "christian-riesen/base32", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/ChristianRiesen/base32.git", + "reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa", + "reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*", + "satooshi/php-coveralls": "0.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Base32\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Riesen", + "email": "chris.riesen@gmail.com", + "homepage": "http://christianriesen.com", + "role": "Developer" + } + ], + "description": "Base32 encoder/decoder according to RFC 4648", + "homepage": "https://github.com/ChristianRiesen/base32", + "keywords": [ + "base32", + "decode", + "encode", + "rfc4648" + ], + "time": "2016-05-05T11:49:03+00:00" + }, { "name": "codeception/codeception", "version": "2.3.6", @@ -5930,6 +6059,7 @@ "mock", "xunit" ], + "abandoned": true, "time": "2017-06-30T09:13:00+00:00" }, { diff --git a/config/app.php b/config/app.php index 73091160c5..8f6ae429a3 100755 --- a/config/app.php +++ b/config/app.php @@ -291,7 +291,7 @@ return [ Collective\Html\HtmlServiceProvider::class, Spatie\Backup\BackupServiceProvider::class, Fideloper\Proxy\TrustedProxyServiceProvider::class, - PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider::class, + PragmaRX\Google2FALaravel\ServiceProvider::class, Laravel\Passport\PassportServiceProvider::class, Laravel\Tinker\TinkerServiceProvider::class, Unicodeveloper\DumbPassword\DumbPasswordServiceProvider::class, diff --git a/resources/lang/en/auth/message.php b/resources/lang/en/auth/message.php index 3aee689e54..7c0cd5b50f 100644 --- a/resources/lang/en/auth/message.php +++ b/resources/lang/en/auth/message.php @@ -9,11 +9,23 @@ return array( 'account_banned' => 'This user account is banned.', 'throttle' => 'Too many failed login attempts. Please try again in :seconds seconds.', + 'two_factor' => array( + 'already_enrolled' => 'Your device is already enrolled.', + 'success' => 'You have successfully logged in.', + 'code_required' => 'Two-factor code is required.', + 'invalid_code' => 'Two-factor code is invalid.', + ), + 'signin' => array( 'error' => 'There was a problem while trying to log you in, please try again.', 'success' => 'You have successfully logged in.', ), + 'logout' => array( + 'error' => 'There was a problem while trying to log you out, please try again.', + 'success' => 'You have successfully logged out.', + ), + 'signup' => array( 'error' => 'There was a problem while trying to create your account, please try again.', 'success' => 'Account sucessfully created.', diff --git a/resources/views/auth/two_factor_enroll.blade.php b/resources/views/auth/two_factor_enroll.blade.php index 1fe9c89c9b..05b367583e 100644 --- a/resources/views/auth/two_factor_enroll.blade.php +++ b/resources/views/auth/two_factor_enroll.blade.php @@ -28,7 +28,7 @@
- + {!! $barcode_obj->getHtmlDiv() !!}