Merge pull request #96 from GameServerPanel/copilot/fix-harden-billing-module

This commit is contained in:
Frank Harris 2026-05-02 06:17:40 -07:00 committed by GitHub
commit 59a7eef4bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 106 additions and 71 deletions

View file

@ -36,7 +36,7 @@ function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
$db = false; $db = false;
try { try {
// suppress direct output; we'll log errors and show a friendly message // suppress direct output; we'll log errors and show a friendly message
$db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
} catch (Throwable $e) { } catch (Throwable $e) {
error_log('[admin_coupons] mysqli_connect exception: ' . $e->getMessage()); error_log('[admin_coupons] mysqli_connect exception: ' . $e->getMessage());
$db = false; $db = false;

View file

@ -12,7 +12,7 @@ require_once __DIR__ . '/classes/GatewayFactory.php';
function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
if (!$db) die('DB connection failed'); if (!$db) die('DB connection failed');
mysqli_set_charset($db, 'utf8mb4'); mysqli_set_charset($db, 'utf8mb4');
$prefix = $table_prefix ?? 'gsp_'; $prefix = $table_prefix ?? 'gsp_';

View file

@ -10,7 +10,7 @@ require_once __DIR__ . '/classes/BillingRepository.php';
function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
$transactions = []; $transactions = [];
$errorMsg = ''; $errorMsg = '';
if (!$db) { if (!$db) {

View file

@ -18,8 +18,8 @@ $siteBaseUrl = isset($SITE_BASE_URL) ? trim((string)$SITE_BASE_URL) : '';
// Protect this page: require admin // Protect this page: require admin
require_once(__DIR__ . '/includes/admin_auth.php'); require_once(__DIR__ . '/includes/admin_auth.php');
// Create database connection (admin_auth already validated DB but we need connection for UI ops) // Create database connection
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
if (!$db) { if (!$db) {
die("Connection failed: " . mysqli_connect_error()); die("Connection failed: " . mysqli_connect_error());
} }
@ -29,7 +29,7 @@ include(__DIR__ . '/includes/top.php');
include(__DIR__ . '/includes/menu.php'); include(__DIR__ . '/includes/menu.php');
echo "<div class='panel mb-12'><strong>Need the XML field reference?</strong> "; echo "<div class='panel mb-12'><strong>Need the XML field reference?</strong> ";
echo "<a href=\"/modules/billing/docs/xml_notes.php\" target=\"_blank\" rel=\"noopener\">Open XML Notes</a>"; echo "<a href=\"docs/xml_notes.php\" target=\"_blank\" rel=\"noopener\">Open XML Notes</a>";
echo "</div>"; echo "</div>";
/* show errors during setup */ /* show errors during setup */

View file

@ -28,7 +28,7 @@ $billing_db_opened_by_bootstrap = false;
*/ */
function billing_get_db() function billing_get_db()
{ {
global $billing_db, $db, $db_host, $db_user, $db_pass, $db_name, $billing_db_opened_by_bootstrap; global $billing_db, $db, $db_host, $db_user, $db_pass, $db_name, $db_port, $billing_db_opened_by_bootstrap;
if (!empty($billing_db) && ($billing_db instanceof mysqli)) { if (!empty($billing_db) && ($billing_db instanceof mysqli)) {
return $billing_db; return $billing_db;
} }
@ -36,8 +36,9 @@ function billing_get_db()
$billing_db = $db; $billing_db = $db;
return $billing_db; return $billing_db;
} }
$port = isset($db_port) ? (int)$db_port : null;
// Try to connect (suppress warnings; caller may check return value) // Try to connect (suppress warnings; caller may check return value)
$conn = @mysqli_connect($db_host ?? null, $db_user ?? null, $db_pass ?? null, $db_name ?? null); $conn = @mysqli_connect($db_host ?? null, $db_user ?? null, $db_pass ?? null, $db_name ?? null, $port);
if ($conn) { if ($conn) {
// Set charset when available // Set charset when available
if (function_exists('mysqli_set_charset')) { if (function_exists('mysqli_set_charset')) {

View file

@ -37,7 +37,7 @@ if (defined('BILLING_CONFIG_PATH') && is_readable(BILLING_CONFIG_PATH)) {
echo "Trying DB connection...\n"; echo "Trying DB connection...\n";
$ok = false; $ok = false;
if (isset($db_host)) { if (isset($db_host)) {
$db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
if ($db) { if ($db) {
echo "DB connect: OK (host=$db_host db=$db_name)\n"; echo "DB connect: OK (host=$db_host db=$db_name)\n";
$ok = true; $ok = true;

View file

@ -14,7 +14,7 @@ require_once(__DIR__ . '/bootstrap.php');
/** @var string $table_prefix Table prefix for database tables */ /** @var string $table_prefix Table prefix for database tables */
// Create database connection // Create database connection
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
if (!$db) { if (!$db) {
die("Connection failed: " . mysqli_connect_error()); die("Connection failed: " . mysqli_connect_error());
} }

View file

@ -4,17 +4,8 @@ require_once(__DIR__ . '/session_bridge.php');
// If not logged in, redirect to login // If not logged in, redirect to login
if (empty($_SESSION['website_user_id'])) { if (empty($_SESSION['website_user_id'])) {
// Build absolute login URL to avoid browser-relative resolution issues $loginUrl = rtrim(dirname($_SERVER['SCRIPT_NAME'] ?? '/'), '/\\') . '/login.php';
$script = $_SERVER['SCRIPT_NAME'] ?? ''; $returnTo = $_SERVER['SCRIPT_NAME'] ?? '/';
$siteRoot = '/';
$pos = strpos($script, '/_website');
if ($pos !== false) {
$siteRoot = substr($script, 0, $pos + strlen('/_website'));
} else {
$siteRoot = rtrim(dirname($script), '/\\');
}
$loginUrl = $siteRoot . '/login.php';
$returnTo = $siteRoot . '/' . basename($_SERVER['PHP_SELF']);
header('Location: ' . $loginUrl . '?return_to=' . urlencode($returnTo)); header('Location: ' . $loginUrl . '?return_to=' . urlencode($returnTo));
exit(); exit();
} }
@ -29,15 +20,13 @@ require_once(__DIR__ . '/config_loader.php');
/** @var string $db_name Database name */ /** @var string $db_name Database name */
/** @var string $table_prefix Table prefix for database tables */ /** @var string $table_prefix Table prefix for database tables */
$auth_db_port = isset($db_port) ? (int)$db_port : null;
// Use a local connection variable so we don't clash with pages that also use $db // Use a local connection variable so we don't clash with pages that also use $db
$auth_db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name); $auth_db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name, $auth_db_port);
if (!$auth_db) { if (!$auth_db) {
// If DB unavailable, deny access gracefully // If DB unavailable, deny access gracefully
// Redirect to absolute login URL $loginUrl = rtrim(dirname($_SERVER['SCRIPT_NAME'] ?? '/'), '/\\') . '/login.php';
$script = $_SERVER['SCRIPT_NAME'] ?? ''; header('Location: ' . $loginUrl);
$pos = strpos($script, '/_website');
$siteRoot = $pos !== false ? substr($script, 0, $pos + strlen('/_website')) : rtrim(dirname($script), '/\\');
header('Location: ' . $siteRoot . '/login.php');
exit(); exit();
} }
@ -52,15 +41,10 @@ mysqli_close($auth_db);
if (strtolower($role) !== 'admin') { if (strtolower($role) !== 'admin') {
// Not an admin — redirect to login or home // Not an admin — redirect to login or home
// Redirect to absolute login URL $loginUrl = rtrim(dirname($_SERVER['SCRIPT_NAME'] ?? '/'), '/\\') . '/login.php';
$script = $_SERVER['SCRIPT_NAME'] ?? ''; header('Location: ' . $loginUrl);
$pos = strpos($script, '/_website');
$siteRoot = $pos !== false ? substr($script, 0, $pos + strlen('/_website')) : rtrim(dirname($script), '/\\');
header('Location: ' . $siteRoot . '/login.php');
exit(); exit();
} }
// If we reach here, user is an admin // If we reach here, user is an admin
?> ?>

View file

@ -0,0 +1,43 @@
<?php
###############################################
# Billing Website Configuration Example
#
# Copy this file to config.inc.php and fill in
# your actual settings.
# config.inc.php is excluded from version control.
#
# This file is used by modules/billing/ both as a
# standalone website and as a panel-integrated module.
# The billing module reads ONLY this file — it does NOT
# depend on the parent panel's includes/config.inc.php.
###############################################
# --- Database connection ---
$db_host = "localhost";
$db_port = "3306"; // MySQL port (default 3306)
$db_user = "your_db_user";
$db_pass = "your_db_password";
$db_name = "your_db_name"; // Panel database name (e.g. "gsp" or "panel")
$table_prefix = "gsp_"; // Table prefix used in the panel database
$db_type = "mysql";
# --- Site base URL ---
# Leave empty to use relative paths (works for any install path).
# Set to your full base URL (without trailing slash) if you need absolute URLs:
# e.g. "https://gameservers.world" or "http://173.208.136.11/testing/modules/billing"
$SITE_BASE_URL = '';
# --- Background image ---
# Relative to the billing site root.
$SITE_BACKGROUND = 'images/dark.jpg';
# --- Data directory ---
# Absolute path where payment webhook JSON files are stored.
# Default: modules/billing/data/
$SITE_DATA_DIR = realpath(__DIR__ . '/..') . DIRECTORY_SEPARATOR . 'data';
# --- PayPal settings ---
$paypal_sandbox = true; // Set to false for live payments
$paypal_client_id = ''; // Your PayPal Client ID
$paypal_client_secret = ''; // Your PayPal Client Secret
$paypal_webhook_id = ''; // Your PayPal Webhook ID (for webhook signature verification)

View file

@ -8,6 +8,7 @@
# database configuration in includes/config.inc.php # database configuration in includes/config.inc.php
############################################### ###############################################
$db_host="localhost"; $db_host="localhost";
$db_port="3306";
$db_user="localuser"; $db_user="localuser";
$db_pass="Pkloyn7yvpht!"; $db_pass="Pkloyn7yvpht!";
$db_name="panel"; $db_name="panel";

View file

@ -2,27 +2,44 @@
/** /**
* Billing config loader * Billing config loader
* *
* Attempts to load the main panel config file first (../includes/config.inc.php). * Priority order (standalone-first):
* If that file is not readable, falls back to a module-local config.inc.php copy. * 1. modules/billing/includes/config.inc.php (local billing config always wins when present)
* When neither file exists, output a plain-text error and stop execution so that * 2. <panel_root>/includes/config.inc.php (panel config fallback when no local config)
* the admin knows to copy the config locally. *
* This ensures that copying modules/billing/ to any web root works correctly
* after editing its own config.inc.php, without being overridden by a parent
* panel installation that may have a different database name.
*/ */
if (defined('BILLING_CONFIG_LOADED')) { if (defined('BILLING_CONFIG_LOADED')) {
return; return;
} }
$localConfig = __DIR__ . '/config.inc.php';
$attempted = [];
// Always prefer the local billing config so the module is self-contained.
if (is_readable($localConfig)) {
$attempted[] = $localConfig;
require_once $localConfig;
if (!defined('BILLING_CONFIG_PATH')) {
define('BILLING_CONFIG_PATH', $localConfig);
}
define('BILLING_CONFIG_LOADED', true);
return;
}
$attempted[] = $localConfig;
// Fallback: try to load the panel's config (useful when running embedded inside the panel
// and no local copy has been made yet).
$panelConfig = null; $panelConfig = null;
$projectRoot = realpath(__DIR__ . '/../../..'); $projectRoot = realpath(__DIR__ . '/../../..');
if ($projectRoot !== false) { if ($projectRoot !== false) {
$panelConfig = $projectRoot . '/includes/config.inc.php'; $panelConfig = $projectRoot . '/includes/config.inc.php';
} else { } else {
// Fallback relative path without resolving symlinks $panelConfig = __DIR__ . '/../../../includes/config.inc.php';
$panelConfig = __DIR__ . '/../../..' . '/includes/config.inc.php';
} }
$localConfig = __DIR__ . '/config.inc.php';
$attempted = [];
if ($panelConfig && is_readable($panelConfig)) { if ($panelConfig && is_readable($panelConfig)) {
$attempted[] = $panelConfig; $attempted[] = $panelConfig;
require_once $panelConfig; require_once $panelConfig;
@ -34,17 +51,6 @@ if ($panelConfig && is_readable($panelConfig)) {
} }
$attempted[] = $panelConfig; $attempted[] = $panelConfig;
if (is_readable($localConfig)) {
$attempted[] = $localConfig;
require_once $localConfig;
if (!defined('BILLING_CONFIG_PATH')) {
define('BILLING_CONFIG_PATH', $localConfig);
}
define('BILLING_CONFIG_LOADED', true);
return;
}
$attempted[] = $localConfig;
$message = "GSP Billing module cannot find config.inc.php.\n"; $message = "GSP Billing module cannot find config.inc.php.\n";
$message .= "Looked in:\n"; $message .= "Looked in:\n";

View file

@ -58,7 +58,8 @@ if ($is_logged_in) {
if (isset($db) && $db instanceof mysqli) { if (isset($db) && $db instanceof mysqli) {
$menu_db = $db; $menu_db = $db;
} else { } else {
$menu_db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name); $menu_db_port = isset($db_port) ? (int)$db_port : null;
$menu_db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name, $menu_db_port);
$menu_db_opened = true; $menu_db_opened = true;
} }

View file

@ -20,10 +20,9 @@ require_once(__DIR__ . '/includes/log.php');
/** @var string $db_name Database name */ /** @var string $db_name Database name */
/** @var string $table_prefix Table prefix for database tables */ /** @var string $table_prefix Table prefix for database tables */
// Determine site root up to /_website so we can enforce absolute redirects within this site // Determine site root (directory of this script) for building absolute redirects within this site
$script = $_SERVER['SCRIPT_NAME'] ?? ''; $script = $_SERVER['SCRIPT_NAME'] ?? '';
$pos = strpos($script, '/_website'); $SITE_ROOT_PATH = rtrim(dirname($script), '/\\');
$SITE_ROOT_PATH = $pos !== false ? substr($script, 0, $pos + strlen('/_website')) : rtrim(dirname($script), '/\\');
// Strict sanitizer that returns an absolute path under $SITE_ROOT_PATH or empty string on invalid // Strict sanitizer that returns an absolute path under $SITE_ROOT_PATH or empty string on invalid
$sanitize_return_path = function($p) use ($SITE_ROOT_PATH) { $sanitize_return_path = function($p) use ($SITE_ROOT_PATH) {
@ -50,7 +49,7 @@ $sanitize_return_path = function($p) use ($SITE_ROOT_PATH) {
}; };
// Create database connection // Create database connection
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
if (!$db) { if (!$db) {
die("Connection failed: " . mysqli_connect_error()); die("Connection failed: " . mysqli_connect_error());
} }

View file

@ -38,7 +38,7 @@ require_once(__DIR__ . '/bootstrap.php');
/** @var string $table_prefix Table prefix for database tables */ /** @var string $table_prefix Table prefix for database tables */
// Create database connection // Create database connection
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
if (!$db) { if (!$db) {
die("Connection failed: " . mysqli_connect_error()); die("Connection failed: " . mysqli_connect_error());
} }

View file

@ -21,7 +21,7 @@ require_once(__DIR__ . '/bootstrap.php');
/** @var string $table_prefix Table prefix for database tables */ /** @var string $table_prefix Table prefix for database tables */
// Create database connection // Create database connection
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
if (!$db) { if (!$db) {
die("Connection failed: " . mysqli_connect_error()); die("Connection failed: " . mysqli_connect_error());
} }

View file

@ -34,7 +34,7 @@ require_once(__DIR__ . '/bootstrap.php');
/** @var string $table_prefix Table prefix for database tables */ /** @var string $table_prefix Table prefix for database tables */
// Create database connection // Create database connection
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
if (!$db) { if (!$db) {
die("Connection failed: " . mysqli_connect_error()); die("Connection failed: " . mysqli_connect_error());
} }

View file

@ -22,7 +22,7 @@ $user_id = isset($_SESSION['website_user_id']) ? intval($_SESSION['website_user_
(isset($_SESSION['user_id']) ? intval($_SESSION['user_id']) : 0); (isset($_SESSION['user_id']) ? intval($_SESSION['user_id']) : 0);
// Connect to database // Connect to database
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
$orders = []; $orders = [];
$total_paid = 0; $total_paid = 0;

View file

@ -12,7 +12,7 @@ require_once(__DIR__ . '/bootstrap.php');
// Simple registration form (creates a user in {table_prefix}users with MD5 password) // Simple registration form (creates a user in {table_prefix}users with MD5 password)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['username']) && !empty($_POST['password'])) { if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['username']) && !empty($_POST['password'])) {
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
if ($db) { if ($db) {
$username = trim($_POST['username']); $username = trim($_POST['username']);
$password = $_POST['password']; $password = $_POST['password'];

View file

@ -19,7 +19,7 @@ require_once(__DIR__ . '/includes/log.php');
/** @var string $table_prefix Table prefix for database tables */ /** @var string $table_prefix Table prefix for database tables */
// Connect to DB (use mysqli like other website modules) // Connect to DB (use mysqli like other website modules)
$db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
if (!$db) { if (!$db) {
// fail silently and redirect back // fail silently and redirect back
$back = $_SERVER['HTTP_REFERER'] ?? 'my_account.php'; $back = $_SERVER['HTTP_REFERER'] ?? 'my_account.php';

View file

@ -14,7 +14,7 @@ require_once(__DIR__ . '/bootstrap.php');
/** @var string $table_prefix Table prefix for database tables */ /** @var string $table_prefix Table prefix for database tables */
// Create database connection // Create database connection
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
if (!$db) { if (!$db) {
die("Connection failed: " . mysqli_connect_error()); die("Connection failed: " . mysqli_connect_error());
} }

View file

@ -11,7 +11,7 @@
require_once(__DIR__ . '/bootstrap.php'); require_once(__DIR__ . '/bootstrap.php');
// Create database connection // Create database connection
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
if (!$db) { if (!$db) {
die("Connection failed: " . mysqli_connect_error()); die("Connection failed: " . mysqli_connect_error());
} }

View file

@ -22,7 +22,7 @@ require_once(__DIR__ . '/bootstrap.php');
/** @var string $table_prefix Table prefix for database tables */ /** @var string $table_prefix Table prefix for database tables */
// Create database connection // Create database connection
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
if (!$db) { if (!$db) {
die("Connection failed: " . mysqli_connect_error()); die("Connection failed: " . mysqli_connect_error());
} }

View file

@ -14,7 +14,7 @@
require_once(__DIR__ . '/bootstrap.php'); require_once(__DIR__ . '/bootstrap.php');
// Create database connection // Create database connection
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
if (!$db) { if (!$db) {
die("Connection failed: " . mysqli_connect_error()); die("Connection failed: " . mysqli_connect_error());
} }

View file

@ -1 +1 @@
Last Updated at 12:49pm on 2026-02-05 Last Updated at 1:15pm on 2026-02-05

View file

@ -1,6 +1,6 @@
<?php <?php
require_once(__DIR__ . '/../includes/config_loader.php'); require_once(__DIR__ . '/../includes/config_loader.php');
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name, isset($db_port) ? (int)$db_port : null);
if (!$db) { if (!$db) {
echo "DB connect failed: " . mysqli_connect_error() . PHP_EOL; echo "DB connect failed: " . mysqli_connect_error() . PHP_EOL;
exit(1); exit(1);