10 Commits

Author SHA1 Message Date
ismailmasseran b76566a2d6 Feat/logs password protection (#28)
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
2026-09-28 15:05:56 +08:00
ismailmasseran 38f6db2e10 Merge pull request 'fix: papar cara terima pada senarai dan cetakan SAG hanya apabila pembiayaan online aktif' (#27) from fix/sag-cara-terima-feature-flag into main
Reviewed-on: #27
2026-09-28 09:44:25 +08:00
nurafrinaalimi16 f95bdf1214 fix: papar cara terima pada senarai dan cetakan SAG hanya apabila pembiayaan online aktif 2026-09-27 16:55:12 +08:00
ISMAIL MASSERAN d614677601 DONE: add proper vector logo arrahn
Build Docker Image / build-frontend (push) Successful in 40s
2026-09-27 15:58:05 +08:00
ismailmasseran 7cd21ef610 Feat/dasar privasi (#26)
Co-authored-by: nurafrinaalimi16 <nurafrinaalimi16@gmail.com>
Reviewed-on: #26
2026-09-27 15:56:09 +08:00
nurafrinaalimi16 ca2a02e354 hotfix: betulkan lajur tarikh untuk semakan ubah dan hapus tebus
Build Docker Image / build-frontend (push) Successful in 40s
2026-09-27 15:10:29 +08:00
nurafrinaalimi16 76176ed453 hotfix:ubah hapus gadai
Build Docker Image / build-frontend (push) Successful in 40s
2026-09-27 13:05:54 +08:00
ISMAIL MASSERAN 17a5bfafdd DONE: update env.staging
Build Docker Image / build-frontend (push) Successful in 41s
2026-09-26 18:51:36 +08:00
ismailmasseran 5f29f064a0 Merge pull request 'feat(khazanah): papar tarikh ansuran terkini dalam senarai gadaian' (#25) from feat/khazanah-tarikh-ansuran-terkini into main
Reviewed-on: #25
2026-09-24 16:16:16 +08:00
nurafrinaalimi16 9dae32a462 feat(khazanah): papar tarikh ansuran terkini dalam senarai gadaian 2026-09-24 16:15:20 +08:00
18 changed files with 427 additions and 30 deletions
+2
View File
@@ -50,3 +50,5 @@ MYKAD_READER_PORT=17321
MYKAD_READER_ENABLED_CAWANGAN=''
INTERNAL_NOTIFICATION_SECRET=
LOGVIEWER_PASSWORD=
+1
View File
@@ -7,6 +7,7 @@ APP_URL=https://ujrah.erahn.com.my
API_URL=https://apiujrah.erahn.com.my
LOG_CHANNEL=stack
LOGVIEWER_PASSWORD="$2y$10$5R7JP/grEqRS6YrAHKKXveuncRoqbbV/IQQLNj3QQDLCyT7g8gL1C"
DB_CONNECTION=mysql
DB_HOST=192.168.1.216
+3 -1
View File
@@ -55,4 +55,6 @@ MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MYKAD_READER_PORT=17321
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED
MYKAD_READER_ENABLED_CAWANGAN=''
INTERNAL_NOTIFICATION_SECRET=staging_noti_secret
+17 -19
View File
@@ -5,15 +5,12 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use App\Dashboard;
use App\User;
use Carbon\Carbon;
class LoginController extends Controller
{
@@ -144,25 +141,26 @@ class LoginController extends Controller
}
}
public function logout(){
$current = Carbon::now();
$current = new Carbon();
$current = $current->toDateString();
public function logout(Request $request)
{
$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){
+5 -1
View File
@@ -680,7 +680,11 @@ class GadaianController extends Controller
$kuantiti = $gadai_detail['pinjaman'] / $komoditi['nilai'];
if ($gadai_detail['tagbaru'] == 1) {
return view('print.sag_ujrah_3', compact('komoditi', 'product', 'kuantiti', 'gadai_detail', 'customer_detail', 'marhun_detail', 'cawangan_detail', 'api_url', 'pembayaran_segera', 'auth_username', 'mutu', 'kadar_ujrah', 'kakitangan'));
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])
->get($api_url . '/api/cawangan/' . $kodcaw)->json();
$pembiayaanOnlineEnabled = !empty(($cawangan_info['features'] ?? [])['pembiayaan_online']);
return view('print.sag_ujrah_3', compact('komoditi', 'product', 'kuantiti', 'gadai_detail', 'customer_detail', 'marhun_detail', 'cawangan_detail', 'api_url', 'pembayaran_segera', 'auth_username', 'mutu', 'kadar_ujrah', 'kakitangan'))->with('pembiayaanOnlineEnabled', $pembiayaanOnlineEnabled);
} else {
return view('print.sag', compact('gadai_detail', 'customer_detail', 'marhun_detail', 'cawangan_detail', 'api_url', 'pembayaran_segera', 'auth_username', 'mutu', 'kakitangan'));
}
@@ -533,6 +533,10 @@ class PembiayaanOnlineController extends Controller
$kuantiti = $nilai > 0 ? ((float) ($gadai_detail['pinjaman'] ?? 0) / $nilai) : 0;
if (($gadai_detail['tagbaru'] ?? 0) == 1) {
$cawangan_info = $this->api()
->get($api_url . '/api/cawangan/' . $kodcaw)->json();
$pembiayaanOnlineEnabled = !empty(($cawangan_info['features'] ?? [])['pembiayaan_online']);
return view('print.sag_ujrah_3', compact(
'komoditi',
'product',
@@ -547,7 +551,7 @@ class PembiayaanOnlineController extends Controller
'mutu',
'kadar_ujrah',
'kakitangan'
));
))->with('pembiayaanOnlineEnabled', $pembiayaanOnlineEnabled);
}
return view('print.sag', compact(
+32
View File
@@ -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;
}
}
+6
View File
@@ -44,6 +44,12 @@ class RoleAccess
return $next($request);
}
break;
case 'masteradmin':
if ($userRole == 'masteradmin') {
return $next($request);
}
break;
case 'penilai':
if ($userRole == 'penilai') {
+1
View File
@@ -1,6 +1,7 @@
<?php
return [
'password' => env('LOGVIEWER_PASSWORD'),
/*
|--------------------------------------------------------------------------
| Pattern and storage path settings
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 166 KiB

+3
View File
@@ -115,6 +115,9 @@
</form>
</div>
<div class="card-footer text-center pt-0 px-lg-2 px-1">
<p class="mb-3 text-sm">
<a href="{{ route('dasar.privasi') }}" class="text-info font-weight-bold">Dasar Privasi</a>
</p>
<p class="mb-4 text-sm mx-auto">
Masalah untuk log masuk?
<a class="text-info text-gradient font-weight-bold">Hubungi
+8 -2
View File
@@ -801,9 +801,12 @@
}
});
var pembiayaanOnlineEnabled = @json(!empty(($cawangan_info['features'] ?? [])['pembiayaan_online']));
var tablegadai = initErahnDatatable('#myTable', {
order: [[1, 'desc']],
columnDefs: [
{ targets: 2, visible: pembiayaanOnlineEnabled, className: pembiayaanOnlineEnabled ? '' : 'no-export' },
{ targets: 0, orderable: false, width: '40px' },
{ targets: -1, orderable: false, searchable: false, width: '70px' }
]
@@ -811,6 +814,7 @@
var xtawarruq = initErahnDatatable('#xtawarruq', {
columnDefs: [
{ targets: 2, visible: pembiayaanOnlineEnabled, className: pembiayaanOnlineEnabled ? '' : 'no-export' },
{ targets: 0, orderable: false, width: '40px' }
]
});
@@ -2067,7 +2071,8 @@
harini.setHours(0, 0, 0, 0);
if ($("input[name='options']:checked").val() == 'belum_tebus') {
let test1 = $('input[name="belum"]:checked').closest('tr').children('td:eq(2)').text();
let selectedRow = $('input[name="belum"]:checked').closest('tr');
let test1 = selectedRow.length ? selectedRow.closest('table').DataTable().row(selectedRow).data()[3] : '';
let tarikhgadai2 = new Date(test1);
tarikhgadai2.setHours(0, 0, 0, 0);
if (+harini !== +tarikhgadai2) {
@@ -2484,7 +2489,8 @@
}
if ($("input[name='options']:checked").val() == 'belum_tebus') {
let test1 = $('input[name="belum"]:checked').closest('tr').children('td:eq(2)').text();
let selectedRow = $('input[name="belum"]:checked').closest('tr');
let test1 = selectedRow.length ? selectedRow.closest('table').DataTable().row(selectedRow).data()[3] : '';
let tarikhgadai2 = new Date(test1);
tarikhgadai2.setHours(0, 0, 0, 0);
+100
View File
@@ -0,0 +1,100 @@
<!DOCTYPE html>
<html lang="ms">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dasar Privasi | Kaunter arOS</title>
<link rel="icon" href="{{ asset('img/icon_pkb.png') }}">
<link href="{{ asset('css/login-v2.css') }}" rel="stylesheet">
<style>
body { background: #f4f7fb; color: #344767; }
.privacy-container { max-width: 900px; margin: 0 auto; padding: 32px 20px; }
.privacy-brand { display: flex; align-items: center; gap: 16px; margin-bottom: 24px; }
.privacy-brand img { width: 60px; height: auto; }
.privacy-card { background: #fff; border-radius: 16px; padding: 40px; box-shadow: 0 8px 32px rgba(24, 49, 83, .08); }
.privacy-card h1 { font-size: 2rem; }
.privacy-card h2 { font-size: 1.2rem; margin-top: 28px; }
.privacy-card p, .privacy-card li { line-height: 1.8; }
.privacy-card ul { padding-left: 24px; }
.privacy-back { display: inline-block; margin-top: 24px; color: #075985; text-decoration: underline; }
a:focus-visible { outline: 3px solid #075985; outline-offset: 4px; }
@media (max-width: 576px) {
.privacy-container { padding: 20px 16px; }
.privacy-card { padding: 24px 20px; }
.privacy-card h1 { font-size: 1.7rem; }
}
</style>
</head>
<body>
<main class="privacy-container">
<div class="privacy-brand">
<img src="{{ asset('img/icon_pkb.png') }}" alt="Logo ar-Rahnu">
<span class="font-weight-bold">Kaunter arOS &middot; ar-rahn Online System</span>
</div>
<article class="privacy-card" aria-labelledby="privacy-title">
<h1 id="privacy-title">Dasar Privasi</h1>
<p class="text-sm text-muted">Versi dasar: 1.0 &middot; Tarikh kemas kini: <time datetime="2026-09-27">27 September 2026</time></p>
<h2>1. Pengenalan</h2>
<p>Selamat datang ke sistem Kaunter arOS Koperasi Permodalan Kelantan Berhad (KoPKB). Kami amat menghargai kepercayaan yang anda berikan untuk mengendalikan maklumat peribadi anda. Dasar Privasi ini digubal untuk membantu anda memahami bagaimana kami mengumpul, menggunakan, mendedahkan dan melindungi data peribadi anda bagi urusan Ar-Rahn, dengan mengambil kira keperluan Akta Perlindungan Data Peribadi 2010 (PDPA) dan amalan keselamatan maklumat yang berkaitan.</p>
<p>Dasar ini terpakai kepada kakitangan, pentadbir dan mana-mana pihak yang diberi kuasa untuk mengakses atau menggunakan sistem Kaunter arOS KoPKB, serta pengendalian data pelanggan dalam perkhidmatan yang diuruskan melalui sistem ini.</p>
<h2>2. Data yang Dikumpul</h2>
<p>Kami mengumpul dan memproses maklumat yang berkaitan dengan urusan sistem, termasuk:</p>
<ul>
<li><strong>Maklumat Pengenalan Peribadi:</strong> Nama penuh, nombor pengenalan, alamat, alamat e-mel dan nombor telefon pelanggan atau pengguna.</li>
<li><strong>Maklumat Akaun:</strong> Maklumat akaun pengguna, cawangan dan peranan akses dalam sistem.</li>
<li><strong>Data Teknikal:</strong> Maklumat seperti alamat IP, jenis pelayar dan log aktiviti sistem yang direkodkan bagi tujuan operasi dan keselamatan.</li>
<li><strong>Data Transaksi:</strong> Rekod pembiayaan, gadaian, maklumat marhun, pembayaran, penebusan, lelongan dan dokumen sokongan berkaitan.</li>
</ul>
<h2>3. Tujuan Penggunaan Data</h2>
<p>Data anda digunakan untuk tujuan berikut:</p>
<ul>
<li><strong>Operasi Sistem:</strong> Menguruskan akaun pengguna dan memproses urusan pelanggan serta transaksi Ar-Rahn.</li>
<li><strong>Pengesahan:</strong> Mengesahkan identiti dan mengawal akses pengguna mengikut peranan serta cawangan yang dibenarkan.</li>
<li><strong>Komunikasi:</strong> Menyampaikan notis berkaitan urusan, makluman pentadbiran dan sokongan pelanggan.</li>
<li><strong>Pelaporan dan Penambahbaikan:</strong> Menyediakan laporan operasi, menyokong audit serta menambah baik prestasi dan kualiti sistem.</li>
</ul>
<h2>4. Perkongsian &amp; Pendedahan Data</h2>
<p>Kami tidak menjual data peribadi anda. Akses, perkongsian atau pendedahan data dihadkan kepada keperluan urusan yang berkaitan, termasuk:</p>
<ul>
<li><strong>Kakitangan yang Diberi Kuasa:</strong> Kakitangan dan pentadbir KoPKB yang memerlukan maklumat untuk menjalankan tugas.</li>
<li><strong>Pembekal Perkhidmatan:</strong> Penyedia teknologi atau perkhidmatan yang menyokong pengehosan, penyelenggaraan sistem dan pemprosesan pembayaran, jika berkaitan.</li>
<li><strong>Pematuhan Undang-undang:</strong> Pihak berkuasa atau pihak lain apabila pendedahan diperlukan oleh undang-undang atau perintah mahkamah.</li>
</ul>
<h2>5. Keselamatan Data</h2>
<p>Langkah keselamatan teknikal dan pentadbiran digunakan untuk membantu melindungi data yang dikendalikan melalui sistem, termasuk kawalan akses mengikut peranan pengguna dan pengesahan akaun.</p>
<p>Pengguna hendaklah mengakses sistem melalui sambungan HTTPS yang selamat, menjaga kerahsiaan kata laluan, tidak berkongsi akaun dan log keluar selepas selesai menggunakan sistem. Sebarang aktiviti yang mencurigakan hendaklah dilaporkan kepada pentadbir sistem.</p>
<h2>6. Hak Pengguna</h2>
<p>Anda boleh menghubungi kami untuk mengemukakan permohonan berkaitan data peribadi anda, termasuk:</p>
<ul>
<li><strong>Akses &amp; Pembetulan:</strong> Memohon semakan dan pembetulan maklumat peribadi yang tidak tepat atau tidak terkini.</li>
<li><strong>Pemadaman:</strong> Memohon pemadaman akaun atau data dalam keadaan yang sesuai, tertakluk kepada keperluan penyimpanan rekod transaksi, audit dan undang-undang.</li>
<li><strong>Sekatan:</strong> Memohon supaya pemprosesan data tertentu dihadkan, tertakluk kepada keperluan perkhidmatan dan kewajipan yang terpakai.</li>
</ul>
<p>Permohonan akan dinilai mengikut keperluan yang terpakai. Pengesahan identiti mungkin diperlukan sebelum permohonan diproses.</p>
<h2>7. Tempoh Penyimpanan</h2>
<p>Data disimpan selama diperlukan untuk pengendalian urusan Ar-Rahn, penyelenggaraan rekod pembiayaan dan transaksi, audit serta pematuhan undang-undang. Selepas keperluan penyimpanan berakhir, data akan diuruskan untuk pemadaman atau penyahkenalpastian mengikut prosedur organisasi yang terpakai.</p>
<h2>8. Kuki &amp; Teknologi Penjejakan</h2>
<p>Sistem Kaunter arOS menggunakan kuki (cookies) dan sesi untuk menyokong log masuk, mengekalkan sesi pengguna dan melindungi permintaan dalam sistem. Anda boleh menguruskan pilihan kuki melalui tetapan pelayar. Menyekat kuki boleh menyebabkan fungsi tertentu, termasuk log masuk, tidak berfungsi dengan baik.</p>
<h2>9. Perubahan Dasar</h2>
<p>Dasar ini boleh dikemas kini dari semasa ke semasa apabila terdapat perubahan pada perkhidmatan, pengendalian sistem atau keperluan organisasi. Versi terkini akan dipaparkan pada halaman ini. Sebarang perubahan besar akan dimaklumkan melalui e-mel atau notis dalam sistem sebelum tarikh kuat kuasa baharu.</p>
<h2>10. Hubungi Kami</h2>
<p>Jika anda mempunyai pertanyaan, komen atau permohonan berkaitan dasar ini dan pengendalian data peribadi dalam sistem Kaunter arOS, sila hubungi:</p>
<p><strong>AFRINA ALIMI</strong><br>
Koperasi Permodalan Kelantan Berhad (KoPKB)<br>
E-mel: <a href="mailto:afrina@koppkb.com">afrina@koppkb.com</a>
</p>
<a class="privacy-back" href="{{ route('login') }}">Kembali ke Log Masuk</a>
</article>
</main>
</body>
</html>
+51 -5
View File
@@ -185,6 +185,7 @@
<th>Keuntungan</th>
<th>Lelong</th>
<th>Pembayaran Terakhir</th>
<th>Tarikh Ansuran Terkini</th>
</tr>
</thead>
</table>
@@ -204,6 +205,7 @@
<th>Keuntungan</th>
<th>Lelong</th>
<th>Pembayaran Terakhir</th>
<th>Tarikh Ansuran Terkini</th>
</tr>
</thead>
</table>
@@ -634,6 +636,45 @@
};
}
function tarikhAnsuranTerkini(transactions)
{
if (!Array.isArray(transactions)) {
throw new Error('Invalid transaction response');
}
return transactions.reduce(function(latest, transaction) {
if (transaction.trans_type !== 'A') return latest;
var date = String(transaction.created_at || '').slice(0, 10);
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return latest;
return date > latest ? date : latest;
}, '') || 'Tiada bayaran';
}
function muatTarikhAnsuranTerkini(row, norujukan)
{
var originalData = row.data();
function update(value) {
// Ignore responses for rows removed when the customer/table changes.
if (row.data() !== originalData) return;
var updatedData = originalData.slice();
updatedData[11] = value;
row.data(updatedData).draw(false);
}
$.ajax({
method: 'get',
url: api_url + '/api/transaction/' + encodeURIComponent(norujukan),
success: function(transactions) {
try {
update(tarikhAnsuranTerkini(transactions));
} catch (error) {
update('Gagal dimuatkan');
}
},
error: function() {
update('Gagal dimuatkan');
}
});
}
function gadaiTable(data,jenistebus){
var counter = 1;
@@ -693,8 +734,8 @@
let table_norujukan = gadai_detail["norujukan"];
// let radio_action = "onclick=checkCanChangeGadaianDetail(" + "'" + table_norujukan + "'" + ")";
if(gadai_detail["percentpinj"] != 0)
xtawarruq.row.add([
if(gadai_detail["percentpinj"] != 0) {
var ansuranRow = xtawarruq.row.add([
'<input type="radio" value='+ gadai_detail["norujukan"] + ' name="belum"/>',
// '<a href="#" onclick="checkserah(this)" data-pinjaman='+ gadai_detail['pinjaman'] +' data-hargajualan='+ gadai_detail['hargajualan'] +' data-norujukan='+ gadai_detail['norujukan'] +' data-url=' + urlroute +'>' + gadai_detail['norujukan'] + '</a>',
(function() {
@@ -724,8 +765,11 @@
let color = bayaranInfo.blocked ? 'red' : 'green';
return '<div style="white-space: nowrap; color: ' + color + '; font-weight: bold;">' +
bayaranInfo.tarikh + '</div>';
})()
})(),
'Memuatkan...'
]).draw();
muatTarikhAnsuranTerkini(ansuranRow, gadai_detail['norujukan']);
}
}else{
var taglel = '';
if(gadai_detail['taglel'] == 1){
@@ -757,7 +801,7 @@
let table_norujukan = gadai_detail["norujukan"];
// let radio_action = "onclick=checkCanChangeGadaianDetail(" + "'" + table_norujukan + "'" + ")";
tablegadai.row.add([
var ansuranRow = tablegadai.row.add([
'<input type="radio" value='+ gadai_detail["norujukan"] + ' name="belum"/>',
// '<a href="#" onclick="checkserah(this)" data-pinjaman='+ gadai_detail['pinjaman'] +' data-hargajualan='+ gadai_detail['hargajualan'] +' data-norujukan='+ gadai_detail['norujukan'] +' data-url=' + urlroute +'>' + gadai_detail['norujukan'] + '</a>',
(function() {
@@ -787,8 +831,10 @@
let color = bayaranInfo.blocked ? 'red' : 'green';
return '<div style="white-space: nowrap; color: ' + color + '; font-weight: bold;">' +
bayaranInfo.tarikh + '</div>';
})()
})(),
'Memuatkan...'
]).draw();
muatTarikhAnsuranTerkini(ansuranRow, gadai_detail['norujukan']);
}
}else if(jenistebus == "sudah_tebus"){
@@ -2112,6 +2112,7 @@
</div>
<div class="main-page" style="font-family: Arial, Helvetica, sans-serif;">
<div class="sub-page" style="font-size: 11px;">
@if ($pembiayaanOnlineEnabled ?? false)
@php
$caraTerimaBayaran = strtoupper(trim((string) ($gadai_detail['cara_terima'] ?? '')));
@endphp
@@ -2128,6 +2129,7 @@
</span>
</div>
</div>
@endif
{{-- HEADER SECTION --}}
<table style="width: 100%; margin-top: 0;">
<tr style="vertical-align: top;">
+9 -1
View File
@@ -17,6 +17,8 @@ use Illuminate\Support\Facades\Storage;
Route::post('/internal/notifications', ['as' => 'internal.notifications.store', 'uses' => 'InternalNotificationController@store']);
Route::view('/dasar-privasi', 'dasar-privasi')->name('dasar.privasi');
Route::get('/health', function () {
return response()->json(['status' => 'ok']);
});
@@ -61,7 +63,13 @@ Route::get('test', function () {
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::post('change-password', ['as'=>'change.password','uses'=>'ChangePasswordController@store']);
+55
View File
@@ -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');
});
}
}