From a41ff59009d3612f4ac7e5464b88e4e494e76927 Mon Sep 17 00:00:00 2001 From: Saufi Date: Sat, 16 May 2026 23:54:11 +0800 Subject: [PATCH] feat: security hardening (Fasa 10) - EnsureIsAdmin middleware: gates all admin routes on is_admin flag - Apply admin middleware to entire admin route group - Fix questionnaire resource route parameter name mismatch ({set}) - Audit log on questionnaire confirmation Co-Authored-By: Claude Sonnet 4.6 --- .../Admin/ProgramQuestionnaireController.php | 3 +++ app/Http/Middleware/EnsureIsAdmin.php | 19 +++++++++++++++++++ bootstrap/app.php | 4 +++- routes/web.php | 4 ++-- 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 app/Http/Middleware/EnsureIsAdmin.php diff --git a/app/Http/Controllers/Admin/ProgramQuestionnaireController.php b/app/Http/Controllers/Admin/ProgramQuestionnaireController.php index 1dcaafb..7f78b1a 100644 --- a/app/Http/Controllers/Admin/ProgramQuestionnaireController.php +++ b/app/Http/Controllers/Admin/ProgramQuestionnaireController.php @@ -6,6 +6,7 @@ use App\Http\Controllers\Controller; use App\Models\Program; use App\Models\ProgramQuestionnaire; use App\Models\QuestionnaireSet; +use App\Services\AuditLogService; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\View\View; @@ -63,6 +64,8 @@ class ProgramQuestionnaireController extends Controller 'confirmed_by' => auth()->id(), ]); + AuditLogService::log('questionnaire.confirmed', $pq, [], ['program_id' => $program->id, 'questionnaire_set_id' => $pq->questionnaire_set_id]); + return back()->with('success', 'Soalselidik telah disahkan untuk program ini.'); } diff --git a/app/Http/Middleware/EnsureIsAdmin.php b/app/Http/Middleware/EnsureIsAdmin.php new file mode 100644 index 0000000..728abb9 --- /dev/null +++ b/app/Http/Middleware/EnsureIsAdmin.php @@ -0,0 +1,19 @@ +user()?->is_admin) { + abort(403, 'Akses ditolak.'); + } + + return $next($request); + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index c183276..f96e164 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -11,7 +11,9 @@ return Application::configure(basePath: dirname(__DIR__)) health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - // + $middleware->alias([ + 'admin' => \App\Http\Middleware\EnsureIsAdmin::class, + ]); }) ->withExceptions(function (Exceptions $exceptions): void { // diff --git a/routes/web.php b/routes/web.php index c81b8ff..0d6c42f 100644 --- a/routes/web.php +++ b/routes/web.php @@ -24,7 +24,7 @@ Route::get('/', fn() => redirect()->route('admin.dashboard')); // ────────────────────────────────────────────── // Admin Routes // ────────────────────────────────────────────── -Route::middleware('auth')->prefix('admin')->name('admin.')->group(function () { +Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(function () { Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard'); @@ -84,7 +84,7 @@ Route::middleware('auth')->prefix('admin')->name('admin.')->group(function () { }); // Questionnaire Sets - Route::resource('questionnaires', QuestionnaireSetController::class)->except(['show']); + Route::resource('questionnaires', QuestionnaireSetController::class)->except(['show'])->parameters(['questionnaires' => 'set']); Route::post('/questionnaires/{set}/publish', [QuestionnaireSetController::class, 'publish'])->name('questionnaires.publish'); Route::post('/questionnaires/{set}/archive', [QuestionnaireSetController::class, 'archive'])->name('questionnaires.archive'); Route::get('/questionnaires/{set}', [QuestionnaireSetController::class, 'show'])->name('questionnaires.show');