8 Commits

Author SHA1 Message Date
ismailmasseran abccad3219 Merge pull request 'DONE: update cicd to auto deploy on staging, clean up garbage files' (#29) from cicd/auto-deploy-staging into main
Build Docker Image / build-frontend (push) Successful in 39s
Deploy to Staging / deploy (push) Successful in 20s
Reviewed-on: #29
2026-10-01 12:01:23 +08:00
ISMAIL MASSERAN 7ad3fd4f86 DONE: update cicd to auto deploy on staging, clean up garbage files 2026-10-01 11:59:50 +08:00
ISMAIL MASSERAN 713d5a4923 DONE: update hashed password in .env.production 2026-09-28 15:58:30 +08:00
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
22 changed files with 412 additions and 247 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$k7eiUyZdm3BruwAaT1gbmelTm.PPgZptOEQT1nlBjyiR84a4KBMlm"
DB_CONNECTION=mysql
DB_HOST=192.168.1.216
+2 -5
View File
@@ -4,17 +4,14 @@ APP_VERSION=v2.2-staging
APP_KEY=base64:O1XZDqK4H6+3GdrODgCaLr2l+OoWvgVhYr0GJ1KizW8=
APP_DEBUG=true
APP_URL=https://staging.erahn.com.my
API_URL=https://api-staging.erahn.com.my
HTTP_PORT=8091
MYSQL_PUBLISH_PORT=3309
API_URL=https://api-staging.erahn.com.my
API_URL_KOOP=https://api-staging.erahn.com.my
API_URL_PKB=https://api-staging.erahn.com.my
API_ARRAHNBID=http://lelong.test/api/
LOG_CHANNEL=stack
LOG_LEVEL=debug
LOGVIEWER_PASSWORD=test123
DB_CONNECTION=mysql
DB_HOST=mysql
-50
View File
@@ -1,50 +0,0 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:WRTHcFTq5YARKVdA8YDUzsh9Ue8vD63ivgZgXNNBiGo=
APP_DEBUG=true
APP_URL=http://ujrahkopkb.erahn.com.my
API_URL=http://apiujrah-kopkb.erahn.com.my
#API_ARRAHNBID=http://lelong.test/api/
LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=kaunter_erahn
DB_USERNAME=spider
DB_PASSWORD=G0dz!ll4
BROADCAST_DRIVER=pusher
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
PUSHER_APP_ID=1132718
PUSHER_APP_KEY=26b0ec2f297f94b7aa9b
PUSHER_APP_SECRET=421b61fb5a8ecce477a6
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MYKAD_READER_PORT=8085
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED
+37 -18
View File
@@ -1,6 +1,9 @@
name: Deploy to Staging
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
image_tag:
@@ -18,10 +21,22 @@ jobs:
runs-on: host
steps:
- name: Resolve image tag
id: resolve
run: |
set -e
if [ "${{ gitea.event_name }}" = "workflow_dispatch" ]; then
IMAGE_TAG="${{ inputs.image_tag }}"
else
IMAGE_TAG="${{ gitea.ref_name }}"
fi
echo "IMAGE_TAG=${IMAGE_TAG}" >> "$GITHUB_OUTPUT"
echo "Deploying image tag: ${IMAGE_TAG}"
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.image_tag }}
ref: ${{ steps.resolve.outputs.IMAGE_TAG }}
- name: Login to Docker registry
run: |
@@ -33,28 +48,35 @@ jobs:
- name: Verify image exists in registry
run: |
set -e
IMAGE_TAG="${{ inputs.image_tag }}"
IMAGE_TAG="${{ steps.resolve.outputs.IMAGE_TAG }}"
IMAGE="${APP_IMAGE}:${IMAGE_TAG}"
echo "Checking if image exists: ${IMAGE}"
if ! docker manifest inspect "${IMAGE}" > /dev/null 2>&1; then
echo "ERROR: Image ${IMAGE} does not exist in registry!"
echo "Run the Build Docker Image workflow first."
exit 1
fi
echo "✓ ${IMAGE} found"
# Tag pushes build in parallel — wait for the image to appear
for i in $(seq 1 30); do
if docker manifest inspect "${IMAGE}" > /dev/null 2>&1; then
echo "✓ ${IMAGE} found"
exit 0
fi
echo "Image not ready yet (attempt ${i}/30), waiting 20s..."
sleep 20
done
echo "ERROR: Image ${IMAGE} does not exist in registry after waiting!"
echo "Run the Build Docker Image workflow first."
exit 1
- name: Pull Docker image
run: |
set -e
IMAGE_TAG="${{ inputs.image_tag }}"
IMAGE_TAG="${{ steps.resolve.outputs.IMAGE_TAG }}"
docker pull "${APP_IMAGE}:${IMAGE_TAG}"
echo "✓ Image pulled successfully"
- name: Sync compose file and deploy
run: |
set -e
IMAGE_TAG="${{ inputs.image_tag }}"
IMAGE_TAG="${{ steps.resolve.outputs.IMAGE_TAG }}"
mkdir -p "${DEPLOY_DIR}/docker/nginx" "${DEPLOY_DIR}/public"
cp docker-compose.staging.yml "${DEPLOY_DIR}/docker-compose.yml"
@@ -62,15 +84,12 @@ jobs:
find "${DEPLOY_DIR}/public" -mindepth 1 -delete
cp -R public/. "${DEPLOY_DIR}/public/"
if [ ! -f "${DEPLOY_DIR}/.env.staging" ]; then
if [ -f .env.staging ]; then
cp .env.staging "${DEPLOY_DIR}/.env.staging"
echo "Copied .env.staging from repo (first deploy). Edit ${DEPLOY_DIR}/.env.staging on the server if needed."
else
echo "ERROR: ${DEPLOY_DIR}/.env.staging is missing on the server"
exit 1
fi
if [ ! -f .env.staging ]; then
echo "ERROR: .env.staging is missing in the repository"
exit 1
fi
cp .env.staging "${DEPLOY_DIR}/.env.staging"
echo "✓ Synced .env.staging from repo"
cd "${DEPLOY_DIR}"
+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
-50
View File
@@ -1,50 +0,0 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:FFRogE6mJj8FaDSSHl/KpXpzqi2M+P4plxVSRgVM8JI=
APP_DEBUG=true
APP_URL=http://kaunterv2.localhost
API_URL=http://apiv2.localhost
API_ARRAHNBID=http://lelong.localhost/api/
LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST=database_zone2
DB_PORT=3306
DB_DATABASE=kaunterpkb
DB_USERNAME=root
DB_PASSWORD=secret
BROADCAST_DRIVER=pusher
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
PUSHER_APP_ID=1132718
PUSHER_APP_KEY=26b0ec2f297f94b7aa9b
PUSHER_APP_SECRET=421b61fb5a8ecce477a6
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MYKAD_READER_PORT=8085
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED
-50
View File
@@ -1,50 +0,0 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:FFRogE6mJj8FaDSSHl/KpXpzqi2M+P4plxVSRgVM8JI=
APP_DEBUG=true
APP_URL=http://kaunterv2.localhost
API_URL=http://apiv2.kgmk.localhost
API_ARRAHNBID=http://lelong.localhost/api/
LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST=database_zone_franchise
DB_PORT=3306
DB_DATABASE=kaunter_koopguru
DB_USERNAME=root
DB_PASSWORD=secret
BROADCAST_DRIVER=pusher
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
PUSHER_APP_ID=1132718
PUSHER_APP_KEY=26b0ec2f297f94b7aa9b
PUSHER_APP_SECRET=421b61fb5a8ecce477a6
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MYKAD_READER_PORT=8085
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED
-50
View File
@@ -1,50 +0,0 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:FFRogE6mJj8FaDSSHl/KpXpzqi2M+P4plxVSRgVM8JI=
APP_DEBUG=true
APP_URL=http://kaunterv2.localhost
API_URL=http://apiv2.krk.localhost
API_ARRAHNBID=http://lelong.localhost/api/
LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST=database_zone_franchise
DB_PORT=3306
DB_DATABASE=kaunter_krk
DB_USERNAME=root
DB_PASSWORD=secret
BROADCAST_DRIVER=pusher
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
PUSHER_APP_ID=1132718
PUSHER_APP_KEY=26b0ec2f297f94b7aa9b
PUSHER_APP_SECRET=421b61fb5a8ecce477a6
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MYKAD_READER_PORT=8085
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED
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(3)').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(3)').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>
@@ -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');
});
}
}