diff --git a/db/migrations/20260218_create_license_table.sql b/db/migrations/20260218_create_license_table.sql new file mode 100644 index 0000000..d3eb201 --- /dev/null +++ b/db/migrations/20260218_create_license_table.sql @@ -0,0 +1,13 @@ +-- Migration: Create system_license table +-- Created at: 2026-02-18 + +CREATE TABLE IF NOT EXISTS system_license ( + id INT AUTO_INCREMENT PRIMARY KEY, + license_key VARCHAR(255) NOT NULL, + activation_token TEXT DEFAULT NULL, + fingerprint VARCHAR(255) DEFAULT NULL, + status ENUM('pending', 'active', 'expired', 'suspended') DEFAULT 'pending', + activated_at DATETIME DEFAULT NULL, + last_checked_at DATETIME DEFAULT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); diff --git a/index.php b/index.php index 40ecc7c..6875128 100644 --- a/index.php +++ b/index.php @@ -23,6 +23,78 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { file_put_contents('post_debug.log', date('Y-m-d H:i:s') . " - POST: " . json_encode($_POST) . "\n", FILE_APPEND); } require_once 'db/config.php'; +require_once 'lib/LicenseService.php'; + +// Licensing Middleware +$is_activated = LicenseService::isActivated(); +$page = $_GET['page'] ?? 'dashboard'; + +if (!$is_activated && $page !== 'activate') { + header("Location: index.php?page=activate"); + exit; +} + +// Activation Page UI (accessible without login) +if ($page === 'activate') { + $error = ''; + $success = ''; + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['activate'])) { + $res = LicenseService::activate($_POST['license_key'] ?? ''); + if ($res['success']) { + $success = "System activated successfully! Redirecting..."; + header("refresh:2;url=index.php"); + } else { + $error = $res['error']; + } + } + ?> + + + + + + Product Activation + + + + +
+

Activate Product

+

Please enter your serial key to continue using the application.

+ + +
+ + +
+ + +
+
+ + +
Example: FLAT-8822-1192-3301
+
+
+ +
+
+ +
+
+

Need a key? Contact Support

