Compare commits
No commits in common. "main" and "v0.4" have entirely different histories.
|
@ -16,7 +16,7 @@ class Config {
|
||||||
* @return array Returns an array with 'success' and 'updated' keys on success, or 'success' and 'error' keys on failure.
|
* @return array Returns an array with 'success' and 'updated' keys on success, or 'success' and 'error' keys on failure.
|
||||||
*/
|
*/
|
||||||
public function editConfigFile($updatedConfig, $config_file) {
|
public function editConfigFile($updatedConfig, $config_file) {
|
||||||
global $logObject, $userId;
|
global $logObject, $user_id;
|
||||||
$allLogs = [];
|
$allLogs = [];
|
||||||
$updated = [];
|
$updated = [];
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ class Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($allLogs)) {
|
if (!empty($allLogs)) {
|
||||||
$logObject->insertLog($userId, implode("\n", $allLogs), 'system');
|
$logObject->insertLog($user_id, implode("\n", $allLogs), 'system');
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -148,7 +148,7 @@ class Config {
|
||||||
'updated' => $updated
|
'updated' => $updated
|
||||||
];
|
];
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$logObject->insertLog($userId, "Config update error: " . $e->getMessage(), 'system');
|
$logObject->insertLog($user_id, "Config update error: " . $e->getMessage(), 'system');
|
||||||
return [
|
return [
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error' => $e->getMessage()
|
'error' => $e->getMessage()
|
||||||
|
|
|
@ -35,10 +35,6 @@ class Feedback {
|
||||||
'type' => self::TYPE_SUCCESS,
|
'type' => self::TYPE_SUCCESS,
|
||||||
'dismissible' => true
|
'dismissible' => true
|
||||||
],
|
],
|
||||||
'SESSION_TIMEOUT' => [
|
|
||||||
'type' => self::TYPE_ERROR,
|
|
||||||
'dismissible' => true
|
|
||||||
],
|
|
||||||
'IP_BLACKLISTED' => [
|
'IP_BLACKLISTED' => [
|
||||||
'type' => self::TYPE_ERROR,
|
'type' => self::TYPE_ERROR,
|
||||||
'dismissible' => false
|
'dismissible' => false
|
||||||
|
|
|
@ -18,23 +18,19 @@ class Log {
|
||||||
* @param object $database The database object to initialize the connection.
|
* @param object $database The database object to initialize the connection.
|
||||||
*/
|
*/
|
||||||
public function __construct($database) {
|
public function __construct($database) {
|
||||||
if ($database instanceof PDO) {
|
$this->db = $database->getConnection();
|
||||||
$this->db = $database;
|
|
||||||
} else {
|
|
||||||
$this->db = $database->getConnection();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert a log event into the database.
|
* Insert a log event into the database.
|
||||||
*
|
*
|
||||||
* @param int $userId The ID of the user associated with the log event.
|
* @param int $user_id The ID of the user associated with the log event.
|
||||||
* @param string $message The log message to insert.
|
* @param string $message The log message to insert.
|
||||||
* @param string $scope The scope of the log event (e.g., 'user', 'system'). Default is 'user'.
|
* @param string $scope The scope of the log event (e.g., 'user', 'system'). Default is 'user'.
|
||||||
*
|
*
|
||||||
* @return bool|string True on success, or an error message on failure.
|
* @return bool|string True on success, or an error message on failure.
|
||||||
*/
|
*/
|
||||||
public function insertLog($userId, $message, $scope='user') {
|
public function insertLog($user_id, $message, $scope='user') {
|
||||||
try {
|
try {
|
||||||
$sql = 'INSERT INTO logs
|
$sql = 'INSERT INTO logs
|
||||||
(user_id, scope, message)
|
(user_id, scope, message)
|
||||||
|
@ -43,7 +39,7 @@ class Log {
|
||||||
|
|
||||||
$query = $this->db->prepare($sql);
|
$query = $this->db->prepare($sql);
|
||||||
$query->execute([
|
$query->execute([
|
||||||
':user_id' => $userId,
|
':user_id' => $user_id,
|
||||||
':scope' => $scope,
|
':scope' => $scope,
|
||||||
':message' => $message,
|
':message' => $message,
|
||||||
]);
|
]);
|
||||||
|
@ -58,7 +54,7 @@ class Log {
|
||||||
/**
|
/**
|
||||||
* Retrieve log entries from the database.
|
* Retrieve log entries from the database.
|
||||||
*
|
*
|
||||||
* @param int $userId The ID of the user whose logs are being retrieved.
|
* @param int $user_id The ID of the user whose logs are being retrieved.
|
||||||
* @param string $scope The scope of the logs ('user' or 'system').
|
* @param string $scope The scope of the logs ('user' or 'system').
|
||||||
* @param int $offset The offset for pagination. Default is 0.
|
* @param int $offset The offset for pagination. Default is 0.
|
||||||
* @param int $items_per_page The number of log entries to retrieve per page. Default is no limit.
|
* @param int $items_per_page The number of log entries to retrieve per page. Default is no limit.
|
||||||
|
@ -66,7 +62,7 @@ class Log {
|
||||||
*
|
*
|
||||||
* @return array An array of log entries.
|
* @return array An array of log entries.
|
||||||
*/
|
*/
|
||||||
public function readLog($userId, $scope, $offset=0, $items_per_page='', $filters=[]) {
|
public function readLog($user_id, $scope, $offset=0, $items_per_page='', $filters=[]) {
|
||||||
$params = [];
|
$params = [];
|
||||||
$where_clauses = [];
|
$where_clauses = [];
|
||||||
|
|
||||||
|
@ -78,7 +74,7 @@ class Log {
|
||||||
// Add scope condition
|
// Add scope condition
|
||||||
if ($scope === 'user') {
|
if ($scope === 'user') {
|
||||||
$where_clauses[] = 'l.user_id = :user_id';
|
$where_clauses[] = 'l.user_id = :user_id';
|
||||||
$params[':user_id'] = $userId;
|
$params[':user_id'] = $user_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add time range filters if specified
|
// Add time range filters if specified
|
||||||
|
|
|
@ -23,11 +23,7 @@ class RateLimiter {
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct($database) {
|
public function __construct($database) {
|
||||||
if ($database instanceof PDO) {
|
$this->db = $database->getConnection();
|
||||||
$this->db = $database;
|
|
||||||
} else {
|
|
||||||
$this->db = $database->getConnection();
|
|
||||||
}
|
|
||||||
$this->log = new Log($database);
|
$this->log = new Log($database);
|
||||||
$this->createTablesIfNotExist();
|
$this->createTablesIfNotExist();
|
||||||
}
|
}
|
||||||
|
@ -427,12 +423,7 @@ class RateLimiter {
|
||||||
return $result['total_attempts'] < $this->autoBlacklistThreshold;
|
return $result['total_attempts'] < $this->autoBlacklistThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function attempt($username, $ipAddress, $failed = true) {
|
public function attempt($username, $ipAddress) {
|
||||||
// Only record failed attempts
|
|
||||||
if (!$failed) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record this attempt
|
// Record this attempt
|
||||||
$sql = "INSERT INTO {$this->authRatelimitTable} (ip_address, username) VALUES (:ip, :username)";
|
$sql = "INSERT INTO {$this->authRatelimitTable} (ip_address, username) VALUES (:ip, :username)";
|
||||||
$stmt = $this->db->prepare($sql);
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
|
@ -1,179 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Session Class
|
|
||||||
*
|
|
||||||
* Core session management functionality for the application
|
|
||||||
*/
|
|
||||||
class Session {
|
|
||||||
private static $sessionOptions = [
|
|
||||||
'cookie_httponly' => 1,
|
|
||||||
'cookie_secure' => 1,
|
|
||||||
'cookie_samesite' => 'Strict',
|
|
||||||
'gc_maxlifetime' => 7200 // 2 hours
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start or resume a session with secure options
|
|
||||||
*/
|
|
||||||
public static function startSession() {
|
|
||||||
session_name('jilo');
|
|
||||||
if (session_status() !== PHP_SESSION_ACTIVE && !headers_sent()) {
|
|
||||||
session_start(self::$sessionOptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy current session and clean up
|
|
||||||
*/
|
|
||||||
public static function destroySession() {
|
|
||||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
|
||||||
session_unset();
|
|
||||||
session_destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current username if set
|
|
||||||
*/
|
|
||||||
public static function getUsername() {
|
|
||||||
return isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current user ID if set
|
|
||||||
*/
|
|
||||||
public static function getUserId() {
|
|
||||||
return isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if current session is valid
|
|
||||||
*/
|
|
||||||
public static function isValidSession() {
|
|
||||||
// Check required session variables
|
|
||||||
if (!isset($_SESSION['user_id']) || !isset($_SESSION['username'])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check session timeout
|
|
||||||
$session_timeout = isset($_SESSION['REMEMBER_ME']) ? (30 * 24 * 60 * 60) : 7200; // 30 days or 2 hours
|
|
||||||
if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $session_timeout)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update last activity time
|
|
||||||
$_SESSION['LAST_ACTIVITY'] = time();
|
|
||||||
|
|
||||||
// Regenerate session ID periodically (every 30 minutes)
|
|
||||||
if (!isset($_SESSION['CREATED'])) {
|
|
||||||
$_SESSION['CREATED'] = time();
|
|
||||||
} else if (time() - $_SESSION['CREATED'] > 1800) {
|
|
||||||
// Regenerate session ID and update creation time
|
|
||||||
if (!headers_sent() && session_status() === PHP_SESSION_ACTIVE) {
|
|
||||||
$oldData = $_SESSION;
|
|
||||||
session_regenerate_id(true);
|
|
||||||
$_SESSION = $oldData;
|
|
||||||
$_SESSION['CREATED'] = time();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set remember me option for extended session
|
|
||||||
*/
|
|
||||||
public static function setRememberMe($value = true) {
|
|
||||||
$_SESSION['REMEMBER_ME'] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear session data and cookies
|
|
||||||
*/
|
|
||||||
public static function cleanup($config) {
|
|
||||||
self::destroySession();
|
|
||||||
|
|
||||||
// Clear cookies if headers not sent
|
|
||||||
if (!headers_sent()) {
|
|
||||||
setcookie('username', '', [
|
|
||||||
'expires' => time() - 3600,
|
|
||||||
'path' => $config['folder'],
|
|
||||||
'domain' => $config['domain'],
|
|
||||||
'secure' => isset($_SERVER['HTTPS']),
|
|
||||||
'httponly' => true,
|
|
||||||
'samesite' => 'Strict'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start fresh session
|
|
||||||
self::startSession();
|
|
||||||
|
|
||||||
// Reset session timeout flag
|
|
||||||
unset($_SESSION['session_timeout_shown']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new authenticated session for a user
|
|
||||||
*/
|
|
||||||
public static function createAuthSession($userId, $username, $rememberMe, $config) {
|
|
||||||
// Set cookie lifetime based on remember me
|
|
||||||
$cookieLifetime = $rememberMe ? time() + (30 * 24 * 60 * 60) : 0;
|
|
||||||
|
|
||||||
// Regenerate session ID to prevent session fixation
|
|
||||||
session_regenerate_id(true);
|
|
||||||
|
|
||||||
// Set cookie with secure options
|
|
||||||
setcookie('username', $username, [
|
|
||||||
'expires' => $cookieLifetime,
|
|
||||||
'path' => $config['folder'],
|
|
||||||
'domain' => $config['domain'],
|
|
||||||
'secure' => isset($_SERVER['HTTPS']),
|
|
||||||
'httponly' => true,
|
|
||||||
'samesite' => 'Strict'
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Set session variables
|
|
||||||
$_SESSION['user_id'] = $userId;
|
|
||||||
$_SESSION['username'] = $username;
|
|
||||||
$_SESSION['LAST_ACTIVITY'] = time();
|
|
||||||
if ($rememberMe) {
|
|
||||||
self::setRememberMe(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store 2FA pending information in session
|
|
||||||
*/
|
|
||||||
public static function store2FAPending($userId, $username, $rememberMe = false) {
|
|
||||||
$_SESSION['2fa_pending_user_id'] = $userId;
|
|
||||||
$_SESSION['2fa_pending_username'] = $username;
|
|
||||||
if ($rememberMe) {
|
|
||||||
$_SESSION['2fa_pending_remember'] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear 2FA pending information from session
|
|
||||||
*/
|
|
||||||
public static function clear2FAPending() {
|
|
||||||
unset($_SESSION['2fa_pending_user_id']);
|
|
||||||
unset($_SESSION['2fa_pending_username']);
|
|
||||||
unset($_SESSION['2fa_pending_remember']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get 2FA pending information
|
|
||||||
*/
|
|
||||||
public static function get2FAPending() {
|
|
||||||
if (!isset($_SESSION['2fa_pending_user_id']) || !isset($_SESSION['2fa_pending_username'])) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'user_id' => $_SESSION['2fa_pending_user_id'],
|
|
||||||
'username' => $_SESSION['2fa_pending_username'],
|
|
||||||
'remember_me' => isset($_SESSION['2fa_pending_remember'])
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -33,12 +33,69 @@ class User {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new user.
|
||||||
|
*
|
||||||
|
* @param string $username The username of the new user.
|
||||||
|
* @param string $password The password for the new user.
|
||||||
|
*
|
||||||
|
* @return bool|string True if registration is successful, error message otherwise.
|
||||||
|
*/
|
||||||
|
public function register($username, $password) {
|
||||||
|
try {
|
||||||
|
// we have two inserts, start a transaction
|
||||||
|
$this->db->beginTransaction();
|
||||||
|
|
||||||
|
// hash the password, don't store it plain
|
||||||
|
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
// insert into users table
|
||||||
|
$sql = 'INSERT
|
||||||
|
INTO users (username, password)
|
||||||
|
VALUES (:username, :password)';
|
||||||
|
$query = $this->db->prepare($sql);
|
||||||
|
$query->bindValue(':username', $username);
|
||||||
|
$query->bindValue(':password', $hashedPassword);
|
||||||
|
|
||||||
|
// execute the first query
|
||||||
|
if (!$query->execute()) {
|
||||||
|
// rollback on error
|
||||||
|
$this->db->rollBack();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert the last user id into users_meta table
|
||||||
|
$sql2 = 'INSERT
|
||||||
|
INTO users_meta (user_id)
|
||||||
|
VALUES (:user_id)';
|
||||||
|
$query2 = $this->db->prepare($sql2);
|
||||||
|
$query2->bindValue(':user_id', $this->db->lastInsertId());
|
||||||
|
|
||||||
|
// execute the second query
|
||||||
|
if (!$query2->execute()) {
|
||||||
|
// rollback on error
|
||||||
|
$this->db->rollBack();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if all is OK, commit the transaction
|
||||||
|
$this->db->commit();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// rollback on any error
|
||||||
|
$this->db->rollBack();
|
||||||
|
return $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs in a user by verifying credentials.
|
* Logs in a user by verifying credentials.
|
||||||
*
|
*
|
||||||
* @param string $username The username of the user.
|
* @param string $username The username of the user.
|
||||||
* @param string $password The password of the user.
|
* @param string $password The password of the user.
|
||||||
* @param string $twoFactorCode Optional. The 2FA code if 2FA is enabled.
|
* @param string $twoFactorCode Optional. The 2FA code if 2FA is enabled.
|
||||||
*
|
*
|
||||||
* @return array Login result with status and any necessary data
|
* @return array Login result with status and any necessary data
|
||||||
*/
|
*/
|
||||||
|
@ -47,6 +104,9 @@ class User {
|
||||||
require_once __DIR__ . '/../helpers/logs.php';
|
require_once __DIR__ . '/../helpers/logs.php';
|
||||||
$ipAddress = getUserIP();
|
$ipAddress = getUserIP();
|
||||||
|
|
||||||
|
// Record attempt
|
||||||
|
$this->rateLimiter->attempt($username, $ipAddress);
|
||||||
|
|
||||||
// Check rate limiting first
|
// Check rate limiting first
|
||||||
if (!$this->rateLimiter->isAllowed($username, $ipAddress)) {
|
if (!$this->rateLimiter->isAllowed($username, $ipAddress)) {
|
||||||
$remainingTime = $this->rateLimiter->getDecayMinutes();
|
$remainingTime = $this->rateLimiter->getDecayMinutes();
|
||||||
|
@ -120,11 +180,11 @@ class User {
|
||||||
/**
|
/**
|
||||||
* Fetches user details by user ID.
|
* Fetches user details by user ID.
|
||||||
*
|
*
|
||||||
* @param int $userId The user ID.
|
* @param int $user_id The user ID.
|
||||||
*
|
*
|
||||||
* @return array|null User details or null if not found.
|
* @return array|null User details or null if not found.
|
||||||
*/
|
*/
|
||||||
public function getUserDetails($userId) {
|
public function getUserDetails($user_id) {
|
||||||
$sql = 'SELECT
|
$sql = 'SELECT
|
||||||
um.*,
|
um.*,
|
||||||
u.username
|
u.username
|
||||||
|
@ -137,7 +197,7 @@ class User {
|
||||||
|
|
||||||
$query = $this->db->prepare($sql);
|
$query = $this->db->prepare($sql);
|
||||||
$query->execute([
|
$query->execute([
|
||||||
':user_id' => $userId,
|
':user_id' => $user_id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $query->fetchAll(PDO::FETCH_ASSOC);
|
return $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
@ -148,19 +208,19 @@ class User {
|
||||||
/**
|
/**
|
||||||
* Grants a user a specific right.
|
* Grants a user a specific right.
|
||||||
*
|
*
|
||||||
* @param int $userId The user ID.
|
* @param int $user_id The user ID.
|
||||||
* @param int $right_id The right ID to grant.
|
* @param int $right_id The right ID to grant.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function addUserRight($userId, $right_id) {
|
public function addUserRight($user_id, $right_id) {
|
||||||
$sql = 'INSERT INTO users_rights
|
$sql = 'INSERT INTO users_rights
|
||||||
(user_id, right_id)
|
(user_id, right_id)
|
||||||
VALUES
|
VALUES
|
||||||
(:user_id, :right_id)';
|
(:user_id, :right_id)';
|
||||||
$query = $this->db->prepare($sql);
|
$query = $this->db->prepare($sql);
|
||||||
$query->execute([
|
$query->execute([
|
||||||
':user_id' => $userId,
|
':user_id' => $user_id,
|
||||||
':right_id' => $right_id,
|
':right_id' => $right_id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -169,12 +229,12 @@ class User {
|
||||||
/**
|
/**
|
||||||
* Revokes a specific right from a user.
|
* Revokes a specific right from a user.
|
||||||
*
|
*
|
||||||
* @param int $userId The user ID.
|
* @param int $user_id The user ID.
|
||||||
* @param int $right_id The right ID to revoke.
|
* @param int $right_id The right ID to revoke.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function removeUserRight($userId, $right_id) {
|
public function removeUserRight($user_id, $right_id) {
|
||||||
$sql = 'DELETE FROM users_rights
|
$sql = 'DELETE FROM users_rights
|
||||||
WHERE
|
WHERE
|
||||||
user_id = :user_id
|
user_id = :user_id
|
||||||
|
@ -182,7 +242,7 @@ class User {
|
||||||
right_id = :right_id';
|
right_id = :right_id';
|
||||||
$query = $this->db->prepare($sql);
|
$query = $this->db->prepare($sql);
|
||||||
$query->execute([
|
$query->execute([
|
||||||
':user_id' => $userId,
|
':user_id' => $user_id,
|
||||||
':right_id' => $right_id,
|
':right_id' => $right_id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -210,11 +270,11 @@ class User {
|
||||||
/**
|
/**
|
||||||
* Retrieves the rights assigned to a specific user.
|
* Retrieves the rights assigned to a specific user.
|
||||||
*
|
*
|
||||||
* @param int $userId The user ID.
|
* @param int $user_id The user ID.
|
||||||
*
|
*
|
||||||
* @return array List of user rights.
|
* @return array List of user rights.
|
||||||
*/
|
*/
|
||||||
public function getUserRights($userId) {
|
public function getUserRights($user_id) {
|
||||||
$sql = 'SELECT
|
$sql = 'SELECT
|
||||||
u.id AS user_id,
|
u.id AS user_id,
|
||||||
r.id AS right_id,
|
r.id AS right_id,
|
||||||
|
@ -230,7 +290,7 @@ class User {
|
||||||
|
|
||||||
$query = $this->db->prepare($sql);
|
$query = $this->db->prepare($sql);
|
||||||
$query->execute([
|
$query->execute([
|
||||||
':user_id' => $userId,
|
':user_id' => $user_id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$result = $query->fetchAll(PDO::FETCH_ASSOC);
|
$result = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
@ -239,7 +299,7 @@ class User {
|
||||||
$specialEntries = [];
|
$specialEntries = [];
|
||||||
|
|
||||||
// user 1 is always superuser
|
// user 1 is always superuser
|
||||||
if ($userId == 1) {
|
if ($user_id == 1) {
|
||||||
$specialEntries = [
|
$specialEntries = [
|
||||||
[
|
[
|
||||||
'user_id' => 1,
|
'user_id' => 1,
|
||||||
|
@ -249,7 +309,7 @@ class User {
|
||||||
];
|
];
|
||||||
|
|
||||||
// user 2 is always demo
|
// user 2 is always demo
|
||||||
} elseif ($userId == 2) {
|
} elseif ($user_id == 2) {
|
||||||
$specialEntries = [
|
$specialEntries = [
|
||||||
[
|
[
|
||||||
'user_id' => 2,
|
'user_id' => 2,
|
||||||
|
@ -273,17 +333,17 @@ class User {
|
||||||
/**
|
/**
|
||||||
* Check if the user has a specific right.
|
* Check if the user has a specific right.
|
||||||
*
|
*
|
||||||
* @param int $userId The user ID.
|
* @param int $user_id The user ID.
|
||||||
* @param string $right_name The human-readable name of the user right.
|
* @param string $right_name The human-readable name of the user right.
|
||||||
*
|
*
|
||||||
* @return bool True if the user has the right, false otherwise.
|
* @return bool True if the user has the right, false otherwise.
|
||||||
*/
|
*/
|
||||||
function hasRight($userId, $right_name) {
|
function hasRight($user_id, $right_name) {
|
||||||
$userRights = $this->getUserRights($userId);
|
$userRights = $this->getUserRights($user_id);
|
||||||
$userHasRight = false;
|
$userHasRight = false;
|
||||||
|
|
||||||
// superuser always has all the rights
|
// superuser always has all the rights
|
||||||
if ($userId === 1) {
|
if ($user_id === 1) {
|
||||||
$userHasRight = true;
|
$userHasRight = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,8 +362,8 @@ class User {
|
||||||
/**
|
/**
|
||||||
* Updates a user's metadata in the database.
|
* Updates a user's metadata in the database.
|
||||||
*
|
*
|
||||||
* @param int $userId The ID of the user to update.
|
* @param int $user_id The ID of the user to update.
|
||||||
* @param array $updatedUser An associative array containing updated user data:
|
* @param array $updatedUser An associative array containing updated user data:
|
||||||
* - 'name' (string): The updated name of the user.
|
* - 'name' (string): The updated name of the user.
|
||||||
* - 'email' (string): The updated email of the user.
|
* - 'email' (string): The updated email of the user.
|
||||||
* - 'timezone' (string): The updated timezone of the user.
|
* - 'timezone' (string): The updated timezone of the user.
|
||||||
|
@ -311,7 +371,7 @@ class User {
|
||||||
*
|
*
|
||||||
* @return bool|string Returns true if the update is successful, or an error message if an exception occurs.
|
* @return bool|string Returns true if the update is successful, or an error message if an exception occurs.
|
||||||
*/
|
*/
|
||||||
public function editUser($userId, $updatedUser) {
|
public function editUser($user_id, $updatedUser) {
|
||||||
try {
|
try {
|
||||||
$sql = 'UPDATE users_meta SET
|
$sql = 'UPDATE users_meta SET
|
||||||
name = :name,
|
name = :name,
|
||||||
|
@ -321,7 +381,7 @@ class User {
|
||||||
WHERE user_id = :user_id';
|
WHERE user_id = :user_id';
|
||||||
$query = $this->db->prepare($sql);
|
$query = $this->db->prepare($sql);
|
||||||
$query->execute([
|
$query->execute([
|
||||||
':user_id' => $userId,
|
':user_id' => $user_id,
|
||||||
':name' => $updatedUser['name'],
|
':name' => $updatedUser['name'],
|
||||||
':email' => $updatedUser['email'],
|
':email' => $updatedUser['email'],
|
||||||
':timezone' => $updatedUser['timezone'],
|
':timezone' => $updatedUser['timezone'],
|
||||||
|
@ -340,12 +400,12 @@ class User {
|
||||||
/**
|
/**
|
||||||
* Removes a user's avatar from the database and deletes the associated file.
|
* Removes a user's avatar from the database and deletes the associated file.
|
||||||
*
|
*
|
||||||
* @param int $userId The ID of the user whose avatar is being removed.
|
* @param int $user_id The ID of the user whose avatar is being removed.
|
||||||
* @param string $old_avatar Optional. The file path of the current avatar to delete. Default is an empty string.
|
* @param string $old_avatar Optional. The file path of the current avatar to delete. Default is an empty string.
|
||||||
*
|
*
|
||||||
* @return bool|string Returns true if the avatar is successfully removed, or an error message if an exception occurs.
|
* @return bool|string Returns true if the avatar is successfully removed, or an error message if an exception occurs.
|
||||||
*/
|
*/
|
||||||
public function removeAvatar($userId, $old_avatar = '') {
|
public function removeAvatar($user_id, $old_avatar = '') {
|
||||||
try {
|
try {
|
||||||
// remove from database
|
// remove from database
|
||||||
$sql = 'UPDATE users_meta SET
|
$sql = 'UPDATE users_meta SET
|
||||||
|
@ -353,7 +413,7 @@ class User {
|
||||||
WHERE user_id = :user_id';
|
WHERE user_id = :user_id';
|
||||||
$query = $this->db->prepare($sql);
|
$query = $this->db->prepare($sql);
|
||||||
$query->execute([
|
$query->execute([
|
||||||
':user_id' => $userId,
|
':user_id' => $user_id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// delete the old avatar file
|
// delete the old avatar file
|
||||||
|
@ -373,14 +433,14 @@ class User {
|
||||||
/**
|
/**
|
||||||
* Updates a user's avatar by uploading a new file and saving its path in the database.
|
* Updates a user's avatar by uploading a new file and saving its path in the database.
|
||||||
*
|
*
|
||||||
* @param int $userId The ID of the user whose avatar is being updated.
|
* @param int $user_id The ID of the user whose avatar is being updated.
|
||||||
* @param array $avatar_file The uploaded avatar file from the $_FILES array.
|
* @param array $avatar_file The uploaded avatar file from the $_FILES array.
|
||||||
* Should include 'tmp_name', 'name', 'error', etc.
|
* Should include 'tmp_name', 'name', 'error', etc.
|
||||||
* @param string $avatars_path The directory path where avatar files should be saved.
|
* @param string $avatars_path The directory path where avatar files should be saved.
|
||||||
*
|
*
|
||||||
* @return bool|string Returns true if the avatar is successfully updated, or an error message if an exception occurs.
|
* @return bool|string Returns true if the avatar is successfully updated, or an error message if an exception occurs.
|
||||||
*/
|
*/
|
||||||
public function changeAvatar($userId, $avatar_file, $avatars_path) {
|
public function changeAvatar($user_id, $avatar_file, $avatars_path) {
|
||||||
try {
|
try {
|
||||||
// check if the file was uploaded
|
// check if the file was uploaded
|
||||||
if (isset($avatar_file) && $avatar_file['error'] === UPLOAD_ERR_OK) {
|
if (isset($avatar_file) && $avatar_file['error'] === UPLOAD_ERR_OK) {
|
||||||
|
@ -403,7 +463,7 @@ class User {
|
||||||
$query = $this->db->prepare($sql);
|
$query = $this->db->prepare($sql);
|
||||||
$query->execute([
|
$query->execute([
|
||||||
':avatar' => $newFileName,
|
':avatar' => $newFileName,
|
||||||
':user_id' => $userId
|
':user_id' => $user_id
|
||||||
]);
|
]);
|
||||||
// all went OK
|
// all went OK
|
||||||
$_SESSION['notice'] .= 'Avatar updated successfully. ';
|
$_SESSION['notice'] .= 'Avatar updated successfully. ';
|
||||||
|
@ -445,9 +505,9 @@ class User {
|
||||||
/**
|
/**
|
||||||
* Enable two-factor authentication for a user
|
* Enable two-factor authentication for a user
|
||||||
*
|
*
|
||||||
* @param int $userId User ID
|
* @param int $userId User ID
|
||||||
* @param string $secret Secret key to use
|
* @param string $secret Secret key to use
|
||||||
* @param string $code Verification code to validate
|
* @param string $code Verification code to validate
|
||||||
* @return bool True if enabled successfully
|
* @return bool True if enabled successfully
|
||||||
*/
|
*/
|
||||||
public function enableTwoFactor($userId, $secret = null, $code = null) {
|
public function enableTwoFactor($userId, $secret = null, $code = null) {
|
||||||
|
@ -467,8 +527,8 @@ class User {
|
||||||
/**
|
/**
|
||||||
* Verify a two-factor authentication code
|
* Verify a two-factor authentication code
|
||||||
*
|
*
|
||||||
* @param int $userId User ID
|
* @param int $userId User ID
|
||||||
* @param string $code The verification code
|
* @param string $code The verification code
|
||||||
* @return bool True if verified
|
* @return bool True if verified
|
||||||
*/
|
*/
|
||||||
public function verifyTwoFactor($userId, $code) {
|
public function verifyTwoFactor($userId, $code) {
|
||||||
|
@ -488,9 +548,9 @@ class User {
|
||||||
/**
|
/**
|
||||||
* Change a user's password
|
* Change a user's password
|
||||||
*
|
*
|
||||||
* @param int $userId User ID
|
* @param int $userId User ID
|
||||||
* @param string $currentPassword Current password for verification
|
* @param string $currentPassword Current password for verification
|
||||||
* @param string $newPassword New password to set
|
* @param string $newPassword New password to set
|
||||||
* @return bool True if password was changed successfully
|
* @return bool True if password was changed successfully
|
||||||
*/
|
*/
|
||||||
public function changePassword($userId, $currentPassword, $newPassword) {
|
public function changePassword($userId, $currentPassword, $newPassword) {
|
||||||
|
|
|
@ -4,33 +4,94 @@
|
||||||
* Session Middleware
|
* Session Middleware
|
||||||
*
|
*
|
||||||
* Validates session status and handles session timeout.
|
* Validates session status and handles session timeout.
|
||||||
* If session is invalid, redirects to login page.
|
* This middleware should be included in all protected pages.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function applySessionMiddleware($config, $app_root, $isTest = false) {
|
function applySessionMiddleware($config, $app_root) {
|
||||||
// Start session if not already started
|
$isTest = defined('PHPUNIT_RUNNING');
|
||||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
|
||||||
Session::startSession();
|
// Access $_SESSION directly in test mode
|
||||||
|
if (!$isTest) {
|
||||||
|
// Start session if not already started
|
||||||
|
if (session_status() !== PHP_SESSION_ACTIVE && !headers_sent()) {
|
||||||
|
session_start([
|
||||||
|
'cookie_httponly' => 1,
|
||||||
|
'cookie_secure' => 1,
|
||||||
|
'cookie_samesite' => 'Strict',
|
||||||
|
'gc_maxlifetime' => 7200 // 2 hours
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check session validity
|
// Check if user is logged in with all required session variables
|
||||||
if (!Session::isValidSession()) {
|
if (!isset($_SESSION['user_id']) || !isset($_SESSION['username'])) {
|
||||||
// Only show session timeout message if there was an active session
|
cleanupSession($config, $app_root, $isTest);
|
||||||
// and we haven't shown it yet
|
|
||||||
if (isset($_SESSION['LAST_ACTIVITY']) && !isset($_SESSION['session_timeout_shown'])) {
|
|
||||||
Feedback::flash('LOGIN', 'SESSION_TIMEOUT');
|
|
||||||
$_SESSION['session_timeout_shown'] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session invalid, clean up and redirect
|
|
||||||
Session::cleanup($config);
|
|
||||||
|
|
||||||
if (!$isTest) {
|
|
||||||
header('Location: ' . $app_root . '?page=login');
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check session timeout
|
||||||
|
$session_timeout = isset($_SESSION['REMEMBER_ME']) ? (30 * 24 * 60 * 60) : 7200; // 30 days or 2 hours
|
||||||
|
if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $session_timeout)) {
|
||||||
|
// Session has expired
|
||||||
|
cleanupSession($config, $app_root, $isTest);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update last activity time
|
||||||
|
$_SESSION['LAST_ACTIVITY'] = time();
|
||||||
|
|
||||||
|
// Regenerate session ID periodically (every 30 minutes)
|
||||||
|
if (!isset($_SESSION['CREATED'])) {
|
||||||
|
$_SESSION['CREATED'] = time();
|
||||||
|
} else if (time() - $_SESSION['CREATED'] > 1800) {
|
||||||
|
// Regenerate session ID and update creation time
|
||||||
|
if (!$isTest && !headers_sent() && session_status() === PHP_SESSION_ACTIVE) {
|
||||||
|
$oldData = $_SESSION;
|
||||||
|
session_regenerate_id(true);
|
||||||
|
$_SESSION = $oldData;
|
||||||
|
$_SESSION['CREATED'] = time();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to clean up session data and redirect
|
||||||
|
*/
|
||||||
|
function cleanupSession($config, $app_root, $isTest) {
|
||||||
|
// Always clear session data
|
||||||
|
$_SESSION = array();
|
||||||
|
|
||||||
|
if (!$isTest) {
|
||||||
|
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||||
|
session_unset();
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
|
// Start a new session to prevent errors
|
||||||
|
if (!headers_sent()) {
|
||||||
|
session_start([
|
||||||
|
'cookie_httponly' => 1,
|
||||||
|
'cookie_secure' => 1,
|
||||||
|
'cookie_samesite' => 'Strict',
|
||||||
|
'gc_maxlifetime' => 7200
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear cookies
|
||||||
|
if (!headers_sent()) {
|
||||||
|
setcookie('username', '', [
|
||||||
|
'expires' => time() - 3600,
|
||||||
|
'path' => $config['folder'],
|
||||||
|
'domain' => $config['domain'],
|
||||||
|
'secure' => isset($_SERVER['HTTPS']),
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Strict'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: ' . $app_root . '?page=login&timeout=1');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ return [
|
||||||
'LOGIN_SUCCESS' => 'Login successful.',
|
'LOGIN_SUCCESS' => 'Login successful.',
|
||||||
'LOGIN_FAILED' => 'Login failed. Please check your credentials.',
|
'LOGIN_FAILED' => 'Login failed. Please check your credentials.',
|
||||||
'LOGOUT_SUCCESS' => 'Logout successful. You can log in again.',
|
'LOGOUT_SUCCESS' => 'Logout successful. You can log in again.',
|
||||||
'SESSION_TIMEOUT' => 'Your session has expired. Please log in again.',
|
|
||||||
'IP_BLACKLISTED' => 'Access denied. Your IP address is blacklisted.',
|
'IP_BLACKLISTED' => 'Access denied. Your IP address is blacklisted.',
|
||||||
'IP_NOT_WHITELISTED' => 'Access denied. Your IP address is not whitelisted.',
|
'IP_NOT_WHITELISTED' => 'Access denied. Your IP address is not whitelisted.',
|
||||||
'TOO_MANY_ATTEMPTS' => 'Too many login attempts. Please try again later.',
|
'TOO_MANY_ATTEMPTS' => 'Too many login attempts. Please try again later.',
|
||||||
|
|
|
@ -50,7 +50,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
|
||||||
// Apply rate limiting for adding new contacts
|
// Apply rate limiting for adding new contacts
|
||||||
require '../app/includes/rate_limit_middleware.php';
|
require '../app/includes/rate_limit_middleware.php';
|
||||||
checkRateLimit($dbWeb, 'contact', $userId);
|
checkRateLimit($dbWeb, 'contact', $user_id);
|
||||||
|
|
||||||
// Validate agent ID for POST operations
|
// Validate agent ID for POST operations
|
||||||
if ($agentId === false || $agentId === null) {
|
if ($agentId === false || $agentId === null) {
|
||||||
|
|
|
@ -51,8 +51,8 @@ if (!$isWritable) {
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
// Check if user has permission to edit config
|
// Check if user has permission to edit config
|
||||||
if (!$userObject->hasRight($userId, 'edit config file')) {
|
if (!$userObject->hasRight($user_id, 'edit config file')) {
|
||||||
$logObject->insertLog($userId, "Unauthorized: User \"$currentUser\" tried to edit config file. IP: $user_IP", 'system');
|
$logObject->insertLog($user_id, "Unauthorized: User \"$currentUser\" tried to edit config file. IP: $user_IP", 'system');
|
||||||
if ($isAjax) {
|
if ($isAjax) {
|
||||||
ApiResponse::error('Forbidden: You do not have permission to edit the config file', null, 403);
|
ApiResponse::error('Forbidden: You do not have permission to edit the config file', null, 403);
|
||||||
exit;
|
exit;
|
||||||
|
@ -64,7 +64,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
|
||||||
// Apply rate limiting
|
// Apply rate limiting
|
||||||
require '../app/includes/rate_limit_middleware.php';
|
require '../app/includes/rate_limit_middleware.php';
|
||||||
checkRateLimit($dbWeb, 'config', $userId);
|
checkRateLimit($dbWeb, 'config', $user_id);
|
||||||
|
|
||||||
// Ensure no output before this point
|
// Ensure no output before this point
|
||||||
ob_clean();
|
ob_clean();
|
||||||
|
@ -74,7 +74,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
// Get raw input
|
// Get raw input
|
||||||
$jsonData = file_get_contents('php://input');
|
$jsonData = file_get_contents('php://input');
|
||||||
if ($jsonData === false) {
|
if ($jsonData === false) {
|
||||||
$logObject->insertLog($userId, "Failed to read request data for config update", 'system');
|
$logObject->insertLog($user_id, "Failed to read request data for config update", 'system');
|
||||||
ApiResponse::error('Failed to read request data');
|
ApiResponse::error('Failed to read request data');
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
@ -115,10 +115,10 @@ if (!$isAjax) {
|
||||||
* Handles GET requests to display templates.
|
* Handles GET requests to display templates.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if ($userObject->hasRight($userId, 'view config file')) {
|
if ($userObject->hasRight($user_id, 'view config file')) {
|
||||||
include '../app/templates/config.php';
|
include '../app/templates/config.php';
|
||||||
} else {
|
} else {
|
||||||
$logObject->insertLog($userId, "Unauthorized: User \"$currentUser\" tried to access \"config\" page. IP: $user_IP", 'system');
|
$logObject->insertLog($user_id, "Unauthorized: User \"$currentUser\" tried to access \"config\" page. IP: $user_IP", 'system');
|
||||||
include '../app/templates/error-unauthorized.php';
|
include '../app/templates/error-unauthorized.php';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,17 @@
|
||||||
* - `password`: Change password
|
* - `password`: Change password
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Check if user is logged in
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header("Location: $app_root?page=login");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
// Initialize user object
|
// Initialize user object
|
||||||
$userObject = new User($dbWeb);
|
$userObject = new User($dbWeb);
|
||||||
|
|
||||||
// Get action and item from request
|
|
||||||
$action = $_REQUEST['action'] ?? '';
|
$action = $_REQUEST['action'] ?? '';
|
||||||
$item = $_REQUEST['item'] ?? '';
|
$item = $_REQUEST['item'] ?? '';
|
||||||
|
|
||||||
|
@ -33,7 +40,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
|
|
||||||
// Apply rate limiting
|
// Apply rate limiting
|
||||||
require_once '../app/includes/rate_limit_middleware.php';
|
require_once '../app/includes/rate_limit_middleware.php';
|
||||||
checkRateLimit($dbWeb, 'credentials', $userId);
|
checkRateLimit($dbWeb, 'credentials', $user_id);
|
||||||
|
|
||||||
switch ($item) {
|
switch ($item) {
|
||||||
case '2fa':
|
case '2fa':
|
||||||
|
@ -43,7 +50,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
$code = $_POST['code'] ?? '';
|
$code = $_POST['code'] ?? '';
|
||||||
$secret = $_POST['secret'] ?? '';
|
$secret = $_POST['secret'] ?? '';
|
||||||
|
|
||||||
if ($userObject->enableTwoFactor($userId, $secret, $code)) {
|
if ($userObject->enableTwoFactor($user_id, $secret, $code)) {
|
||||||
Feedback::flash('NOTICE', 'DEFAULT', 'Two-factor authentication has been enabled successfully.');
|
Feedback::flash('NOTICE', 'DEFAULT', 'Two-factor authentication has been enabled successfully.');
|
||||||
header("Location: $app_root?page=credentials");
|
header("Location: $app_root?page=credentials");
|
||||||
exit();
|
exit();
|
||||||
|
@ -60,7 +67,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
case 'verify':
|
case 'verify':
|
||||||
// This is a user-initiated verification
|
// This is a user-initiated verification
|
||||||
$code = $_POST['code'] ?? '';
|
$code = $_POST['code'] ?? '';
|
||||||
if ($userObject->verifyTwoFactor($userId, $code)) {
|
if ($userObject->verifyTwoFactor($user_id, $code)) {
|
||||||
$_SESSION['2fa_verified'] = true;
|
$_SESSION['2fa_verified'] = true;
|
||||||
header("Location: $app_root?page=dashboard");
|
header("Location: $app_root?page=dashboard");
|
||||||
exit();
|
exit();
|
||||||
|
@ -72,7 +79,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'disable':
|
case 'disable':
|
||||||
if ($userObject->disableTwoFactor($userId)) {
|
if ($userObject->disableTwoFactor($user_id)) {
|
||||||
Feedback::flash('NOTICE', 'DEFAULT', 'Two-factor authentication has been disabled.');
|
Feedback::flash('NOTICE', 'DEFAULT', 'Two-factor authentication has been disabled.');
|
||||||
} else {
|
} else {
|
||||||
Feedback::flash('ERROR', 'DEFAULT', 'Failed to disable two-factor authentication.');
|
Feedback::flash('ERROR', 'DEFAULT', 'Failed to disable two-factor authentication.');
|
||||||
|
@ -89,7 +96,8 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
$validator = new Validator($_POST);
|
$validator = new Validator($_POST);
|
||||||
$rules = [
|
$rules = [
|
||||||
'current_password' => [
|
'current_password' => [
|
||||||
'required' => true
|
'required' => true,
|
||||||
|
'min' => 8
|
||||||
],
|
],
|
||||||
'new_password' => [
|
'new_password' => [
|
||||||
'required' => true,
|
'required' => true,
|
||||||
|
@ -107,7 +115,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($userObject->changePassword($userId, $_POST['current_password'], $_POST['new_password'])) {
|
if ($userObject->changePassword($user_id, $_POST['current_password'], $_POST['new_password'])) {
|
||||||
Feedback::flash('NOTICE', 'DEFAULT', 'Password has been changed successfully.');
|
Feedback::flash('NOTICE', 'DEFAULT', 'Password has been changed successfully.');
|
||||||
} else {
|
} else {
|
||||||
Feedback::flash('ERROR', 'DEFAULT', 'Failed to change password. Please verify your current password.');
|
Feedback::flash('ERROR', 'DEFAULT', 'Failed to change password. Please verify your current password.');
|
||||||
|
@ -128,12 +136,12 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
$security->generateCsrfToken();
|
$security->generateCsrfToken();
|
||||||
|
|
||||||
// Get 2FA status for the template
|
// Get 2FA status for the template
|
||||||
$has2fa = $userObject->isTwoFactorEnabled($userId);
|
$has2fa = $userObject->isTwoFactorEnabled($user_id);
|
||||||
|
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
case 'setup':
|
case 'setup':
|
||||||
if (!$has2fa) {
|
if (!$has2fa) {
|
||||||
$result = $userObject->enableTwoFactor($userId);
|
$result = $userObject->enableTwoFactor($user_id);
|
||||||
if ($result['success']) {
|
if ($result['success']) {
|
||||||
$setupData = $result['data'];
|
$setupData = $result['data'];
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -33,23 +33,21 @@ try {
|
||||||
if ($action === 'verify' && isset($_SESSION['2fa_pending_user_id'])) {
|
if ($action === 'verify' && isset($_SESSION['2fa_pending_user_id'])) {
|
||||||
// Handle 2FA verification
|
// Handle 2FA verification
|
||||||
$code = $_POST['code'] ?? '';
|
$code = $_POST['code'] ?? '';
|
||||||
$pending2FA = Session::get2FAPending();
|
$userId = $_SESSION['2fa_pending_user_id'];
|
||||||
|
$username = $_SESSION['2fa_pending_username'];
|
||||||
if (!$pending2FA) {
|
$rememberMe = isset($_SESSION['2fa_pending_remember']);
|
||||||
header('Location: ' . htmlspecialchars($app_root) . '?page=login');
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once '../app/classes/twoFactorAuth.php';
|
require_once '../app/classes/twoFactorAuth.php';
|
||||||
$twoFactorAuth = new TwoFactorAuthentication($db);
|
$twoFactorAuth = new TwoFactorAuthentication($db);
|
||||||
|
|
||||||
if ($twoFactorAuth->verify($pending2FA['user_id'], $code)) {
|
if ($twoFactorAuth->verify($userId, $code)) {
|
||||||
// Complete login
|
// Complete login
|
||||||
handleSuccessfulLogin($pending2FA['user_id'], $pending2FA['username'],
|
handleSuccessfulLogin($userId, $username, $rememberMe, $config, $logObject, $user_IP);
|
||||||
$pending2FA['remember_me'], $config, $app_root, $logObject, $user_IP);
|
|
||||||
|
|
||||||
// Clean up 2FA session data
|
// Clean up 2FA session data
|
||||||
Session::clear2FAPending();
|
unset($_SESSION['2fa_pending_user_id']);
|
||||||
|
unset($_SESSION['2fa_pending_username']);
|
||||||
|
unset($_SESSION['2fa_pending_remember']);
|
||||||
|
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
@ -62,9 +60,6 @@ try {
|
||||||
// Get any new feedback messages
|
// Get any new feedback messages
|
||||||
include '../app/helpers/feedback.php';
|
include '../app/helpers/feedback.php';
|
||||||
|
|
||||||
// Make userId available to template
|
|
||||||
$userId = $pending2FA['user_id'];
|
|
||||||
|
|
||||||
// Load the 2FA verification template
|
// Load the 2FA verification template
|
||||||
include '../app/templates/credentials-2fa-verify.php';
|
include '../app/templates/credentials-2fa-verify.php';
|
||||||
exit();
|
exit();
|
||||||
|
@ -201,7 +196,8 @@ try {
|
||||||
],
|
],
|
||||||
'password' => [
|
'password' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'required' => true
|
'required' => true,
|
||||||
|
'min' => 5
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -224,6 +220,9 @@ try {
|
||||||
if ($rateLimiter->tooManyAttempts($username, $user_IP)) {
|
if ($rateLimiter->tooManyAttempts($username, $user_IP)) {
|
||||||
throw new Exception(Feedback::get('LOGIN', 'TOO_MANY_ATTEMPTS')['message']);
|
throw new Exception(Feedback::get('LOGIN', 'TOO_MANY_ATTEMPTS')['message']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record this attempt
|
||||||
|
$rateLimiter->attempt($username, $user_IP);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt login
|
// Attempt login
|
||||||
|
@ -233,8 +232,11 @@ try {
|
||||||
switch ($loginResult['status']) {
|
switch ($loginResult['status']) {
|
||||||
case 'requires_2fa':
|
case 'requires_2fa':
|
||||||
// Store pending 2FA info
|
// Store pending 2FA info
|
||||||
Session::store2FAPending($loginResult['user_id'], $loginResult['username'],
|
$_SESSION['2fa_pending_user_id'] = $loginResult['user_id'];
|
||||||
isset($formData['remember_me']));
|
$_SESSION['2fa_pending_username'] = $loginResult['username'];
|
||||||
|
if (isset($formData['remember_me'])) {
|
||||||
|
$_SESSION['2fa_pending_remember'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Redirect to 2FA verification
|
// Redirect to 2FA verification
|
||||||
header('Location: ?page=login&action=verify');
|
header('Location: ?page=login&action=verify');
|
||||||
|
@ -243,7 +245,7 @@ try {
|
||||||
case 'success':
|
case 'success':
|
||||||
// Complete login
|
// Complete login
|
||||||
handleSuccessfulLogin($loginResult['user_id'], $loginResult['username'],
|
handleSuccessfulLogin($loginResult['user_id'], $loginResult['username'],
|
||||||
isset($formData['remember_me']), $config, $app_root, $logObject, $user_IP);
|
isset($formData['remember_me']), $config, $logObject, $user_IP);
|
||||||
exit();
|
exit();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -256,9 +258,8 @@ try {
|
||||||
// Log the failed attempt
|
// Log the failed attempt
|
||||||
Feedback::flash('ERROR', 'DEFAULT', $e->getMessage());
|
Feedback::flash('ERROR', 'DEFAULT', $e->getMessage());
|
||||||
if (isset($username)) {
|
if (isset($username)) {
|
||||||
$userId = $userObject->getUserId($username)[0]['id'] ?? 0;
|
$user_id = $userObject->getUserId($username)[0]['id'] ?? 0;
|
||||||
$logObject->insertLog($userId, "Login: Failed login attempt for user \"$username\". IP: $user_IP. Reason: {$e->getMessage()}", 'user');
|
$logObject->insertLog($user_id, "Login: Failed login attempt for user \"$username\". IP: $user_IP. Reason: {$e->getMessage()}", 'user');
|
||||||
$rateLimiter->attempt($username, $user_IP);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -268,7 +269,7 @@ try {
|
||||||
|
|
||||||
// Show configured login message if any
|
// Show configured login message if any
|
||||||
if (!empty($config['login_message'])) {
|
if (!empty($config['login_message'])) {
|
||||||
echo Feedback::render('NOTICE', 'DEFAULT', $config['login_message'], false, false, false);
|
echo Feedback::render('NOTICE', 'DEFAULT', $config['login_message'], false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get any new feedback messages
|
// Get any new feedback messages
|
||||||
|
@ -280,9 +281,37 @@ include '../app/templates/form-login.php';
|
||||||
/**
|
/**
|
||||||
* Handle successful login by setting up session and cookies
|
* Handle successful login by setting up session and cookies
|
||||||
*/
|
*/
|
||||||
function handleSuccessfulLogin($userId, $username, $rememberMe, $config, $app_root, $logObject, $userIP) {
|
function handleSuccessfulLogin($userId, $username, $rememberMe, $config, $logObject, $userIP) {
|
||||||
// Create authenticated session
|
if ($rememberMe) {
|
||||||
Session::createAuthSession($userId, $username, $rememberMe, $config);
|
// 30*24*60*60 = 30 days
|
||||||
|
$cookie_lifetime = 30 * 24 * 60 * 60;
|
||||||
|
$setcookie_lifetime = time() + 30 * 24 * 60 * 60;
|
||||||
|
} else {
|
||||||
|
// 0 - session end on browser close
|
||||||
|
$cookie_lifetime = 0;
|
||||||
|
$setcookie_lifetime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regenerate session ID to prevent session fixation
|
||||||
|
session_regenerate_id(true);
|
||||||
|
|
||||||
|
// set session lifetime and cookies
|
||||||
|
setcookie('username', $username, [
|
||||||
|
'expires' => $setcookie_lifetime,
|
||||||
|
'path' => $config['folder'],
|
||||||
|
'domain' => $config['domain'],
|
||||||
|
'secure' => isset($_SERVER['HTTPS']),
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Strict'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Set session variables
|
||||||
|
$_SESSION['user_id'] = $userId;
|
||||||
|
$_SESSION['USERNAME'] = $username;
|
||||||
|
$_SESSION['LAST_ACTIVITY'] = time();
|
||||||
|
if ($rememberMe) {
|
||||||
|
$_SESSION['REMEMBER_ME'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Log successful login
|
// Log successful login
|
||||||
$logObject->insertLog($userId, "Login: User \"$username\" logged in. IP: $userIP", 'user');
|
$logObject->insertLog($userId, "Login: User \"$username\" logged in. IP: $userIP", 'user');
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
include '../app/helpers/feedback.php';
|
include '../app/helpers/feedback.php';
|
||||||
|
|
||||||
// Check for rights; user or system
|
// Check for rights; user or system
|
||||||
$has_system_access = ($userObject->hasRight($userId, 'superuser') ||
|
$has_system_access = ($userObject->hasRight($user_id, 'superuser') ||
|
||||||
$userObject->hasRight($userId, 'view app logs'));
|
$userObject->hasRight($user_id, 'view app logs'));
|
||||||
|
|
||||||
// Get current page for pagination
|
// Get current page for pagination
|
||||||
$currentPage = $_REQUEST['page_num'] ?? 1;
|
$currentPage = $_REQUEST['page_num'] ?? 1;
|
||||||
|
@ -69,8 +69,8 @@ if (isset($_REQUEST['tab'])) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare the result
|
// prepare the result
|
||||||
$search = $logObject->readLog($userId, $scope, $offset, $items_per_page, $filters);
|
$search = $logObject->readLog($user_id, $scope, $offset, $items_per_page, $filters);
|
||||||
$search_all = $logObject->readLog($userId, $scope, 0, 0, $filters);
|
$search_all = $logObject->readLog($user_id, $scope, 0, 0, $filters);
|
||||||
|
|
||||||
if (!empty($search)) {
|
if (!empty($search)) {
|
||||||
// we get total items and number of pages
|
// we get total items and number of pages
|
||||||
|
@ -103,7 +103,7 @@ if (!empty($search)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$username = $userObject->getUserDetails($userId)[0]['username'];
|
$username = $userObject->getUserDetails($user_id)[0]['username'];
|
||||||
|
|
||||||
// Load the template
|
// Load the template
|
||||||
include '../app/templates/logs.php';
|
include '../app/templates/logs.php';
|
||||||
|
|
|
@ -30,11 +30,11 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
|
|
||||||
// Apply rate limiting for profile operations
|
// Apply rate limiting for profile operations
|
||||||
require_once '../app/includes/rate_limit_middleware.php';
|
require_once '../app/includes/rate_limit_middleware.php';
|
||||||
checkRateLimit($dbWeb, 'profile', $userId);
|
checkRateLimit($dbWeb, 'profile', $user_id);
|
||||||
|
|
||||||
// avatar removal
|
// avatar removal
|
||||||
if ($item === 'avatar' && $action === 'remove') {
|
if ($item === 'avatar' && $action === 'remove') {
|
||||||
$validator = new Validator(['user_id' => $userId]);
|
$validator = new Validator(['user_id' => $user_id]);
|
||||||
$rules = [
|
$rules = [
|
||||||
'user_id' => [
|
'user_id' => [
|
||||||
'required' => true,
|
'required' => true,
|
||||||
|
@ -48,7 +48,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $userObject->removeAvatar($userId, $config['avatars_path'].$userDetails[0]['avatar']);
|
$result = $userObject->removeAvatar($user_id, $config['avatars_path'].$userDetails[0]['avatar']);
|
||||||
if ($result === true) {
|
if ($result === true) {
|
||||||
Feedback::flash('NOTICE', 'DEFAULT', "Avatar for user \"{$userDetails[0]['username']}\" is removed.");
|
Feedback::flash('NOTICE', 'DEFAULT', "Avatar for user \"{$userDetails[0]['username']}\" is removed.");
|
||||||
} else {
|
} else {
|
||||||
|
@ -89,54 +89,50 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
'timezone' => htmlspecialchars($_POST['timezone'] ?? ''),
|
'timezone' => htmlspecialchars($_POST['timezone'] ?? ''),
|
||||||
'bio' => htmlspecialchars($_POST['bio'] ?? ''),
|
'bio' => htmlspecialchars($_POST['bio'] ?? ''),
|
||||||
];
|
];
|
||||||
$result = $userObject->editUser($userId, $updatedUser);
|
$result = $userObject->editUser($user_id, $updatedUser);
|
||||||
if ($result === true) {
|
if ($result === true) {
|
||||||
Feedback::flash('NOTICE', 'DEFAULT', "User details for \"{$userDetails[0]['username']}\" are edited.");
|
Feedback::flash('NOTICE', 'DEFAULT', "User details for \"{$updatedUser['name']}\" are edited.");
|
||||||
} else {
|
} else {
|
||||||
Feedback::flash('ERROR', 'DEFAULT', "Editing the user details failed. Error: $result");
|
Feedback::flash('ERROR', 'DEFAULT', "Editing the user details failed. Error: $result");
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the rights
|
// update the rights
|
||||||
// Get current rights IDs
|
if (isset($_POST['rights'])) {
|
||||||
$userRightsIds = array_column($userRights, 'right_id');
|
$validator = new Validator(['rights' => $_POST['rights']]);
|
||||||
|
$rules = [
|
||||||
|
'rights' => [
|
||||||
|
'array' => true
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
// If no rights are selected, remove all rights
|
if (!$validator->validate($rules)) {
|
||||||
if (!isset($_POST['rights'])) {
|
Feedback::flash('ERROR', 'DEFAULT', $validator->getFirstError());
|
||||||
$_POST['rights'] = [];
|
header("Location: $app_root?page=profile");
|
||||||
}
|
exit();
|
||||||
|
|
||||||
$validator = new Validator(['rights' => $_POST['rights']]);
|
|
||||||
$rules = [
|
|
||||||
'rights' => [
|
|
||||||
'array' => true
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!$validator->validate($rules)) {
|
|
||||||
Feedback::flash('ERROR', 'DEFAULT', $validator->getFirstError());
|
|
||||||
header("Location: $app_root?page=profile");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
$newRights = $_POST['rights'];
|
|
||||||
// what rights we need to add
|
|
||||||
$rightsToAdd = array_diff($newRights, $userRightsIds);
|
|
||||||
if (!empty($rightsToAdd)) {
|
|
||||||
foreach ($rightsToAdd as $rightId) {
|
|
||||||
$userObject->addUserRight($userId, $rightId);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// what rights we need to remove
|
$newRights = $_POST['rights'];
|
||||||
$rightsToRemove = array_diff($userRightsIds, $newRights);
|
// extract the new right_ids
|
||||||
if (!empty($rightsToRemove)) {
|
$userRightsIds = array_column($userRights, 'right_id');
|
||||||
foreach ($rightsToRemove as $rightId) {
|
// what rights we need to add
|
||||||
$userObject->removeUserRight($userId, $rightId);
|
$rightsToAdd = array_diff($newRights, $userRightsIds);
|
||||||
|
if (!empty($rightsToAdd)) {
|
||||||
|
foreach ($rightsToAdd as $rightId) {
|
||||||
|
$userObject->addUserRight($user_id, $rightId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// what rights we need to remove
|
||||||
|
$rightsToRemove = array_diff($userRightsIds, $newRights);
|
||||||
|
if (!empty($rightsToRemove)) {
|
||||||
|
foreach ($rightsToRemove as $rightId) {
|
||||||
|
$userObject->removeUserRight($user_id, $rightId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the avatar
|
// update the avatar
|
||||||
if (!empty($_FILES['avatar_file']['tmp_name'])) {
|
if (!empty($_FILES['avatar_file']['tmp_name'])) {
|
||||||
$result = $userObject->changeAvatar($userId, $_FILES['avatar_file'], $config['avatars_path']);
|
$result = $userObject->changeAvatar($user_id, $_FILES['avatar_file'], $config['avatars_path']);
|
||||||
}
|
}
|
||||||
|
|
||||||
header("Location: $app_root?page=profile");
|
header("Location: $app_root?page=profile");
|
||||||
|
|
|
@ -8,15 +8,6 @@
|
||||||
* and redirects to the login page on success or displays an error message on failure.
|
* and redirects to the login page on success or displays an error message on failure.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Define plugin base path if not already defined
|
|
||||||
if (!defined('PLUGIN_REGISTER_PATH')) {
|
|
||||||
define('PLUGIN_REGISTER_PATH', dirname(__FILE__, 2) . '/');
|
|
||||||
}
|
|
||||||
require_once PLUGIN_REGISTER_PATH . 'models/register.php';
|
|
||||||
require_once dirname(__FILE__, 4) . '/app/classes/user.php';
|
|
||||||
require_once dirname(__FILE__, 4) . '/app/classes/validator.php';
|
|
||||||
require_once dirname(__FILE__, 4) . '/app/helpers/security.php';
|
|
||||||
|
|
||||||
// registration is allowed, go on
|
// registration is allowed, go on
|
||||||
if ($config['registration_enabled'] == true) {
|
if ($config['registration_enabled'] == true) {
|
||||||
|
|
||||||
|
@ -26,13 +17,15 @@ if ($config['registration_enabled'] == true) {
|
||||||
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
|
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
|
||||||
|
|
||||||
// Apply rate limiting
|
// Apply rate limiting
|
||||||
require_once dirname(__FILE__, 4) . '/app/includes/rate_limit_middleware.php';
|
require '../app/includes/rate_limit_middleware.php';
|
||||||
checkRateLimit($dbWeb, 'register');
|
checkRateLimit($dbWeb, 'register');
|
||||||
|
|
||||||
|
require_once '../app/classes/validator.php';
|
||||||
|
require_once '../app/helpers/security.php';
|
||||||
$security = SecurityHelper::getInstance();
|
$security = SecurityHelper::getInstance();
|
||||||
|
|
||||||
// Sanitize input
|
// Sanitize input
|
||||||
$formData = $security->sanitizeArray($_POST, ['username', 'password', 'confirm_password', 'csrf_token', 'terms']);
|
$formData = $security->sanitizeArray($_POST, ['username', 'password', 'confirm_password', 'csrf_token']);
|
||||||
|
|
||||||
// Validate CSRF token
|
// Validate CSRF token
|
||||||
if (!$security->verifyCsrfToken($formData['csrf_token'] ?? '')) {
|
if (!$security->verifyCsrfToken($formData['csrf_token'] ?? '')) {
|
||||||
|
@ -54,10 +47,6 @@ if ($config['registration_enabled'] == true) {
|
||||||
'confirm_password' => [
|
'confirm_password' => [
|
||||||
'required' => true,
|
'required' => true,
|
||||||
'matches' => 'password'
|
'matches' => 'password'
|
||||||
],
|
|
||||||
'terms' => [
|
|
||||||
'required' => true,
|
|
||||||
'equals' => 'on'
|
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -67,42 +56,41 @@ if ($config['registration_enabled'] == true) {
|
||||||
$password = $formData['password'];
|
$password = $formData['password'];
|
||||||
|
|
||||||
// registering
|
// registering
|
||||||
$register = new Register($dbWeb);
|
$result = $userObject->register($username, $password);
|
||||||
$result = $register->register($username, $password);
|
|
||||||
|
|
||||||
// redirect to login
|
// redirect to login
|
||||||
if ($result === true) {
|
if ($result === true) {
|
||||||
// Get the new user's ID for logging
|
// Get the new user's ID for logging
|
||||||
$userId = $userObject->getUserId($username)[0]['id'];
|
$user_id = $userObject->getUserId($username)[0]['id'];
|
||||||
$logObject->insertLog($userId, "Registration: New user \"$username\" registered successfully. IP: $user_IP", 'user');
|
$logObject->insertLog($user_id, "Registration: New user \"$username\" registered successfully. IP: $user_IP", 'user');
|
||||||
Feedback::flash('NOTICE', 'DEFAULT', "Registration successful. You can log in now.");
|
Feedback::flash('NOTICE', 'DEFAULT', "Registration successful. You can log in now.");
|
||||||
header('Location: ' . htmlspecialchars($app_root . '?page=login'));
|
header('Location: ' . htmlspecialchars($app_root));
|
||||||
exit();
|
exit();
|
||||||
// registration fail, redirect to login
|
// registration fail, redirect to login
|
||||||
} else {
|
} else {
|
||||||
$logObject->insertLog(null, "Registration: Failed registration attempt for user \"$username\". IP: $user_IP. Reason: $result", 'system');
|
$logObject->insertLog(0, "Registration: Failed registration attempt for user \"$username\". IP: $user_IP. Reason: $result", 'system');
|
||||||
Feedback::flash('ERROR', 'DEFAULT', "Registration failed. $result");
|
Feedback::flash('ERROR', 'DEFAULT', "Registration failed. $result");
|
||||||
header('Location: ' . htmlspecialchars($app_root . '?page=register'));
|
header('Location: ' . htmlspecialchars($app_root));
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$error = $validator->getFirstError();
|
$error = $validator->getFirstError();
|
||||||
$logObject->insertLog(null, "Registration: Failed validation for user \"" . ($username ?? 'unknown') . "\". IP: $user_IP. Reason: $error", 'system');
|
$logObject->insertLog(0, "Registration: Failed validation for user \"" . ($username ?? 'unknown') . "\". IP: $user_IP. Reason: $error", 'system');
|
||||||
Feedback::flash('ERROR', 'DEFAULT', $error);
|
Feedback::flash('ERROR', 'DEFAULT', $error);
|
||||||
header('Location: ' . htmlspecialchars($app_root . '?page=register'));
|
header('Location: ' . htmlspecialchars($app_root . '?page=register'));
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$logObject->insertLog(null, "Registration: System error. IP: $user_IP. Error: " . $e->getMessage(), 'system');
|
$logObject->insertLog(0, "Registration: System error. IP: $user_IP. Error: " . $e->getMessage(), 'system');
|
||||||
Feedback::flash('ERROR', 'DEFAULT', $e->getMessage());
|
Feedback::flash('ERROR', 'DEFAULT', $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get any new feedback messages
|
// Get any new feedback messages
|
||||||
include dirname(__FILE__, 4) . '/app/helpers/feedback.php';
|
include '../app/helpers/feedback.php';
|
||||||
|
|
||||||
// Load the template
|
// Load the template
|
||||||
include PLUGIN_REGISTER_PATH . 'views/form-register.php';
|
include '../app/templates/form-register.php';
|
||||||
|
|
||||||
// registration disabled
|
// registration disabled
|
||||||
} else {
|
} else {
|
|
@ -1,10 +1,15 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
// Check if user has any of the required rights
|
// Check if user has any of the required rights
|
||||||
if (!($userObject->hasRight($userId, 'superuser') ||
|
if (!($userObject->hasRight($user_id, 'superuser') ||
|
||||||
$userObject->hasRight($userId, 'edit whitelist') ||
|
$userObject->hasRight($user_id, 'edit whitelist') ||
|
||||||
$userObject->hasRight($userId, 'edit blacklist') ||
|
$userObject->hasRight($user_id, 'edit blacklist') ||
|
||||||
$userObject->hasRight($userId, 'edit ratelimiting'))) {
|
$userObject->hasRight($user_id, 'edit ratelimiting'))) {
|
||||||
|
include '../app/templates/error-unauthorized.php';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($currentUser)) {
|
||||||
include '../app/templates/error-unauthorized.php';
|
include '../app/templates/error-unauthorized.php';
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +27,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
|
|
||||||
// Apply rate limiting for security operations
|
// Apply rate limiting for security operations
|
||||||
require_once '../app/includes/rate_limit_middleware.php';
|
require_once '../app/includes/rate_limit_middleware.php';
|
||||||
checkRateLimit($dbWeb, 'security', $userId);
|
checkRateLimit($dbWeb, 'security', $user_id);
|
||||||
|
|
||||||
$action = $_POST['action'];
|
$action = $_POST['action'];
|
||||||
$validator = new Validator($_POST);
|
$validator = new Validator($_POST);
|
||||||
|
@ -30,7 +35,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
try {
|
try {
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
case 'add_whitelist':
|
case 'add_whitelist':
|
||||||
if (!$userObject->hasRight($userId, 'superuser') && !$userObject->hasRight($userId, 'edit whitelist')) {
|
if (!$userObject->hasRight($user_id, 'superuser') && !$userObject->hasRight($user_id, 'edit whitelist')) {
|
||||||
Feedback::flash('SECURITY', 'PERMISSION_DENIED');
|
Feedback::flash('SECURITY', 'PERMISSION_DENIED');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -49,7 +54,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
|
|
||||||
if ($validator->validate($rules)) {
|
if ($validator->validate($rules)) {
|
||||||
$is_network = isset($_POST['is_network']) && $_POST['is_network'] === 'on';
|
$is_network = isset($_POST['is_network']) && $_POST['is_network'] === 'on';
|
||||||
if (!$rateLimiter->addToWhitelist($_POST['ip_address'], $is_network, $_POST['description'] ?? '', $currentUser, $userId)) {
|
if (!$rateLimiter->addToWhitelist($_POST['ip_address'], $is_network, $_POST['description'] ?? '', $currentUser, $user_id)) {
|
||||||
Feedback::flash('SECURITY', 'WHITELIST_ADD_FAILED');
|
Feedback::flash('SECURITY', 'WHITELIST_ADD_FAILED');
|
||||||
} else {
|
} else {
|
||||||
Feedback::flash('SECURITY', 'WHITELIST_ADD_SUCCESS');
|
Feedback::flash('SECURITY', 'WHITELIST_ADD_SUCCESS');
|
||||||
|
@ -60,7 +65,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'remove_whitelist':
|
case 'remove_whitelist':
|
||||||
if (!$userObject->hasRight($userId, 'superuser') && !$userObject->hasRight($userId, 'edit whitelist')) {
|
if (!$userObject->hasRight($user_id, 'superuser') && !$userObject->hasRight($user_id, 'edit whitelist')) {
|
||||||
Feedback::flash('SECURITY', 'PERMISSION_DENIED');
|
Feedback::flash('SECURITY', 'PERMISSION_DENIED');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -74,7 +79,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($validator->validate($rules)) {
|
if ($validator->validate($rules)) {
|
||||||
if (!$rateLimiter->removeFromWhitelist($_POST['ip_address'], $currentUser, $userId)) {
|
if (!$rateLimiter->removeFromWhitelist($_POST['ip_address'], $currentUser, $user_id)) {
|
||||||
Feedback::flash('SECURITY', 'WHITELIST_REMOVE_FAILED');
|
Feedback::flash('SECURITY', 'WHITELIST_REMOVE_FAILED');
|
||||||
} else {
|
} else {
|
||||||
Feedback::flash('SECURITY', 'WHITELIST_REMOVE_SUCCESS');
|
Feedback::flash('SECURITY', 'WHITELIST_REMOVE_SUCCESS');
|
||||||
|
@ -85,7 +90,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'add_blacklist':
|
case 'add_blacklist':
|
||||||
if (!$userObject->hasRight($userId, 'superuser') && !$userObject->hasRight($userId, 'edit blacklist')) {
|
if (!$userObject->hasRight($user_id, 'superuser') && !$userObject->hasRight($user_id, 'edit blacklist')) {
|
||||||
Feedback::flash('SECURITY', 'PERMISSION_DENIED');
|
Feedback::flash('SECURITY', 'PERMISSION_DENIED');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -111,7 +116,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
$is_network = isset($_POST['is_network']) && $_POST['is_network'] === 'on';
|
$is_network = isset($_POST['is_network']) && $_POST['is_network'] === 'on';
|
||||||
$expiry_hours = !empty($_POST['expiry_hours']) ? (int)$_POST['expiry_hours'] : null;
|
$expiry_hours = !empty($_POST['expiry_hours']) ? (int)$_POST['expiry_hours'] : null;
|
||||||
|
|
||||||
if (!$rateLimiter->addToBlacklist($_POST['ip_address'], $is_network, $_POST['reason'], $currentUser, $userId, $expiry_hours)) {
|
if (!$rateLimiter->addToBlacklist($_POST['ip_address'], $is_network, $_POST['reason'], $currentUser, $user_id, $expiry_hours)) {
|
||||||
Feedback::flash('SECURITY', 'BLACKLIST_ADD_FAILED');
|
Feedback::flash('SECURITY', 'BLACKLIST_ADD_FAILED');
|
||||||
} else {
|
} else {
|
||||||
Feedback::flash('SECURITY', 'BLACKLIST_ADD_SUCCESS');
|
Feedback::flash('SECURITY', 'BLACKLIST_ADD_SUCCESS');
|
||||||
|
@ -122,7 +127,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'remove_blacklist':
|
case 'remove_blacklist':
|
||||||
if (!$userObject->hasRight($userId, 'superuser') && !$userObject->hasRight($userId, 'edit blacklist')) {
|
if (!$userObject->hasRight($user_id, 'superuser') && !$userObject->hasRight($user_id, 'edit blacklist')) {
|
||||||
Feedback::flash('SECURITY', 'PERMISSION_DENIED');
|
Feedback::flash('SECURITY', 'PERMISSION_DENIED');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -136,7 +141,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($validator->validate($rules)) {
|
if ($validator->validate($rules)) {
|
||||||
if (!$rateLimiter->removeFromBlacklist($_POST['ip_address'], $currentUser, $userId)) {
|
if (!$rateLimiter->removeFromBlacklist($_POST['ip_address'], $currentUser, $user_id)) {
|
||||||
Feedback::flash('SECURITY', 'BLACKLIST_REMOVE_FAILED');
|
Feedback::flash('SECURITY', 'BLACKLIST_REMOVE_FAILED');
|
||||||
} else {
|
} else {
|
||||||
Feedback::flash('SECURITY', 'BLACKLIST_REMOVE_SUCCESS');
|
Feedback::flash('SECURITY', 'BLACKLIST_REMOVE_SUCCESS');
|
||||||
|
|
|
@ -31,7 +31,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
|
|
||||||
// Apply rate limiting for profile operations
|
// Apply rate limiting for profile operations
|
||||||
require_once '../app/includes/rate_limit_middleware.php';
|
require_once '../app/includes/rate_limit_middleware.php';
|
||||||
checkRateLimit($dbWeb, 'profile', $userId);
|
checkRateLimit($dbWeb, 'profile', $user_id);
|
||||||
|
|
||||||
// Get hash from URL if present
|
// Get hash from URL if present
|
||||||
$hash = parse_url($_SERVER['REQUEST_URI'], PHP_URL_FRAGMENT) ?? '';
|
$hash = parse_url($_SERVER['REQUEST_URI'], PHP_URL_FRAGMENT) ?? '';
|
||||||
|
@ -170,7 +170,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
* Handles GET requests to display templates.
|
* Handles GET requests to display templates.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if ($userObject->hasRight($userId, 'view settings')) {
|
if ($userObject->hasRight($user_id, 'view settings')) {
|
||||||
$jilo_agent_types = $agentObject->getAgentTypes();
|
$jilo_agent_types = $agentObject->getAgentTypes();
|
||||||
include '../app/templates/settings.php';
|
include '../app/templates/settings.php';
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<i class="fas fa-wrench me-2 text-secondary"></i>
|
<i class="fas fa-wrench me-2 text-secondary"></i>
|
||||||
<?= htmlspecialchars($config['site_name']) ?> app configuration
|
<?= htmlspecialchars($config['site_name']) ?> app configuration
|
||||||
</h5>
|
</h5>
|
||||||
<?php if ($userObject->hasRight($userId, 'edit config file')) { ?>
|
<?php if ($userObject->hasRight($user_id, 'edit config file')) { ?>
|
||||||
<div>
|
<div>
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm toggle-edit" <?= !$isWritable ? 'disabled' : '' ?>>
|
<button type="button" class="btn btn-outline-primary btn-sm toggle-edit" <?= !$isWritable ? 'disabled' : '' ?>>
|
||||||
<i class="fas fa-edit me-2"></i>Edit
|
<i class="fas fa-edit me-2"></i>Edit
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<form id="configForm">
|
<form id="configForm">
|
||||||
<?php
|
<?php
|
||||||
include CSRF_TOKEN_INCLUDE;
|
include 'csrf_token.php';
|
||||||
|
|
||||||
function renderConfigItem($key, $value, $path = '') {
|
function renderConfigItem($key, $value, $path = '') {
|
||||||
$fullPath = $path ? $path . '[' . $key . ']' : $key;
|
$fullPath = $path ? $path . '[' . $key . ']' : $key;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text"><strong>Welcome to <?= htmlspecialchars($config['site_name']); ?>!</strong><br />Please enter login credentials:</p>
|
<p class="card-text"><strong>Welcome to <?= htmlspecialchars($config['site_name']); ?>!</strong><br />Please enter login credentials:</p>
|
||||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=login">
|
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=login">
|
||||||
<?php include CSRF_TOKEN_INCLUDE; ?>
|
<?php include 'csrf_token.php'; ?>
|
||||||
<div class="form-group mb-3">
|
<div class="form-group mb-3">
|
||||||
<input type="text" class="form-control w-50 mx-auto" name="username" placeholder="Username"
|
<input type="text" class="form-control w-50 mx-auto" name="username" placeholder="Username"
|
||||||
pattern="[A-Za-z0-9_\-]{3,20}" title="3-20 characters, letters, numbers, - and _"
|
pattern="[A-Za-z0-9_\-]{3,20}" title="3-20 characters, letters, numbers, - and _"
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mb-3">
|
<div class="form-group mb-3">
|
||||||
<input type="password" class="form-control w-50 mx-auto" name="password" placeholder="Password"
|
<input type="password" class="form-control w-50 mx-auto" name="password" placeholder="Password"
|
||||||
pattern=".{8,}" title="Eight or more characters"
|
pattern=".{5,}" title="Eight or more characters"
|
||||||
required />
|
required />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mb-3">
|
<div class="form-group mb-3">
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<p>Enter your email address and we will send you<br />
|
<p>Enter your email address and we will send you<br />
|
||||||
instructions to reset your password.</p>
|
instructions to reset your password.</p>
|
||||||
<form method="post" action="?page=login&action=forgot">
|
<form method="post" action="?page=login&action=forgot">
|
||||||
<?php include CSRF_TOKEN_INCLUDE; ?>
|
<?php include 'csrf_token.php'; ?>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="email">email address:</label>
|
<label for="email">email address:</label>
|
||||||
<input type="email"
|
<input type="email"
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h3 class="card-title mb-4">Set new password</h3>
|
<h3 class="card-title mb-4">Set new password</h3>
|
||||||
<form method="post" action="?page=login&action=reset&token=<?= htmlspecialchars(urlencode($token)) ?>">
|
<form method="post" action="?page=login&action=reset&token=<?= htmlspecialchars(urlencode($token)) ?>">
|
||||||
<?php include CSRF_TOKEN_INCLUDE; ?>
|
<?php include 'csrf_token.php'; ?>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="new_password">new password:</label>
|
<label for="new_password">new password:</label>
|
||||||
<input type="password"
|
<input type="password"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text">Enter credentials for registration:</p>
|
<p class="card-text">Enter credentials for registration:</p>
|
||||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=register">
|
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=register">
|
||||||
<?php include CSRF_TOKEN_INCLUDE; ?>
|
<?php include 'csrf_token.php'; ?>
|
||||||
<div class="form-group mb-3">
|
<div class="form-group mb-3">
|
||||||
<input type="text" class="form-control w-50 mx-auto" name="username" placeholder="Username"
|
<input type="text" class="form-control w-50 mx-auto" name="username" placeholder="Username"
|
||||||
pattern="[A-Za-z0-9_\-]{3,20}" title="3-20 characters, letters, numbers, - and _"
|
pattern="[A-Za-z0-9_\-]{3,20}" title="3-20 characters, letters, numbers, - and _"
|
||||||
|
@ -20,17 +20,6 @@
|
||||||
pattern=".{8,}" title="Eight or more characters"
|
pattern=".{8,}" title="Eight or more characters"
|
||||||
required />
|
required />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mb-3">
|
|
||||||
<div class="form-check">
|
|
||||||
<label class="form-check-label" for="terms">
|
|
||||||
<input type="checkbox" class="form-check-input" id="terms" name="terms" required>
|
|
||||||
I agree to the <a href="<?= htmlspecialchars($app_root) ?>?page=terms" target="_blank">terms & conditions</a> and <a href="<?= htmlspecialchars($app_root) ?>?page=privacy" target="_blank">privacy policy</a>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<small class="text-muted mt-2">
|
|
||||||
We use cookies to improve your experience. See our <a href="<?= htmlspecialchars($app_root) ?>?page=cookies" target="_blank">cookies policy</a>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
<input type="submit" class="btn btn-primary" value="Register" />
|
<input type="submit" class="btn btn-primary" value="Register" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if (Session::getUsername() && $page !== 'logout') { ?>
|
<?php if (isset($currentUser) && $page !== 'logout') { ?>
|
||||||
<script src="static/js/sidebar.js"></script>
|
<script src="static/js/sidebar.js"></script>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<link rel="stylesheet" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/css/main.css">
|
<link rel="stylesheet" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/css/main.css">
|
||||||
<link rel="stylesheet" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/css/messages.css">
|
<link rel="stylesheet" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/css/messages.css">
|
||||||
<script src="<?= htmlspecialchars($app_root) ?>static/js/messages.js"></script>
|
<script src="<?= htmlspecialchars($app_root) ?>static/js/messages.js"></script>
|
||||||
<?php if (Session::getUsername()) { ?>
|
<?php if (isset($currentUser)) { ?>
|
||||||
<script>
|
<script>
|
||||||
// restore sidebar state before the page is rendered
|
// restore sidebar state before the page is rendered
|
||||||
(function () {
|
(function () {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
version <?= htmlspecialchars($config['version']) ?>
|
version <?= htmlspecialchars($config['version']) ?>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<?php if (Session::isValidSession()) { ?>
|
<?php if (isset($_SESSION['username']) && isset($_SESSION['user_id'])) { ?>
|
||||||
|
|
||||||
<?php foreach ($platformsAll as $platform) {
|
<?php foreach ($platformsAll as $platform) {
|
||||||
$platform_switch_url = switchPlatform($platform['id']);
|
$platform_switch_url = switchPlatform($platform['id']);
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul class="menu-right">
|
<ul class="menu-right">
|
||||||
<?php if (Session::isValidSession()) { ?>
|
<?php if (isset($_SESSION['username']) && isset($_SESSION['user_id'])) { ?>
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
|
||||||
<i class="fas fa-user"></i>
|
<i class="fas fa-user"></i>
|
||||||
|
@ -59,48 +59,10 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown">
|
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
|
|
||||||
<i class="fas fa-cog"></i>
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
|
||||||
<h6 class="dropdown-header">system</h6>
|
|
||||||
<?php if ($userObject->hasRight($userId, 'view config file')) {?>
|
|
||||||
<a class="dropdown-item" href="<?= htmlspecialchars($app_root) ?>?page=config">
|
|
||||||
<i class="fas fa-wrench"></i>Configuration
|
|
||||||
</a>
|
|
||||||
<?php } ?>
|
|
||||||
<?php if ($userObject->hasRight($userId, 'superuser') ||
|
|
||||||
$userObject->hasRight($userId, 'edit whitelist') ||
|
|
||||||
$userObject->hasRight($userId, 'edit blacklist') ||
|
|
||||||
$userObject->hasRight($userId, 'edit ratelimiting')) { ?>
|
|
||||||
<a class="dropdown-item" href="<?= htmlspecialchars($app_root) ?>?page=security">
|
|
||||||
<i class="fas fa-shield-alt"></i>Security
|
|
||||||
</a>
|
|
||||||
<?php } ?>
|
|
||||||
<?php if ($userObject->hasRight($userId, 'view app logs')) {?>
|
|
||||||
<a class="dropdown-item" href="<?= htmlspecialchars($app_root) ?>?page=logs">
|
|
||||||
<i class="fas fa-list"></i>Logs
|
|
||||||
</a>
|
|
||||||
<?php } ?>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<?php } else { ?>
|
<?php } else { ?>
|
||||||
<li><a href="<?= htmlspecialchars($app_root) ?>?page=login">login</a></li>
|
<li><a href="<?= htmlspecialchars($app_root) ?>?page=login">login</a></li>
|
||||||
<?php do_hook('main_public_menu', ['app_root' => $app_root, 'section' => 'main', 'position' => 100]); ?>
|
<li><a href="<?= htmlspecialchars($app_root) ?>?page=register">register</a></li>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
<li class="dropdown">
|
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
|
|
||||||
<i class="fas fa-info-circle"></i>
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
|
||||||
<h6 class="dropdown-header">resources</h6>
|
|
||||||
<a class="dropdown-item" href="<?= htmlspecialchars($app_root) ?>?page=help">
|
|
||||||
<i class="fas fa-question-circle"></i>Help
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!-- /Menu -->
|
<!-- /Menu -->
|
||||||
|
|
|
@ -72,11 +72,45 @@ $timeNow = new DateTime('now', new DateTimeZone($userTimezone));
|
||||||
<i class="fas fa-cog" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="jilo settings"></i>settings
|
<i class="fas fa-cog" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="jilo settings"></i>settings
|
||||||
</li>
|
</li>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<li class="list-group-item bg-light" style="border: none;"><p class="text-end mb-0"><small>system</small></p></li>
|
||||||
|
|
||||||
|
<?php if ($userObject->hasRight($user_id, 'view config file')) {?>
|
||||||
|
<a href="<?= htmlspecialchars($app_root) ?>?page=config">
|
||||||
|
<li class="list-group-item<?php if ($page === 'config') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||||
|
<i class="fas fa-wrench" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="app config"></i>config
|
||||||
|
</li>
|
||||||
|
</a>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ($userObject->hasRight($user_id, 'superuser') ||
|
||||||
|
$userObject->hasRight($user_id, 'edit whitelist') ||
|
||||||
|
$userObject->hasRight($user_id, 'edit blacklist') ||
|
||||||
|
$userObject->hasRight($user_id, 'edit ratelimiting')) { ?>
|
||||||
|
<a href="<?= htmlspecialchars($app_root) ?>?page=security">
|
||||||
|
<li class="list-group-item<?php if ($page === 'security') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||||
|
<i class="fas fa-shield-alt" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="security"></i>security
|
||||||
|
</li>
|
||||||
|
</a>
|
||||||
|
<?php } ?>
|
||||||
<a href="<?= htmlspecialchars($app_root) ?>?page=status">
|
<a href="<?= htmlspecialchars($app_root) ?>?page=status">
|
||||||
<li class="list-group-item<?php if ($page === 'status' && $item === '') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
<li class="list-group-item<?php if ($page === 'status' && $item === '') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||||
<i class="fas fa-heartbeat" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="status"></i>status
|
<i class="fas fa-heartbeat" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="status"></i>status
|
||||||
</li>
|
</li>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<?php if ($userObject->hasRight($user_id, 'view app logs')) {?>
|
||||||
|
<a href="<?= htmlspecialchars($app_root) ?>?page=logs">
|
||||||
|
<li class="list-group-item<?php if ($page === 'logs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||||
|
<i class="fas fa-shoe-prints" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="logs"></i>logs
|
||||||
|
</li>
|
||||||
|
</a>
|
||||||
|
<?php } ?>
|
||||||
|
<a href="<?= htmlspecialchars($app_root) ?>?page=help">
|
||||||
|
<li class="list-group-item<?php if ($page === 'help') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||||
|
<i class="fas fa-question-circle" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="help"></i>help
|
||||||
|
</li>
|
||||||
|
</a>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,14 +6,11 @@
|
||||||
* $totalPages - Total number of pages
|
* $totalPages - Total number of pages
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Validate required pagination variables
|
// Ensure required variables are set
|
||||||
if (!isset($currentPage) || !isset($totalPages)) {
|
if (!isset($currentPage) || !isset($totalPages)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure valid values
|
|
||||||
$currentPage = max(1, min($currentPage, $totalPages));
|
|
||||||
|
|
||||||
// Number of page links to show before and after current page
|
// Number of page links to show before and after current page
|
||||||
$range = 2;
|
$range = 2;
|
||||||
?>
|
?>
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=profile" enctype="multipart/form-data">
|
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=profile" enctype="multipart/form-data">
|
||||||
<?php include CSRF_TOKEN_INCLUDE; ?>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<p class="border rounded bg-light mb-4"><small>edit the profile fields</small></p>
|
<p class="border rounded bg-light mb-4"><small>edit the profile fields</small></p>
|
||||||
<div class="col-md-4 avatar-container">
|
<div class="col-md-4 avatar-container">
|
||||||
|
@ -133,7 +132,6 @@
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
||||||
<form id="remove-avatar-form" data-action="remove-avatar" method="POST" action="<?= htmlspecialchars($app_root) ?>?page=profile&action=remove&item=avatar">
|
<form id="remove-avatar-form" data-action="remove-avatar" method="POST" action="<?= htmlspecialchars($app_root) ?>?page=profile&action=remove&item=avatar">
|
||||||
<?php include CSRF_TOKEN_INCLUDE; ?>
|
|
||||||
<button type="button" class="btn btn-danger" id="confirm-delete">Delete Avatar</button>
|
<button type="button" class="btn btn-danger" id="confirm-delete">Delete Avatar</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,17 +5,17 @@
|
||||||
<h2 class="mb-0">Security settings</h2>
|
<h2 class="mb-0">Security settings</h2>
|
||||||
<small>network restrictions to control flooding and brute force attacks</small>
|
<small>network restrictions to control flooding and brute force attacks</small>
|
||||||
<ul class="nav nav-tabs mt-5">
|
<ul class="nav nav-tabs mt-5">
|
||||||
<?php if ($userObject->hasRight($userId, 'superuser') || $userObject->hasRight($userId, 'edit whitelist')) { ?>
|
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist')) { ?>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= $section === 'whitelist' ? 'active' : '' ?>" href="?page=security§ion=whitelist">IP whitelist</a>
|
<a class="nav-link <?= $section === 'whitelist' ? 'active' : '' ?>" href="?page=security§ion=whitelist">IP whitelist</a>
|
||||||
</li>
|
</li>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
<?php if ($userObject->hasRight($userId, 'superuser') || $userObject->hasRight($userId, 'edit blacklist')) { ?>
|
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit blacklist')) { ?>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= $section === 'blacklist' ? 'active' : '' ?>" href="?page=security§ion=blacklist">IP blacklist</a>
|
<a class="nav-link <?= $section === 'blacklist' ? 'active' : '' ?>" href="?page=security§ion=blacklist">IP blacklist</a>
|
||||||
</li>
|
</li>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
<?php if ($userObject->hasRight($userId, 'superuser') || $userObject->hasRight($userId, 'edit ratelimiting')) { ?>
|
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit ratelimiting')) { ?>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= $section === 'ratelimit' ? 'active' : '' ?>" href="?page=security§ion=ratelimit">Rate limiting</a>
|
<a class="nav-link <?= $section === 'ratelimit' ? 'active' : '' ?>" href="?page=security§ion=ratelimit">Rate limiting</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if ($section === 'whitelist' && ($userObject->hasRight($userId, 'superuser') || $userObject->hasRight($userId, 'edit whitelist'))) { ?>
|
<?php if ($section === 'whitelist' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist'))) { ?>
|
||||||
<!-- whitelist section -->
|
<!-- whitelist section -->
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST" class="mb-4">
|
<form method="POST" class="mb-4">
|
||||||
<?php include CSRF_TOKEN_INCLUDE; ?>
|
<?php include 'csrf_token.php'; ?>
|
||||||
<input type="hidden" name="action" value="add_whitelist">
|
<input type="hidden" name="action" value="add_whitelist">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
|
@ -77,7 +77,7 @@
|
||||||
<td><?= htmlspecialchars($ip['created_at']) ?></td>
|
<td><?= htmlspecialchars($ip['created_at']) ?></td>
|
||||||
<td>
|
<td>
|
||||||
<form method="POST" style="display: inline;">
|
<form method="POST" style="display: inline;">
|
||||||
<?php include CSRF_TOKEN_INCLUDE; ?>
|
<?php include 'csrf_token.php'; ?>
|
||||||
<input type="hidden" name="action" value="remove_whitelist">
|
<input type="hidden" name="action" value="remove_whitelist">
|
||||||
<input type="hidden" name="ip_address" value="<?= htmlspecialchars($ip['ip_address']) ?>">
|
<input type="hidden" name="ip_address" value="<?= htmlspecialchars($ip['ip_address']) ?>">
|
||||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to remove this IP from whitelist?')">Remove</button>
|
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to remove this IP from whitelist?')">Remove</button>
|
||||||
|
@ -93,7 +93,7 @@
|
||||||
</div>
|
</div>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
|
|
||||||
<?php if ($section === 'blacklist' && ($userObject->hasRight($userId, 'superuser') || $userObject->hasRight($userId, 'edit blacklist'))) { ?>
|
<?php if ($section === 'blacklist' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit blacklist'))) { ?>
|
||||||
<!-- blacklist section -->
|
<!-- blacklist section -->
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
@ -104,7 +104,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST" class="mb-4">
|
<form method="POST" class="mb-4">
|
||||||
<?php include CSRF_TOKEN_INCLUDE; ?>
|
<?php include 'csrf_token.php'; ?>
|
||||||
<input type="hidden" name="action" value="add_blacklist">
|
<input type="hidden" name="action" value="add_blacklist">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
|
@ -151,7 +151,7 @@
|
||||||
<td><?= $ip['expiry_time'] ? htmlspecialchars($ip['expiry_time']) : 'Never' ?></td>
|
<td><?= $ip['expiry_time'] ? htmlspecialchars($ip['expiry_time']) : 'Never' ?></td>
|
||||||
<td>
|
<td>
|
||||||
<form method="POST" style="display: inline;">
|
<form method="POST" style="display: inline;">
|
||||||
<?php include CSRF_TOKEN_INCLUDE; ?>
|
<?php include 'csrf_token.php'; ?>
|
||||||
<input type="hidden" name="action" value="remove_blacklist">
|
<input type="hidden" name="action" value="remove_blacklist">
|
||||||
<input type="hidden" name="ip_address" value="<?= htmlspecialchars($ip['ip_address']) ?>">
|
<input type="hidden" name="ip_address" value="<?= htmlspecialchars($ip['ip_address']) ?>">
|
||||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to remove this IP from blacklist?')">Remove</button>
|
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to remove this IP from blacklist?')">Remove</button>
|
||||||
|
@ -167,7 +167,7 @@
|
||||||
</div>
|
</div>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
|
|
||||||
<?php if ($section === 'ratelimit' && ($userObject->hasRight($userId, 'superuser') || $userObject->hasRight($userId, 'edit ratelimiting'))) { ?>
|
<?php if ($section === 'ratelimit' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit ratelimiting'))) { ?>
|
||||||
<!-- rate limiting section -->
|
<!-- rate limiting section -->
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
<button type="button" class="btn btn-outline-secondary cancel-edit platform-edit-mode" style="display: none;">
|
<button type="button" class="btn btn-outline-secondary cancel-edit platform-edit-mode" style="display: none;">
|
||||||
<i class="fas fa-times me-1"></i>Cancel
|
<i class="fas fa-times me-1"></i>Cancel
|
||||||
</button>
|
</button>
|
||||||
<?php if ($userObject->hasRight($userId, 'delete platform')): ?>
|
<?php if ($userObject->hasRight($user_id, 'delete platform')): ?>
|
||||||
<button type="button" class="btn btn-outline-danger platform-view-mode" onclick="showDeletePlatformModal(<?= htmlspecialchars($platform['id']) ?>, '<?= htmlspecialchars(addslashes($platform['name'])) ?>', '<?= htmlspecialchars(addslashes($platform['jitsi_url'])) ?>', '<?= htmlspecialchars(addslashes($platform['jilo_database'])) ?>')">
|
<button type="button" class="btn btn-outline-danger platform-view-mode" onclick="showDeletePlatformModal(<?= htmlspecialchars($platform['id']) ?>, '<?= htmlspecialchars(addslashes($platform['name'])) ?>', '<?= htmlspecialchars(addslashes($platform['jitsi_url'])) ?>', '<?= htmlspecialchars(addslashes($platform['jilo_database'])) ?>')">
|
||||||
<i class="fas fa-trash me-1"></i>Delete platform
|
<i class="fas fa-trash me-1"></i>Delete platform
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
username TEXT NOT NULL UNIQUE,
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
@ -24,95 +25,43 @@ CREATE TABLE rights (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
name TEXT NOT NULL UNIQUE
|
name TEXT NOT NULL UNIQUE
|
||||||
);
|
);
|
||||||
CREATE TABLE IF NOT EXISTS "jilo_agent_types" (
|
INSERT INTO rights VALUES(1,'superuser');
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
INSERT INTO rights VALUES(2,'edit users');
|
||||||
description TEXT,
|
INSERT INTO rights VALUES(3,'view settings');
|
||||||
endpoint TEXT
|
INSERT INTO rights VALUES(4,'edit settings');
|
||||||
);
|
INSERT INTO rights VALUES(5,'view config file');
|
||||||
|
INSERT INTO rights VALUES(6,'edit config file');
|
||||||
|
INSERT INTO rights VALUES(7,'view own profile');
|
||||||
|
INSERT INTO rights VALUES(8,'edit own profile');
|
||||||
|
INSERT INTO rights VALUES(9,'view all profiles');
|
||||||
|
INSERT INTO rights VALUES(10,'edit all profiles');
|
||||||
|
INSERT INTO rights VALUES(11,'view app logs');
|
||||||
|
INSERT INTO rights VALUES(12,'view all platforms');
|
||||||
|
INSERT INTO rights VALUES(13,'edit all platforms');
|
||||||
|
INSERT INTO rights VALUES(14,'view all agents');
|
||||||
|
INSERT INTO rights VALUES(15,'edit all agents');
|
||||||
CREATE TABLE platforms (
|
CREATE TABLE platforms (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
name TEXT NOT NULL UNIQUE,
|
name TEXT NOT NULL UNIQUE,
|
||||||
jitsi_url TEXT NOT NULL,
|
jitsi_url TEXT NOT NULL,
|
||||||
jilo_database TEXT NOT NULL
|
jilo_database TEXT NOT NULL
|
||||||
);
|
);
|
||||||
CREATE TABLE hosts (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
address TEXT NOT NULL,
|
|
||||||
platform_id INTEGER NOT NULL,
|
|
||||||
name TEXT,
|
|
||||||
FOREIGN KEY(platform_id) REFERENCES platforms(id)
|
|
||||||
);
|
|
||||||
CREATE TABLE jilo_agents (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
host_id INTEGER NOT NULL,
|
|
||||||
agent_type_id INTEGER NOT NULL,
|
|
||||||
url TEXT NOT NULL,
|
|
||||||
secret_key TEXT,
|
|
||||||
check_period INTEGER DEFAULT 0,
|
|
||||||
FOREIGN KEY(agent_type_id) REFERENCES jilo_agent_types(id),
|
|
||||||
FOREIGN KEY(host_id) REFERENCES hosts(id)
|
|
||||||
);
|
|
||||||
CREATE TABLE jilo_agent_checks (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
agent_id INTEGER,
|
|
||||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
status_code INTEGER,
|
|
||||||
response_time_ms INTEGER,
|
|
||||||
response_content TEXT,
|
|
||||||
FOREIGN KEY(agent_id) REFERENCES jilo_agents(id)
|
|
||||||
);
|
|
||||||
CREATE TABLE ip_whitelist (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
ip_address TEXT NOT NULL UNIQUE,
|
|
||||||
is_network BOOLEAN DEFAULT 0 CHECK(is_network IN (0,1)),
|
|
||||||
description TEXT,
|
|
||||||
created_at TEXT DEFAULT (DATETIME('now')),
|
|
||||||
created_by TEXT
|
|
||||||
);
|
|
||||||
CREATE TABLE ip_blacklist (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
ip_address TEXT NOT NULL UNIQUE,
|
|
||||||
is_network BOOLEAN DEFAULT 0 CHECK(is_network IN (0,1)),
|
|
||||||
reason TEXT,
|
|
||||||
expiry_time TEXT NULL,
|
|
||||||
created_at TEXT DEFAULT (DATETIME('now')),
|
|
||||||
created_by TEXT
|
|
||||||
);
|
|
||||||
CREATE TABLE logs (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
user_id INTEGER NOT NULL,
|
|
||||||
time TEXT DEFAULT (DATETIME('now')),
|
|
||||||
scope TEXT NOT NULL,
|
|
||||||
message TEXT NOT NULL,
|
|
||||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
|
||||||
);
|
|
||||||
CREATE TABLE pages_rate_limits (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
ip_address TEXT NOT NULL,
|
|
||||||
endpoint TEXT NOT NULL,
|
|
||||||
request_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
CREATE TABLE login_attempts (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
ip_address TEXT NOT NULL,
|
|
||||||
username TEXT NOT NULL,
|
|
||||||
attempted_at TEXT DEFAULT (DATETIME('now'))
|
|
||||||
);
|
|
||||||
CREATE TABLE user_2fa (
|
CREATE TABLE user_2fa (
|
||||||
user_id INTEGER NOT NULL PRIMARY KEY,
|
user_id INTEGER NOT NULL PRIMARY KEY,
|
||||||
secret_key TEXT NOT NULL,
|
secret_key TEXT NOT NULL,
|
||||||
backup_codes TEXT,
|
backup_codes TEXT,
|
||||||
enabled INTEGER NOT NULL DEFAULT 0,
|
enabled INTEGER NOT NULL DEFAULT 0,
|
||||||
created_at TEXT NOT NULL,
|
created_at TEXT NOT NULL,
|
||||||
last_used TEXT,
|
last_used TEXT,
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
);
|
);
|
||||||
CREATE TABLE user_2fa_temp (
|
CREATE TABLE user_2fa_temp (
|
||||||
user_id INTEGER NOT NULL PRIMARY KEY,
|
user_id INTEGER NOT NULL,
|
||||||
code TEXT NOT NULL,
|
code TEXT NOT NULL,
|
||||||
created_at TEXT NOT NULL,
|
created_at TEXT NOT NULL,
|
||||||
expires_at TEXT NOT NULL,
|
expires_at TEXT NOT NULL,
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
PRIMARY KEY (user_id, code),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
);
|
);
|
||||||
CREATE TABLE user_password_reset (
|
CREATE TABLE user_password_reset (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
@ -121,5 +70,85 @@ CREATE TABLE user_password_reset (
|
||||||
expires INTEGER NOT NULL,
|
expires INTEGER NOT NULL,
|
||||||
used INTEGER NOT NULL DEFAULT 0,
|
used INTEGER NOT NULL DEFAULT 0,
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
CREATE TABLE logs (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
time TEXT DEFAULT (DATETIME('now')),
|
||||||
|
scope TEXT NOT NULL,
|
||||||
|
message TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS "jilo_agent_types" (
|
||||||
|
"id" INTEGER,
|
||||||
|
"description" TEXT,
|
||||||
|
"endpoint" TEXT,
|
||||||
|
PRIMARY KEY("id" AUTOINCREMENT)
|
||||||
|
);
|
||||||
|
INSERT INTO jilo_agent_types VALUES(1,'jvb','/jvb');
|
||||||
|
INSERT INTO jilo_agent_types VALUES(2,'jicofo','/jicofo');
|
||||||
|
INSERT INTO jilo_agent_types VALUES(3,'prosody','/prosody');
|
||||||
|
INSERT INTO jilo_agent_types VALUES(4,'nginx','/nginx');
|
||||||
|
INSERT INTO jilo_agent_types VALUES(5,'jibri','/jibri');
|
||||||
|
CREATE TABLE jilo_agent_checks (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
agent_id INTEGER,
|
||||||
|
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
status_code INTEGER,
|
||||||
|
response_time_ms INTEGER,
|
||||||
|
response_content TEXT,
|
||||||
|
FOREIGN KEY(agent_id) REFERENCES jilo_agents(id)
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS "jilo_agents" (
|
||||||
|
"id" INTEGER,
|
||||||
|
"host_id" INTEGER NOT NULL,
|
||||||
|
"agent_type_id" INTEGER NOT NULL,
|
||||||
|
"url" TEXT NOT NULL,
|
||||||
|
"secret_key" TEXT,
|
||||||
|
"check_period" INTEGER DEFAULT 0,
|
||||||
|
PRIMARY KEY("id" AUTOINCREMENT),
|
||||||
|
FOREIGN KEY("agent_type_id") REFERENCES "jilo_agent_types"("id"),
|
||||||
|
FOREIGN KEY("host_id") REFERENCES "hosts"("id")
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS "hosts" (
|
||||||
|
"id" INTEGER NOT NULL,
|
||||||
|
"address" TEXT NOT NULL,
|
||||||
|
"platform_id" INTEGER NOT NULL,
|
||||||
|
"name" TEXT,
|
||||||
|
PRIMARY KEY("id" AUTOINCREMENT),
|
||||||
|
FOREIGN KEY("platform_id") REFERENCES "platforms"("id")
|
||||||
|
);
|
||||||
|
CREATE TABLE login_attempts (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
ip_address TEXT NOT NULL,
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
attempted_at TEXT DEFAULT (DATETIME('now'))
|
||||||
|
);
|
||||||
|
CREATE TABLE ip_whitelist (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
ip_address TEXT NOT NULL UNIQUE,
|
||||||
|
is_network BOOLEAN DEFAULT 0 CHECK(is_network IN (0,1)),
|
||||||
|
description TEXT,
|
||||||
|
created_at TEXT DEFAULT (DATETIME('now')),
|
||||||
|
created_by TEXT
|
||||||
|
);
|
||||||
|
INSERT INTO ip_whitelist VALUES(1,'127.0.0.1',0,'localhost IPv4','2025-01-04 11:39:08','system');
|
||||||
|
INSERT INTO ip_whitelist VALUES(2,'::1',0,'localhost IPv6','2025-01-04 11:39:08','system');
|
||||||
|
INSERT INTO ip_whitelist VALUES(3,'10.0.0.0/8',1,'Private network (Class A)','2025-01-04 11:39:08','system');
|
||||||
|
INSERT INTO ip_whitelist VALUES(4,'172.16.0.0/12',1,'Private network (Class B)','2025-01-04 11:39:08','system');
|
||||||
|
INSERT INTO ip_whitelist VALUES(5,'192.168.0.0/16',1,'Private network (Class C)','2025-01-04 11:39:08','system');
|
||||||
|
CREATE TABLE ip_blacklist (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
ip_address TEXT NOT NULL UNIQUE,
|
||||||
|
is_network BOOLEAN DEFAULT 0 CHECK(is_network IN (0,1)),
|
||||||
|
reason TEXT,
|
||||||
|
expiry_time TEXT NULL,
|
||||||
|
created_at TEXT DEFAULT (DATETIME('now')),
|
||||||
|
created_by TEXT
|
||||||
|
);
|
||||||
|
INSERT INTO ip_blacklist VALUES(1,'0.0.0.0/8',1,'Reserved address space - RFC 1122',NULL,'2025-01-04 11:39:08','system');
|
||||||
|
INSERT INTO ip_blacklist VALUES(2,'100.64.0.0/10',1,'Carrier-grade NAT space - RFC 6598',NULL,'2025-01-04 11:39:08','system');
|
||||||
|
INSERT INTO ip_blacklist VALUES(3,'192.0.2.0/24',1,'TEST-NET-1 Documentation space - RFC 5737',NULL,'2025-01-04 11:39:08','system');
|
||||||
|
INSERT INTO ip_blacklist VALUES(4,'198.51.100.0/24',1,'TEST-NET-2 Documentation space - RFC 5737',NULL,'2025-01-04 11:39:08','system');
|
||||||
|
INSERT INTO ip_blacklist VALUES(5,'203.0.113.0/24',1,'TEST-NET-3 Documentation space - RFC 5737',NULL,'2025-01-04 11:39:08','system');
|
||||||
|
|
|
@ -5,38 +5,24 @@ INSERT INTO users VALUES(2,'demo1','$2y$10$LtV9m.rMCJ.K/g45e6tzDexZ8C/9xxu3qFCkv
|
||||||
INSERT INTO users_meta VALUES(1,1,'demo admin user','admin@example.com',NULL,NULL,'This is a demo user of the demo install of Jilo Web');
|
INSERT INTO users_meta VALUES(1,1,'demo admin user','admin@example.com',NULL,NULL,'This is a demo user of the demo install of Jilo Web');
|
||||||
INSERT INTO users_meta VALUES(2,2,'demo user','demo@example.com',NULL,NULL,'This is a demo user of the demo install of Jilo Web');
|
INSERT INTO users_meta VALUES(2,2,'demo user','demo@example.com',NULL,NULL,'This is a demo user of the demo install of Jilo Web');
|
||||||
|
|
||||||
INSERT INTO rights VALUES(1,'superuser');
|
INSERT INTO platforms VALUES(1,'meet.lindeas.com','https://meet.lindeas.com','../jilo-meet.lindeas.db');
|
||||||
INSERT INTO rights VALUES(2,'edit users');
|
INSERT INTO platforms VALUES(2,'example.com','https://meet.example.com','../jilo.db');
|
||||||
INSERT INTO rights VALUES(3,'view settings');
|
|
||||||
INSERT INTO rights VALUES(4,'edit settings');
|
|
||||||
INSERT INTO rights VALUES(5,'view own profile');
|
|
||||||
INSERT INTO rights VALUES(6,'edit own profile');
|
|
||||||
INSERT INTO rights VALUES(7,'view all profiles');
|
|
||||||
INSERT INTO rights VALUES(8,'edit all profiles');
|
|
||||||
INSERT INTO rights VALUES(9,'view app logs');
|
|
||||||
INSERT INTO rights VALUES(10,'manage plugins');
|
|
||||||
INSERT INTO rights VALUES(11,'view all platforms');
|
|
||||||
INSERT INTO rights VALUES(12,'edit all platforms');
|
|
||||||
INSERT INTO rights VALUES(13,'view all agents');
|
|
||||||
INSERT INTO rights VALUES(14,'edit all agents');
|
|
||||||
INSERT INTO rights VALUES(15,'view jilo config');
|
|
||||||
|
|
||||||
INSERT INTO jilo_agent_types VALUES(1,'jvb','/jvb');
|
INSERT INTO logs VALUES(1,2,'2024-09-30 09:54:50','user','Logout: User "demo" logged out. IP: 151.237.101.43');
|
||||||
INSERT INTO jilo_agent_types VALUES(2,'jicofo','/jicofo');
|
INSERT INTO logs VALUES(2,2,'2024-09-30 09:54:54','user','Login: User "demo" logged in. IP: 151.237.101.43');
|
||||||
INSERT INTO jilo_agent_types VALUES(3,'prosody','/prosody');
|
INSERT INTO logs VALUES(3,2,'2024-10-03 16:34:49','user','Logout: User "demo" logged out. IP: 151.237.101.43');
|
||||||
INSERT INTO jilo_agent_types VALUES(4,'nginx','/nginx');
|
INSERT INTO logs VALUES(4,2,'2024-10-03 16:34:56','user','Login: User "demo" logged in. IP: 151.237.101.43');
|
||||||
INSERT INTO jilo_agent_types VALUES(5,'jibri','/jibri');
|
INSERT INTO logs VALUES(5,2,'2024-10-09 11:08:16','user','Logout: User "demo" logged out. IP: 151.237.101.43');
|
||||||
|
INSERT INTO logs VALUES(6,2,'2024-10-09 11:08:20','user','Login: User "demo" logged in. IP: 151.237.101.43');
|
||||||
|
INSERT INTO logs VALUES(7,2,'2024-10-17 16:22:57','user','Logout: User "demo" logged out. IP: 151.237.101.43');
|
||||||
|
INSERT INTO logs VALUES(8,2,'2024-10-17 16:23:08','user','Login: User "demo" logged in. IP: 151.237.101.43');
|
||||||
|
INSERT INTO logs VALUES(9,2,'2024-10-18 08:07:25','user','Login: User "demo" logged in. IP: 42.104.201.119');
|
||||||
|
|
||||||
INSERT INTO platforms VALUES(1,'example.com','https://meet.example.com','../../jilo/jilo.db');
|
INSERT INTO jilo_agents VALUES(1,1,1,'https://meet.lindeas.com:8081','mysecretkey',5);
|
||||||
|
INSERT INTO jilo_agents VALUES(4,1,2,'https://meet.lindeas.com:8081','mysecretkey',5);
|
||||||
|
INSERT INTO jilo_agents VALUES(7,1,3,'http://meet.lindeas.com:8081','mysecretkey',5);
|
||||||
|
INSERT INTO jilo_agents VALUES(8,1,4,'http://meet.lindeas.com:8081','mysecretkey',5);
|
||||||
|
|
||||||
INSERT INTO ip_whitelist VALUES(1,'127.0.0.1',0,'localhost IPv4','2025-01-04 11:39:08','system');
|
INSERT INTO hosts VALUES(1,'meet.lindeas.com',2,'main machine');
|
||||||
INSERT INTO ip_whitelist VALUES(2,'::1',0,'localhost IPv6','2025-01-04 11:39:08','system');
|
INSERT INTO hosts VALUES(2,'meet.example.com',2,'test');
|
||||||
INSERT INTO ip_whitelist VALUES(3,'10.0.0.0/8',1,'Private network (Class A)','2025-01-04 11:39:08','system');
|
|
||||||
INSERT INTO ip_whitelist VALUES(4,'172.16.0.0/12',1,'Private network (Class B)','2025-01-04 11:39:08','system');
|
|
||||||
INSERT INTO ip_whitelist VALUES(5,'192.168.0.0/16',1,'Private network (Class C)','2025-01-04 11:39:08','system');
|
|
||||||
|
|
||||||
INSERT INTO ip_blacklist VALUES(1,'0.0.0.0/8',1,'Reserved address space - RFC 1122',NULL,'2025-01-04 11:39:08','system');
|
|
||||||
INSERT INTO ip_blacklist VALUES(2,'100.64.0.0/10',1,'Carrier-grade NAT space - RFC 6598',NULL,'2025-01-04 11:39:08','system');
|
|
||||||
INSERT INTO ip_blacklist VALUES(3,'192.0.2.0/24',1,'TEST-NET-1 Documentation space - RFC 5737',NULL,'2025-01-04 11:39:08','system');
|
|
||||||
INSERT INTO ip_blacklist VALUES(4,'198.51.100.0/24',1,'TEST-NET-2 Documentation space - RFC 5737',NULL,'2025-01-04 11:39:08','system');
|
|
||||||
INSERT INTO ip_blacklist VALUES(5,'203.0.113.0/24',1,'TEST-NET-3 Documentation space - RFC 5737',NULL,'2025-01-04 11:39:08','system');
|
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
// Add to allowed URLs
|
|
||||||
register_hook('filter_allowed_urls', function($urls) {
|
|
||||||
$urls[] = 'register';
|
|
||||||
return $urls;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add to publicly accessible pages
|
|
||||||
register_hook('filter_public_pages', function($pages) {
|
|
||||||
$pages[] = 'register';
|
|
||||||
return $pages;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configuration for main menu injection
|
|
||||||
define('REGISTRATIONS_MAIN_MENU_SECTION', 'main');
|
|
||||||
define('REGISTRATIONS_MAIN_MENU_POSITION', 30);
|
|
||||||
register_hook('main_public_menu', function($ctx) {
|
|
||||||
$section = defined('REGISTRATIONS_MAIN_MENU_SECTION') ? REGISTRATIONS_MAIN_MENU_SECTION : 'main';
|
|
||||||
$position = defined('REGISTRATIONS_MAIN_MENU_POSITION') ? REGISTRATIONS_MAIN_MENU_POSITION : 100;
|
|
||||||
echo '<li><a href="?page=register">register</a></li>';
|
|
||||||
});
|
|
|
@ -1,92 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* class Register
|
|
||||||
*
|
|
||||||
* Handles user registration.
|
|
||||||
*/
|
|
||||||
class Register {
|
|
||||||
/**
|
|
||||||
* @var PDO|null $db The database connection instance.
|
|
||||||
*/
|
|
||||||
private $db;
|
|
||||||
private $rateLimiter;
|
|
||||||
private $twoFactorAuth;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register constructor.
|
|
||||||
* Initializes the database connection.
|
|
||||||
*
|
|
||||||
* @param object $database The database object to initialize the connection.
|
|
||||||
*/
|
|
||||||
public function __construct($database) {
|
|
||||||
if ($database instanceof PDO) {
|
|
||||||
$this->db = $database;
|
|
||||||
} else {
|
|
||||||
$this->db = $database->getConnection();
|
|
||||||
}
|
|
||||||
require_once dirname(__FILE__, 4) . '/app/classes/ratelimiter.php';
|
|
||||||
require_once dirname(__FILE__, 4) . '/app/classes/twoFactorAuth.php';
|
|
||||||
|
|
||||||
$this->rateLimiter = new RateLimiter($database);
|
|
||||||
$this->twoFactorAuth = new TwoFactorAuthentication($database);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a new user.
|
|
||||||
*
|
|
||||||
* @param string $username The username of the new user.
|
|
||||||
* @param string $password The password for the new user.
|
|
||||||
*
|
|
||||||
* @return bool|string True if registration is successful, error message otherwise.
|
|
||||||
*/
|
|
||||||
public function register($username, $password) {
|
|
||||||
try {
|
|
||||||
// we have two inserts, start a transaction
|
|
||||||
$this->db->beginTransaction();
|
|
||||||
|
|
||||||
// hash the password, don't store it plain
|
|
||||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
|
||||||
|
|
||||||
// insert into users table
|
|
||||||
$sql = 'INSERT
|
|
||||||
INTO users (username, password)
|
|
||||||
VALUES (:username, :password)';
|
|
||||||
$query = $this->db->prepare($sql);
|
|
||||||
$query->bindValue(':username', $username);
|
|
||||||
$query->bindValue(':password', $hashedPassword);
|
|
||||||
|
|
||||||
// execute the first query
|
|
||||||
if (!$query->execute()) {
|
|
||||||
// rollback on error
|
|
||||||
$this->db->rollBack();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert the last user id into users_meta table
|
|
||||||
$sql2 = 'INSERT
|
|
||||||
INTO users_meta (user_id)
|
|
||||||
VALUES (:user_id)';
|
|
||||||
$query2 = $this->db->prepare($sql2);
|
|
||||||
$query2->bindValue(':user_id', $this->db->lastInsertId());
|
|
||||||
|
|
||||||
// execute the second query
|
|
||||||
if (!$query2->execute()) {
|
|
||||||
// rollback on error
|
|
||||||
$this->db->rollBack();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if all is OK, commit the transaction
|
|
||||||
$this->db->commit();
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
// rollback on any error
|
|
||||||
$this->db->rollBack();
|
|
||||||
return $e->getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Registration Plugin",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"enabled": true,
|
|
||||||
"description": "Provides registration functionality as a plugin."
|
|
||||||
}
|
|
|
@ -11,68 +11,18 @@
|
||||||
* Version: 0.4
|
* Version: 0.4
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Preparing plugins and hooks
|
|
||||||
$GLOBALS['plugin_hooks'] = [];
|
|
||||||
$enabled_plugins = [];
|
|
||||||
|
|
||||||
// Plugin discovery
|
|
||||||
$plugins_dir = dirname(__DIR__) . '/plugins/';
|
|
||||||
foreach (glob($plugins_dir . '*', GLOB_ONLYDIR) as $plugin_path) {
|
|
||||||
$manifest = $plugin_path . '/plugin.json';
|
|
||||||
if (file_exists($manifest)) {
|
|
||||||
$meta = json_decode(file_get_contents($manifest), true);
|
|
||||||
if (!empty($meta['enabled'])) {
|
|
||||||
$plugin_name = basename($plugin_path);
|
|
||||||
$enabled_plugins[$plugin_name] = [
|
|
||||||
'path' => $plugin_path,
|
|
||||||
'meta' => $meta
|
|
||||||
];
|
|
||||||
// Autoload plugin bootstrap if exists
|
|
||||||
$bootstrap = $plugin_path . '/bootstrap.php';
|
|
||||||
if (file_exists($bootstrap)) {
|
|
||||||
include_once $bootstrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$GLOBALS['enabled_plugins'] = $enabled_plugins;
|
|
||||||
|
|
||||||
// Simple hook system
|
|
||||||
function register_hook($hook, $callback) {
|
|
||||||
$GLOBALS['plugin_hooks'][$hook][] = $callback;
|
|
||||||
}
|
|
||||||
function do_hook($hook, $context = []) {
|
|
||||||
if (!empty($GLOBALS['plugin_hooks'][$hook])) {
|
|
||||||
foreach ($GLOBALS['plugin_hooks'][$hook] as $callback) {
|
|
||||||
call_user_func($callback, $context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define CSRF token include path globally
|
|
||||||
if (!defined('CSRF_TOKEN_INCLUDE')) {
|
|
||||||
define('CSRF_TOKEN_INCLUDE', dirname(__DIR__) . '/app/includes/csrf_token.php');
|
|
||||||
}
|
|
||||||
|
|
||||||
// we start output buffering and
|
// we start output buffering and
|
||||||
// flush it later only when there is no redirect
|
// flush it later only when there is no redirect
|
||||||
ob_start();
|
ob_start();
|
||||||
|
|
||||||
// Start session before any session-dependent code
|
|
||||||
require_once '../app/classes/session.php';
|
|
||||||
Session::startSession();
|
|
||||||
|
|
||||||
// Apply security headers
|
// Apply security headers
|
||||||
require_once '../app/includes/security_headers_middleware.php';
|
require_once '../app/includes/security_headers_middleware.php';
|
||||||
|
|
||||||
// sanitize all input vars that may end up in URLs or forms
|
// sanitize all input vars that may end up in URLs or forms
|
||||||
require '../app/includes/sanitize.php';
|
require '../app/includes/sanitize.php';
|
||||||
|
|
||||||
// Check session validity
|
session_name('jilo');
|
||||||
$validSession = Session::isValidSession();
|
session_start();
|
||||||
|
|
||||||
// Get user ID early if session is valid
|
|
||||||
$userId = $validSession ? Session::getUserId() : null;
|
|
||||||
|
|
||||||
// Initialize feedback message system
|
// Initialize feedback message system
|
||||||
require_once '../app/classes/feedback.php';
|
require_once '../app/classes/feedback.php';
|
||||||
|
@ -113,21 +63,9 @@ $allowed_urls = [
|
||||||
|
|
||||||
'login',
|
'login',
|
||||||
'logout',
|
'logout',
|
||||||
|
'register',
|
||||||
'about',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Let plugins filter/extend allowed_urls
|
|
||||||
function filter_allowed_urls($urls) {
|
|
||||||
if (!empty($GLOBALS['plugin_hooks']['filter_allowed_urls'])) {
|
|
||||||
foreach ($GLOBALS['plugin_hooks']['filter_allowed_urls'] as $callback) {
|
|
||||||
$urls = call_user_func($callback, $urls);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $urls;
|
|
||||||
}
|
|
||||||
$allowed_urls = filter_allowed_urls($allowed_urls);
|
|
||||||
|
|
||||||
// cnfig file
|
// cnfig file
|
||||||
// possible locations, in order of preference
|
// possible locations, in order of preference
|
||||||
$config_file_locations = [
|
$config_file_locations = [
|
||||||
|
@ -154,40 +92,17 @@ if ($config_file) {
|
||||||
|
|
||||||
$app_root = $config['folder'];
|
$app_root = $config['folder'];
|
||||||
|
|
||||||
// List of pages that don't require authentication
|
// check if logged in
|
||||||
$public_pages = ['login', 'help', 'about'];
|
unset($currentUser);
|
||||||
|
if (isset($_COOKIE['username'])) {
|
||||||
// Let plugins filter/extend public_pages
|
if ( !isset($_SESSION['username']) ) {
|
||||||
function filter_public_pages($pages) {
|
$_SESSION['username'] = $_COOKIE['username'];
|
||||||
if (!empty($GLOBALS['plugin_hooks']['filter_public_pages'])) {
|
|
||||||
foreach ($GLOBALS['plugin_hooks']['filter_public_pages'] as $callback) {
|
|
||||||
$pages = call_user_func($callback, $pages);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return $pages;
|
$currentUser = htmlspecialchars($_SESSION['username']);
|
||||||
}
|
|
||||||
$public_pages = filter_public_pages($public_pages);
|
|
||||||
|
|
||||||
// Check if the requested page requires authentication
|
|
||||||
if (!isset($_COOKIE['username']) && !$validSession && !in_array($page, $public_pages)) {
|
|
||||||
require_once '../app/includes/session_middleware.php';
|
|
||||||
applySessionMiddleware($config, $app_root);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check session and redirect if needed
|
// redirect to login
|
||||||
$currentUser = null;
|
if ( !isset($_COOKIE['username']) && ($page !== 'login' && $page !== 'register') ) {
|
||||||
if ($validSession) {
|
|
||||||
$currentUser = Session::getUsername();
|
|
||||||
} else if (isset($_COOKIE['username']) && !in_array($page, $public_pages)) {
|
|
||||||
// Cookie exists but session is invalid - redirect to login
|
|
||||||
if (!isset($_SESSION['session_timeout_shown'])) {
|
|
||||||
Feedback::flash('LOGIN', 'SESSION_TIMEOUT');
|
|
||||||
$_SESSION['session_timeout_shown'] = true;
|
|
||||||
}
|
|
||||||
header('Location: ' . htmlspecialchars($app_root) . '?page=login');
|
|
||||||
exit();
|
|
||||||
} else if (!in_array($page, $public_pages)) {
|
|
||||||
// No valid session or cookie, and not a public page
|
|
||||||
header('Location: ' . htmlspecialchars($app_root) . '?page=login');
|
header('Location: ' . htmlspecialchars($app_root) . '?page=login');
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
@ -245,16 +160,20 @@ $userObject = new User($dbWeb);
|
||||||
|
|
||||||
// logout is a special case, as we can't use session vars for notices
|
// logout is a special case, as we can't use session vars for notices
|
||||||
if ($page == 'logout') {
|
if ($page == 'logout') {
|
||||||
|
// get user info before destroying session
|
||||||
|
$user_id = $userObject->getUserId($currentUser)[0]['id'];
|
||||||
|
|
||||||
// clean up session
|
// clean up session
|
||||||
Session::destroySession();
|
session_unset();
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
// start new session for the login page
|
// start new session for the login page
|
||||||
Session::startSession();
|
session_start();
|
||||||
|
|
||||||
setcookie('username', "", time() - 100, $config['folder'], $config['domain'], isset($_SERVER['HTTPS']), true);
|
setcookie('username', "", time() - 100, $config['folder'], $config['domain'], isset($_SERVER['HTTPS']), true);
|
||||||
|
|
||||||
// Log successful logout
|
// Log successful logout
|
||||||
$logObject->insertLog($userId, "Logout: User \"$currentUser\" logged out. IP: $user_IP", 'user');
|
$logObject->insertLog($user_id, "Logout: User \"$currentUser\" logged out. IP: $user_IP", 'user');
|
||||||
|
|
||||||
// Set success message
|
// Set success message
|
||||||
Feedback::flash('LOGIN', 'LOGOUT_SUCCESS');
|
Feedback::flash('LOGIN', 'LOGOUT_SUCCESS');
|
||||||
|
@ -267,14 +186,16 @@ if ($page == 'logout') {
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// if user is logged in, we need user details and rights
|
// if user is logged in, we need user details and rights
|
||||||
if ($validSession) {
|
if (isset($currentUser)) {
|
||||||
|
|
||||||
// If by error a logged in user requests the login page
|
// If by error a logged in user requests the login page
|
||||||
if ($page === 'login') {
|
if ($page === 'login') {
|
||||||
header('Location: ' . htmlspecialchars($app_root));
|
header('Location: ' . htmlspecialchars($app_root));
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
$userDetails = $userObject->getUserDetails($userId);
|
$user_id = $userObject->getUserId($currentUser)[0]['id'];
|
||||||
$userRights = $userObject->getUserRights($userId);
|
$userDetails = $userObject->getUserDetails($user_id);
|
||||||
|
$userRights = $userObject->getUserRights($user_id);
|
||||||
$userTimezone = (!empty($userDetails[0]['timezone'])) ? $userDetails[0]['timezone'] : 'UTC'; // Default to UTC if no timezone is set (or is missing)
|
$userTimezone = (!empty($userDetails[0]['timezone'])) ? $userDetails[0]['timezone'] : 'UTC'; // Default to UTC if no timezone is set (or is missing)
|
||||||
|
|
||||||
// check if the Jilo Server is running
|
// check if the Jilo Server is running
|
||||||
|
@ -290,28 +211,23 @@ if ($page == 'logout') {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Plugin loading logic for all enabled plugins ---
|
// List of pages that don't require authentication
|
||||||
$plugin_controllers = [];
|
$public_pages = ['login', 'register'];
|
||||||
foreach ($GLOBALS['enabled_plugins'] as $plugin_name => $plugin_info) {
|
|
||||||
$controller_path = $plugin_info['path'] . '/controllers/' . $plugin_name . '.php';
|
// Check if the requested page requires authentication
|
||||||
if (file_exists($controller_path)) {
|
if (!in_array($page, $public_pages)) {
|
||||||
$plugin_controllers[$plugin_name] = $controller_path;
|
require_once '../app/includes/session_middleware.php';
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// page building
|
// page building
|
||||||
include '../app/templates/page-header.php';
|
include '../app/templates/page-header.php';
|
||||||
include '../app/templates/page-menu.php';
|
include '../app/templates/page-menu.php';
|
||||||
if ($validSession) {
|
if (isset($currentUser)) {
|
||||||
include '../app/templates/page-sidebar.php';
|
include '../app/templates/page-sidebar.php';
|
||||||
}
|
}
|
||||||
if (in_array($page, $allowed_urls)) {
|
if (in_array($page, $allowed_urls)) {
|
||||||
// all normal pages
|
// all normal pages
|
||||||
if (isset($plugin_controllers[$page])) {
|
include "../app/pages/{$page}.php";
|
||||||
include $plugin_controllers[$page];
|
|
||||||
} else {
|
|
||||||
include "../app/pages/{$page}.php";
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// the page is not in allowed urls, loading "not found" page
|
// the page is not in allowed urls, loading "not found" page
|
||||||
include '../app/templates/error-notfound.php';
|
include '../app/templates/error-notfound.php';
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Feature\Middleware\Mock;
|
|
||||||
|
|
||||||
class Feedback {
|
|
||||||
public static function flash($type, $message) {}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Feature\Middleware\Mock;
|
|
||||||
|
|
||||||
class Session {
|
|
||||||
public static function startSession() {}
|
|
||||||
|
|
||||||
public static function isValidSession() {
|
|
||||||
return isset($_SESSION["user_id"]) &&
|
|
||||||
isset($_SESSION["username"]) &&
|
|
||||||
(!isset($_SESSION["LAST_ACTIVITY"]) ||
|
|
||||||
$_SESSION["LAST_ACTIVITY"] > time() - 7200 ||
|
|
||||||
isset($_SESSION["REMEMBER_ME"]));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function cleanup($config) {
|
|
||||||
$_SESSION = [];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
require_once dirname(__DIR__, 3) . '/app/includes/session_middleware.php';
|
||||||
use Tests\Feature\Middleware\Mock\Session;
|
|
||||||
use Tests\Feature\Middleware\Mock\Feedback;
|
|
||||||
|
|
||||||
require_once __DIR__ . '/MockSession.php';
|
use PHPUnit\Framework\TestCase;
|
||||||
require_once __DIR__ . '/MockFeedback.php';
|
|
||||||
|
|
||||||
class SessionMiddlewareTest extends TestCase
|
class SessionMiddlewareTest extends TestCase
|
||||||
{
|
{
|
||||||
|
@ -41,24 +38,11 @@ class SessionMiddlewareTest extends TestCase
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
$_SESSION = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function applyMiddleware()
|
public function testSessionStart()
|
||||||
{
|
{
|
||||||
// Check session validity
|
$result = applySessionMiddleware($this->config, $this->app_root);
|
||||||
if (!Session::isValidSession()) {
|
|
||||||
// Session invalid, clean up
|
|
||||||
Session::cleanup($this->config);
|
|
||||||
Feedback::flash("LOGIN", "SESSION_TIMEOUT");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testValidSession()
|
|
||||||
{
|
|
||||||
$result = $this->applyMiddleware();
|
|
||||||
|
|
||||||
$this->assertTrue($result);
|
$this->assertTrue($result);
|
||||||
$this->assertArrayHasKey('LAST_ACTIVITY', $_SESSION);
|
$this->assertArrayHasKey('LAST_ACTIVITY', $_SESSION);
|
||||||
|
@ -70,10 +54,24 @@ class SessionMiddlewareTest extends TestCase
|
||||||
public function testSessionTimeout()
|
public function testSessionTimeout()
|
||||||
{
|
{
|
||||||
$_SESSION['LAST_ACTIVITY'] = time() - (self::SESSION_TIMEOUT + 60); // 2 hours + 1 minute ago
|
$_SESSION['LAST_ACTIVITY'] = time() - (self::SESSION_TIMEOUT + 60); // 2 hours + 1 minute ago
|
||||||
$result = $this->applyMiddleware();
|
|
||||||
|
$result = applySessionMiddleware($this->config, $this->app_root);
|
||||||
|
|
||||||
$this->assertFalse($result);
|
$this->assertFalse($result);
|
||||||
$this->assertEmpty($_SESSION);
|
$this->assertArrayNotHasKey('user_id', $_SESSION, 'Session should be cleared after timeout');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSessionRegeneration()
|
||||||
|
{
|
||||||
|
$now = time();
|
||||||
|
$_SESSION['CREATED'] = $now - 1900; // 31+ minutes ago
|
||||||
|
|
||||||
|
$result = applySessionMiddleware($this->config, $this->app_root);
|
||||||
|
|
||||||
|
$this->assertTrue($result);
|
||||||
|
$this->assertEquals(1, $_SESSION['user_id']);
|
||||||
|
$this->assertGreaterThanOrEqual($now - 1900, $_SESSION['CREATED']);
|
||||||
|
$this->assertLessThanOrEqual($now + 10, $_SESSION['CREATED']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRememberMe()
|
public function testRememberMe()
|
||||||
|
@ -81,7 +79,7 @@ class SessionMiddlewareTest extends TestCase
|
||||||
$_SESSION['REMEMBER_ME'] = true;
|
$_SESSION['REMEMBER_ME'] = true;
|
||||||
$_SESSION['LAST_ACTIVITY'] = time() - (self::SESSION_TIMEOUT + 60); // More than 2 hours ago
|
$_SESSION['LAST_ACTIVITY'] = time() - (self::SESSION_TIMEOUT + 60); // More than 2 hours ago
|
||||||
|
|
||||||
$result = $this->applyMiddleware();
|
$result = applySessionMiddleware($this->config, $this->app_root);
|
||||||
|
|
||||||
$this->assertTrue($result);
|
$this->assertTrue($result);
|
||||||
$this->assertArrayHasKey('user_id', $_SESSION);
|
$this->assertArrayHasKey('user_id', $_SESSION);
|
||||||
|
@ -90,19 +88,19 @@ class SessionMiddlewareTest extends TestCase
|
||||||
public function testNoUserSession()
|
public function testNoUserSession()
|
||||||
{
|
{
|
||||||
unset($_SESSION['user_id']);
|
unset($_SESSION['user_id']);
|
||||||
$result = $this->applyMiddleware();
|
$result = applySessionMiddleware($this->config, $this->app_root);
|
||||||
|
|
||||||
$this->assertFalse($result);
|
$this->assertFalse($result);
|
||||||
$this->assertEmpty($_SESSION);
|
$this->assertArrayNotHasKey('user_id', $_SESSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInvalidSession()
|
public function testSessionHeaders()
|
||||||
{
|
{
|
||||||
$_SESSION['LAST_ACTIVITY'] = time() - (self::SESSION_TIMEOUT + 60); // 2 hours + 1 minute ago
|
$_SESSION['LAST_ACTIVITY'] = time() - (self::SESSION_TIMEOUT + 60); // 2 hours + 1 minute ago
|
||||||
unset($_SESSION['REMEMBER_ME']);
|
|
||||||
$result = $this->applyMiddleware();
|
$result = applySessionMiddleware($this->config, $this->app_root);
|
||||||
|
|
||||||
$this->assertFalse($result);
|
$this->assertFalse($result);
|
||||||
$this->assertEmpty($_SESSION);
|
$this->assertArrayNotHasKey('user_id', $_SESSION, 'Session should be cleared after timeout');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ require_once dirname(__DIR__, 3) . '/app/helpers/security.php';
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class TestLogger {
|
class TestLogger {
|
||||||
public static function insertLog($userId, $message, $scope = 'user') {
|
public static function insertLog($user_id, $message, $scope = 'user') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\Classes;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
|
|
||||||
class SessionTest extends TestCase
|
|
||||||
{
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
require_once __DIR__ . '/../../../app/classes/session.php';
|
|
||||||
$_SESSION = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function tearDown(): void
|
|
||||||
{
|
|
||||||
parent::tearDown();
|
|
||||||
$_SESSION = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetUsername()
|
|
||||||
{
|
|
||||||
$_SESSION['username'] = 'testuser';
|
|
||||||
$this->assertEquals('testuser', \Session::getUsername());
|
|
||||||
unset($_SESSION['username']);
|
|
||||||
$this->assertNull(\Session::getUsername());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetUserId()
|
|
||||||
{
|
|
||||||
$_SESSION['user_id'] = 123;
|
|
||||||
$this->assertEquals(123, \Session::getUserId());
|
|
||||||
unset($_SESSION['user_id']);
|
|
||||||
$this->assertNull(\Session::getUserId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testIsValidSession()
|
|
||||||
{
|
|
||||||
// Invalid without required variables
|
|
||||||
$this->assertFalse(\Session::isValidSession());
|
|
||||||
|
|
||||||
// Valid with required variables
|
|
||||||
$_SESSION['user_id'] = 123;
|
|
||||||
$_SESSION['username'] = 'testuser';
|
|
||||||
$_SESSION['LAST_ACTIVITY'] = time();
|
|
||||||
$this->assertTrue(\Session::isValidSession());
|
|
||||||
|
|
||||||
// Invalid after timeout
|
|
||||||
$_SESSION['LAST_ACTIVITY'] = time() - 8000; // More than 2 hours
|
|
||||||
$this->assertFalse(\Session::isValidSession());
|
|
||||||
|
|
||||||
// Valid with remember me
|
|
||||||
$_SESSION = [
|
|
||||||
'user_id' => 123,
|
|
||||||
'username' => 'testuser',
|
|
||||||
'REMEMBER_ME' => true,
|
|
||||||
'LAST_ACTIVITY' => time() - 8000
|
|
||||||
];
|
|
||||||
$this->assertTrue(\Session::isValidSession());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSetRememberMe()
|
|
||||||
{
|
|
||||||
\Session::setRememberMe(true);
|
|
||||||
$this->assertTrue($_SESSION['REMEMBER_ME']);
|
|
||||||
\Session::setRememberMe(false);
|
|
||||||
$this->assertFalse($_SESSION['REMEMBER_ME']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test2FASession()
|
|
||||||
{
|
|
||||||
// Test storing 2FA pending info
|
|
||||||
\Session::store2FAPending(123, 'testuser', true);
|
|
||||||
$this->assertEquals(123, $_SESSION['2fa_pending_user_id']);
|
|
||||||
$this->assertEquals('testuser', $_SESSION['2fa_pending_username']);
|
|
||||||
$this->assertTrue(isset($_SESSION['2fa_pending_remember']));
|
|
||||||
|
|
||||||
// Test getting 2FA pending info
|
|
||||||
$pendingInfo = \Session::get2FAPending();
|
|
||||||
$this->assertEquals([
|
|
||||||
'user_id' => 123,
|
|
||||||
'username' => 'testuser',
|
|
||||||
'remember_me' => true
|
|
||||||
], $pendingInfo);
|
|
||||||
|
|
||||||
// Test clearing 2FA pending info
|
|
||||||
\Session::clear2FAPending();
|
|
||||||
$this->assertNull(\Session::get2FAPending());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,186 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
require_once dirname(__DIR__, 3) . '/app/classes/database.php';
|
|
||||||
require_once dirname(__DIR__, 3) . '/app/classes/user.php';
|
|
||||||
require_once dirname(__DIR__, 3) . '/plugins/register/models/register.php';
|
|
||||||
require_once dirname(__DIR__, 3) . '/app/classes/ratelimiter.php';
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
|
|
||||||
class UserRegisterTest extends TestCase
|
|
||||||
{
|
|
||||||
private $db;
|
|
||||||
private $register;
|
|
||||||
private $user;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
// Set up test database
|
|
||||||
$this->db = new Database([
|
|
||||||
'type' => 'sqlite',
|
|
||||||
'dbFile' => ':memory:'
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Create users table
|
|
||||||
$this->db->getConnection()->exec("
|
|
||||||
CREATE TABLE users (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
username TEXT NOT NULL UNIQUE,
|
|
||||||
password TEXT NOT NULL
|
|
||||||
)
|
|
||||||
");
|
|
||||||
|
|
||||||
// Create users_meta table
|
|
||||||
$this->db->getConnection()->exec("
|
|
||||||
CREATE TABLE users_meta (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
user_id INTEGER NOT NULL,
|
|
||||||
name TEXT,
|
|
||||||
email TEXT,
|
|
||||||
timezone TEXT,
|
|
||||||
bio TEXT,
|
|
||||||
avatar TEXT,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
)
|
|
||||||
");
|
|
||||||
|
|
||||||
// Create user_2fa table for two-factor authentication
|
|
||||||
$this->db->getConnection()->exec("
|
|
||||||
CREATE TABLE user_2fa (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
user_id INTEGER NOT NULL,
|
|
||||||
secret_key TEXT NOT NULL,
|
|
||||||
backup_codes TEXT,
|
|
||||||
enabled TINYINT(1) NOT NULL DEFAULT 0,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
||||||
)
|
|
||||||
");
|
|
||||||
|
|
||||||
// Create tables for rate limiter
|
|
||||||
$this->db->getConnection()->exec("
|
|
||||||
CREATE TABLE login_attempts (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
ip_address TEXT NOT NULL,
|
|
||||||
username TEXT NOT NULL,
|
|
||||||
attempted_at TEXT DEFAULT (DATETIME('now'))
|
|
||||||
)
|
|
||||||
");
|
|
||||||
|
|
||||||
$this->db->getConnection()->exec("
|
|
||||||
CREATE TABLE ip_whitelist (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
ip_address TEXT NOT NULL UNIQUE,
|
|
||||||
is_network BOOLEAN DEFAULT 0 CHECK(is_network IN (0,1)),
|
|
||||||
description TEXT,
|
|
||||||
created_at TEXT DEFAULT (DATETIME('now')),
|
|
||||||
created_by TEXT
|
|
||||||
)
|
|
||||||
");
|
|
||||||
|
|
||||||
$this->db->getConnection()->exec("
|
|
||||||
CREATE TABLE ip_blacklist (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
ip_address TEXT NOT NULL UNIQUE,
|
|
||||||
is_network BOOLEAN DEFAULT 0 CHECK(is_network IN (0,1)),
|
|
||||||
reason TEXT,
|
|
||||||
expiry_time TEXT NULL,
|
|
||||||
created_at TEXT DEFAULT (DATETIME('now')),
|
|
||||||
created_by TEXT
|
|
||||||
)
|
|
||||||
");
|
|
||||||
|
|
||||||
$this->register = new Register($this->db);
|
|
||||||
$this->user = new User($this->db);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRegister()
|
|
||||||
{
|
|
||||||
$result = $this->register->register('testuser', 'password123');
|
|
||||||
$this->assertTrue($result);
|
|
||||||
|
|
||||||
// Verify user was created
|
|
||||||
$stmt = $this->db->getConnection()->prepare('SELECT * FROM users WHERE username = ?');
|
|
||||||
$stmt->execute(['testuser']);
|
|
||||||
$user = $stmt->fetch(\PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
$this->assertEquals('testuser', $user['username']);
|
|
||||||
$this->assertTrue(password_verify('password123', $user['password']));
|
|
||||||
|
|
||||||
// Verify user_meta was created
|
|
||||||
$stmt = $this->db->getConnection()->prepare('SELECT * FROM users_meta WHERE user_id = ?');
|
|
||||||
$stmt->execute([$user['id']]);
|
|
||||||
$meta = $stmt->fetch(\PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
$this->assertNotNull($meta);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testLogin()
|
|
||||||
{
|
|
||||||
// Create a test user
|
|
||||||
$password = 'password123';
|
|
||||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
|
||||||
|
|
||||||
$stmt = $this->db->getConnection()->prepare('INSERT INTO users (username, password) VALUES (?, ?)');
|
|
||||||
$stmt->execute(['testuser', $hashedPassword]);
|
|
||||||
|
|
||||||
// Mock $_SERVER['REMOTE_ADDR'] for rate limiter
|
|
||||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
|
||||||
|
|
||||||
// Test successful login
|
|
||||||
try {
|
|
||||||
$result = $this->user->login('testuser', $password);
|
|
||||||
$this->assertIsArray($result);
|
|
||||||
$this->assertEquals('success', $result['status']);
|
|
||||||
$this->assertArrayHasKey('user_id', $result);
|
|
||||||
$this->assertArrayHasKey('username', $result);
|
|
||||||
$this->assertArrayHasKey('user_id', $_SESSION);
|
|
||||||
$this->assertArrayHasKey('CREATED', $_SESSION);
|
|
||||||
$this->assertArrayHasKey('LAST_ACTIVITY', $_SESSION);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$this->fail('Login should not throw an exception for valid credentials: ' . $e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test failed login
|
|
||||||
try {
|
|
||||||
$this->user->login('testuser', 'wrongpassword');
|
|
||||||
$this->fail('Login should throw an exception for invalid credentials');
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$this->assertStringContainsString('Invalid credentials', $e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test nonexistent user
|
|
||||||
try {
|
|
||||||
$this->user->login('nonexistent', $password);
|
|
||||||
$this->fail('Login should throw an exception for nonexistent user');
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$this->assertStringContainsString('Invalid credentials', $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetUserDetails()
|
|
||||||
{
|
|
||||||
// Create a test user
|
|
||||||
$stmt = $this->db->getConnection()->prepare('INSERT INTO users (username, password) VALUES (?, ?)');
|
|
||||||
$stmt->execute(['testuser', 'hashedpassword']);
|
|
||||||
$userId = $this->db->getConnection()->lastInsertId();
|
|
||||||
|
|
||||||
// Create user meta with some data
|
|
||||||
$stmt = $this->db->getConnection()->prepare('INSERT INTO users_meta (user_id, name, email) VALUES (?, ?, ?)');
|
|
||||||
$stmt->execute([$userId, 'Test User', 'test@example.com']);
|
|
||||||
|
|
||||||
$userDetails = $this->user->getUserDetails($userId);
|
|
||||||
$this->assertIsArray($userDetails);
|
|
||||||
$this->assertCount(1, $userDetails); // Should return one row
|
|
||||||
$user = $userDetails[0]; // Get the first row
|
|
||||||
$this->assertEquals('testuser', $user['username']);
|
|
||||||
$this->assertEquals('Test User', $user['name']);
|
|
||||||
$this->assertEquals('test@example.com', $user['email']);
|
|
||||||
|
|
||||||
// Test nonexistent user
|
|
||||||
$userDetails = $this->user->getUserDetails(999);
|
|
||||||
$this->assertEmpty($userDetails);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -93,6 +93,27 @@ class UserTest extends TestCase
|
||||||
$this->user = new User($this->db);
|
$this->user = new User($this->db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRegister()
|
||||||
|
{
|
||||||
|
$result = $this->user->register('testuser', 'password123');
|
||||||
|
$this->assertTrue($result);
|
||||||
|
|
||||||
|
// Verify user was created
|
||||||
|
$stmt = $this->db->getConnection()->prepare('SELECT * FROM users WHERE username = ?');
|
||||||
|
$stmt->execute(['testuser']);
|
||||||
|
$user = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$this->assertEquals('testuser', $user['username']);
|
||||||
|
$this->assertTrue(password_verify('password123', $user['password']));
|
||||||
|
|
||||||
|
// Verify user_meta was created
|
||||||
|
$stmt = $this->db->getConnection()->prepare('SELECT * FROM users_meta WHERE user_id = ?');
|
||||||
|
$stmt->execute([$user['id']]);
|
||||||
|
$meta = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$this->assertNotNull($meta);
|
||||||
|
}
|
||||||
|
|
||||||
public function testLogin()
|
public function testLogin()
|
||||||
{
|
{
|
||||||
// Create a test user
|
// Create a test user
|
||||||
|
|
Loading…
Reference in New Issue