forked from Alex/Pterodactyl-Panel
Finalize email/password changing in UI
This commit is contained in:
parent
81da55d46b
commit
0cc895f2d5
@ -3,9 +3,11 @@
|
|||||||
namespace Pterodactyl\Http\Controllers\Api\Client;
|
namespace Pterodactyl\Http\Controllers\Api\Client;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
use Pterodactyl\Services\Users\UserUpdateService;
|
use Pterodactyl\Services\Users\UserUpdateService;
|
||||||
use Pterodactyl\Transformers\Api\Client\AccountTransformer;
|
use Pterodactyl\Transformers\Api\Client\AccountTransformer;
|
||||||
use Pterodactyl\Http\Requests\Api\Client\Account\UpdateEmailRequest;
|
use Pterodactyl\Http\Requests\Api\Client\Account\UpdateEmailRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Client\Account\UpdatePasswordRequest;
|
||||||
|
|
||||||
class AccountController extends ClientApiController
|
class AccountController extends ClientApiController
|
||||||
{
|
{
|
||||||
@ -38,20 +40,34 @@ class AccountController extends ClientApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the authenticated user's email address if their password matches.
|
* Update the authenticated user's email address.
|
||||||
*
|
*
|
||||||
* @param \Pterodactyl\Http\Requests\Api\Client\Account\UpdateEmailRequest $request
|
* @param \Pterodactyl\Http\Requests\Api\Client\Account\UpdateEmailRequest $request
|
||||||
* @return array
|
* @return \Illuminate\Http\Response
|
||||||
*
|
*
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||||
*/
|
*/
|
||||||
public function updateEmail(UpdateEmailRequest $request): array
|
public function updateEmail(UpdateEmailRequest $request): Response
|
||||||
{
|
{
|
||||||
$updated = $this->updateService->handle($request->user(), $request->validated());
|
$this->updateService->handle($request->user(), $request->validated());
|
||||||
|
|
||||||
return $this->fractal->item($updated->get('model'))
|
return response('', Response::HTTP_CREATED);
|
||||||
->transformWith($this->getTransformer(AccountTransformer::class))
|
}
|
||||||
->toArray();
|
|
||||||
|
/**
|
||||||
|
* Update the authenticated user's password.
|
||||||
|
*
|
||||||
|
* @param \Pterodactyl\Http\Requests\Api\Client\Account\UpdatePasswordRequest $request
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
|
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||||
|
*/
|
||||||
|
public function updatePassword(UpdatePasswordRequest $request): Response
|
||||||
|
{
|
||||||
|
$this->updateService->handle($request->user(), $request->validated());
|
||||||
|
|
||||||
|
return response('', Response::HTTP_CREATED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Requests\Api\Client\Account;
|
||||||
|
|
||||||
|
use Pterodactyl\Models\User;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
|
||||||
|
use Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException;
|
||||||
|
|
||||||
|
class UpdatePasswordRequest extends ClientApiRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
if (! parent::authorize()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify password matches when changing password or email.
|
||||||
|
if (! password_verify($this->input('current_password'), $this->user()->password)) {
|
||||||
|
throw new InvalidPasswordProvidedException(trans('base.account.invalid_password'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
$rules = User::getUpdateRulesForId($this->user()->id);
|
||||||
|
|
||||||
|
return ['password' => array_merge($rules['password'], ['confirmed'])];
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
"name": "pterodactyl-panel",
|
"name": "pterodactyl-panel",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"date-fns": "^1.29.0",
|
"date-fns": "^1.29.0",
|
||||||
|
"vee-validate": "^2.1.0-beta.2",
|
||||||
"vue": "^2.5.7",
|
"vue": "^2.5.7",
|
||||||
"vue-axios": "^2.1.1",
|
"vue-axios": "^2.1.1",
|
||||||
"vue-router": "^3.0.1",
|
"vue-router": "^3.0.1",
|
||||||
@ -57,7 +58,8 @@
|
|||||||
"watch": "NODE_ENV=development ./node_modules/.bin/webpack --watch --progress",
|
"watch": "NODE_ENV=development ./node_modules/.bin/webpack --watch --progress",
|
||||||
"build": "NODE_ENV=development ./node_modules/.bin/webpack --progress",
|
"build": "NODE_ENV=development ./node_modules/.bin/webpack --progress",
|
||||||
"build:production": "NODE_ENV=production ./node_modules/.bin/webpack",
|
"build:production": "NODE_ENV=production ./node_modules/.bin/webpack",
|
||||||
"serve": "NODE_ENV=development webpack-serve --hot --config ./webpack.config.js --no-clipboard",
|
"serve": "NODE_ENV=development webpack-serve --hot --config ./webpack.config.js --no-clipboard --progress",
|
||||||
"v:serve": "PUBLIC_PATH=http://192.168.50.2:8080 NODE_ENV=development webpack-serve --hot --config ./webpack.config.js --host 192.168.50.2 --no-clipboard"
|
"v:serve": "PUBLIC_PATH=http://192.168.50.2:8080 NODE_ENV=development webpack-serve --hot --config ./webpack.config.js --host 192.168.50.2 --no-clipboard",
|
||||||
|
"compile:assets": "php artisan vue-i18n:generate & php artisan ziggy:generate resources/assets/scripts/helpers/ziggy.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import Vue from 'vue';
|
|||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
import vuexI18n from 'vuex-i18n';
|
import vuexI18n from 'vuex-i18n';
|
||||||
import VueRouter from 'vue-router';
|
import VueRouter from 'vue-router';
|
||||||
|
import VeeValidate from 'vee-validate';
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
require('./bootstrap');
|
require('./bootstrap');
|
||||||
@ -19,6 +20,7 @@ window.Ziggy = Ziggy;
|
|||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter);
|
||||||
Vue.use(vuexI18n.plugin, store);
|
Vue.use(vuexI18n.plugin, store);
|
||||||
|
Vue.use(VeeValidate);
|
||||||
|
|
||||||
const route = require('./../../../vendor/tightenco/ziggy/src/js/route').default;
|
const route = require('./../../../vendor/tightenco/ziggy/src/js/route').default;
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<modal :show="modalVisible" v-on:close="modalVisible = false">
|
<modal :show="modalVisible" v-on:close="modalVisible = false">
|
||||||
<TwoFactorAuthentication/>
|
<TwoFactorAuthentication/>
|
||||||
</modal>
|
</modal>
|
||||||
<flash container="mt-2 sm:mt-6 mb-2 sm:mx-4"/>
|
<flash container="mt-2 sm:mt-6 mb-2"/>
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
<div class="w-full md:w-1/2">
|
<div class="w-full md:w-1/2">
|
||||||
<div class="sm:m-4 md:ml-0">
|
<div class="sm:m-4 md:ml-0">
|
||||||
|
@ -1,20 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<form action="" method="post">
|
<form method="post" v-on:submit.prevent="submitForm">
|
||||||
<div class="content-box">
|
<div class="content-box">
|
||||||
<h2 class="mb-6 text-grey-darkest font-medium">Change your password</h2>
|
<h2 class="mb-6 text-grey-darkest font-medium">Change your password</h2>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<label for="grid-password-current" class="input-label">Current password</label>
|
<label for="grid-password-current" class="input-label">Current password</label>
|
||||||
<input id="grid-password-current" name="password" type="password" class="input" required>
|
<input id="grid-password-current" name="current_password" type="password" class="input" required
|
||||||
|
ref="current"
|
||||||
|
v-model="current"
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<label for="grid-password-new" class="input-label">New password</label>
|
<label for="grid-password-new" class="input-label">New password</label>
|
||||||
<input id="grid-password-new" name="password" type="password" class="input" required>
|
<input id="grid-password-new" name="password" type="password" class="input" required
|
||||||
<p class="input-help">Your new password should be at least 8 characters in length, contain one number, and be mixed case.</p>
|
:class="{ error: errors.has('password') }"
|
||||||
|
v-model="newPassword"
|
||||||
|
v-validate="'min:8'"
|
||||||
|
>
|
||||||
|
<p class="input-help error" v-show="errors.has('password')">{{ errors.first('password') }}</p>
|
||||||
|
<p class="input-help">Your new password should be at least 8 characters in length.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<label for="grid-password-new-confirm" class="input-label">Confirm new password</label>
|
<label for="grid-password-new-confirm" class="input-label">Confirm new password</label>
|
||||||
<input id="grid-password-new-confirm" name="password_confirmation" type="password" class="input" required>
|
<input id="grid-password-new-confirm" name="password_confirmation" type="password" class="input" required
|
||||||
|
:class="{ error: errors.has('password_confirmation') }"
|
||||||
|
v-model="confirmNew"
|
||||||
|
v-validate="{is: newPassword}"
|
||||||
|
data-vv-as="password"
|
||||||
|
>
|
||||||
|
<p class="input-help error" v-show="errors.has('password_confirmation')">{{ errors.first('password_confirmation') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-6 text-right">
|
<div class="mt-6 text-right">
|
||||||
<button class="btn btn-blue btn-sm text-right" type="submit">Save</button>
|
<button class="btn btn-blue btn-sm text-right" type="submit">Save</button>
|
||||||
@ -25,7 +39,53 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import isObject from 'lodash/isObject';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'change-password'
|
name: 'change-password',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
current: '',
|
||||||
|
newPassword: '',
|
||||||
|
confirmNew: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
submitForm: function () {
|
||||||
|
window.axios.put(this.route('api.client.account.update-password'), {
|
||||||
|
current_password: this.$data.current,
|
||||||
|
password: this.$data.newPassword,
|
||||||
|
password_confirmation: this.$data.confirmNew,
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.clearFlashes();
|
||||||
|
this.$validator.pause();
|
||||||
|
this.$data.current = '';
|
||||||
|
this.$refs.current.focus();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.$data.newPassword = '';
|
||||||
|
this.$data.confirmNew = '';
|
||||||
|
|
||||||
|
this.success('Your password has been updated.');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (!err.response) {
|
||||||
|
return console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = err.response;
|
||||||
|
if (response.data && isObject(response.data.errors)) {
|
||||||
|
response.data.errors.forEach(error => {
|
||||||
|
this.error(error.detail);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.$validator.resume();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -6,9 +6,11 @@
|
|||||||
<div>
|
<div>
|
||||||
<label for="grid-email" class="input-label">Email address</label>
|
<label for="grid-email" class="input-label">Email address</label>
|
||||||
<input id="grid-email" name="email" type="email" class="input" required
|
<input id="grid-email" name="email" type="email" class="input" required
|
||||||
v-model="email"
|
:class="{ error: errors.has('email') }"
|
||||||
|
v-validate
|
||||||
|
v-model="email"
|
||||||
>
|
>
|
||||||
<p class="input-help">If your email is no longer {{ user.email }} enter a new email in the field above.</p>
|
<p class="input-help error" v-show="errors.has('email')">{{ errors.first('email') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<label for="grid-password" class="input-label">Password</label>
|
<label for="grid-password" class="input-label">Password</label>
|
||||||
@ -25,14 +27,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import isObject from 'lodash/isObject';
|
import { isObject, get } from 'lodash';
|
||||||
import { mapState, mapActions } from 'vuex';
|
import { mapState, mapActions } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'update-email',
|
name: 'update-email',
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
email: '',
|
email: get(this.$store.state, 'auth.user.email', ''),
|
||||||
password: '',
|
password: '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -41,7 +43,6 @@
|
|||||||
user: state => state.auth.user,
|
user: state => state.auth.user,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
/**
|
/**
|
||||||
* Update a user's email address on the Panel.
|
* Update a user's email address on the Panel.
|
||||||
@ -52,9 +53,11 @@
|
|||||||
email: this.$data.email,
|
email: this.$data.email,
|
||||||
password: this.$data.password
|
password: this.$data.password
|
||||||
})
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.$data.password = '';
|
||||||
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.success('Your email address has been updated.');
|
this.success('Your email address has been updated.');
|
||||||
this.$data.password = '';
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (!error.response) {
|
if (!error.response) {
|
||||||
|
@ -49,12 +49,20 @@ textarea, select, input, button {
|
|||||||
&:required, &:invalid {
|
&:required, &:invalid {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
@apply .text-red-dark .border-red;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-label {
|
.input-label {
|
||||||
@apply .block .uppercase .tracking-wide .text-grey-darkest .text-xs .font-bold .mb-1;
|
@apply .block .uppercase .tracking-wide .text-grey-darkest .text-xs .font-bold .mb-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-help {
|
.input-help {
|
||||||
@apply .text-xs .text-grey .pt-2;
|
@apply .text-xs .text-grey .pt-2;
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
@apply .text-red-dark;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ Route::group(['prefix' => '/account'], function () {
|
|||||||
Route::get('/', 'AccountController@index')->name('api.client.account');
|
Route::get('/', 'AccountController@index')->name('api.client.account');
|
||||||
|
|
||||||
Route::put('/email', 'AccountController@updateEmail')->name('api.client.account.update-email');
|
Route::put('/email', 'AccountController@updateEmail')->name('api.client.account.update-email');
|
||||||
|
Route::put('/password', 'AccountController@updatePassword')->name('api.client.account.update-password');
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -6170,6 +6170,10 @@ vary@^1.0.0:
|
|||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||||
|
|
||||||
|
vee-validate@^2.1.0-beta.2:
|
||||||
|
version "2.1.0-beta.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/vee-validate/-/vee-validate-2.1.0-beta.2.tgz#b4a15f7aa0c4b1a9c78132d649b72a4dd4e2fa61"
|
||||||
|
|
||||||
vendors@^1.0.0:
|
vendors@^1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.2.tgz#7fcb5eef9f5623b156bcea89ec37d63676f21801"
|
resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.2.tgz#7fcb5eef9f5623b156bcea89ec37d63676f21801"
|
||||||
|
Loading…
Reference in New Issue
Block a user