feat: protect log viewer with a dedicated password
This commit is contained in:
@@ -50,3 +50,6 @@ MYKAD_READER_PORT=17321
|
||||
MYKAD_READER_ENABLED_CAWANGAN=''
|
||||
|
||||
INTERNAL_NOTIFICATION_SECRET=
|
||||
|
||||
# Separate password for /logs (username: logs). Empty disables access.
|
||||
LOGVIEWER_PASSWORD=
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
|
||||
class LogViewerPassword
|
||||
{
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$password = config('logviewer.password');
|
||||
|
||||
if (!is_string($password) || $password === '') {
|
||||
return response('Akses log belum dikonfigurasi.', 503)
|
||||
->header('Cache-Control', 'no-store');
|
||||
}
|
||||
|
||||
$provided = $request->getPassword();
|
||||
if ($request->getUser() !== 'logs' || !is_string($provided) ||
|
||||
!hash_equals(hash('sha256', $password), hash('sha256', $provided))) {
|
||||
return response('Kata laluan diperlukan untuk mengakses log.', 401, [
|
||||
'WWW-Authenticate' => 'Basic realm="Log Viewer", charset="UTF-8"',
|
||||
'Cache-Control' => 'no-store',
|
||||
]);
|
||||
}
|
||||
|
||||
$response = $next($request);
|
||||
$response->headers->set('Cache-Control', 'no-store');
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'password' => env('LOGVIEWER_PASSWORD'),
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Pattern and storage path settings
|
||||
|
||||
+2
-1
@@ -63,7 +63,8 @@ Route::get('test', function () {
|
||||
return "event test je";
|
||||
});
|
||||
|
||||
Route::get('logs', '\Rap2hpoutre\LaravelLogViewer\LogViewerController@index');
|
||||
Route::get('logs', '\Rap2hpoutre\LaravelLogViewer\LogViewerController@index')
|
||||
->middleware(['throttle:10,1', \App\Http\Middleware\LogViewerPassword::class]);
|
||||
Route::get('change-password', ['as'=>'user.password','uses'=>'ChangePasswordController@index']);
|
||||
Route::post('change-password', ['as'=>'change.password','uses'=>'ChangePasswordController@store']);
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Http\Middleware\LogViewerPassword;
|
||||
use Illuminate\Http\Request;
|
||||
use Tests\TestCase;
|
||||
|
||||
class LogViewerPasswordTest extends TestCase
|
||||
{
|
||||
public function test_missing_configuration_denies_access()
|
||||
{
|
||||
config(['logviewer.password' => null]);
|
||||
$response = $this->checkAccess();
|
||||
$this->assertSame(503, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function test_missing_or_incorrect_credentials_are_rejected()
|
||||
{
|
||||
config(['logviewer.password' => 'test-only-secret']);
|
||||
foreach ([[], ['PHP_AUTH_USER' => 'logs', 'PHP_AUTH_PW' => 'wrong'],
|
||||
['PHP_AUTH_USER' => 'other', 'PHP_AUTH_PW' => 'test-only-secret']] as $server) {
|
||||
$response = $this->checkAccess($server);
|
||||
$this->assertSame(401, $response->getStatusCode());
|
||||
$this->assertStringContainsString('Basic realm=', $response->headers->get('WWW-Authenticate'));
|
||||
}
|
||||
}
|
||||
|
||||
public function test_correct_credentials_allow_access_without_caching()
|
||||
{
|
||||
config(['logviewer.password' => 'test-only-secret']);
|
||||
$response = $this->checkAccess(['PHP_AUTH_USER' => 'logs', 'PHP_AUTH_PW' => 'test-only-secret']);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('allowed', $response->getContent());
|
||||
$this->assertStringContainsString('no-store', $response->headers->get('Cache-Control'));
|
||||
}
|
||||
|
||||
public function test_logs_route_has_password_and_throttle_middleware()
|
||||
{
|
||||
$route = app('router')->getRoutes()->match(Request::create('/logs'));
|
||||
$this->assertContains(LogViewerPassword::class, $route->gatherMiddleware());
|
||||
$this->assertContains('throttle:10,1', $route->gatherMiddleware());
|
||||
}
|
||||
|
||||
private function checkAccess(array $server = [])
|
||||
{
|
||||
$request = Request::create('/logs?dl=example', 'GET', [], [], [], $server);
|
||||
return (new LogViewerPassword)->handle($request, function () {
|
||||
return response('allowed');
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user