Feat/logs password protection (#28)
Build Docker Image / build-frontend (push) Successful in 42s
Build Docker Image / build-frontend (push) Successful in 42s
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local> Co-authored-by: nurafrinaalimi16 <nurafrinaalimi16@gmail.com> Reviewed-on: #28
This commit was merged in pull request #28.
This commit is contained in:
@@ -50,3 +50,5 @@ MYKAD_READER_PORT=17321
|
|||||||
MYKAD_READER_ENABLED_CAWANGAN=''
|
MYKAD_READER_ENABLED_CAWANGAN=''
|
||||||
|
|
||||||
INTERNAL_NOTIFICATION_SECRET=
|
INTERNAL_NOTIFICATION_SECRET=
|
||||||
|
|
||||||
|
LOGVIEWER_PASSWORD=
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ APP_URL=https://ujrah.erahn.com.my
|
|||||||
API_URL=https://apiujrah.erahn.com.my
|
API_URL=https://apiujrah.erahn.com.my
|
||||||
|
|
||||||
LOG_CHANNEL=stack
|
LOG_CHANNEL=stack
|
||||||
|
LOGVIEWER_PASSWORD="$2y$10$5R7JP/grEqRS6YrAHKKXveuncRoqbbV/IQQLNj3QQDLCyT7g8gL1C"
|
||||||
|
|
||||||
DB_CONNECTION=mysql
|
DB_CONNECTION=mysql
|
||||||
DB_HOST=192.168.1.216
|
DB_HOST=192.168.1.216
|
||||||
|
|||||||
@@ -5,15 +5,12 @@ namespace App\Http\Controllers\Auth;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Providers\RouteServiceProvider;
|
use App\Providers\RouteServiceProvider;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Session;
|
|
||||||
use Illuminate\Support\Facades\Redirect;
|
|
||||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
use App\Dashboard;
|
use App\Dashboard;
|
||||||
use App\User;
|
use App\User;
|
||||||
use Carbon\Carbon;
|
|
||||||
|
|
||||||
class LoginController extends Controller
|
class LoginController extends Controller
|
||||||
{
|
{
|
||||||
@@ -144,25 +141,26 @@ class LoginController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function logout(){
|
public function logout(Request $request)
|
||||||
$current = Carbon::now();
|
{
|
||||||
$current = new Carbon();
|
|
||||||
$current = $current->toDateString();
|
|
||||||
$auth_user = Auth::guard('web')->user();
|
$auth_user = Auth::guard('web')->user();
|
||||||
Auth::logout();
|
|
||||||
Session::flush();
|
|
||||||
$session = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/logoutsession/' . $auth_user['cawangan'], [
|
|
||||||
'id' => $auth_user['id'],
|
|
||||||
'nama' => $auth_user['name'],
|
|
||||||
'nokp' => $auth_user['no_kp'],
|
|
||||||
'email' => $auth_user['email'],
|
|
||||||
'taraf' => $auth_user['role_harian'],
|
|
||||||
'tarikh' => $current,
|
|
||||||
|
|
||||||
|
|
||||||
]);
|
Auth::guard('web')->logout();
|
||||||
|
$request->session()->invalidate();
|
||||||
|
$request->session()->regenerateToken();
|
||||||
|
|
||||||
return Redirect::to('/');
|
if ($auth_user) {
|
||||||
|
Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/logoutsession/' . $auth_user['cawangan'], [
|
||||||
|
'id' => $auth_user['id'],
|
||||||
|
'nama' => $auth_user['name'],
|
||||||
|
'nokp' => $auth_user['no_kp'],
|
||||||
|
'email' => $auth_user['email'],
|
||||||
|
'taraf' => $auth_user['role_harian'],
|
||||||
|
'tarikh' => now()->toDateString(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetRolesHarian(Request $request, $kodcaw){
|
public function GetRolesHarian(Request $request, $kodcaw){
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
|
||||||
|
class LogViewerPassword
|
||||||
|
{
|
||||||
|
public function handle($request, Closure $next)
|
||||||
|
{
|
||||||
|
$passwordHash = config('logviewer.password');
|
||||||
|
|
||||||
|
if (!is_string($passwordHash) || $passwordHash === '') {
|
||||||
|
return response('Akses log belum dikonfigurasi.', 503)
|
||||||
|
->header('Cache-Control', 'no-store');
|
||||||
|
}
|
||||||
|
|
||||||
|
$provided = $request->getPassword();
|
||||||
|
if ($request->getUser() !== 'logviewer' || !is_string($provided) ||
|
||||||
|
!password_verify($provided, $passwordHash)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,12 @@ class RoleAccess
|
|||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'masteradmin':
|
||||||
|
if ($userRole == 'masteradmin') {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 'penilai':
|
case 'penilai':
|
||||||
if ($userRole == 'penilai') {
|
if ($userRole == 'penilai') {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
'password' => env('LOGVIEWER_PASSWORD'),
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Pattern and storage path settings
|
| Pattern and storage path settings
|
||||||
|
|||||||
+7
-1
@@ -63,7 +63,13 @@ Route::get('test', function () {
|
|||||||
return "event test je";
|
return "event test je";
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::get('logs', '\Rap2hpoutre\LaravelLogViewer\LogViewerController@index');
|
Route::get('logs', '\Rap2hpoutre\LaravelLogViewer\LogViewerController@index')
|
||||||
|
->middleware([
|
||||||
|
'auth',
|
||||||
|
'role:admin,masteradmin,operasi',
|
||||||
|
'throttle:10,1',
|
||||||
|
\App\Http\Middleware\LogViewerPassword::class,
|
||||||
|
]);
|
||||||
Route::get('change-password', ['as'=>'user.password','uses'=>'ChangePasswordController@index']);
|
Route::get('change-password', ['as'=>'user.password','uses'=>'ChangePasswordController@index']);
|
||||||
Route::post('change-password', ['as'=>'change.password','uses'=>'ChangePasswordController@store']);
|
Route::post('change-password', ['as'=>'change.password','uses'=>'ChangePasswordController@store']);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?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' => password_hash('test-only-secret', PASSWORD_DEFAULT)]);
|
||||||
|
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' => password_hash('test-only-secret', PASSWORD_DEFAULT)]);
|
||||||
|
$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_auth_role_password_and_throttle_middleware()
|
||||||
|
{
|
||||||
|
$route = app('router')->getRoutes()->match(Request::create('/logs'));
|
||||||
|
$middleware = $route->gatherMiddleware();
|
||||||
|
$this->assertContains('auth', $middleware);
|
||||||
|
$this->assertContains('role:admin,masteradmin', $middleware);
|
||||||
|
$this->assertContains(LogViewerPassword::class, $middleware);
|
||||||
|
$this->assertContains('throttle:10,1', $middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
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