diff --git a/app/Http/Controllers/Api/Client/AccountController.php b/app/Http/Controllers/Api/Client/AccountController.php new file mode 100644 index 000000000..e683d3a0b --- /dev/null +++ b/app/Http/Controllers/Api/Client/AccountController.php @@ -0,0 +1,16 @@ +fractal->item($request->user()) + ->transformWith($this->getTransformer(AccountTransformer::class)) + ->toArray(); + } +} diff --git a/app/Http/Controllers/Auth/AbstractLoginController.php b/app/Http/Controllers/Auth/AbstractLoginController.php index 150841fab..58a48dfe2 100644 --- a/app/Http/Controllers/Auth/AbstractLoginController.php +++ b/app/Http/Controllers/Auth/AbstractLoginController.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http\Controllers\Auth; -use Cake\Chronos\Chronos; use Lcobucci\JWT\Builder; use Illuminate\Http\Request; use Pterodactyl\Models\User; @@ -16,6 +15,7 @@ use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Pterodactyl\Traits\Helpers\ProvidesJWTServices; +use Pterodactyl\Transformers\Api\Client\AccountTransformer; use Illuminate\Contracts\Cache\Repository as CacheRepository; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; @@ -137,24 +137,18 @@ abstract class AbstractLoginController extends Controller $request->session()->regenerate(); $this->clearLoginAttempts($request); - $token = $this->builder->setIssuer(config('app.url')) - ->setAudience(config('app.url')) - ->setId(str_random(12), true) - ->setIssuedAt(Chronos::now()->getTimestamp()) - ->setNotBefore(Chronos::now()->getTimestamp()) - ->setExpiration(Chronos::now()->addSeconds(config('session.lifetime'))->getTimestamp()) - ->set('user', $user->only([ - 'id', 'uuid', 'username', 'email', 'name_first', 'name_last', 'language', 'root_admin', - ])) - ->sign($this->getJWTSigner(), $this->getJWTSigningKey()) - ->getToken(); - $this->auth->guard()->login($user, true); + debug($request->cookies->all()); + return response()->json([ 'complete' => true, 'intended' => $this->redirectPath(), - 'token' => $token->__toString(), + 'cookie' => [ + 'name' => config('session.cookie'), + 'value' => $this->encrypter->encrypt($request->cookie(config('session.cookie'))), + ], + 'user' => (new AccountTransformer())->transform($user), ]); } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index d21c8d3c8..dc7837f90 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http; -use Pterodactyl\Http\Middleware\MaintenanceMiddleware; use Pterodactyl\Models\ApiKey; use Illuminate\Auth\Middleware\Authorize; use Illuminate\Auth\Middleware\Authenticate; @@ -21,6 +20,7 @@ use Illuminate\Routing\Middleware\SubstituteBindings; use Pterodactyl\Http\Middleware\AccessingValidServer; use Pterodactyl\Http\Middleware\Api\SetSessionDriver; use Illuminate\View\Middleware\ShareErrorsFromSession; +use Pterodactyl\Http\Middleware\MaintenanceMiddleware; use Pterodactyl\Http\Middleware\RedirectIfAuthenticated; use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; use Pterodactyl\Http\Middleware\Api\AuthenticateIPAccess; @@ -71,7 +71,7 @@ class Kernel extends HttpKernel RequireTwoFactorAuthentication::class, ], 'api' => [ - 'throttle:120,1', + 'throttle:240,1', ApiSubstituteBindings::class, SetSessionDriver::class, 'api..key:' . ApiKey::TYPE_APPLICATION, @@ -79,7 +79,9 @@ class Kernel extends HttpKernel AuthenticateIPAccess::class, ], 'client-api' => [ - 'throttle:60,1', + 'throttle:240,1', + EncryptCookies::class, + StartSession::class, SubstituteClientApiBindings::class, SetSessionDriver::class, 'api..key:' . ApiKey::TYPE_ACCOUNT, diff --git a/app/Http/Middleware/Api/AuthenticateKey.php b/app/Http/Middleware/Api/AuthenticateKey.php index 998eb378c..e5dd91159 100644 --- a/app/Http/Middleware/Api/AuthenticateKey.php +++ b/app/Http/Middleware/Api/AuthenticateKey.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Middleware\Api; use Closure; use Lcobucci\JWT\Parser; use Cake\Chronos\Chronos; +use Illuminate\Support\Str; use Illuminate\Http\Request; use Pterodactyl\Models\ApiKey; use Illuminate\Auth\AuthManager; @@ -63,19 +64,24 @@ class AuthenticateKey public function handle(Request $request, Closure $next, int $keyType) { if (is_null($request->bearerToken())) { - throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']); + if (! Str::startsWith($request->route()->getName(), ['api.client']) && ! $request->user()) { + throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']); + } } - $raw = $request->bearerToken(); + if (is_null($request->bearerToken())) { + $model = (new ApiKey)->forceFill([ + 'user_id' => $request->user()->id, + 'key_type' => ApiKey::TYPE_ACCOUNT, + ]); + } - // This is an internal JWT, treat it differently to get the correct user before passing it along. - if (strlen($raw) > ApiKey::IDENTIFIER_LENGTH + ApiKey::KEY_LENGTH) { - $model = $this->authenticateJWT($raw); - } else { + if (! isset($model)) { + $raw = $request->bearerToken(); $model = $this->authenticateApiKey($raw, $keyType); + $this->auth->guard()->loginUsingId($model->user_id); } - $this->auth->guard()->loginUsingId($model->user_id); $request->attributes->set('api_key', $model); return $next($request); diff --git a/app/Transformers/Api/Client/AccountTransformer.php b/app/Transformers/Api/Client/AccountTransformer.php new file mode 100644 index 000000000..30bed0d24 --- /dev/null +++ b/app/Transformers/Api/Client/AccountTransformer.php @@ -0,0 +1,36 @@ + $model->root_admin, + 'username' => $model->username, + 'email' => $model->email, + 'first_name' => $model->name_first, + 'last_name' => $model->name_last, + 'language' => $model->language, + ]; + } +} diff --git a/resources/assets/scripts/store.js b/resources/assets/scripts/_store.js similarity index 100% rename from resources/assets/scripts/store.js rename to resources/assets/scripts/_store.js diff --git a/resources/assets/scripts/app.js b/resources/assets/scripts/app.js index 204ae6e1c..3aa7f6979 100644 --- a/resources/assets/scripts/app.js +++ b/resources/assets/scripts/app.js @@ -3,21 +3,24 @@ import Vuex from 'vuex'; import vuexI18n from 'vuex-i18n'; import VueRouter from 'vue-router'; +require('./bootstrap'); + // Helpers import { Ziggy } from './helpers/ziggy'; import Locales from './../../../resources/lang/locales'; import { flash } from './mixins/flash'; import { routes } from './routes'; -import { storeData } from './store'; +import storeData from './store/index.js'; window.events = new Vue; window.Ziggy = Ziggy; +Vue.config.productionTip = false; Vue.use(Vuex); + const store = new Vuex.Store(storeData); const route = require('./../../../vendor/tightenco/ziggy/src/js/route').default; -Vue.config.productionTip = false; Vue.mixin({ methods: { route } }); Vue.mixin(flash); @@ -31,6 +34,4 @@ const router = new VueRouter({ mode: 'history', routes }); -require('./bootstrap'); - const app = new Vue({ store, router }).$mount('#pterodactyl'); diff --git a/resources/assets/scripts/bootstrap.js b/resources/assets/scripts/bootstrap.js index 05dccd8e8..35667f59a 100644 --- a/resources/assets/scripts/bootstrap.js +++ b/resources/assets/scripts/bootstrap.js @@ -1,3 +1,5 @@ +import axios from './helpers/axios'; + window._ = require('lodash'); /** @@ -10,24 +12,7 @@ try { window.$ = window.jQuery = require('jquery'); } catch (e) {} -/** - * We'll load the axios HTTP library which allows us to easily issue requests - * to our Laravel back-end. This library automatically handles sending the - * CSRF token as a header based on the value of the "XSRF" token cookie. - */ - -window.axios = require('axios'); -window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; -window.axios.defaults.headers.common['Accept'] = 'application/json'; -window.axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.token || ''; - -if (typeof phpdebugbar !== 'undefined') { - window.axios.interceptors.response.use(function (response) { - phpdebugbar.ajaxHandler.handle(response.request); - - return response; - }); -} +window.axios = axios; /** * Next we will register the CSRF Token as a common header with Axios so that diff --git a/resources/assets/scripts/components/auth/LoginForm.vue b/resources/assets/scripts/components/auth/LoginForm.vue index 39708ec2e..af63c101c 100644 --- a/resources/assets/scripts/components/auth/LoginForm.vue +++ b/resources/assets/scripts/components/auth/LoginForm.vue @@ -77,32 +77,21 @@ this.$data.showSpinner = true; this.clearFlashes(); - axios.post(this.route('auth.login'), { - user: this.$props.user.email, - password: this.$props.user.password, - }) - .then(function (response) { - // If there is a 302 redirect or some other odd behavior (basically, response that isnt - // in JSON format) throw an error and don't try to continue with the login. - if (!(response.data instanceof Object)) { - throw new Error('An error was encountered while processing this request.'); + this.$store.dispatch('auth/login', { user: this.$props.user.email, password: this.$props.user.password }) + .then(response => { + if (response.complete) { + return window.location = response.intended; } - if (response.data.complete) { - localStorage.setItem('token', response.data.token); - self.$store.dispatch('login'); - return window.location = response.data.intended; - } - - self.$props.user.password = ''; - self.$data.showSpinner = false; - self.$router.push({name: 'checkpoint', query: {token: response.data.login_token}}); + this.$props.user.password = ''; + this.$data.showSpinner = false; + this.$router.push({name: 'checkpoint', query: {token: response.login_token}}); }) - .catch(function (err) { - self.$props.user.password = ''; - self.$data.showSpinner = false; - self.$refs.password.focus(); - self.$store.dispatch('logout'); + .catch(err => { + this.$props.user.password = ''; + this.$data.showSpinner = false; + this.$refs.password.focus(); + this.$store.dispatch('auth/logout'); if (!err.response) { return console.error(err); diff --git a/resources/assets/scripts/components/dashboard/Dashboard.vue b/resources/assets/scripts/components/dashboard/Dashboard.vue index 59b1bdb0f..ca3a5555a 100644 --- a/resources/assets/scripts/components/dashboard/Dashboard.vue +++ b/resources/assets/scripts/components/dashboard/Dashboard.vue @@ -18,7 +18,7 @@ @@ -29,7 +29,7 @@