diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ce55ec5a..dd10b6458 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,14 @@ This project follows [Semantic Versioning](http://semver.org) guidelines. ### Added * Remote routes for daemon to contact in order to allow Daemon to retrieve updated service configuration files on boot. Centralizes services to the panel rather than to each daemon. * Basic service pack implementation to allow assignment of modpacks or software to a server to pre-install applications and allow users to update. +* Users can now have a username as well as client name assigned to thier account. ### Fixed +* Bug causing error logs to be spammed if someone timed out on an ajax based page. ### 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 diff --git a/app/Http/Controllers/API/UserController.php b/app/Http/Controllers/API/UserController.php index 59e9af975..c3a658a0e 100755 --- a/app/Http/Controllers/API/UserController.php +++ b/app/Http/Controllers/API/UserController.php @@ -122,6 +122,9 @@ class UserController extends BaseController { try { $user = new UserRepository; + $create = $user->create($request->only([ + 'email', 'username', 'name_first', 'name_last', 'password', 'root_admin', 'custom_id', + ])); $create = $user->create($request->input('email'), $request->input('password'), $request->input('admin'), $request->input('custom_id')); return ['id' => $create]; @@ -156,7 +159,9 @@ class UserController extends BaseController { try { $user = new UserRepository; - $user->update($id, $request->all()); + $user->update($id, $request->only([ + 'username', 'email', 'name_first', 'name_last', 'password', 'root_admin', 'language', + ])); return Models\User::findOrFail($id); } catch (DisplayValidationException $ex) { diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 36e2590ba..8854d39ad 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -116,7 +116,13 @@ class UserController extends Controller { try { $user = new UserRepository; - $userid = $user->create($request->input('email'), $request->input('password')); + $userid = $user->create($request->only([ + 'email', + 'password', + 'name_first', + 'name_last', + 'username' + ])); Alert::success('Account has been successfully created.')->flash(); return redirect()->route('admin.users.view', $userid); @@ -132,19 +138,16 @@ class UserController extends Controller public function updateUser(Request $request, $user) { - $data = [ - 'email' => $request->input('email'), - 'root_admin' => $request->input('root_admin'), - 'password_confirmation' => $request->input('password_confirmation'), - ]; - - if ($request->input('password')) { - $data['password'] = $request->input('password'); - } - try { $repo = new UserRepository; - $repo->update($user, $data); + $repo->update($user, $request->only([ + 'email', + 'password', + 'name_first', + 'name_last', + 'username', + 'root_admin', + ])); Alert::success('User account was successfully updated.')->flash(); } catch (DisplayValidationException $ex) { return redirect()->route('admin.users.view', $user)->withErrors(json_decode($ex->getMessage())); diff --git a/app/Models/User.php b/app/Models/User.php index ef7bda0bd..c13a9d133 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -37,13 +37,24 @@ use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; -class User extends Model implements - AuthenticatableContract, - AuthorizableContract, - CanResetPasswordContract +class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract { use Authenticatable, Authorizable, CanResetPassword, Notifiable; + /** + * The rules for user passwords. + * + * @var string + */ + const PASSWORD_RULES = 'regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})'; + + /** + * The regex rules for usernames. + * + * @var string + */ + const USERNAME_RULES = 'regex:/^([\w\d\.\-]{1,255})$/'; + /** * The table associated with the model. * @@ -52,11 +63,11 @@ class User extends Model implements protected $table = 'users'; /** - * The attributes that are not mass assignable. + * A list of mass-assignable variables. * - * @var array + * @var [type] */ - protected $guarded = ['id', 'remeber_token', 'created_at', 'updated_at']; + protected $fillable = ['username', 'email', 'name_first', 'name_last', 'password', 'language', 'use_totp', 'totp_secret', 'gravatar']; /** * Cast values to correct type. @@ -66,6 +77,7 @@ class User extends Model implements protected $casts = [ 'root_admin' => 'integer', 'use_totp' => 'integer', + 'gravatar' => 'integer', ]; /** @@ -76,12 +88,10 @@ class User extends Model implements protected $hidden = ['password', 'remember_token', 'totp_secret']; /** - * The rules for user passwords. + * Determines if a user has permissions. * - * @var string + * @return bool */ - const PASSWORD_RULES = 'min:8|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})'; - public function permissions() { return $this->hasMany(Permission::class); diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index add04c920..db715fbbc 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -29,6 +29,7 @@ use DB; use Auth; use Hash; use Carbon; +use Settings; use Validator; use Pterodactyl\Models; use Pterodactyl\Services\UuidService; @@ -52,18 +53,16 @@ class UserRepository * @param int $token A custom user ID. * @return bool|int */ - public function create($email, $password = null, $admin = false, $token = null) + public function create(array $data) { - $validator = Validator::make([ - 'email' => $email, - 'password' => $password, - 'root_admin' => $admin, - 'custom_id' => $token, - ], [ + $validator = Validator::make($data, [ 'email' => 'required|email|unique:users,email', - 'password' => 'nullable|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})', + 'username' => 'required|string|between:1,255|unique:users,username|' . Models\User::USERNAME_RULES, + 'name_first' => 'required|string|between:1,255', + 'name_last' => 'required|string|between:1,255', + 'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES, 'root_admin' => 'required|boolean', - 'custom_id' => 'nullable|unique:users,id', + 'custom_id' => 'sometimes|nullable|unique:users,id', ]); // Run validator, throw catchable and displayable exception if it fails. @@ -79,26 +78,36 @@ class UserRepository $uuid = new UuidService; // Support for API Services - if (! is_null($token)) { + if (isset($data['custom_id']) && ! is_null($data['custom_id'])) { $user->id = $token; } + // UUIDs are not mass-fillable. $user->uuid = $uuid->generate('users', 'uuid'); - $user->email = $email; - $user->password = Hash::make((is_null($password)) ? str_random(30) : $password); - $user->language = 'en'; - $user->root_admin = ($admin) ? 1 : 0; + + $user->fill([ + 'email' => $data['email'], + 'username' => $data['username'], + 'name_first' => $data['name_first'], + 'name_last' => $data['name_last'], + 'password' => Hash::make((empty($data['password'])) ? str_random(30) : $password), + 'root_admin' => $data['root_admin'], + 'language' => Settings::get('default_language', 'en'), + ]); $user->save(); // Setup a Password Reset to use when they set a password. - $token = str_random(32); - DB::table('password_resets')->insert([ - 'email' => $user->email, - 'token' => $token, - 'created_at' => Carbon::now()->toDateTimeString(), - ]); + // Only used if no password is provided. + if (empty($data['password'])) { + $token = str_random(32); + DB::table('password_resets')->insert([ + 'email' => $user->email, + 'token' => $token, + 'created_at' => Carbon::now()->toDateTimeString(), + ]); - $user->notify((new AccountCreated($token))); + $user->notify((new AccountCreated($token))); + } DB::commit(); @@ -122,7 +131,10 @@ class UserRepository $validator = Validator::make($data, [ 'email' => 'sometimes|required|email|unique:users,email,' . $id, - 'password' => 'sometimes|required|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})', + 'username' => 'sometimes|required|string|between:1,255|unique:users,username,' . $user->id . '|' . Models\User::USERNAME_RULES, + 'name_first' => 'sometimes|required|string|between:1,255', + 'name_last' => 'sometimes|required|string|between:1,255', + 'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES, 'root_admin' => 'sometimes|required|boolean', 'language' => 'sometimes|required|string|min:1|max:5', 'use_totp' => 'sometimes|required|boolean', @@ -135,12 +147,15 @@ class UserRepository throw new DisplayValidationException($validator->errors()); } - if (array_key_exists('password', $data)) { + // The password and root_admin fields are not mass assignable. + if (! empty($data['password'])) { $data['password'] = Hash::make($data['password']); + } else { + unset($data['password']); } - if (isset($data['password_confirmation'])) { - unset($data['password_confirmation']); + if (! empty($data['root_admin'])) { + $user->root_admin = $data['root_admin']; } $user->fill($data); diff --git a/database/migrations/2017_01_12_135449_add_more_user_data.php b/database/migrations/2017_01_12_135449_add_more_user_data.php new file mode 100644 index 000000000..7240d2838 --- /dev/null +++ b/database/migrations/2017_01_12_135449_add_more_user_data.php @@ -0,0 +1,50 @@ +string('name_first')->after('email')->nullable(); + $table->string('name_last')->after('name_first')->nullable(); + $table->string('username')->after('uuid'); + $table->boolean('gravatar')->after('totp_secret')->default(true); + }); + + DB::transaction(function () { + foreach(User::all() as &$user) { + $user->username = $user->email; + $user->save(); + } + }); + + Schema::table('users', function (Blueprint $table) { + $table->string('username')->unique()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('name_first'); + $table->dropColumn('name_last'); + $table->dropColumn('username'); + $table->dropColumn('gravatar'); + }); + } +} diff --git a/resources/views/admin/users/index.blade.php b/resources/views/admin/users/index.blade.php index 04eaaf207..5b9f59ce0 100644 --- a/resources/views/admin/users/index.blade.php +++ b/resources/views/admin/users/index.blade.php @@ -42,17 +42,21 @@ - - - + + + @foreach ($users as $user) - - - - + + + + + + @endforeach diff --git a/resources/views/admin/users/new.blade.php b/resources/views/admin/users/new.blade.php index 39ad6ecab..abd7cadb0 100644 --- a/resources/views/admin/users/new.blade.php +++ b/resources/views/admin/users/new.blade.php @@ -34,15 +34,38 @@

Create New Account


-
- -
- +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
-
+
+

Providing a user password is optional. New user emails prompt users to create a password the first time they login. If a password is provided here you will need to find a different method of providing it to the user.

diff --git a/resources/views/admin/users/view.blade.php b/resources/views/admin/users/view.blade.php index 6ec648ef5..7b817e3a4 100644 --- a/resources/views/admin/users/view.blade.php +++ b/resources/views/admin/users/view.blade.php @@ -31,7 +31,9 @@
  • Accounts
  • {{ $user->email }}
  • -

    Viewing User: {{ $user->email }}


    +

    Viewing User: {{ $user->email }}

    +

    Registered {{ (new Carbon($user->created_at))->toRfc1123String() }}

    +
    @@ -43,19 +45,21 @@
    - +
    - +
    - +
    - -

    Setting this to 'Yes' gives a user full administrative access.

    + +
    +
    +
    + +
    +
    @@ -66,7 +70,6 @@
    -

    {{ trans('base.account.update_pass') }}


    @@ -74,16 +77,22 @@
    -
    - -
    - -
    -
    +
    +
    + +
    + +

    Setting this to 'Yes' gives a user full administrative access.

    +
    +
    +
    EmailAccount CreatedAccount UpdatedID + Email + Client NameUsername
    {{ $user->email }} @if($user->root_admin === 1)Administrator@endif{{ $user->created_at }}{{ $user->updated_at }}
    #{{ $user->id }}{{ $user->email }}{{ $user->name_last }}, {{ $user->name_first }}{{ $user->username }}