| = htmlspecialchars($a['product_name']) ?> |
= htmlspecialchars($a['license_key']) ?> |
- = htmlspecialchars($a['hardware_id']) ?> |
+
+ = htmlspecialchars($a['hardware_id']) ?>
+
+ Quelle: = htmlspecialchars($a['hwid_source']) ?>
+
+ |
+
+
+ = htmlspecialchars(strtoupper($a['platform'] ?? 'WIN')) ?> (v= (int)($a['hwid_version'] ?? 1) ?>)
+
+ |
= htmlspecialchars($a['hostname'] ?? '-') ?> |
= htmlspecialchars($a['last_seen']) ?> |
@@ -1215,6 +1247,13 @@ $baseUrl = $protocol . '://' . $host;
= $a['is_blocked'] ? 'Entsperren' : 'Sperren' ?>
+
|
diff --git a/sql/migrations/v2_hardware_id.sql b/sql/migrations/v2_hardware_id.sql
new file mode 100644
index 0000000..df09e1c
--- /dev/null
+++ b/sql/migrations/v2_hardware_id.sql
@@ -0,0 +1,7 @@
+-- Migration: Hardware-ID v2 support for license_activations
+-- Date: 2026-08-06
+
+ALTER TABLE license_activations
+ ADD COLUMN hwid_version TINYINT NOT NULL DEFAULT 1 AFTER hardware_id,
+ ADD COLUMN hwid_source VARCHAR(32) NULL AFTER hwid_version,
+ ADD COLUMN platform VARCHAR(8) NULL AFTER hwid_source;
diff --git a/sql/schema.sql b/sql/schema.sql
index 3214ce6..4abd8de 100644
--- a/sql/schema.sql
+++ b/sql/schema.sql
@@ -70,6 +70,9 @@ CREATE TABLE license_activations (
id INT AUTO_INCREMENT PRIMARY KEY,
license_id INT NOT NULL,
hardware_id VARCHAR(128) NOT NULL,
+ hwid_version TINYINT NOT NULL DEFAULT 1,
+ hwid_source VARCHAR(32) NULL,
+ platform VARCHAR(8) NULL,
hostname VARCHAR(190) NULL,
app_version VARCHAR(64) NULL,
first_seen DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
diff --git a/src/Modules/License/LicenseService.php b/src/Modules/License/LicenseService.php
index 9b76402..cd5e1b9 100644
--- a/src/Modules/License/LicenseService.php
+++ b/src/Modules/License/LicenseService.php
@@ -15,12 +15,16 @@ class LicenseService
public function validate(array $requestData, string $clientIp): array
{
- $productSlug = trim($requestData['product'] ?? '');
- $licenseKey = trim($requestData['license_key'] ?? '');
- $hardwareId = trim($requestData['hardware_id'] ?? '');
- $nonce = trim($requestData['nonce'] ?? '');
- $hostname = trim($requestData['hostname'] ?? '');
- $appVersion = trim($requestData['app_version'] ?? '');
+ $productSlug = trim($requestData['product'] ?? '');
+ $licenseKey = trim($requestData['license_key'] ?? '');
+ $hardwareId = trim($requestData['hardware_id'] ?? '');
+ $legacyHardwareId = trim($requestData['legacy_hardware_id'] ?? '');
+ $hwidVersion = (int)($requestData['hwid_version'] ?? 1);
+ $hwidSource = trim($requestData['hwid_source'] ?? '') ?: null;
+ $platform = trim($requestData['platform'] ?? '') ?: null;
+ $nonce = trim($requestData['nonce'] ?? '');
+ $hostname = trim($requestData['hostname'] ?? '');
+ $appVersion = trim($requestData['app_version'] ?? '');
$issuedAt = time();
$endpoints = $this->getEndpoints();
@@ -96,10 +100,47 @@ class LicenseService
}
// 4. Activation management
+ // 4.1 Try match by hardware_id (v2)
$stmt = $this->db->prepare('SELECT * FROM license_activations WHERE license_id = :lic_id AND hardware_id = :hw_id');
$stmt->execute([':lic_id' => $license['id'], ':hw_id' => $hardwareId]);
$activation = $stmt->fetch();
+ // 4.2 If not found by v2 hardware_id, try migration matching by legacy_hardware_id (v1)
+ if (!$activation && !empty($legacyHardwareId)) {
+ $legStmt = $this->db->prepare('SELECT * FROM license_activations WHERE license_id = :lic_id AND hardware_id = :legacy_hw_id');
+ $legStmt->execute([':lic_id' => $license['id'], ':legacy_hw_id' => $legacyHardwareId]);
+ $legacyActivation = $legStmt->fetch();
+
+ if ($legacyActivation) {
+ // Migrate activation to v2 format
+ $migUpd = $this->db->prepare('
+ UPDATE license_activations
+ SET hardware_id = :hw_id, hwid_version = :ver, hwid_source = :src, platform = :plat, last_seen = NOW(), hostname = :host, app_version = :app_ver
+ WHERE id = :id
+ ');
+ $migUpd->execute([
+ ':hw_id' => $hardwareId,
+ ':ver' => $hwidVersion,
+ ':src' => $hwidSource,
+ ':plat' => $platform,
+ ':host' => $hostname,
+ ':app_ver' => $appVersion,
+ ':id' => $legacyActivation['id']
+ ]);
+
+ Audit::log($this->db, 'api', 'hwid_migrated', [
+ 'license_id' => $license['id'],
+ 'old_hardware_id' => $legacyHardwareId,
+ 'new_hardware_id' => $hardwareId,
+ 'hwid_source' => $hwidSource,
+ 'platform' => $platform,
+ 'ip' => $clientIp
+ ]);
+
+ $activation = $legacyActivation;
+ }
+ }
+
if ($activation) {
if ((int)$activation['is_blocked'] === 1) {
return $makePayload('revoked', [
@@ -107,8 +148,20 @@ class LicenseService
'message' => 'This hardware activation is blocked'
]);
}
- $upd = $this->db->prepare('UPDATE license_activations SET last_seen = NOW(), hostname = :host, app_version = :ver WHERE id = :id');
- $upd->execute([':host' => $hostname, ':ver' => $appVersion, ':id' => $activation['id']]);
+ $upd = $this->db->prepare('
+ UPDATE license_activations
+ SET last_seen = NOW(), hostname = :host, app_version = :ver, hwid_version = :hw_ver,
+ hwid_source = COALESCE(:hw_src, hwid_source), platform = COALESCE(:plat, platform)
+ WHERE id = :id
+ ');
+ $upd->execute([
+ ':host' => $hostname,
+ ':ver' => $appVersion,
+ ':hw_ver' => $hwidVersion,
+ ':hw_src' => $hwidSource,
+ ':plat' => $platform,
+ ':id' => $activation['id']
+ ]);
} else {
$cntStmt = $this->db->prepare('SELECT COUNT(*) FROM license_activations WHERE license_id = :lic_id AND is_blocked = 0');
$cntStmt->execute([':lic_id' => $license['id']]);
@@ -121,10 +174,16 @@ class LicenseService
]);
}
- $ins = $this->db->prepare('INSERT INTO license_activations (license_id, hardware_id, hostname, app_version) VALUES (:lic_id, :hw_id, :host, :ver)');
+ $ins = $this->db->prepare('
+ INSERT INTO license_activations (license_id, hardware_id, hwid_version, hwid_source, platform, hostname, app_version)
+ VALUES (:lic_id, :hw_id, :hw_ver, :hw_src, :plat, :host, :ver)
+ ');
$ins->execute([
':lic_id' => $license['id'],
':hw_id' => $hardwareId,
+ ':hw_ver' => $hwidVersion,
+ ':hw_src' => $hwidSource,
+ ':plat' => $platform,
':host' => $hostname,
':ver' => $appVersion
]);