+
+
+ + + 'users_view', 'users' => 'users_view', 'backups' => 'users_view', + 'logs' => 'users_view', ]; if (isset($page_permissions[$page]) && !can($page_permissions[$page])) { @@ -1397,6 +1470,17 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; + + + @@ -5576,6 +5660,40 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; + +
+
+
+
System Logs
+

Monitor system activity and errors

+
+
+ +
+
+
+
+
--- " . htmlspecialchars(basename($file)) . " ---
"; + $lines = shell_exec("tail -n 50 " . escapeshellarg($path)); + echo "
" . htmlspecialchars((string)$lines) . "
"; + } + } + if (!$found_logs) { + echo "

No accessible log files found.

"; + } + ?> +
+
+ diff --git a/lib/LicenseService.php b/lib/LicenseService.php new file mode 100644 index 0000000..e7da6a4 --- /dev/null +++ b/lib/LicenseService.php @@ -0,0 +1,130 @@ +prepare("SELECT * FROM system_license WHERE status = 'active' LIMIT 1"); + $stmt->execute(); + $license = $stmt->fetch(); + + if (!$license) return false; + + // 1. Verify fingerprint matches (Physical Protection) + if ($license['fingerprint'] !== self::getFingerprint()) { + return false; + } + + // 2. Periodic Remote Validation (e.g., every 7 days) + // This ensures if you disable a key on your server, the client will stop working. + $last_check = strtotime($license['activated_at']); // In real use, store 'last_verified_at' + if (time() - $last_check > (7 * 24 * 60 * 60)) { + // It's been more than 7 days, let's re-verify + $res = self::callRemoteApi('/verify', [ + 'license_key' => $license['license_key'], + 'fingerprint' => $license['fingerprint'], + 'token' => $license['activation_token'] + ]); + + if (!$res['success']) { + db()->exec("UPDATE system_license SET status = 'suspended'"); + return false; + } + + // Update last verified timestamp + db()->exec("UPDATE system_license SET activated_at = NOW()"); + } + + return true; + } + + /** + * Attempts to activate the product online. + */ + public static function activate($license_key) { + $license_key = trim($license_key); + $fingerprint = self::getFingerprint(); + + // Call remote API for real validation + $response = self::callRemoteApi('/activate', [ + 'license_key' => $license_key, + 'fingerprint' => $fingerprint, + 'domain' => $_SERVER['HTTP_HOST'] ?? 'unknown', + 'product' => 'Flatlogic Admin Panel' + ]); + + if (!$response['success']) { + return ['success' => false, 'error' => $response['error'] ?? 'Remote activation failed.']; + } + + require_once __DIR__ . '/../db/config.php'; + + // Clear previous pending/failed attempts + db()->exec("DELETE FROM system_license"); + + $stmt = db()->prepare("INSERT INTO system_license (license_key, fingerprint, status, activated_at, activation_token) VALUES (?, ?, 'active', NOW(), ?)"); + $token = $response['activation_token'] ?? bin2hex(random_bytes(32)); + $stmt->execute([$license_key, $fingerprint, $token]); + + return ['success' => true]; + } + + /** + * Remote API Caller + */ + private static function callRemoteApi($endpoint, $params) { + // In a real production environment, this would hit your licensing server. + // For this demonstration, we simulate the request logic. + + $url = self::$remote_api_url . $endpoint; + + /* + // Real implementation would look like this: + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params)); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); + $resp = curl_exec($ch); + curl_close($ch); + $data = json_decode($resp, true); + return $data; + */ + + // SIMULATION: If the key starts with 'FLAT-' (case-insensitive), we treat it as valid. + $clean_key = strtoupper(trim($params['license_key'] ?? '')); + if (strpos($clean_key, 'FLAT-') === 0) { + // Check if it's a verification request + if ($endpoint === '/verify') { + return ['success' => true]; + } + + return [ + 'success' => true, + 'activation_token' => hash('sha256', $params['license_key'] . $params['fingerprint'] . 'SECRET_SALT') + ]; + } + + return [ + 'success' => false, + 'error' => 'License key invalid or expired. Please contact support.' + ]; + } +} diff --git a/licensing_server_sample.php b/licensing_server_sample.php new file mode 100644 index 0000000..48dd8fb --- /dev/null +++ b/licensing_server_sample.php @@ -0,0 +1,53 @@ + ['status' => 'active', 'max_activations' => 1], + 'FLAT-TEST-KEY-0001' => ['status' => 'active', 'max_activations' => 3], +]; + +// 2. Mock Database of current activations (Key => Fingerprint) +$activations = [ + // 'FLAT-8822-1192-3301' => 'hash_from_client' +]; + +$input = json_decode(file_get_contents('php://input'), true); +$endpoint = $_SERVER['PATH_INFO'] ?? ''; + +if ($endpoint === '/activate') { + $key = strtoupper(trim($input['license_key'] ?? '')); + $fingerprint = $input['fingerprint'] ?? ''; + + if (!isset($valid_keys[$key])) { + echo json_encode(['success' => false, 'error' => 'Invalid license key.']); + exit; + } + + if ($valid_keys[$key]['status'] !== 'active') { + echo json_encode(['success' => false, 'error' => 'License is expired or suspended.']); + exit; + } + + // Check if already activated on another machine + if (isset($activations[$key]) && $activations[$key] !== $fingerprint) { + echo json_encode(['success' => false, 'error' => 'License already in use on another server.']); + exit; + } + + // Success: Return a signed token + echo json_encode([ + 'success' => true, + 'activation_token' => hash_hmac('sha256', $key . $fingerprint, 'YOUR_SECRET_SERVER_SALT') + ]); +} + +if ($endpoint === '/verify') { + // Similar logic to check if the token is still valid or if the key was revoked + echo json_encode(['success' => true]); +} diff --git a/post_debug.log b/post_debug.log index 0da4d7f..cd1e1c7 100644 --- a/post_debug.log +++ b/post_debug.log @@ -6,3 +6,6 @@ 2026-02-18 10:10:35 - POST: {"name":"Accountant","permissions":["accounting_view","accounting_add","accounting_edit"],"add_role_group":""} 2026-02-18 10:10:56 - POST: {"id":"7","delete_role_group":""} 2026-02-18 10:12:05 - POST: {"create_backup":""} +2026-02-18 10:46:04 - POST: {"license_key":"Flat-8822-1192-3301","activate":""} +2026-02-18 10:46:14 - POST: {"license_key":": FLAT-8822-1192-3301","activate":""} +2026-02-18 10:48:04 - POST: {"license_key":" FLAT-8822-1192-3301","activate":""}