Sync current dev state
This commit is contained in:
@@ -750,4 +750,211 @@ class VAuth
|
||||
'token' => substr($token, 0, 8) . '...' // Log partial token for debugging
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate JWT token for API authentication
|
||||
* @param array $user User data
|
||||
* @param int|null $expiryTime Optional custom expiry time in seconds
|
||||
* @return string JWT token
|
||||
*/
|
||||
public function generateJWTToken($user, $expiryTime = null)
|
||||
{
|
||||
try {
|
||||
$expiryTime = $expiryTime ?? (24 * 60 * 60); // Default 24 hours
|
||||
|
||||
$header = json_encode([
|
||||
'typ' => 'JWT',
|
||||
'alg' => 'HS256'
|
||||
]);
|
||||
|
||||
$payload = json_encode([
|
||||
'user_id' => $user['user_id'],
|
||||
'username' => $user['username'],
|
||||
'email' => $user['email'],
|
||||
'role' => $user['role'] ?? 'member',
|
||||
'iat' => time(),
|
||||
'exp' => time() + $expiryTime
|
||||
]);
|
||||
|
||||
$headerEncoded = $this->base64UrlEncode($header);
|
||||
$payloadEncoded = $this->base64UrlEncode($payload);
|
||||
|
||||
$jwtSecret = getenv('JWT_SECRET') ?: (defined('JWT_SECRET') ? JWT_SECRET : 'change_this_jwt_secret');
|
||||
$signature = hash_hmac('sha256', $headerEncoded . '.' . $payloadEncoded, $jwtSecret, true);
|
||||
$signatureEncoded = $this->base64UrlEncode($signature);
|
||||
|
||||
return $headerEncoded . '.' . $payloadEncoded . '.' . $signatureEncoded;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('JWT generation failed', [
|
||||
'error' => $e->getMessage(),
|
||||
'user_id' => $user['user_id'] ?? 'unknown'
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate JWT token
|
||||
* @param string $token JWT token
|
||||
* @return array|null User data or null if invalid
|
||||
*/
|
||||
public function validateJWTToken($token)
|
||||
{
|
||||
try {
|
||||
$parts = explode('.', $token);
|
||||
if (count($parts) !== 3) {
|
||||
$this->logger->logSecurityEvent('Invalid JWT format', ['token' => substr($token, 0, 20) . '...']);
|
||||
return null;
|
||||
}
|
||||
|
||||
list($headerEncoded, $payloadEncoded, $signatureProvided) = $parts;
|
||||
|
||||
// Verify signature
|
||||
$jwtSecret = getenv('JWT_SECRET') ?: (defined('JWT_SECRET') ? JWT_SECRET : 'change_this_jwt_secret');
|
||||
$expectedSignature = hash_hmac('sha256', $headerEncoded . '.' . $payloadEncoded, $jwtSecret, true);
|
||||
$expectedSignatureEncoded = $this->base64UrlEncode($expectedSignature);
|
||||
|
||||
if (!hash_equals($expectedSignatureEncoded, $signatureProvided)) {
|
||||
$this->logger->logSecurityEvent('JWT signature verification failed', []);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Decode payload
|
||||
$payload = json_decode($this->base64UrlDecode($payloadEncoded), true);
|
||||
|
||||
if (!$payload || !isset($payload['user_id'])) {
|
||||
$this->logger->logSecurityEvent('Invalid JWT payload', []);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check expiration
|
||||
if (isset($payload['exp']) && $payload['exp'] < time()) {
|
||||
$this->logger->logSecurityEvent('JWT token expired', ['user_id' => $payload['user_id']]);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Verify user exists and is active
|
||||
$sql = "SELECT `user_id`, `username`, `email`, `role`, `status`
|
||||
FROM `db_users`
|
||||
WHERE `user_id` = ? AND `status` = 'active'";
|
||||
$result = $this->db->dbConnection()->Execute($sql, [$payload['user_id']]);
|
||||
|
||||
if (!$result || $result->EOF) {
|
||||
$this->logger->logSecurityEvent('JWT user not found or inactive', ['user_id' => $payload['user_id']]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$user = $result->fields;
|
||||
|
||||
return [
|
||||
'user_id' => $user['user_id'],
|
||||
'username' => $user['username'],
|
||||
'email' => $user['email'],
|
||||
'role' => $user['role']
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('JWT validation error', [
|
||||
'error' => $e->getMessage(),
|
||||
'token' => substr($token, 0, 20) . '...'
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Login with JWT token return (for API clients)
|
||||
* @param string $identifier Username or email
|
||||
* @param string $password Password
|
||||
* @param int|null $expiryTime Optional token expiry time
|
||||
* @return array Result with token and user data
|
||||
*/
|
||||
public function loginWithToken($identifier, $password, $expiryTime = null)
|
||||
{
|
||||
try {
|
||||
// Use regular login to validate credentials
|
||||
$loginResult = $this->login($identifier, $password, false);
|
||||
|
||||
if (!$loginResult['success']) {
|
||||
return $loginResult;
|
||||
}
|
||||
|
||||
// Generate JWT token
|
||||
$token = $this->generateJWTToken($loginResult['user'], $expiryTime);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Login successful',
|
||||
'token' => $token,
|
||||
'token_type' => 'Bearer',
|
||||
'expires_in' => $expiryTime ?? (24 * 60 * 60),
|
||||
'user' => $loginResult['user']
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('Token login error', [
|
||||
'error' => $e->getMessage(),
|
||||
'identifier' => $identifier ?? 'unknown'
|
||||
]);
|
||||
return ['success' => false, 'message' => 'An error occurred during login'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate request via Bearer token (for API requests)
|
||||
* @param string|null $authHeader Authorization header value
|
||||
* @return array|null User data or null if not authenticated
|
||||
*/
|
||||
public function authenticateBearer($authHeader = null)
|
||||
{
|
||||
try {
|
||||
// Get Authorization header if not provided
|
||||
if ($authHeader === null) {
|
||||
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? '';
|
||||
|
||||
// Apache mod_rewrite workaround
|
||||
if (empty($authHeader) && function_exists('apache_request_headers')) {
|
||||
$headers = apache_request_headers();
|
||||
$authHeader = $headers['Authorization'] ?? $headers['authorization'] ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($authHeader)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract Bearer token
|
||||
if (strpos($authHeader, 'Bearer ') === 0) {
|
||||
$token = substr($authHeader, 7);
|
||||
return $this->validateJWTToken($token);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('Bearer authentication error', ['error' => $e->getMessage()]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 URL-safe encoding
|
||||
* @param string $data Data to encode
|
||||
* @return string Encoded data
|
||||
*/
|
||||
private function base64UrlEncode($data)
|
||||
{
|
||||
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 URL-safe decoding
|
||||
* @param string $data Data to decode
|
||||
* @return string Decoded data
|
||||
*/
|
||||
private function base64UrlDecode($data)
|
||||
{
|
||||
return base64_decode(strtr($data, '-_', '+/'));
|
||||
}
|
||||
}
|
||||
@@ -130,7 +130,8 @@ class VDatabase
|
||||
|
||||
if (is_array($cfg_vars) and count($cfg_vars) > 0) {
|
||||
foreach ($cfg_vars as $key => $post_field) {
|
||||
$query = $db->execute(sprintf("UPDATE `%s` SET `cfg_data` = '%s' WHERE `cfg_name` = '%s' LIMIT 1; ", $db_tbl, $post_field, $key));
|
||||
// Fixed: Changed cfg_data to cfg_value to match actual database column name
|
||||
$query = $db->execute(sprintf("UPDATE `%s` SET `cfg_value` = '%s' WHERE `cfg_name` = '%s' LIMIT 1; ", $db_tbl, $post_field, $key));
|
||||
$count = $db->Affected_Rows() > 0 ? $count + 1 : $count;
|
||||
|
||||
if ($_GET['s'] == 'backend-menu-entry1-sub9' and $cfg['activity_logging'] == 1) {
|
||||
@@ -271,14 +272,15 @@ class VDatabase
|
||||
|
||||
$q_get = '`cfg_name` IN ("' . implode('", "', $settings_array) . '")';
|
||||
|
||||
$q_result = $db->Execute(sprintf("SELECT `cfg_name`, `cfg_data` FROM `%s` WHERE %s;", $db_table, $q_get));
|
||||
// Fixed: Changed cfg_data to cfg_value to match actual database column name
|
||||
$q_result = $db->Execute(sprintf("SELECT `cfg_name`, `cfg_value` FROM `%s` WHERE %s;", $db_table, $q_get));
|
||||
|
||||
if ($q_result) {
|
||||
while (!$q_result->EOF) {
|
||||
$cfg_name = $q_result->fields['cfg_name'];
|
||||
$cfg_data = $q_result->fields['cfg_data'];
|
||||
$cfg[$cfg_name] = $cfg_data;
|
||||
$smarty->assign($cfg_name, $cfg_data);
|
||||
$cfg_value = $q_result->fields['cfg_value'];
|
||||
$cfg[$cfg_name] = $cfg_value;
|
||||
$smarty->assign($cfg_name, $cfg_value);
|
||||
@$q_result->MoveNext();
|
||||
}
|
||||
}
|
||||
@@ -458,6 +460,62 @@ class VDatabase
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* General-purpose query execution method
|
||||
* Returns array of results instead of ADORecordSet for easier frontend use
|
||||
*
|
||||
* @param string $sql SQL query to execute
|
||||
* @param array $params Optional parameters for prepared statement
|
||||
* @param int|false $cache_time Optional cache time in seconds (false = no cache)
|
||||
* @return array Array of associative arrays with query results, empty array on failure
|
||||
*/
|
||||
public function execute($sql, $params = [], $cache_time = false)
|
||||
{
|
||||
global $db;
|
||||
$rows = [];
|
||||
|
||||
try {
|
||||
// Validate SQL input
|
||||
if (empty($sql) || !is_string($sql)) {
|
||||
throw new InvalidArgumentException('Invalid SQL query');
|
||||
}
|
||||
|
||||
// Ensure params is an array
|
||||
if (!is_array($params)) {
|
||||
$params = [$params];
|
||||
}
|
||||
|
||||
// Execute query with or without caching
|
||||
if ($cache_time && is_numeric($cache_time) && $cache_time > 0) {
|
||||
$result = $db->CacheExecute($cache_time, $sql, $params);
|
||||
} else {
|
||||
$result = $db->Execute($sql, $params);
|
||||
}
|
||||
|
||||
// Check for query errors
|
||||
if (!$result) {
|
||||
$logger = VLogger::getInstance();
|
||||
$logger->logDatabaseError($db->ErrorMsg(), $sql, $params);
|
||||
return [];
|
||||
}
|
||||
|
||||
// Convert ADORecordSet to plain array
|
||||
if ($result && !$result->EOF) {
|
||||
while (!$result->EOF) {
|
||||
$rows[] = $result->fields;
|
||||
$result->MoveNext();
|
||||
}
|
||||
}
|
||||
|
||||
return $rows;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$logger = VLogger::getInstance();
|
||||
$logger->logDatabaseError($e->getMessage(), $sql ?? '', $params ?? []);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize input for database queries
|
||||
* @param mixed $input Input to sanitize
|
||||
|
||||
@@ -4047,7 +4047,7 @@ class VFiles
|
||||
}
|
||||
|
||||
/* number format */
|
||||
public function numFormat($for)
|
||||
public static function numFormat($for)
|
||||
{
|
||||
return VGenerate::nrf($for);
|
||||
}
|
||||
@@ -4109,7 +4109,7 @@ class VFiles
|
||||
}
|
||||
}
|
||||
/* duration from seconds */
|
||||
public function fileDuration($seconds_count)
|
||||
public static function fileDuration($seconds_count)
|
||||
{
|
||||
$delimiter = ':';
|
||||
|
||||
|
||||
@@ -630,7 +630,7 @@ class VGenerate
|
||||
return $html;
|
||||
}
|
||||
/* generate file href html */
|
||||
public function fileHref($type, $key, $title = '')
|
||||
public static function fileHref($type, $key, $title = '')
|
||||
{
|
||||
require 'f_core/config.href.php';
|
||||
global $class_database;
|
||||
|
||||
@@ -381,7 +381,8 @@ class VMiddleware
|
||||
$this->sendAPIResponse(['success' => false, 'message' => 'Authentication required'], 401);
|
||||
} else {
|
||||
$redirectUrl = urlencode($_SERVER['REQUEST_URI'] ?? '/');
|
||||
header("Location: /login?redirect={$redirectUrl}");
|
||||
if (strpos($_SERVER["REQUEST_URI"] ?? "", "/signin") === 0) { http_response_code(401); return; }
|
||||
header("Location: /signin?redirect={$redirectUrl}");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ defined('_ISVALID') or header('Location: /error');
|
||||
class VRedirect
|
||||
{
|
||||
/* do redirect */
|
||||
public function to($url, $code = 301)
|
||||
public function to($code = '', $url)
|
||||
{
|
||||
session_write_close();
|
||||
if (headers_sent()) {
|
||||
|
||||
@@ -722,7 +722,7 @@ class VUseraccount
|
||||
}
|
||||
}
|
||||
/* get the user profile image */
|
||||
public function getProfileImage_inc(int $usr_key, $usr_photo = '', int $inc)
|
||||
public static function getProfileImage_inc(int $usr_key, $usr_photo = '', int $inc)
|
||||
{
|
||||
global $cfg, $class_database, $db;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user