Restrict document edit/delete to owners and close public registration

Adds DocumentPolicy gating update/delete to the creator (admins bypass via
before()), invokes $this->authorize() in DocumentEditor mount/save/delete,
applies can:update,document on the edit route, hides the edit button for
non-owners, and removes the open /register routes so accounts must be
provisioned via the admin panel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yutaka Kurosaki
2026-05-09 10:22:18 +09:00
parent 01a11328ec
commit 692f4d5492
5 changed files with 45 additions and 9 deletions
+6
View File
@@ -18,6 +18,8 @@ class DocumentEditor extends Component
public function mount(?Document $document = null) public function mount(?Document $document = null)
{ {
if ($document) { if ($document) {
$this->authorize('update', $document);
$this->document = $document; $this->document = $document;
$this->title = $document->title; $this->title = $document->title;
$this->content = $document->content; $this->content = $document->content;
@@ -40,6 +42,8 @@ public function save(DocumentService $documentService)
try { try {
if ($this->isEditMode && $this->document) { if ($this->isEditMode && $this->document) {
$this->authorize('update', $this->document);
$this->document = $documentService->updateDocument( $this->document = $documentService->updateDocument(
$this->document, $this->document,
$this->title, $this->title,
@@ -71,6 +75,8 @@ public function delete(DocumentService $documentService)
return; return;
} }
$this->authorize('delete', $this->document);
try { try {
$documentService->deleteDocument($this->document); $documentService->deleteDocument($this->document);
session()->flash('message', 'Document deleted successfully!'); session()->flash('message', 'Document deleted successfully!');
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace App\Policies;
use App\Models\Document;
use App\Models\User;
class DocumentPolicy
{
public function before(User $user): ?bool
{
return $user->isAdmin() ? true : null;
}
public function view(User $user, Document $document): bool
{
return true;
}
public function create(User $user): bool
{
return true;
}
public function update(User $user, Document $document): bool
{
return $document->created_by === $user->id;
}
public function delete(User $user, Document $document): bool
{
return $document->created_by === $user->id;
}
}
@@ -6,7 +6,7 @@
{{ $document->title }} {{ $document->title }}
</h1> </h1>
@auth @can('update', $document)
<a <a
href="{{ route('documents.edit', $document) }}" href="{{ route('documents.edit', $document) }}"
class="inline-flex items-center justify-center px-3 sm:px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 whitespace-nowrap" class="inline-flex items-center justify-center px-3 sm:px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 whitespace-nowrap"
@@ -16,7 +16,7 @@ class="inline-flex items-center justify-center px-3 sm:px-4 py-2 bg-indigo-600 t
</svg> </svg>
{{ __('messages.documents.edit') }} {{ __('messages.documents.edit') }}
</a> </a>
@endauth @endcan
</div> </div>
<div class="flex flex-col sm:flex-row sm:items-center text-xs sm:text-sm text-gray-500 gap-2 sm:gap-4"> <div class="flex flex-col sm:flex-row sm:items-center text-xs sm:text-sm text-gray-500 gap-2 sm:gap-4">
-6
View File
@@ -7,16 +7,10 @@
use App\Http\Controllers\Auth\NewPasswordController; use App\Http\Controllers\Auth\NewPasswordController;
use App\Http\Controllers\Auth\PasswordController; use App\Http\Controllers\Auth\PasswordController;
use App\Http\Controllers\Auth\PasswordResetLinkController; use App\Http\Controllers\Auth\PasswordResetLinkController;
use App\Http\Controllers\Auth\RegisteredUserController;
use App\Http\Controllers\Auth\VerifyEmailController; use App\Http\Controllers\Auth\VerifyEmailController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::middleware('guest')->group(function () { Route::middleware('guest')->group(function () {
Route::get('register', [RegisteredUserController::class, 'create'])
->name('register');
Route::post('register', [RegisteredUserController::class, 'store']);
Route::get('login', [AuthenticatedSessionController::class, 'create']) Route::get('login', [AuthenticatedSessionController::class, 'create'])
->name('login'); ->name('login');
+3 -1
View File
@@ -44,7 +44,9 @@
// 認証が必要なルート(より具体的なルートを先に定義) // 認証が必要なルート(より具体的なルートを先に定義)
Route::middleware('auth')->group(function () { Route::middleware('auth')->group(function () {
Route::get('/create', DocumentEditor::class)->name('create'); Route::get('/create', DocumentEditor::class)->name('create');
Route::get('/{document}/edit', DocumentEditor::class)->name('edit'); Route::get('/{document}/edit', DocumentEditor::class)
->middleware('can:update,document')
->name('edit');
}); });
// 公開ルート(動的ルートは最後に) // 公開ルート(動的ルートは最後に)