From 324b989a29732b824bc2121d632a60ddf41e0422 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 1 Apr 2018 17:46:16 -0500 Subject: [PATCH] Get a working rough copy of the login page --- app/Http/Controllers/Auth/LoginController.php | 80 +++++++++--------- package.json | 1 + resources/assets/pterodactyl/scripts/app.js | 27 +++++- .../components/auth/ForgotPassword.vue | 7 +- .../scripts/components/auth/Login.vue | 27 +++--- .../scripts/components/auth/LoginForm.vue | 83 ++++++++++++++++--- .../scripts/components/auth/TwoFactorForm.vue | 64 ++++++++++++++ .../components/errors/Flash.template.js | 28 +++++++ .../components/{shared => forms}/CSRF.vue | 0 .../styles/components/authentication.css | 2 +- resources/lang/en/auth.php | 6 ++ routes/auth.php | 4 +- yarn.lock | 4 + 13 files changed, 265 insertions(+), 68 deletions(-) create mode 100644 resources/assets/pterodactyl/scripts/components/auth/TwoFactorForm.vue create mode 100644 resources/assets/pterodactyl/scripts/components/errors/Flash.template.js rename resources/assets/pterodactyl/scripts/components/{shared => forms}/CSRF.vue (100%) diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 127802d22..3d336741a 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -4,9 +4,10 @@ namespace Pterodactyl\Http\Controllers\Auth; use Illuminate\Http\Request; use Illuminate\Auth\AuthManager; +use Illuminate\Http\JsonResponse; use PragmaRX\Google2FA\Google2FA; use Illuminate\Auth\Events\Failed; -use Illuminate\Http\RedirectResponse; +use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Encryption\Encrypter; @@ -106,11 +107,12 @@ class LoginController extends Controller * Handle a login request to the application. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse * + * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Illuminate\Validation\ValidationException */ - public function login(Request $request) + public function login(Request $request): JsonResponse { $username = $request->input(self::USER_INPUT_FIELD); $useColumn = $this->getField($username); @@ -128,37 +130,28 @@ class LoginController extends Controller $validCredentials = password_verify($request->input('password'), $user->password); if ($user->use_totp) { - $token = str_random(64); - $this->cache->put($token, ['user_id' => $user->id, 'valid_credentials' => $validCredentials], 5); + $token = str_random(128); + $this->cache->put($token, [ + 'user_id' => $user->id, + 'valid_credentials' => $validCredentials, + 'request_ip' => $request->ip(), + ], 5); - return redirect()->route('auth.totp')->with('authentication_token', $token); + return response()->json([ + 'complete' => false, + 'token' => $token, + ]); } if ($validCredentials) { $this->auth->guard()->login($user, true); - return $this->sendLoginResponse($request); + return response()->json(['complete' => true]); } return $this->sendFailedLoginResponse($request, $user); } - /** - * Handle a TOTP implementation page. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View - */ - public function totp(Request $request) - { - $token = $request->session()->get('authentication_token'); - if (is_null($token) || $this->auth->guard()->user()) { - return redirect()->route('auth.login'); - } - - return view('auth.totp', ['verify_key' => $token]); - } - /** * Handle a login where the user is required to provide a TOTP authentication * token. In order to add additional layers of security, users are not @@ -167,27 +160,29 @@ class LoginController extends Controller * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\DisplayException */ - public function loginUsingTotp(Request $request) + public function loginCheckpoint(Request $request) { - if (is_null($request->input('verify_token'))) { + if (is_null($request->input('confirmation_token')) || is_null($request->input('authentication_code'))) { return $this->sendFailedLoginResponse($request); } try { - $cache = $this->cache->pull($request->input('verify_token'), []); + $cache = $this->cache->pull($request->input('confirmation_token'), []); $user = $this->repository->find(array_get($cache, 'user_id', 0)); } catch (RecordNotFoundException $exception) { return $this->sendFailedLoginResponse($request); } - if (is_null($request->input('2fa_token')) || ! array_get($cache, 'valid_credentials')) { + if (! array_get($cache, 'valid_credentials') || array_get($cache, 'request_ip') !== $request->ip()) { return $this->sendFailedLoginResponse($request, $user); } if (! $this->google2FA->verifyKey( $this->encrypter->decrypt($user->totp_secret), - $request->input('2fa_token'), + $request->input('authentication_code'), $this->config->get('pterodactyl.auth.2fa.window') )) { return $this->sendFailedLoginResponse($request, $user); @@ -203,24 +198,35 @@ class LoginController extends Controller * * @param \Illuminate\Http\Request $request * @param \Illuminate\Contracts\Auth\Authenticatable|null $user - * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException */ - protected function sendFailedLoginResponse(Request $request, Authenticatable $user = null): RedirectResponse + protected function sendFailedLoginResponse(Request $request, Authenticatable $user = null) { $this->incrementLoginAttempts($request); $this->fireFailedLoginEvent($user, [ $this->getField($request->input(self::USER_INPUT_FIELD)) => $request->input(self::USER_INPUT_FIELD), ]); - $errors = [self::USER_INPUT_FIELD => trans('auth.failed')]; + throw new DisplayException(trans('auth.failed')); + } - if ($request->expectsJson()) { - return response()->json($errors, 422); - } + /** + * Send the response after the user was authenticated. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + protected function sendLoginResponse(Request $request) + { + $request->session()->regenerate(); - return redirect()->route('auth.login') - ->withInput($request->only(self::USER_INPUT_FIELD)) - ->withErrors($errors); + $this->clearLoginAttempts($request); + + return $this->authenticated($request, $this->guard()->user()) + ?: response()->json([ + 'intended' => $this->redirectPath(), + ]); } /** diff --git a/package.json b/package.json index a790e437f..45825c2ab 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "vue-template-compiler": "^2.5.16", "vueify-insert-css": "^1.0.0", "vuex": "^3.0.1", + "vuex-flash": "^1.0.0", "vuex-i18n": "^1.10.5", "webpack": "^4.4.1", "webpack-stream": "^4.0.3", diff --git a/resources/assets/pterodactyl/scripts/app.js b/resources/assets/pterodactyl/scripts/app.js index 2645b4728..9100c82c2 100644 --- a/resources/assets/pterodactyl/scripts/app.js +++ b/resources/assets/pterodactyl/scripts/app.js @@ -1,6 +1,8 @@ import Vue from 'vue'; import Vuex from 'vuex'; import vuexI18n from 'vuex-i18n'; +import VuexFlash from 'vuex-flash'; +import { createFlashStore } from 'vuex-flash'; import VueRouter from 'vue-router'; // Helpers @@ -15,7 +17,11 @@ window.Ziggy = Ziggy; Vue.use(Vuex); -const store = new Vuex.Store(); +const store = new Vuex.Store({ + plugins: [ + createFlashStore(), + ], +}); const route = require('./../../../../vendor/tightenco/ziggy/src/js/route').default; Vue.config.productionTip = false; @@ -26,6 +32,10 @@ Vue.mixin({ }); Vue.use(VueRouter); +Vue.use(VuexFlash, { + mixin: true, + template: require('./components/errors/Flash.template') +}); Vue.use(vuexI18n.plugin, store); Vue.i18n.add('en', Locales.en); @@ -34,9 +44,20 @@ Vue.i18n.set('en'); const router = new VueRouter({ routes: [ { - path: '/:action?', + name: 'login', + path: '/', component: Login, - } + }, + { + name: 'forgot-password', + path: '/forgot-password', + component: Login, + }, + { + name: 'checkpoint', + path: '/checkpoint', + component: Login, + }, ] }); diff --git a/resources/assets/pterodactyl/scripts/components/auth/ForgotPassword.vue b/resources/assets/pterodactyl/scripts/components/auth/ForgotPassword.vue index af46c1c06..a252dab94 100644 --- a/resources/assets/pterodactyl/scripts/components/auth/ForgotPassword.vue +++ b/resources/assets/pterodactyl/scripts/components/auth/ForgotPassword.vue @@ -3,7 +3,7 @@
- @@ -27,7 +27,7 @@ diff --git a/resources/assets/pterodactyl/scripts/components/auth/LoginForm.vue b/resources/assets/pterodactyl/scripts/components/auth/LoginForm.vue index bd85849b0..d5201e6c1 100644 --- a/resources/assets/pterodactyl/scripts/components/auth/LoginForm.vue +++ b/resources/assets/pterodactyl/scripts/components/auth/LoginForm.vue @@ -1,10 +1,25 @@ diff --git a/resources/assets/pterodactyl/scripts/components/errors/Flash.template.js b/resources/assets/pterodactyl/scripts/components/errors/Flash.template.js new file mode 100644 index 000000000..78ff0561f --- /dev/null +++ b/resources/assets/pterodactyl/scripts/components/errors/Flash.template.js @@ -0,0 +1,28 @@ +module.exports = ` +
+ + + + + + + + + + + + +
+`; diff --git a/resources/assets/pterodactyl/scripts/components/shared/CSRF.vue b/resources/assets/pterodactyl/scripts/components/forms/CSRF.vue similarity index 100% rename from resources/assets/pterodactyl/scripts/components/shared/CSRF.vue rename to resources/assets/pterodactyl/scripts/components/forms/CSRF.vue diff --git a/resources/assets/pterodactyl/styles/components/authentication.css b/resources/assets/pterodactyl/styles/components/authentication.css index 9faacff67..a529bbf23 100644 --- a/resources/assets/pterodactyl/styles/components/authentication.css +++ b/resources/assets/pterodactyl/styles/components/authentication.css @@ -13,7 +13,7 @@ &:focus + label, &:valid + label { @apply .text-grey-darker .px-0 .cursor-pointer; - transform:translateY(-24px) + transform:translateY(-26px) } &:invalid + label { diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php index 58665d022..a1a553074 100644 --- a/resources/lang/en/auth.php +++ b/resources/lang/en/auth.php @@ -10,6 +10,12 @@ return [ 'go_to_login' => 'Go to Login', 'reset_help_text' => 'Enter your account email address to recive instructions on resetting your password.', 'recover_account' => 'Recover Account', + + 'two_factor' => [ + 'label' => '2-Factor Token', + 'label_help' => 'This account requires a second layer of authentication in order to continue. Please enter the code generated by your device to complete this login.', + ], + 'reset_password_text' => 'Reset your account password.', 'reset_password' => 'Reset Account Password', 'email_sent' => 'An email has been sent to you with further instructions for resetting your password.', diff --git a/routes/auth.php b/routes/auth.php index f0ac210b5..7145f02de 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -10,12 +10,10 @@ */ Route::group(['middleware' => 'guest'], function () { Route::get('/login', 'LoginController@showLoginForm')->name('auth.login'); - Route::get('/login/totp', 'LoginController@totp')->name('auth.totp'); - Route::get('/password', 'ForgotPasswordController@showLinkRequestForm')->name('auth.password'); Route::get('/password/reset/{token}', 'ResetPasswordController@showResetForm')->name('auth.reset'); Route::post('/login', 'LoginController@login')->middleware('recaptcha'); - Route::post('/login/totp', 'LoginController@loginUsingTotp'); + Route::post('/login/checkpoint', 'LoginController@loginCheckpoint')->name('auth.checkpoint'); Route::post('/password', 'ForgotPasswordController@sendResetLinkEmail')->middleware('recaptcha'); Route::post('/password/reset', 'ResetPasswordController@reset')->name('auth.reset.post')->middleware('recaptcha'); Route::post('/password/reset/{token}', 'ForgotPasswordController@sendResetLinkEmail')->middleware('recaptcha'); diff --git a/yarn.lock b/yarn.lock index 5041d118f..1746d4655 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6004,6 +6004,10 @@ vueify-insert-css@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/vueify-insert-css/-/vueify-insert-css-1.0.0.tgz#57e5d791907e8c9d87ae6de099a2174bd0a7f990" +vuex-flash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/vuex-flash/-/vuex-flash-1.0.0.tgz#4bbeb2093d4857ddef45a6c50f10243539acee6e" + vuex-i18n@^1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/vuex-i18n/-/vuex-i18n-1.10.5.tgz#635ea2204e0aa3f8fd512f0fab7f6b994d3f666c"