From 4abdee0efbe84a14a88ee3281a0bb5b68760f1bb Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 1 Feb 2017 22:58:48 -0500 Subject: [PATCH] Better 2FA implementation on logins --- CHANGELOG.md | 3 +- app/Http/Controllers/Auth/LoginController.php | 72 +++++++++++++------ app/Http/Routes/AuthRoutes.php | 8 ++- resources/lang/en/auth.php | 2 + .../themes/pterodactyl/auth/totp.blade.php | 46 ++++++++++++ 5 files changed, 106 insertions(+), 25 deletions(-) create mode 100644 resources/themes/pterodactyl/auth/totp.blade.php diff --git a/CHANGELOG.md b/CHANGELOG.md index f2ee47a5c..3c582aa84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,8 +18,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines. ### Changed * Admin API and base routes for user management now define the fields that should be passed to repositories rather than passing all fields. * User model now defines mass assignment fields using `$fillable` rather than `$guarded`. - -### Deprecated +* 2FA checkpoint on login is now its own page, and not an AJAX based call. Improves security on that front. ## v0.5.6 (Bodacious Boreopterus) ### Added diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 9890077f1..8d9c94eb9 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -27,6 +27,7 @@ namespace Pterodactyl\Http\Controllers\Auth; use Auth; use Alert; +use Cache; use Illuminate\Http\Request; use Pterodactyl\Models\User; use PragmaRX\Google2FA\Google2FA; @@ -110,33 +111,62 @@ class LoginController extends Controller } // Verify TOTP Token was Valid - if (Auth::user()->use_totp === 1) { - $G2FA = new Google2FA(); - if (is_null($request->input('totp_token')) || ! $G2FA->verifyKey(Auth::user()->totp_secret, $request->input('totp_token'))) { - if (! $lockedOut) { - $this->incrementLoginAttempts($request); - } + if (Auth::user()->use_totp) { + $verifyKey = str_random(64); + Cache::put($verifyKey, Auth::user()->id, 5); - Alert::danger(trans('auth.totp_failed'))->flash(); + return redirect()->route('auth.totp')->with('authentication_token', $verifyKey); + } else { + Auth::login(Auth::user(), $request->has('remember')); - return $this->sendFailedLoginResponse($request); - } + return $this->sendLoginResponse($request); + } + } + + public function totp(Request $request) + { + $verifyKey = $request->session()->get('authentication_token'); + + if (is_null($verifyKey) || Auth::user()) { + return redirect()->route('auth.login'); } - // Successfully Authenticated. - Auth::login(Auth::user(), $request->has('remember')); - - return $this->sendLoginResponse($request); + return view('auth.totp', [ + 'verify_key' => $verifyKey, + 'remember' => $request->has('remember'), + ]); } - /** - * Check if the provided user has TOTP enabled. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - */ - public function checkTotp(Request $request) + public function totpCheckpoint(Request $request) { - return response()->json(User::select('id')->where('email', $request->input('email'))->where('use_totp', 1)->first()); + $G2FA = new Google2FA(); + + if (is_null($request->input('verify_token'))) { + $this->incrementLoginAttempts($request); + Alert::danger(trans('auth.totp_failed'))->flash(); + + return redirect()->route('auth.login'); + } + + $user = User::where('id', Cache::pull($request->input('verify_token')))->first(); + if (! $user) { + $this->incrementLoginAttempts($request); + Alert::danger(trans('auth.totp_failed'))->flash(); + + return redirect()->route('auth.login'); + } + + + if (! is_null($request->input('2fa_token')) && $G2FA->verifyKey($user->totp_secret, $request->input('2fa_token'))) { + Auth::login($user, $request->has('remember')); + + return redirect()->intended($this->redirectPath()); + } else { + $this->incrementLoginAttempts($request); + Alert::danger(trans('auth.2fa_failed'))->flash(); + + return redirect()->route('auth.login'); + } } + } diff --git a/app/Http/Routes/AuthRoutes.php b/app/Http/Routes/AuthRoutes.php index 491bebe41..77968321c 100644 --- a/app/Http/Routes/AuthRoutes.php +++ b/app/Http/Routes/AuthRoutes.php @@ -51,9 +51,13 @@ class AuthRoutes 'uses' => 'Auth\LoginController@login', ]); - // Determine if we need to ask for a TOTP Token + $router->get('login/totp', [ + 'as' => 'auth.totp', + 'uses' => 'Auth\LoginController@totp', + ]); + $router->post('login/totp', [ - 'uses' => 'Auth\LoginController@checkTotp', + 'uses' => 'Auth\LoginController@totpCheckpoint', ]); // Show Password Reset Form diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php index 0d2e305a8..3748167f6 100644 --- a/resources/lang/en/auth.php +++ b/resources/lang/en/auth.php @@ -15,4 +15,6 @@ return [ 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 'password_requirements' => 'Passwords must contain at least one uppercase, lowecase, and numeric character and must be at least 8 characters in length.', 'request_reset' => 'Locate Account', + '2fa_required' => '2-Factor Authentication', + '2fa_failed' => 'The 2FA token provided was invalid.', ]; diff --git a/resources/themes/pterodactyl/auth/totp.blade.php b/resources/themes/pterodactyl/auth/totp.blade.php new file mode 100644 index 000000000..4a021603d --- /dev/null +++ b/resources/themes/pterodactyl/auth/totp.blade.php @@ -0,0 +1,46 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.auth') + +@section('title') + 2FA Checkpoint +@endsection + +@section('content') + +@endsection