diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 0a6ee3176..96549a628 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -2,9 +2,11 @@ namespace Pterodactyl\Http\Controllers\Auth; +use Lcobucci\JWT\Builder; use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; use Illuminate\Contracts\View\View; +use Lcobucci\JWT\Signer\Hmac\Sha256; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; class LoginController extends AbstractLoginController @@ -63,11 +65,26 @@ class LoginController extends AbstractLoginController 'request_ip' => $request->ip(), ], 5); - return response()->json(['complete' => false, 'token' => $token]); + return response()->json(['complete' => false, 'login_token' => $token]); } + $signer = new Sha256(); + $token = (new Builder)->setIssuer('http://pterodactyl.local') + ->setAudience('http://pterodactyl.local') + ->setId(str_random(12), true) + ->setIssuedAt(time()) + ->setNotBefore(time()) + ->setExpiration(time() + 3600) + ->set('uid', $user->id) + ->sign($signer, env('APP_JWT_KEY')) + ->getToken(); + $this->auth->guard()->login($user, true); - return response()->json(['complete' => true]); + return response()->json([ + 'complete' => true, + 'intended' => $this->redirectPath(), + 'token' => $token->__toString(), + ]); } } diff --git a/app/Http/Middleware/Api/AuthenticateKey.php b/app/Http/Middleware/Api/AuthenticateKey.php index 8f400bb4d..774fb930b 100644 --- a/app/Http/Middleware/Api/AuthenticateKey.php +++ b/app/Http/Middleware/Api/AuthenticateKey.php @@ -63,6 +63,7 @@ class AuthenticateKey } $raw = $request->bearerToken(); + $identifier = substr($raw, 0, ApiKey::IDENTIFIER_LENGTH); $token = substr($raw, ApiKey::IDENTIFIER_LENGTH); diff --git a/resources/assets/scripts/app.js b/resources/assets/scripts/app.js index d2a5be98e..204ae6e1c 100644 --- a/resources/assets/scripts/app.js +++ b/resources/assets/scripts/app.js @@ -8,13 +8,13 @@ import { Ziggy } from './helpers/ziggy'; import Locales from './../../../resources/lang/locales'; import { flash } from './mixins/flash'; import { routes } from './routes'; +import { storeData } from './store'; window.events = new Vue; window.Ziggy = Ziggy; Vue.use(Vuex); - -const store = new Vuex.Store(); +const store = new Vuex.Store(storeData); const route = require('./../../../vendor/tightenco/ziggy/src/js/route').default; Vue.config.productionTip = false; diff --git a/resources/assets/scripts/bootstrap.js b/resources/assets/scripts/bootstrap.js index 8d2009067..24ea8c7f0 100644 --- a/resources/assets/scripts/bootstrap.js +++ b/resources/assets/scripts/bootstrap.js @@ -19,6 +19,7 @@ try { window.axios = require('axios'); window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; +window.axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.token || ''; /** * 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 07236d550..bd217c26e 100644 --- a/resources/assets/scripts/components/auth/LoginForm.vue +++ b/resources/assets/scripts/components/auth/LoginForm.vue @@ -82,16 +82,20 @@ }) .then(function (response) { if (response.data.complete) { - return window.location = '/'; + 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.token}}); + self.$router.push({name: 'checkpoint', query: {token: response.data.login_token}}); }) .catch(function (err) { self.$props.user.password = ''; self.$data.showSpinner = false; + self.$store.dispatch('logout'); + if (!err.response) { return console.error(err); } diff --git a/resources/assets/scripts/components/auth/TwoFactorForm.vue b/resources/assets/scripts/components/auth/TwoFactorForm.vue index 27d2b2282..e42df8451 100644 --- a/resources/assets/scripts/components/auth/TwoFactorForm.vue +++ b/resources/assets/scripts/components/auth/TwoFactorForm.vue @@ -49,9 +49,12 @@ authentication_code: this.$data.code, }) .then(function (response) { + localStorage.setItem('token', response.data.token); + self.$store.dispatch('login'); window.location = response.data.intended; }) .catch(function (err) { + self.$store.dispatch('logout'); if (!err.response) { return console.error(err); } diff --git a/resources/assets/scripts/models/user.js b/resources/assets/scripts/models/user.js new file mode 100644 index 000000000..c76123779 --- /dev/null +++ b/resources/assets/scripts/models/user.js @@ -0,0 +1,33 @@ +import JwtDecode from 'jwt-decode'; + +const User = function () { + this.id = 0; + this.admin = false; + this.email = ''; +}; + +/** + * Return a new instance of the user model using a JWT. + * + * @param {string} token + * @returns {User} + */ +User.prototype.fromJwt = function (token) { + return this.newModel(JwtDecode(token)); +}; + +/** + * Return an instance of this user model with the properties set on it. + * + * @param {object} obj + * @returns {User} + */ +User.prototype.newModel = function (obj) { + this.id = obj.id; + this.admin = obj.admin; + this.email = obj.email; + + return this; +}; + +export default User; diff --git a/resources/assets/scripts/store.js b/resources/assets/scripts/store.js new file mode 100644 index 000000000..f0e1da067 --- /dev/null +++ b/resources/assets/scripts/store.js @@ -0,0 +1,28 @@ +import User from './models/user'; + +export const storeData = { + state: { + user: null, + }, + actions: { + login: function ({ commit }) { + commit('login'); + }, + logout: function ({ commit }) { + commit('logout'); + }, + }, + getters: { + user: function (state) { + return state.user; + }, + }, + mutations: { + login: function (state) { + state.user = new User().fromJwt(localStorage.getItem('token')); + }, + logout: function (state) { + state.user = null; + } + } +};