Skip to content

Commit e91362e

Browse files
committed
Update user controller
1 parent f292080 commit e91362e

File tree

9 files changed

+200
-77
lines changed

9 files changed

+200
-77
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
77
### Added
88
* 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.
99
* Basic service pack implementation to allow assignment of modpacks or software to a server to pre-install applications and allow users to update.
10+
* Users can now have a username as well as client name assigned to thier account.
1011

1112
### Fixed
13+
* Bug causing error logs to be spammed if someone timed out on an ajax based page.
1214

1315
### Changed
16+
* Admin API and base routes for user management now define the fields that should be passed to repositories rather than passing all fields.
17+
* User model now defines mass assignment fields using `$fillable` rather than `$guarded`.
1418

1519
### Deprecated
1620

app/Http/Controllers/API/UserController.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ public function create(Request $request)
122122
{
123123
try {
124124
$user = new UserRepository;
125+
$create = $user->create($request->only([
126+
'email', 'username', 'name_first', 'name_last', 'password', 'root_admin', 'custom_id',
127+
]));
125128
$create = $user->create($request->input('email'), $request->input('password'), $request->input('admin'), $request->input('custom_id'));
126129

127130
return ['id' => $create];
@@ -156,7 +159,9 @@ public function update(Request $request, $id)
156159
{
157160
try {
158161
$user = new UserRepository;
159-
$user->update($id, $request->all());
162+
$user->update($id, $request->only([
163+
'username', 'email', 'name_first', 'name_last', 'password', 'root_admin', 'language',
164+
]));
160165

161166
return Models\User::findOrFail($id);
162167
} catch (DisplayValidationException $ex) {

app/Http/Controllers/Admin/UserController.php

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,13 @@ public function postNew(Request $request)
116116
{
117117
try {
118118
$user = new UserRepository;
119-
$userid = $user->create($request->input('email'), $request->input('password'));
119+
$userid = $user->create($request->only([
120+
'email',
121+
'password',
122+
'name_first',
123+
'name_last',
124+
'username'
125+
]));
120126
Alert::success('Account has been successfully created.')->flash();
121127

122128
return redirect()->route('admin.users.view', $userid);
@@ -132,19 +138,16 @@ public function postNew(Request $request)
132138

133139
public function updateUser(Request $request, $user)
134140
{
135-
$data = [
136-
'email' => $request->input('email'),
137-
'root_admin' => $request->input('root_admin'),
138-
'password_confirmation' => $request->input('password_confirmation'),
139-
];
140-
141-
if ($request->input('password')) {
142-
$data['password'] = $request->input('password');
143-
}
144-
145141
try {
146142
$repo = new UserRepository;
147-
$repo->update($user, $data);
143+
$repo->update($user, $request->only([
144+
'email',
145+
'password',
146+
'name_first',
147+
'name_last',
148+
'username',
149+
'root_admin',
150+
]));
148151
Alert::success('User account was successfully updated.')->flash();
149152
} catch (DisplayValidationException $ex) {
150153
return redirect()->route('admin.users.view', $user)->withErrors(json_decode($ex->getMessage()));

app/Models/User.php

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,24 @@
3737
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
3838
use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification;
3939

40-
class User extends Model implements
41-
AuthenticatableContract,
42-
AuthorizableContract,
43-
CanResetPasswordContract
40+
class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract
4441
{
4542
use Authenticatable, Authorizable, CanResetPassword, Notifiable;
4643

44+
/**
45+
* The rules for user passwords.
46+
*
47+
* @var string
48+
*/
49+
const PASSWORD_RULES = 'regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})';
50+
51+
/**
52+
* The regex rules for usernames.
53+
*
54+
* @var string
55+
*/
56+
const USERNAME_RULES = 'regex:/^([\w\d\.\-]{1,255})$/';
57+
4758
/**
4859
* The table associated with the model.
4960
*
@@ -52,11 +63,11 @@ class User extends Model implements
5263
protected $table = 'users';
5364

5465
/**
55-
* The attributes that are not mass assignable.
66+
* A list of mass-assignable variables.
5667
*
57-
* @var array
68+
* @var [type]
5869
*/
59-
protected $guarded = ['id', 'remeber_token', 'created_at', 'updated_at'];
70+
protected $fillable = ['username', 'email', 'name_first', 'name_last', 'password', 'language', 'use_totp', 'totp_secret', 'gravatar'];
6071

6172
/**
6273
* Cast values to correct type.
@@ -66,6 +77,7 @@ class User extends Model implements
6677
protected $casts = [
6778
'root_admin' => 'integer',
6879
'use_totp' => 'integer',
80+
'gravatar' => 'integer',
6981
];
7082

7183
/**
@@ -76,12 +88,10 @@ class User extends Model implements
7688
protected $hidden = ['password', 'remember_token', 'totp_secret'];
7789

7890
/**
79-
* The rules for user passwords.
91+
* Determines if a user has permissions.
8092
*
81-
* @var string
93+
* @return bool
8294
*/
83-
const PASSWORD_RULES = 'min:8|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})';
84-
8595
public function permissions()
8696
{
8797
return $this->hasMany(Permission::class);

app/Repositories/UserRepository.php

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use Auth;
3030
use Hash;
3131
use Carbon;
32+
use Settings;
3233
use Validator;
3334
use Pterodactyl\Models;
3435
use Pterodactyl\Services\UuidService;
@@ -52,18 +53,16 @@ public function __construct()
5253
* @param int $token A custom user ID.
5354
* @return bool|int
5455
*/
55-
public function create($email, $password = null, $admin = false, $token = null)
56+
public function create(array $data)
5657
{
57-
$validator = Validator::make([
58-
'email' => $email,
59-
'password' => $password,
60-
'root_admin' => $admin,
61-
'custom_id' => $token,
62-
], [
58+
$validator = Validator::make($data, [
6359
'email' => 'required|email|unique:users,email',
64-
'password' => 'nullable|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})',
60+
'username' => 'required|string|between:1,255|unique:users,username|' . Models\User::USERNAME_RULES,
61+
'name_first' => 'required|string|between:1,255',
62+
'name_last' => 'required|string|between:1,255',
63+
'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES,
6564
'root_admin' => 'required|boolean',
66-
'custom_id' => 'nullable|unique:users,id',
65+
'custom_id' => 'sometimes|nullable|unique:users,id',
6766
]);
6867

6968
// Run validator, throw catchable and displayable exception if it fails.
@@ -79,26 +78,36 @@ public function create($email, $password = null, $admin = false, $token = null)
7978
$uuid = new UuidService;
8079

8180
// Support for API Services
82-
if (! is_null($token)) {
81+
if (isset($data['custom_id']) && ! is_null($data['custom_id'])) {
8382
$user->id = $token;
8483
}
8584

85+
// UUIDs are not mass-fillable.
8686
$user->uuid = $uuid->generate('users', 'uuid');
87-
$user->email = $email;
88-
$user->password = Hash::make((is_null($password)) ? str_random(30) : $password);
89-
$user->language = 'en';
90-
$user->root_admin = ($admin) ? 1 : 0;
91-
$user->save();
9287

93-
// Setup a Password Reset to use when they set a password.
94-
$token = str_random(32);
95-
DB::table('password_resets')->insert([
96-
'email' => $user->email,
97-
'token' => $token,
98-
'created_at' => Carbon::now()->toDateTimeString(),
88+
$user->fill([
89+
'email' => $data['email'],
90+
'username' => $data['username'],
91+
'name_first' => $data['name_first'],
92+
'name_last' => $data['name_last'],
93+
'password' => Hash::make((empty($data['password'])) ? str_random(30) : $password),
94+
'root_admin' => $data['root_admin'],
95+
'language' => Settings::get('default_language', 'en'),
9996
]);
97+
$user->save();
10098

101-
$user->notify((new AccountCreated($token)));
99+
// Setup a Password Reset to use when they set a password.
100+
// Only used if no password is provided.
101+
if (empty($data['password'])) {
102+
$token = str_random(32);
103+
DB::table('password_resets')->insert([
104+
'email' => $user->email,
105+
'token' => $token,
106+
'created_at' => Carbon::now()->toDateTimeString(),
107+
]);
108+
109+
$user->notify((new AccountCreated($token)));
110+
}
102111

103112
DB::commit();
104113

@@ -122,7 +131,10 @@ public function update($id, array $data)
122131

123132
$validator = Validator::make($data, [
124133
'email' => 'sometimes|required|email|unique:users,email,' . $id,
125-
'password' => 'sometimes|required|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})',
134+
'username' => 'sometimes|required|string|between:1,255|unique:users,username,' . $user->id . '|' . Models\User::USERNAME_RULES,
135+
'name_first' => 'sometimes|required|string|between:1,255',
136+
'name_last' => 'sometimes|required|string|between:1,255',
137+
'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES,
126138
'root_admin' => 'sometimes|required|boolean',
127139
'language' => 'sometimes|required|string|min:1|max:5',
128140
'use_totp' => 'sometimes|required|boolean',
@@ -135,12 +147,15 @@ public function update($id, array $data)
135147
throw new DisplayValidationException($validator->errors());
136148
}
137149

138-
if (array_key_exists('password', $data)) {
150+
// The password and root_admin fields are not mass assignable.
151+
if (! empty($data['password'])) {
139152
$data['password'] = Hash::make($data['password']);
153+
} else {
154+
unset($data['password']);
140155
}
141156

142-
if (isset($data['password_confirmation'])) {
143-
unset($data['password_confirmation']);
157+
if (! empty($data['root_admin'])) {
158+
$user->root_admin = $data['root_admin'];
144159
}
145160

146161
$user->fill($data);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
use Pterodactyl\Models\User;
4+
use Illuminate\Support\Facades\Schema;
5+
use Illuminate\Database\Schema\Blueprint;
6+
use Illuminate\Database\Migrations\Migration;
7+
8+
class AddMoreUserData extends Migration
9+
{
10+
/**
11+
* Run the migrations.
12+
*
13+
* @return void
14+
*/
15+
public function up()
16+
{
17+
Schema::table('users', function (Blueprint $table) {
18+
$table->string('name_first')->after('email')->nullable();
19+
$table->string('name_last')->after('name_first')->nullable();
20+
$table->string('username')->after('uuid');
21+
$table->boolean('gravatar')->after('totp_secret')->default(true);
22+
});
23+
24+
DB::transaction(function () {
25+
foreach(User::all() as &$user) {
26+
$user->username = $user->email;
27+
$user->save();
28+
}
29+
});
30+
31+
Schema::table('users', function (Blueprint $table) {
32+
$table->string('username')->unique()->change();
33+
});
34+
}
35+
36+
/**
37+
* Reverse the migrations.
38+
*
39+
* @return void
40+
*/
41+
public function down()
42+
{
43+
Schema::table('users', function (Blueprint $table) {
44+
$table->dropColumn('name_first');
45+
$table->dropColumn('name_last');
46+
$table->dropColumn('username');
47+
$table->dropColumn('gravatar');
48+
});
49+
}
50+
}

resources/views/admin/users/index.blade.php

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,21 @@
4242
<table class="table table-striped table-bordered table-hover">
4343
<thead>
4444
<tr>
45-
<th>Email</th>
46-
<th>Account Created</th>
47-
<th>Account Updated</th>
45+
<th>ID</td>
46+
<th>Email</td>
47+
<th>Client Name</th>
48+
<th>Username</th>
49+
<th></th>
4850
</tr>
4951
</thead>
5052
<tbody>
5153
@foreach ($users as $user)
52-
<tr>
53-
<td><a href="/admin/users/view/{{ $user->id }}"><code>{{ $user->email }}</code></a> @if($user->root_admin === 1)<span class="badge">Administrator</span>@endif</td>
54-
<td>{{ $user->created_at }}</td>
55-
<td>{{ $user->updated_at }}</td>
54+
<tr class="align-middle">
55+
<td><code>#{{ $user->id }}</code></td>
56+
<td><a href="{{ route('admin.users.view', $user->id) }}">{{ $user->email }}</a></td>
57+
<td>{{ $user->name_last }}, {{ $user->name_first }}</td>
58+
<td><code>{{ $user->username }}</code></td>
59+
<td class="text-center"><img src="https://www.gravatar.com/avatar/{{ md5(strtolower($user->email)) }}?s=20" class="img-circle" /></td>
5660
</tr>
5761
@endforeach
5862
</tbody>

resources/views/admin/users/new.blade.php

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,38 @@
3434
<h3>Create New Account</h3><hr />
3535
<form action="new" method="post">
3636
<fieldset>
37-
<div class="form-group">
38-
<label for="email" class="control-label">Email</label>
39-
<div>
40-
<input type="text" autocomplete="off" name="email" class="form-control" />
37+
<div class="row">
38+
<div class="form-group col-md-6">
39+
<label for="email" class="control-label">Email</label>
40+
<div>
41+
<input type="text" autocomplete="off" name="email" value="{{ old('email') }}" class="form-control" />
42+
</div>
43+
</div>
44+
<div class="form-group col-md-6">
45+
<label for="username" class="control-label">Username</label>
46+
<div>
47+
<input type="text" autocomplete="off" name="username" value="{{ old('username') }}" class="form-control" />
48+
</div>
49+
</div>
50+
</div>
51+
<div class="row">
52+
<div class="form-group col-md-6">
53+
<label for="name_first" class="control-label">Client First Name</label>
54+
<div>
55+
<input type="text" autocomplete="off" name="name_first" value="{{ old('name_first') }}" class="form-control" />
56+
</div>
57+
</div>
58+
<div class="form-group col-md-6">
59+
<label for="name_last" class="control-label">Client Last Name</label>
60+
<div>
61+
<input type="text" autocomplete="off" name="name_last" value="{{ old('name_last') }}" class="form-control" />
62+
</div>
4163
</div>
4264
</div>
4365
<div class="row">
4466
<div class="col-md-12">
45-
<div class="well well-sm">
67+
<hr />
68+
<div class="alert alert-info">
4669
<p>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.</p>
4770
</div>
4871
</div>

0 commit comments

Comments
 (0)