Added user audit details in panel

This commit is contained in:
Pinga 2024-01-05 14:18:40 +02:00
parent 2c48d48777
commit 6d1f934d36
8 changed files with 197 additions and 25 deletions

View file

@ -204,6 +204,10 @@
"audit": true,
"skip": null
},
"users_audit": {
"audit": true,
"skip": null
},
"users_confirmations": {
"audit": null,
"skip": null

View file

@ -277,6 +277,23 @@ class Auth
public static function changeCurrentPassword($oldPassword, $newPassword){
$auth = self::$auth;
try {
global $container;
$db = $container->get('db');
$currentDateTime = new \DateTime();
$currentDate = $currentDateTime->format('Y-m-d H:i:s.v'); // Current timestamp
$db->insert(
'users_audit',
[
'user_id' => $_SESSION['auth_user_id'],
'user_event' => 'user.update.password',
'user_resource' => 'control.panel',
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'user_ip' => get_client_ip(),
'user_location' => get_client_location(),
'event_time' => $currentDate,
'user_data' => null
]
);
$auth->changePassword($oldPassword, $newPassword);
redirect()->route('profile')->with('success','Password has been changed');
}

View file

@ -59,6 +59,8 @@ class AuthController extends Controller
* @throws \Pinga\Auth\AuthError
*/
public function login(Request $request, Response $response){
global $container;
$data = $request->getParsedBody();
if(isset($data['remember'])){
$remember = $data['remember'];
@ -71,8 +73,25 @@ class AuthController extends Controller
$code = null;
}
$login = Auth::login($data['email'], $data['password'], $remember, $code);
if($login===true)
if($login===true) {
$db = $container->get('db');
$currentDateTime = new \DateTime();
$currentDate = $currentDateTime->format('Y-m-d H:i:s.v'); // Current timestamp
$db->insert(
'users_audit',
[
'user_id' => $_SESSION['auth_user_id'],
'user_event' => 'user.login',
'user_resource' => 'control.panel',
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'user_ip' => get_client_ip(),
'user_location' => get_client_location(),
'event_time' => $currentDate,
'user_data' => null
]
);
redirect()->route('home');
}
}
/**
@ -80,6 +99,23 @@ class AuthController extends Controller
*/
public function logout()
{
global $container;
$db = $container->get('db');
$currentDateTime = new \DateTime();
$currentDate = $currentDateTime->format('Y-m-d H:i:s.v'); // Current timestamp
$db->insert(
'users_audit',
[
'user_id' => $_SESSION['auth_user_id'],
'user_event' => 'user.logout',
'user_resource' => 'control.panel',
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'user_ip' => get_client_ip(),
'user_location' => get_client_location(),
'event_time' => $currentDate,
'user_data' => null
]
);
Auth::logout();
redirect()->route('login');
}

View file

@ -73,12 +73,16 @@ class ProfileController extends Controller
'SELECT * FROM users_webauthn WHERE user_id = ?',
[$userId]
);
$user_audit = $db->select(
'SELECT * FROM users_audit WHERE user_id = ? ORDER BY event_time DESC',
[$userId]
);
if ($is_2fa_activated) {
return view($response,'admin/profile/profile.twig',['email' => $email, 'username' => $username, 'status' => $status, 'role' => $role, 'csrf_name' => $csrfName, 'csrf_value' => $csrfValue]);
return view($response,'admin/profile/profile.twig',['email' => $email, 'username' => $username, 'status' => $status, 'role' => $role, 'csrf_name' => $csrfName, 'csrf_value' => $csrfValue, 'userAudit' => $user_audit]);
} else if ($is_weba_activated) {
return view($response,'admin/profile/profile.twig',['email' => $email, 'username' => $username, 'status' => $status, 'role' => $role, 'qrcodeDataUri' => $qrcodeDataUri, 'secret' => $secret, 'csrf_name' => $csrfName, 'csrf_value' => $csrfValue, 'weba' => $is_weba_activated]);
return view($response,'admin/profile/profile.twig',['email' => $email, 'username' => $username, 'status' => $status, 'role' => $role, 'qrcodeDataUri' => $qrcodeDataUri, 'secret' => $secret, 'csrf_name' => $csrfName, 'csrf_value' => $csrfValue, 'weba' => $is_weba_activated, 'userAudit' => $user_audit]);
} else {
return view($response,'admin/profile/profile.twig',['email' => $email, 'username' => $username, 'status' => $status, 'role' => $role, 'qrcodeDataUri' => $qrcodeDataUri, 'secret' => $secret, 'csrf_name' => $csrfName, 'csrf_value' => $csrfValue]);
return view($response,'admin/profile/profile.twig',['email' => $email, 'username' => $username, 'status' => $status, 'role' => $role, 'qrcodeDataUri' => $qrcodeDataUri, 'secret' => $secret, 'csrf_name' => $csrfName, 'csrf_value' => $csrfValue, 'userAudit' => $user_audit]);
}
}
@ -114,6 +118,21 @@ class ProfileController extends Controller
}
try {
$currentDateTime = new \DateTime();
$currentDate = $currentDateTime->format('Y-m-d H:i:s.v'); // Current timestamp
$db->insert(
'users_audit',
[
'user_id' => $_SESSION['auth_user_id'],
'user_event' => 'user.enable.2fa',
'user_resource' => 'control.panel',
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'user_ip' => get_client_ip(),
'user_location' => get_client_location(),
'event_time' => $currentDate,
'user_data' => null
]
);
$db->update(
'users',
[

View file

@ -396,4 +396,33 @@ function createUuidFromId($id) {
// Handle exception
return null;
}
}
// Function to get the client IP address
function get_client_ip() {
$ipaddress = '';
if (getenv('HTTP_CLIENT_IP'))
$ipaddress = getenv('HTTP_CLIENT_IP');
else if(getenv('HTTP_X_FORWARDED_FOR'))
$ipaddress = getenv('HTTP_X_FORWARDED_FOR');
else if(getenv('HTTP_X_FORWARDED'))
$ipaddress = getenv('HTTP_X_FORWARDED');
else if(getenv('HTTP_FORWARDED_FOR'))
$ipaddress = getenv('HTTP_FORWARDED_FOR');
else if(getenv('HTTP_FORWARDED'))
$ipaddress = getenv('HTTP_FORWARDED');
else if(getenv('REMOTE_ADDR'))
$ipaddress = getenv('REMOTE_ADDR');
else
$ipaddress = 'UNKNOWN';
return $ipaddress;
}
function get_client_location() {
$PublicIP = get_client_ip();
$json = file_get_contents("http://ipinfo.io/$PublicIP/geo");
$json = json_decode($json, true);
$country = $json['country'];
return $country;
}

View file

@ -36,6 +36,9 @@
<li class="nav-item">
<a href="#tabs-webauthn" class="nav-link" data-bs-toggle="tab">WebAuthn</a>
</li>
<li class="nav-item">
<a href="#tabs-log" class="nav-link" data-bs-toggle="tab">Log</a>
</li>
</ul>
</div>
<div class="card-body">
@ -158,19 +161,55 @@
</tr>
</thead>
<tbody>
{% for device in weba %}
<tr>
<td>{{ device.user_agent }}</td>
<td>{{ device.created_at }}</td>
<td>
<a href="/path/to/action?deviceId={{ device.id }}">Edit</a>
</td>
</tr>
{% else %}
<tr>
<td colspan="3">No devices found.</td>
</tr>
{% endfor %}
{% for device in weba %}
<tr>
<td>{{ device.user_agent }}</td>
<td>{{ device.created_at }}</td>
<td>
<a href="/path/to/action?deviceId={{ device.id }}">Edit</a>
</td>
</tr>
{% else %}
<tr>
<td colspan="3">No devices found.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="tabs-log">
<h4 class="card-title">User Audit Log</h4>
<div class="card">
<div class="card-body">
<p>Track and review all user activities in your account below. Monitor logins, profile changes, and other key actions to ensure security and transparency.</p>
<div class="table-responsive mt-4">
<table class="table table-striped">
<thead>
<tr>
<th>Event</th>
<th>User Agent</th>
<th>IP</th>
<th>Location</th>
<th>Timestamp</th>
</tr>
</thead>
<tbody>
{% for user in userAudit %}
<tr>
<td>{{ user.user_event }}</td>
<td>{{ user.user_agent }}</td>
<td>{{ user.user_ip }}</td>
<td>{{ user.user_location }}</td>
<td>{{ user.event_time }}</td>
</tr>
{% else %}
<tr>
<td colspan="5">No log data for user.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

View file

@ -601,6 +601,20 @@ CREATE TABLE IF NOT EXISTS `registry`.`users` (
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Panel Users';
CREATE TABLE IF NOT EXISTS `registry`.`users_audit` (
`user_id` int(10) unsigned NOT NULL,
`user_event` VARCHAR(255) NOT NULL,
`user_resource` VARCHAR(255) default NULL,
`user_agent` VARCHAR(255) NOT NULL,
`user_ip` VARCHAR(45) NOT NULL,
`user_location` VARCHAR(45) default NULL,
`event_time` DATETIME(3) NOT NULL,
`user_data` JSON default NULL,
KEY `user_id` (`user_id`),
KEY `user_event` (`user_event`),
KEY `user_ip` (`user_ip`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Panel User Audit';
CREATE TABLE IF NOT EXISTS `registry`.`users_confirmations` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL,
@ -616,24 +630,24 @@ CREATE TABLE IF NOT EXISTS `registry`.`users_confirmations` (
CREATE TABLE IF NOT EXISTS `registry`.`users_remembered` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user` int(10) unsigned NOT NULL,
`user_id` int(10) unsigned NOT NULL,
`selector` varchar(24) CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
`token` varchar(255) CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
`expires` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `selector` (`selector`),
KEY `user` (`user`)
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Panel Users Remember';
CREATE TABLE IF NOT EXISTS `registry`.`users_resets` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user` int(10) unsigned NOT NULL,
`user_id` int(10) unsigned NOT NULL,
`selector` varchar(20) CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
`token` varchar(255) CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
`expires` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `selector` (`selector`),
KEY `user_expires` (`user`,`expires`)
KEY `user_expires` (`user_id`,`expires`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Panel Users Reset';
CREATE TABLE IF NOT EXISTS `registry`.`users_throttling` (

View file

@ -573,6 +573,20 @@ CREATE TABLE IF NOT EXISTS registry.users (
"backup_codes" TEXT,
);
CREATE TABLE IF NOT EXISTS registry.users_audit (
"user_id" SERIAL PRIMARY KEY CHECK ("id" >= 0),
"user_event" VARCHAR(255) NOT NULL,
"user_resource" VARCHAR(255) DEFAULT NULL,
"user_agent" VARCHAR(255) NOT NULL,
"user_ip" VARCHAR(45) NOT NULL,
"user_location" VARCHAR(45) DEFAULT NULL,
"event_time" TIMESTAMP(3) NOT NULL,
"user_data" JSONB DEFAULT NULL,
CONSTRAINT pk_users_audit PRIMARY KEY (user_id)
);
CREATE INDEX idx_user_event ON registry.users_audit (user_event);
CREATE INDEX idx_user_ip ON registry.users_audit (user_ip);
CREATE TABLE IF NOT EXISTS registry.users_confirmations (
"id" SERIAL PRIMARY KEY CHECK ("id" >= 0),
"user_id" INTEGER NOT NULL CHECK ("user_id" >= 0),
@ -586,21 +600,21 @@ CREATE INDEX IF NOT EXISTS "user_id" ON registry.users_confirmations ("user_id")
CREATE TABLE IF NOT EXISTS registry.users_remembered (
"id" BIGSERIAL PRIMARY KEY CHECK ("id" >= 0),
"user" INTEGER NOT NULL CHECK ("user" >= 0),
"user_id" INTEGER NOT NULL CHECK ("user_id" >= 0),
"selector" VARCHAR(24) UNIQUE NOT NULL,
"token" VARCHAR(255) NOT NULL,
"expires" INTEGER NOT NULL CHECK ("expires" >= 0)
);
CREATE INDEX IF NOT EXISTS "user" ON registry.users_remembered ("user");
CREATE INDEX IF NOT EXISTS "user_id" ON registry.users_remembered ("user_id");
CREATE TABLE IF NOT EXISTS registry.users_resets (
"id" BIGSERIAL PRIMARY KEY CHECK ("id" >= 0),
"user" INTEGER NOT NULL CHECK ("user" >= 0),
"user_id" INTEGER NOT NULL CHECK ("user_id" >= 0),
"selector" VARCHAR(20) UNIQUE NOT NULL,
"token" VARCHAR(255) NOT NULL,
"expires" INTEGER NOT NULL CHECK ("expires" >= 0)
);
CREATE INDEX IF NOT EXISTS "user_expires" ON registry.users_resets ("user", "expires");
CREATE INDEX IF NOT EXISTS "user_expires" ON registry.users_resets ("user_id", "expires");
CREATE TABLE IF NOT EXISTS registry.users_throttling (
"bucket" VARCHAR(44) PRIMARY KEY,