Fix login and order

This commit is contained in:
Frank Harris 2026-06-17 14:53:00 -05:00
parent dbecad8606
commit 484a36ce11
22 changed files with 399 additions and 520 deletions

View file

@ -63,24 +63,4 @@ $install_queries[1] = array(
KEY `idx_logger_home` (`home_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;");
$install_queries[2] = array(
"CREATE TABLE IF NOT EXISTS `".OGP_DB_PREFIX."sso_tokens`
(
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`token_hash` CHAR(64) NOT NULL,
`user_id` INT(11) NOT NULL,
`source` VARCHAR(32) NOT NULL,
`destination` VARCHAR(32) NOT NULL,
`created_at` DATETIME NOT NULL,
`expires_at` DATETIME NOT NULL,
`used_at` DATETIME DEFAULT NULL,
`nonce` VARCHAR(64) NOT NULL,
`originating_ip` VARCHAR(64) DEFAULT NULL,
`user_agent_hash` CHAR(64) DEFAULT NULL,
`return_path` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_sso_token_hash` (`token_hash`),
KEY `idx_sso_user_destination` (`user_id`, `destination`),
KEY `idx_sso_expires` (`expires_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
?>

View file

@ -29,7 +29,7 @@ function exec_ogp_module()
$projectRequestUrl = htmlspecialchars(gsp_project_request_url(), ENT_QUOTES, 'UTF-8');
$discordInviteUrl = htmlspecialchars(gsp_discord_invite_url(), ENT_QUOTES, 'UTF-8');
$serverStatusUrl = htmlspecialchars(gsp_server_status_url(), ENT_QUOTES, 'UTF-8');
$orderAnotherUrl = htmlspecialchars(gsp_panel_to_website_sso_url('serverlist.php'), ENT_QUOTES, 'UTF-8');
$orderAnotherUrl = htmlspecialchars(gsp_website_url('serverlist.php'), ENT_QUOTES, 'UTF-8');
$theme = isset($settings['theme']) ? $settings['theme'] : 'SimpleBootstrap';
$themeBase = 'themes/' . htmlspecialchars($theme, ENT_QUOTES, 'UTF-8') . '/images/icons/';
$cards = array(

View file

@ -15,6 +15,7 @@ This module is the public Gameservers.World sales and documentation website.
Panel/modules/website/
index.php
serverlist.php
cart.php
docs.php
login.php
pricing.php
@ -34,6 +35,7 @@ Panel/modules/website/
pages/
home.php
game_servers.php
cart.php
documentation.php
pricing.php
locations.php
@ -72,22 +74,19 @@ Effects:
- `serverlist.php` shows a clean fallback message instead of a fatal include error
- shared navigation never crashes because billing config is missing
## Shared Accounts and SSO
## Shared Accounts
The Panel user table is the identity source for the website. Website login checks
the same `users` table and legacy Panel password hash format instead of creating
a second password database.
The website and Panel run on different parent domains, so PHP session cookies are
not shared. Authenticated navigation uses short-lived one-time SSO tokens stored
in `OGP_DB_PREFIXsso_tokens`:
not shared. SSO is deferred in the current implementation. Users can use the same
username and password on both sites, but the website and Panel keep separate
sessions. Control Panel and staff links point directly to the configured Panel URL.
- website to Panel: `Panel/modules/website/sso.php` creates a token and redirects to `Panel/sso.php`
- Panel to website: `Panel/sso.php` creates a token and redirects to `Panel/modules/website/sso.php`
- tokens are random, stored only as SHA-256 hashes, expire in 30-60 seconds, and are marked used after one successful validation
- passwords, password hashes, permanent API keys, and PHP session IDs are never placed in URLs
Production SSO requires HTTPS. Localhost is allowed only for development testing.
`sso.php` endpoints are compatibility redirects only. They do not create tokens or
sessions.
## Ordering
@ -96,15 +95,16 @@ entry point:
- `order.php?service_id=...`
That page validates `service_id` server-side against enabled catalog rows before
continuing. Logged-out customers are sent through `login.php` and returned to the
same service after successful login. The removed `billing/order.php` route is
obsolete and should not be used for customer-facing links.
That page validates `service_id` server-side against enabled catalog rows, then
lets anonymous visitors configure slots and location before adding the service to
the session cart. Login or registration is required only when the customer proceeds
to checkout. The removed `billing/order.php` route is obsolete and should not be
used for customer-facing links.
The website owns catalog display, login-return intent, pricing display, and
checkout entry. Payment approval and final server provisioning remain server-side
responsibilities; browser requests must not call private provisioning methods
directly.
The website owns catalog display, cart storage, checkout intent, pricing display,
and customer confirmation. Payment approval and final server provisioning remain
server-side responsibilities; browser requests must not call private provisioning
methods directly.
## Documentation source

View file

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/bootstrap.php';
$message = '';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = (string)($_POST['action'] ?? '');
if ($action === 'remove') {
website_cart_remove((string)($_POST['cart_key'] ?? ''));
header('Location: ' . website_cart_url(), true, 302);
exit;
}
if ($action === 'checkout') {
if (website_cart_count() === 0) {
$error = 'Your cart is empty.';
} elseif (!website_is_logged_in()) {
$_SESSION['website_checkout_intent'] = true;
$_SESSION['website_login_return'] = 'cart.php?checkout=1';
header('Location: ' . website_login_url('cart.php?checkout=1'), true, 302);
exit;
} else {
website_log_activity('Website checkout requested', (int)($_SESSION['website_user_id'] ?? 0), 'checkout_requested');
$message = 'Checkout is ready for account validation, but the payment gateway is not connected in this repository checkout. Please contact support to complete this order.';
}
}
}
if (isset($_GET['checkout']) && (string)$_GET['checkout'] === '1') {
if (website_cart_count() === 0) {
$error = 'Your cart is empty.';
} elseif (!website_is_logged_in()) {
$_SESSION['website_checkout_intent'] = true;
$_SESSION['website_login_return'] = 'cart.php?checkout=1';
header('Location: ' . website_login_url('cart.php?checkout=1'), true, 302);
exit;
} else {
$message = 'You are logged in and your cart is preserved. Payment checkout still needs the active payment runtime before public orders can be completed.';
}
}
website_render(
'cart.php',
[
'activePage' => 'cart',
'pageTitle' => 'Cart - Gameservers.World',
'metaDescription' => 'Review your Gameservers.World server cart before checkout.',
'canonicalPath' => 'cart.php',
'items' => website_cart_items(),
'cartTotal' => website_cart_total(),
'message' => $message,
'error' => $error,
]
);

View file

@ -19,7 +19,6 @@ return [
// Active panel URL. Do not point the public site at /panel/ unless that route is real.
'panel_url' => 'https://panel.iaregamer.com/',
'login_url' => 'https://panel.iaregamer.com/',
'panel_sso_url' => 'https://panel.iaregamer.com/sso.php',
'company' => [
'name' => 'Runlevel Systems',

View file

@ -3,9 +3,6 @@
declare(strict_types=1);
require_once __DIR__ . '/paths.php';
if (is_readable(WEBSITE_PANEL_INCLUDE_DIR . '/sso.php')) {
require_once WEBSITE_PANEL_INCLUDE_DIR . '/sso.php';
}
if (defined('GSP_WEBSITE_BOOTSTRAPPED')) {
return;
@ -44,7 +41,6 @@ $websiteDefaults = [
'billing_base_url' => '/billing',
'panel_url' => 'https://panel.iaregamer.com/',
'login_url' => 'https://panel.iaregamer.com/',
'panel_sso_url' => 'https://panel.iaregamer.com/sso.php',
'company' => [
'name' => 'Runlevel Systems',
'url' => 'https://runlevelsystems.com/',
@ -476,6 +472,9 @@ function website_authenticate_user(string $login, string $password): ?array
if (!$user || !website_verify_panel_password($user, $password)) {
return null;
}
if ((string)($user['users_role'] ?? '') === 'banned') {
return null;
}
return $user;
}
@ -543,7 +542,7 @@ function website_log_activity(string $message, int $userId = 0, string $eventTyp
}
$safeTable = $db->real_escape_string($table);
$ip = substr((function_exists('gsp_sso_client_ip') ? gsp_sso_client_ip() : (string)($_SERVER['REMOTE_ADDR'] ?? '')), 0, 255);
$ip = substr((string)($_SERVER['REMOTE_ADDR'] ?? ''), 0, 255);
$stmt = $db->prepare(
"INSERT INTO `{$safeTable}` (`date`, `user_id`, `ip`, `message`, `source_type`, `category`, `event_type`, `severity`)
VALUES (FROM_UNIXTIME(UNIX_TIMESTAMP(), '%d-%m-%Y %H:%i:%s'), ?, ?, ?, 'website', 'authentication', ?, 'info')"
@ -560,15 +559,16 @@ function website_log_activity(string $message, int $userId = 0, string $eventTyp
function website_safe_return_path(string $returnPath, string $default = 'index.php'): string
{
if (function_exists('gsp_sso_safe_return_path')) {
return gsp_sso_safe_return_path($returnPath, $default);
}
if ($returnPath === '' || preg_match('#^[a-z][a-z0-9+.-]*://#i', $returnPath) === 1 || str_starts_with($returnPath, '//')) {
return $default;
}
return ltrim($returnPath, '/');
$returnPath = ltrim($returnPath, '/');
if (str_contains($returnPath, "\0") || str_starts_with($returnPath, '../') || str_contains($returnPath, '/../')) {
return $default;
}
return $returnPath;
}
function website_login_url(string $returnPath = ''): string
@ -580,15 +580,9 @@ function website_login_url(string $returnPath = ''): string
return website_url($path);
}
function website_panel_sso_url(string $returnPath = 'home.php?m=dashboard&p=dashboard'): string
{
$path = 'sso.php?destination=panel&return=' . rawurlencode(website_safe_return_path($returnPath, 'home.php?m=dashboard&p=dashboard'));
return website_url($path);
}
function website_control_panel_url(string $returnPath = 'home.php?m=dashboard&p=dashboard'): string
{
return website_is_logged_in() ? website_panel_sso_url($returnPath) : website_login_url('panel');
return panel_url(website_safe_return_path($returnPath, 'home.php?m=dashboard&p=dashboard'));
}
function website_order_url(int|string $serviceId): string
@ -596,6 +590,21 @@ function website_order_url(int|string $serviceId): string
return website_url('order.php?service_id=' . rawurlencode((string)$serviceId));
}
function website_cart_url(): string
{
return website_url('cart.php');
}
function website_checkout_url(): string
{
return website_url('cart.php?checkout=1');
}
function website_register_url(string $returnPath = 'cart.php'): string
{
return panel_url('index.php?m=register');
}
function website_fetch_service_by_id(int $serviceId): ?array
{
$db = website_db();
@ -638,6 +647,96 @@ function website_fetch_service_by_id(int $serviceId): ?array
return $service;
}
function website_service_name(array $service): string
{
$name = trim((string)($service['cfg_game_name'] ?? ''));
if ($name === '') {
$name = trim((string)($service['service_name'] ?? ''));
}
return $name === '' ? 'Game Server' : $name;
}
function website_service_min_slots(array $service): int
{
foreach (['min_slots', 'minimum_slots', 'slots_min'] as $column) {
if (isset($service[$column]) && (int)$service[$column] > 0) {
return (int)$service[$column];
}
}
$pricing = website_config('pricing', []);
return max(1, (int)($pricing['standard_min_slots'] ?? 16));
}
function website_service_max_slots(array $service): int
{
foreach (['max_slots', 'maximum_slots', 'slots_max', 'max_players'] as $column) {
if (isset($service[$column]) && (int)$service[$column] > 0) {
return (int)$service[$column];
}
}
return 0;
}
function website_service_locations(array $service): array
{
$raw = trim((string)($service['remote_server_id'] ?? ''));
if ($raw === '') {
return [];
}
$locations = [];
foreach (preg_split('/\s*,\s*/', $raw) ?: [] as $remoteServerId) {
$remoteServerId = trim($remoteServerId);
if ($remoteServerId === '' || !ctype_digit($remoteServerId)) {
continue;
}
$locations[$remoteServerId] = 'Location ' . $remoteServerId;
}
return $locations;
}
function website_cart_items(): array
{
website_start_session();
return is_array($_SESSION['website_cart'] ?? null) ? $_SESSION['website_cart'] : [];
}
function website_cart_count(): int
{
return count(website_cart_items());
}
function website_cart_add(array $item): void
{
website_start_session();
if (!isset($_SESSION['website_cart']) || !is_array($_SESSION['website_cart'])) {
$_SESSION['website_cart'] = [];
}
$key = bin2hex(random_bytes(8));
$_SESSION['website_cart'][$key] = $item;
}
function website_cart_remove(string $key): void
{
website_start_session();
if (isset($_SESSION['website_cart'][$key])) {
unset($_SESSION['website_cart'][$key]);
}
}
function website_cart_total(): float
{
$total = 0.0;
foreach (website_cart_items() as $item) {
$total += (float)($item['monthly_total'] ?? 0);
}
return $total;
}
function website_billing_docs_root(): ?string
{
if (is_dir(WEBSITE_BILLING_DOCS_DIR)) {

View file

@ -40,17 +40,20 @@ $currentUser = website_current_user();
<li><a href="<?= website_escape(website_url('account.php')) ?>">My Account</a></li>
<li><a href="<?= website_escape(website_url('serverlist.php')) ?>">Order a Server</a></li>
<li><a href="<?= website_escape(website_control_panel_url()) ?>">Control Panel</a></li>
<li><a href="<?= website_escape(website_panel_sso_url('home.php?m=gamemanager&p=game_monitor')) ?>">My Servers</a></li>
<li><a href="<?= website_escape(panel_url('home.php?m=gamemanager&p=game_monitor')) ?>">My Servers</a></li>
<li><a href="<?= website_escape(website_cart_url()) ?>">Cart</a></li>
<li><a href="<?= website_escape(website_url('logout.php')) ?>">Log Out</a></li>
<?php else: ?>
<li><a href="<?= website_escape(website_login_url()) ?>">Account Login</a></li>
<li><a href="<?= website_escape(website_register_url()) ?>">Create Account</a></li>
<li><a href="<?= website_escape(website_url('serverlist.php')) ?>">Order a Server</a></li>
<li><a href="<?= website_escape(website_control_panel_url()) ?>">Control Panel</a></li>
<li><a href="<?= website_escape(website_cart_url()) ?>">Cart</a></li>
<?php endif; ?>
<li><a href="<?= website_escape(website_url('docs.php')) ?>">Server Guides</a></li>
<li><a href="<?= website_escape(website_custom_project_url()) ?>">Request Custom Work</a></li>
<?php if ($currentUser && website_current_user_is_staff()): ?>
<li><a href="<?= website_escape(website_panel_sso_url('home.php?m=administration&p=watch_logger')) ?>">Staff Tools</a></li>
<li><a href="<?= website_escape(panel_url('home.php?m=administration&p=watch_logger')) ?>">Staff Tools</a></li>
<?php endif; ?>
<?php if ($discordUrl !== ''): ?>
<li><a href="<?= website_escape($discordUrl) ?>" target="_blank" rel="noopener noreferrer">Discord</a></li>

View file

@ -3,6 +3,8 @@
declare(strict_types=1);
$activePage = $activePage ?? '';
$currentUser = website_current_user();
$cartCount = website_cart_count();
$navLinks = [
['key' => 'home', 'label' => 'Home', 'href' => website_url('index.php')],
['key' => 'servers', 'label' => 'Game Servers', 'href' => website_url('serverlist.php')],
@ -11,6 +13,15 @@ $navLinks = [
['key' => 'locations', 'label' => 'Locations', 'href' => website_url('locations.php')],
['key' => 'support', 'label' => 'Support', 'href' => website_url('support.php')],
];
if ($currentUser) {
$navLinks[] = ['key' => 'account', 'label' => 'My Account', 'href' => website_url('account.php')];
$navLinks[] = ['key' => 'orders', 'label' => 'My Orders', 'href' => website_url('cart.php')];
$navLinks[] = ['key' => 'servers_panel', 'label' => 'My Servers', 'href' => panel_url('home.php?m=gamemanager&p=game_monitor')];
} else {
$navLinks[] = ['key' => 'account', 'label' => 'Login', 'href' => website_login_url()];
$navLinks[] = ['key' => 'register', 'label' => 'Create Account', 'href' => website_register_url()];
}
$navLinks[] = ['key' => 'cart', 'label' => 'Cart' . ($cartCount > 0 ? ' (' . $cartCount . ')' : ''), 'href' => website_cart_url()];
?>
<header class="site-header">
<div class="container header-shell">
@ -40,6 +51,9 @@ $navLinks = [
<div class="header-actions" data-header-actions>
<a class="button button-secondary" href="<?= website_escape(website_custom_project_url()) ?>">Custom Projects</a>
<a class="button button-primary" href="<?= website_escape(website_control_panel_url()) ?>">Control Panel</a>
<?php if ($currentUser): ?>
<a class="button button-secondary" href="<?= website_escape(website_url('logout.php')) ?>">Logout</a>
<?php endif; ?>
</div>
</div>
</header>

View file

@ -9,7 +9,7 @@ $returnPath = website_safe_return_path((string)($_GET['return'] ?? $_POST['retur
if (website_is_logged_in()) {
if ($returnPath === 'panel') {
header('Location: ' . website_panel_sso_url(), true, 302);
header('Location: ' . website_control_panel_url(), true, 302);
exit;
}
header('Location: ' . website_url($returnPath), true, 302);
@ -27,7 +27,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
unset($_SESSION['website_login_return']);
if ($returnPath === 'panel') {
header('Location: ' . website_panel_sso_url(), true, 302);
header('Location: ' . website_control_panel_url(), true, 302);
exit;
}

View file

@ -6,6 +6,7 @@ require_once __DIR__ . '/includes/bootstrap.php';
$serviceId = filter_input(INPUT_GET, 'service_id', FILTER_VALIDATE_INT);
$service = $serviceId ? website_fetch_service_by_id((int)$serviceId) : null;
$error = '';
if (!$service) {
website_log_activity('Invalid or unavailable service_id requested from website order flow', (int)($_SESSION['website_user_id'] ?? 0), 'invalid_service');
@ -23,14 +24,45 @@ if (!$service) {
exit;
}
if (!website_is_logged_in()) {
$_SESSION['website_pending_order_service_id'] = (int)$service['service_id'];
$_SESSION['website_login_return'] = 'order.php?service_id=' . (int)$service['service_id'];
header('Location: ' . website_login_url('order.php?service_id=' . (int)$service['service_id']), true, 302);
exit;
$locations = website_service_locations($service);
$minSlots = website_service_min_slots($service);
$maxSlots = website_service_max_slots($service);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$slots = filter_input(INPUT_POST, 'slots', FILTER_VALIDATE_INT);
$locationId = trim((string)($_POST['location_id'] ?? ''));
$durationMonths = filter_input(INPUT_POST, 'duration_months', FILTER_VALIDATE_INT);
if ($slots === false || $slots === null || $slots < $minSlots) {
$error = 'Select at least ' . $minSlots . ' slots for this service.';
} elseif ($maxSlots > 0 && $slots > $maxSlots) {
$error = 'This service supports a maximum of ' . $maxSlots . ' slots.';
} elseif (empty($locations) || !array_key_exists($locationId, $locations)) {
$error = 'Select an available location for this service.';
} elseif (!in_array((int)$durationMonths, [1, 3, 6, 12], true)) {
$error = 'Select a valid billing duration.';
} else {
$priceMonthly = (float)($service['price_monthly'] ?? 0);
$monthlyTotal = $priceMonthly > 0 ? $priceMonthly : 0.0;
website_cart_add([
'service_id' => (int)$service['service_id'],
'service_name' => website_service_name($service),
'slots' => (int)$slots,
'location_id' => $locationId,
'location_name' => $locations[$locationId],
'duration_months' => (int)$durationMonths,
'price_monthly' => $priceMonthly,
'monthly_total' => $monthlyTotal,
'added_at' => time(),
]);
website_log_activity('Website cart item added for service_id ' . (int)$service['service_id'], (int)($_SESSION['website_user_id'] ?? 0), 'cart_item_added');
header('Location: ' . website_cart_url(), true, 302);
exit;
}
}
website_log_activity('Website order flow started for service_id ' . (int)$service['service_id'], (int)$_SESSION['website_user_id'], 'order_started');
website_log_activity('Website order flow opened for service_id ' . (int)$service['service_id'], (int)($_SESSION['website_user_id'] ?? 0), 'order_started');
website_render(
'order.php',
[
@ -39,6 +71,9 @@ website_render(
'metaDescription' => 'Configure your Gameservers.World game server order.',
'canonicalPath' => 'order.php',
'service' => $service,
'error' => null,
'error' => $error,
'locations' => $locations,
'minSlots' => $minSlots,
'maxSlots' => $maxSlots,
]
);

View file

@ -19,7 +19,7 @@ $isStaff = website_current_user_is_staff();
<h3>My Servers</h3>
<p>Open the GSP control panel to manage assigned game servers, files, logs, updates, backups, and server state.</p>
<div class="card-actions">
<a class="button button-primary" href="<?= website_escape(website_panel_sso_url('home.php?m=gamemanager&p=game_monitor')) ?>">Open My Servers</a>
<a class="button button-primary" href="<?= website_escape(panel_url('home.php?m=gamemanager&p=game_monitor')) ?>">Open My Servers</a>
</div>
</article>
<article class="summary-card">
@ -42,7 +42,7 @@ $isStaff = website_current_user_is_staff();
<h3>Staff Access</h3>
<p>Staff links are shown only after account role checks. Server-side authorization still applies inside the Panel.</p>
<div class="card-actions">
<a class="button button-secondary" href="<?= website_escape(website_panel_sso_url('home.php?m=administration&p=watch_logger')) ?>">Panel Administration</a>
<a class="button button-secondary" href="<?= website_escape(panel_url('home.php?m=administration&p=watch_logger')) ?>">Panel Administration</a>
</div>
</article>
<?php endif; ?>

View file

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
?>
<section class="page-heading">
<div class="container">
<h1>Cart</h1>
<p>Review selected server packages. You can configure and add servers before logging in; login or account creation is required when you proceed to checkout.</p>
</div>
</section>
<section class="section">
<div class="container">
<?php if ($error !== ''): ?>
<div class="alert warning"><?= website_escape($error) ?></div>
<?php endif; ?>
<?php if ($message !== ''): ?>
<div class="alert info"><?= website_escape($message) ?></div>
<?php endif; ?>
<?php if (empty($items)): ?>
<div class="empty-state">
<h2>Your cart is empty</h2>
<p>Browse the catalog to choose a game server, slots, and location.</p>
<div class="card-actions">
<a class="button button-primary" href="<?= website_escape(website_url('serverlist.php')) ?>">View Game Servers</a>
</div>
</div>
<?php else: ?>
<div class="summary-grid">
<?php foreach ($items as $key => $item): ?>
<article class="summary-card">
<h3><?= website_escape((string)($item['service_name'] ?? 'Game Server')) ?></h3>
<p>Service ID: <?= website_escape((string)($item['service_id'] ?? '')) ?></p>
<p>Slots: <?= website_escape((string)($item['slots'] ?? '')) ?></p>
<p>Location: <?= website_escape((string)($item['location_name'] ?? '')) ?></p>
<p>Billing duration: <?= website_escape((string)($item['duration_months'] ?? 1)) ?> month(s)</p>
<p><strong><?= ((float)($item['monthly_total'] ?? 0) > 0) ? '$' . website_escape(number_format((float)$item['monthly_total'], 2)) . ' / month' : 'Contact for pricing' ?></strong></p>
<form method="post" action="<?= website_escape(website_cart_url()) ?>">
<input type="hidden" name="action" value="remove">
<input type="hidden" name="cart_key" value="<?= website_escape((string)$key) ?>">
<button class="button button-secondary" type="submit">Remove</button>
</form>
</article>
<?php endforeach; ?>
</div>
<div class="summary-card" style="margin-top: 18px;">
<h3>Checkout</h3>
<p>Estimated monthly total: <strong><?= $cartTotal > 0 ? '$' . website_escape(number_format((float)$cartTotal, 2)) : 'Contact for pricing' ?></strong></p>
<p class="muted">Prices and service availability are revalidated server-side before payment or provisioning. Adding an item to the cart does not create a running server.</p>
<div class="card-actions">
<form method="post" action="<?= website_escape(website_cart_url()) ?>">
<input type="hidden" name="action" value="checkout">
<button class="button button-primary" type="submit">Proceed to Checkout</button>
</form>
<?php if (!website_is_logged_in()): ?>
<a class="button button-secondary" href="<?= website_escape(website_login_url('cart.php?checkout=1')) ?>">Log In</a>
<a class="button button-secondary" href="<?= website_escape(website_register_url('cart.php')) ?>">Create Account</a>
<?php endif; ?>
<a class="button button-secondary" href="<?= website_escape(website_url('serverlist.php')) ?>">Continue Shopping</a>
</div>
</div>
<?php endif; ?>
</div>
</section>

View file

@ -22,6 +22,7 @@ declare(strict_types=1);
<input id="password" name="password" type="password" autocomplete="current-password" required>
<button class="button button-primary" type="submit">Log In</button>
</form>
<p class="muted">After login, Control Panel and server-management links use a short-lived one-time SSO handoff. Passwords and PHP session IDs are never passed between domains.</p>
<p class="muted">Gameservers.World and the GSP Panel use the same account credentials, but they keep separate secure sessions. You may be asked to log in separately when opening the Panel.</p>
<p class="muted">Need an account? <a href="<?= website_escape(website_register_url($returnPath)) ?>">Create one through the GSP Panel registration page.</a></p>
</div>
</section>

View file

@ -25,18 +25,20 @@ endif;
$serviceName = trim((string)($service['cfg_game_name'] ?? $service['service_name'] ?? 'Game Server'));
$description = trim((string)($service['description'] ?? ''));
$price = (float)($service['price_monthly'] ?? 0);
$minSlots = (int)($service['min_slots'] ?? $service['minimum_slots'] ?? website_config('pricing', [])['standard_min_slots'] ?? 16);
$maxSlots = (int)($service['max_slots'] ?? $service['maximum_slots'] ?? 0);
$selectedSlots = max((int)$minSlots, (int)($_POST['slots'] ?? $minSlots));
?>
<section class="page-heading">
<div class="container">
<h1>Configure <?= website_escape($serviceName) ?></h1>
<p><?= website_escape($description !== '' ? $description : 'Review this server plan before checkout. Pricing and provisioning are validated server-side from the active catalog.') ?></p>
<p><?= website_escape($description !== '' ? $description : 'Configure this server package before adding it to your cart. Login is only required when you proceed to checkout.') ?></p>
</div>
</section>
<section class="section">
<div class="container">
<?php if ($error !== ''): ?>
<div class="alert warning"><?= website_escape($error) ?></div>
<?php endif; ?>
<div class="summary-grid">
<article class="summary-card">
<h3>Plan</h3>
@ -52,16 +54,35 @@ $maxSlots = (int)($service['max_slots'] ?? $service['maximum_slots'] ?? 0);
</article>
<article class="summary-card">
<h3>Checkout boundary</h3>
<p>This website validates the catalog service and keeps your shared account identity. Payment and final provisioning must complete server-side before the Panel creates a running game server.</p>
<p>You can add this server to your cart before logging in. Payment and final provisioning must complete server-side before the Panel creates a running game server.</p>
<p class="muted">The legacy <code>billing/order.php</code> route is no longer used.</p>
</article>
</div>
<div class="alert info" style="margin-top: 18px;">
Checkout/payment handlers are not present in this repository checkout. Contact support to complete this order or connect the active payment module before enabling public checkout.
</div>
<div class="card-actions" style="margin-top: 18px;">
<a class="button button-primary" href="<?= website_escape(website_url('support.php')) ?>">Contact Support</a>
<a class="button button-secondary" href="<?= website_escape(website_url('serverlist.php')) ?>">Back to Catalog</a>
</div>
<form class="website-form summary-card" method="post" action="<?= website_escape(website_order_url((int)$service['service_id'])) ?>" style="margin-top: 18px;">
<h3>Server configuration</h3>
<label for="slots">Slots</label>
<input id="slots" name="slots" type="number" min="<?= website_escape((string)$minSlots) ?>"<?= $maxSlots > 0 ? ' max="' . website_escape((string)$maxSlots) . '"' : '' ?> value="<?= website_escape((string)$selectedSlots) ?>" required>
<label for="location_id">Location</label>
<select id="location_id" name="location_id" required>
<?php foreach ($locations as $locationId => $locationName): ?>
<option value="<?= website_escape((string)$locationId) ?>"<?= (string)($_POST['location_id'] ?? '') === (string)$locationId ? ' selected' : '' ?>><?= website_escape($locationName) ?></option>
<?php endforeach; ?>
</select>
<label for="duration_months">Billing duration</label>
<select id="duration_months" name="duration_months" required>
<?php foreach ([1, 3, 6, 12] as $duration): ?>
<option value="<?= website_escape((string)$duration) ?>"<?= (int)($_POST['duration_months'] ?? 1) === $duration ? ' selected' : '' ?>><?= website_escape((string)$duration) ?> month<?= $duration === 1 ? '' : 's' ?></option>
<?php endforeach; ?>
</select>
<p class="muted">The website stores this selection in your cart and revalidates service, slots, location, and price during checkout.</p>
<div class="card-actions">
<button class="button button-primary" type="submit">Add to Cart</button>
<a class="button button-secondary" href="<?= website_escape(website_url('serverlist.php')) ?>">Back to Catalog</a>
</div>
</form>
</div>
</section>

View file

@ -4,78 +4,15 @@ declare(strict_types=1);
require_once __DIR__ . '/includes/bootstrap.php';
$db = website_db();
$prefix = website_table_prefix();
$destination = trim((string)($_GET['destination'] ?? 'panel'));
$returnPath = website_safe_return_path((string)($_GET['return'] ?? ''), 'home.php?m=dashboard&p=dashboard');
if (!$db instanceof mysqli || $prefix === '') {
http_response_code(503);
echo 'SSO is temporarily unavailable.';
website_log_activity('Deprecated website SSO endpoint redirected to direct login/navigation', (int)($_SESSION['website_user_id'] ?? 0), 'sso_deprecated_redirect');
if ($destination === 'panel') {
header('Location: ' . panel_url($returnPath), true, 302);
exit;
}
$token = trim((string)($_GET['token'] ?? ''));
if ($token !== '') {
if (!function_exists('gsp_sso_validate_token')) {
http_response_code(503);
echo 'SSO is not configured.';
exit;
}
$validation = gsp_sso_validate_token($db, $prefix, $token, 'website');
if (empty($validation['success'])) {
website_log_activity('Website SSO failed: ' . (string)$validation['error'], 0, 'sso_failure');
http_response_code(403);
echo website_escape((string)$validation['error']);
exit;
}
$user = website_panel_user_by_id((int)$validation['user_id']);
if (!$user) {
http_response_code(403);
echo 'SSO user is no longer available.';
exit;
}
website_set_user_session($user);
website_log_activity('Website SSO login succeeded for ' . (string)$user['users_login'], (int)$user['user_id'], 'sso_success');
$returnPath = website_safe_return_path((string)$validation['return_path'], 'index.php');
header('Location: ' . website_url($returnPath), true, 302);
exit;
}
$destination = trim((string)($_GET['destination'] ?? ''));
if ($destination !== 'panel') {
http_response_code(400);
echo 'Invalid SSO destination.';
exit;
}
$user = website_current_user();
if (!$user) {
$_SESSION['website_login_return'] = 'panel';
header('Location: ' . website_login_url('panel'), true, 302);
exit;
}
if (!function_exists('gsp_sso_create_token')) {
http_response_code(503);
echo 'SSO is not configured.';
exit;
}
$returnPath = website_safe_return_path((string)($_GET['return'] ?? 'home.php?m=dashboard&p=dashboard'), 'home.php?m=dashboard&p=dashboard');
$newToken = gsp_sso_create_token($db, $prefix, (int)$user['user_id'], 'website', 'panel', $returnPath, 60);
if ($newToken === null) {
http_response_code(403);
echo 'SSO requires HTTPS.';
exit;
}
website_log_activity('Website-to-panel SSO token created', (int)$user['user_id'], 'sso_token_created');
$panelSsoUrl = trim((string)website_config('panel_sso_url', ''));
if ($panelSsoUrl === '') {
$panelSsoUrl = panel_url('sso.php');
}
header('Location: ' . $panelSsoUrl . '?token=' . rawurlencode($newToken), true, 302);
header('Location: ' . website_url('index.php'), true, 302);
exit;