mirror of https://github.com/kcal-app/kcal.git
Add User admin flag
This commit is contained in:
parent
d18247e68d
commit
ef719c9175
|
@ -53,6 +53,7 @@ class UserController extends Controller
|
||||||
$attributes = $request->validated();
|
$attributes = $request->validated();
|
||||||
$attributes['remember_token'] = Str::random(10);
|
$attributes['remember_token'] = Str::random(10);
|
||||||
$attributes['password'] = Hash::make($attributes['password']);
|
$attributes['password'] = Hash::make($attributes['password']);
|
||||||
|
$attributes['admin'] = $attributes['admin'] ?? false;
|
||||||
$user->fill($attributes)->save();
|
$user->fill($attributes)->save();
|
||||||
session()->flash('message', "User {$user->name} updated!");
|
session()->flash('message', "User {$user->name} updated!");
|
||||||
return redirect()->route('users.index');
|
return redirect()->route('users.index');
|
||||||
|
|
|
@ -18,6 +18,7 @@ class UpdateUserRequest extends FormRequest
|
||||||
'name' => ['nullable', 'string'],
|
'name' => ['nullable', 'string'],
|
||||||
'password' => ['nullable', 'string', 'confirmed'],
|
'password' => ['nullable', 'string', 'confirmed'],
|
||||||
'password_confirmation' => ['nullable', 'string'],
|
'password_confirmation' => ['nullable', 'string'],
|
||||||
|
'admin' => ['nullable', 'boolean'],
|
||||||
];
|
];
|
||||||
if (!$this->user) {
|
if (!$this->user) {
|
||||||
$rules['password'] = ['required', 'string', 'confirmed'];
|
$rules['password'] = ['required', 'string', 'confirmed'];
|
||||||
|
|
|
@ -15,9 +15,11 @@ use Illuminate\Support\Facades\Auth;
|
||||||
* App\Models\User
|
* App\Models\User
|
||||||
*
|
*
|
||||||
* @property int $id
|
* @property int $id
|
||||||
|
* @property string $slug
|
||||||
* @property string $name
|
* @property string $name
|
||||||
* @property string $username
|
* @property string $username
|
||||||
* @property string $password
|
* @property string $password
|
||||||
|
* @property bool $admin
|
||||||
* @property string|null $remember_token
|
* @property string|null $remember_token
|
||||||
* @property Carbon|null $created_at
|
* @property Carbon|null $created_at
|
||||||
* @property Carbon|null $updated_at
|
* @property Carbon|null $updated_at
|
||||||
|
@ -38,6 +40,10 @@ use Illuminate\Support\Facades\Auth;
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereRememberToken($value)
|
* @method static \Illuminate\Database\Eloquent\Builder|User whereRememberToken($value)
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value)
|
* @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value)
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereUsername($value)
|
* @method static \Illuminate\Database\Eloquent\Builder|User whereUsername($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|User findSimilarSlugs(string $attribute, array $config, string $slug)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|User whereAdmin($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|User whereSlug($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|User withUniqueSlugConstraints(\Illuminate\Database\Eloquent\Model $model, string $attribute, array $config, string $slug)
|
||||||
* @mixin \Eloquent
|
* @mixin \Eloquent
|
||||||
*/
|
*/
|
||||||
final class User extends Authenticatable
|
final class User extends Authenticatable
|
||||||
|
@ -51,6 +57,7 @@ final class User extends Authenticatable
|
||||||
'username',
|
'username',
|
||||||
'password',
|
'password',
|
||||||
'name',
|
'name',
|
||||||
|
'admin',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,6 +68,13 @@ final class User extends Authenticatable
|
||||||
'remember_token',
|
'remember_token',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'admin' => 'bool',
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the User's goals.
|
* Get the User's goals.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -9,11 +9,18 @@ class UserPolicy
|
||||||
{
|
{
|
||||||
use HandlesAuthorization;
|
use HandlesAuthorization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can administer (the application).
|
||||||
|
*/
|
||||||
|
public function administer(User $user): bool {
|
||||||
|
return $user->admin;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can delete the model.
|
* Determine whether the user can delete the model.
|
||||||
*/
|
*/
|
||||||
public function delete(User $user, User $model): bool
|
public function delete(User $user, User $model): bool
|
||||||
{
|
{
|
||||||
return $user->id !== $model->id;
|
return $this->administer($user) && $user->id !== $model->id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,16 @@ class UserFactory extends Factory
|
||||||
'username' => $this->faker->unique()->userName,
|
'username' => $this->faker->unique()->userName,
|
||||||
'password' => Hash::make('password'),
|
'password' => Hash::make('password'),
|
||||||
'name' => $this->faker->name,
|
'name' => $this->faker->name,
|
||||||
|
'admin' => $this->faker->boolean,
|
||||||
'remember_token' => Str::random(10),
|
'remember_token' => Str::random(10),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an admin user.
|
||||||
|
*/
|
||||||
|
public function admin(): static
|
||||||
|
{
|
||||||
|
return $this->state(['admin' => true]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ class DatabaseSeeder extends Seeder
|
||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
$user = User::factory()->create([
|
$user = User::factory()->admin()->create([
|
||||||
'username' => 'kcal',
|
'username' => 'kcal',
|
||||||
'password' => Hash::make('kcal'),
|
'password' => Hash::make('kcal'),
|
||||||
'name' => 'Admin',
|
'name' => 'Admin',
|
||||||
|
|
|
@ -19,6 +19,7 @@ class CreateUsersTable extends Migration
|
||||||
$table->string('username')->unique();
|
$table->string('username')->unique();
|
||||||
$table->string('password');
|
$table->string('password');
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
|
$table->boolean('admin')->default(false);
|
||||||
$table->rememberToken();
|
$table->rememberToken();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,7 +34,9 @@
|
||||||
<x-slot name="content">
|
<x-slot name="content">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<x-dropdown-link :href="route('goals.index')">Goals</x-dropdown-link>
|
<x-dropdown-link :href="route('goals.index')">Goals</x-dropdown-link>
|
||||||
<x-dropdown-link :href="route('users.index')">Users</x-dropdown-link>
|
@can('administer', \App\Models\User::class)
|
||||||
|
<x-dropdown-link :href="route('users.index')">Users</x-dropdown-link>
|
||||||
|
@endcan
|
||||||
<form method="POST" action="{{ route('logout') }}" x-data>
|
<form method="POST" action="{{ route('logout') }}" x-data>
|
||||||
@csrf
|
@csrf
|
||||||
<x-dropdown-link :href="route('logout')" @click.prevent="$el.closest('form').submit();">Logout</x-dropdown-link>
|
<x-dropdown-link :href="route('logout')" @click.prevent="$el.closest('form').submit();">Logout</x-dropdown-link>
|
||||||
|
|
|
@ -53,6 +53,17 @@
|
||||||
:hasError="$errors->has('password')"
|
:hasError="$errors->has('password')"
|
||||||
:required="!$user->exists"/>
|
:required="!$user->exists"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Admin -->
|
||||||
|
<div>
|
||||||
|
<x-inputs.label for="admin" value="Site Admin"/>
|
||||||
|
|
||||||
|
<x-inputs.input name="admin"
|
||||||
|
type="checkbox"
|
||||||
|
value="1"
|
||||||
|
:checked="old('admin', $user->admin)" />
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
<tr class="bg-gray-200 text-gray-600 uppercase text-sm leading-normal">
|
<tr class="bg-gray-200 text-gray-600 uppercase text-sm leading-normal">
|
||||||
<th class="py-3 px-6 text-left">Username</th>
|
<th class="py-3 px-6 text-left">Username</th>
|
||||||
<th class="py-3 px-6 text-left">Name</th>
|
<th class="py-3 px-6 text-left">Name</th>
|
||||||
|
<th class="py-3 px-6 text-left">Admin</th>
|
||||||
<th class="py-3 px-6 text-left">Created</th>
|
<th class="py-3 px-6 text-left">Created</th>
|
||||||
<th class="py-3 px-6 text-left">Updated</th>
|
<th class="py-3 px-6 text-left">Updated</th>
|
||||||
<th class="py-3 px-6 text-left"> </th>
|
<th class="py-3 px-6 text-left"> </th>
|
||||||
|
@ -23,6 +24,7 @@
|
||||||
<tr class="border-b border-gray-200">
|
<tr class="border-b border-gray-200">
|
||||||
<td class="py-3 px-6">{{ $user->username }}</td>
|
<td class="py-3 px-6">{{ $user->username }}</td>
|
||||||
<td class="py-3 px-6">{{ $user->name }}</td>
|
<td class="py-3 px-6">{{ $user->name }}</td>
|
||||||
|
<td class="py-3 px-6">@if($user->admin) Yes @else No @endif</td>
|
||||||
<td class="py-3 px-6">{{ $user->created_at }}</td>
|
<td class="py-3 px-6">{{ $user->created_at }}</td>
|
||||||
<td class="py-3 px-6">{{ $user->updated_at }}</td>
|
<td class="py-3 px-6">{{ $user->updated_at }}</td>
|
||||||
<td class="py-3 px-6">
|
<td class="py-3 px-6">
|
||||||
|
|
|
@ -9,7 +9,7 @@ use Illuminate\Support\Facades\Route;
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Route::middleware(['auth', 'can:administer'])->group(function () {
|
Route::middleware(['auth', 'can:administer,\App\Models\User'])->group(function () {
|
||||||
Route::resource('users', UserController::class);
|
Route::resource('users', UserController::class);
|
||||||
Route::get('/users/{user}/delete', [UserController::class, 'delete'])->name('users.delete');
|
Route::get('/users/{user}/delete', [UserController::class, 'delete'])->name('users.delete');
|
||||||
});
|
});
|
||||||
|
|
|
@ -66,4 +66,12 @@ class UserControllerTest extends HttpControllerTestCase
|
||||||
$response->assertForbidden();
|
$response->assertForbidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCanNotAccessIndexAsNonAdmin(): void {
|
||||||
|
$this->logout();
|
||||||
|
$this->loginUser();
|
||||||
|
$index_url = action([$this->class(), 'index']);
|
||||||
|
$response = $this->get($index_url);
|
||||||
|
$response->assertForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ abstract class LoggedInTestCase extends TestCase
|
||||||
public function setUp(): void
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
$this->loginUser();
|
$this->loginAdmin();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,12 @@ trait LogsIn
|
||||||
protected User $user;
|
protected User $user;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a user and logs the user in.
|
* Creates an admin and logs in.
|
||||||
*/
|
*/
|
||||||
public function loginUser(): void
|
public function loginAdmin(): void
|
||||||
{
|
{
|
||||||
$this->user = User::factory()
|
$this->user = User::factory()
|
||||||
|
->admin()
|
||||||
->hasGoals(2)
|
->hasGoals(2)
|
||||||
->hasJournalEntries(5)
|
->hasJournalEntries(5)
|
||||||
->create();
|
->create();
|
||||||
|
@ -22,4 +23,23 @@ trait LogsIn
|
||||||
'password' => 'password',
|
'password' => 'password',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a regular user and logs in.
|
||||||
|
*/
|
||||||
|
public function loginUser(): void
|
||||||
|
{
|
||||||
|
$this->user = User::factory()
|
||||||
|
->hasGoals(2)
|
||||||
|
->hasJournalEntries(5)
|
||||||
|
->create(['admin' => false]);
|
||||||
|
$this->post('/login', [
|
||||||
|
'username' => $this->user->username,
|
||||||
|
'password' => 'password',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logout(): void {
|
||||||
|
$this->post('/logout');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue