@extends('layouts.front') @section('meta') @endsection @section('title', 'License System — সম্পূর্ণ ডকুমেন্টেশন') @section('contents')
{{ route('docs.license_security_api') }}
Admin লাইসেন্স ম্যানেজমেন্ট, User পেমেন্ট, Public verify-license API, cache-signal, kill_switch, HMAC security, stolen domain এবং deploy গাইড — সব একসাথে।
{{ url('/') }}
POST /api/verify-license শুধু routes/web.php থেকে রেজিস্টার (CSRF exempt)।
routes/api.php এ ডুপ্লিকেট রুট রাখবেন না — Laravel API ফাইল আগে লোড হলে পুরনো ক্লায়েন্ট ভুল কন্ট্রোলারে যায়।
throttle:60,1 (প্রতি IP প্রতি মিনিটে ৬০ রিকোয়েস্ট)। সীমা ছাড়ালে HTTP 429।| এন্ডপয়েন্ট | কন্ট্রোলার | মেথড |
|---|---|---|
POST /api/verify-license | Admin\LicenseController | verifyLicense |
POST /api/log-unauthorized | Api\LicenseSecurityApiController | logUnauthorized |
GET /api/cache-signal | Api\LicenseSecurityApiController | cacheSignal |
POST /api/check-update | Api\CheckUpdateController | checkUpdate (+ signature) |
POST /api/download-update | Api\DownloadUpdateController | download |
ডোমেইন Normalize নিয়ম
| Input | Normalized Output |
|---|---|
https://www.example.com/path | example.com |
http://example.com/ | example.com |
www.EXAMPLE.COM | example.com |
Example.Com | example.com |
HMAC signature এই normalize করা স্ট্রিং দিয়েই গণনা করতে হবে।
licenses — মূল লাইসেন্স ডেটা| Column | Type | বিবরণ |
|---|---|---|
user_id | bigint | লাইসেন্সের মালিক (users) |
domain_name | varchar | Normalized ডোমেইন (www/https ছাড়া, lowercase) |
license_key | varchar | Format: LIC-XXXX-XXXX-XXXX |
expiry_date | datetime | মেয়াদ শেষের তারিখ |
status | int | 1 = Active 0 = Inactive |
stolen_domains — চুরি লগ| Column | বিবরণ |
|---|---|
domain_name | চেষ্টা করা ডোমেইন |
ip_address | আক্রমণকারীর IP |
cache_signal | Random token — পরিবর্তন হলে ক্লায়েন্ট cache মুছবে |
is_blocked | 1 = Blocked 0 = Unblocked |
status | 0 = Pending 1 = Resolved |
unauthorized_logs — Kill Switch লগ| Column | বিবরণ |
|---|---|
domain | ডোমেইন নাম |
ip | IP ঠিকানা |
reason | কারণ |
attempted_at | সময় |
kill_switch | 1 = wipe_out চালু 0 = বন্ধ |
license_renewal_orders — নবায়ন অর্ডার| Column | বিবরণ |
|---|---|
license_id | কোন লাইসেন্সের জন্য |
amount | পেমেন্টের পরিমাণ |
invoice_id | UddoktaPay Invoice ID |
status | pending / paid / cancelled |
POST /api/verify-licenseলাইসেন্স বৈধতা যাচাই। বডিতে signature থাকলে → HMAC মোড। না থাকলে → Legacy মোড। সব response এ action থাকে।
signature ফিল্ড নেই বা খালিstolen_domains এ auto-blockis_blocked = 0 (auto-unblock)cache_signal ও secure_tokensignature = hash_hmac('sha256', domain, HMAC_SALT)stolen_domains block হয় নাis_blocked = 0 (auto-unblock)cache_signal ও secure_tokenRequest ফিল্ড
| ফিল্ড | টাইপ | নোট |
|---|---|---|
domain | string | Required সার্ভার normalize করে। |
license_key | string | Required যেমন LIC-XXXX-XXXX-XXXX |
url | string | Optional Legacy মোডে stolen log এ সেভ হয়। |
signature | string | Optional থাকলে HMAC মোড চালু হয়। |
Response ফিল্ড
| ফিল্ড | মান | বিবরণ |
|---|---|---|
status | valid / invalid | লাইসেন্স বৈধ কিনা |
blocked | true / false | ডোমেইন blocked কিনা |
secure_token | md5 hash | md5(domain + TOKEN_SALT) |
cache_signal | random string / null | পরিবর্তন হলে ক্লায়েন্ট cache মুছবে |
action | none / wipe_out | wipe_out হলে সাইট disable করতে হবে |
{
"status": "valid",
"blocked": false,
"domain_name": "example.com",
"license_key": "LIC-ABCD-EFGH-IJKL",
"expiry_date": "2028-01-15 00:00:00",
"secure_token": "a1b2c3d4e5f6...",
"cache_signal": "xK9mP2rT...",
"action": "none"
}{
"status": "invalid",
"blocked": true,
"message": "License key or domain is incorrect or has expired.",
"action": "none"
}{
"status": "invalid",
"message": "Signature verification failed.",
"action": "none"
}cURL — Legacy
curl -X POST "{{ url('/api/verify-license') }}" \
-H "Content-Type: application/json" \
-d '{"domain":"example.com","license_key":"LIC-YOUR-KEY","url":"https://example.com"}'cURL — HMAC
curl -X POST "{{ url('/api/verify-license') }}" \
-H "Content-Type: application/json" \
-d '{"domain":"example.com","license_key":"LIC-YOUR-KEY","signature":"HMAC_SHA256_HEX_HERE"}'LicenseController এ — HMAC_SALT = 'your_secret_salt_key' এবং TOKEN_SALT = 'YOUR_SECRET_SALT_HERE_12345'। উৎপাদন ব্যবহারের আগে পরিবর্তন করুন।
GET /api/cache-signalBackground এ poll করুন (যেমন প্রতি ৩০ মিনিট)। signal বদলালে local cache মুছে পুনরায় verify করুন। প্রতিটি response এ action থাকে (kill_switch একই নিয়ম)।
{ "signal": null, "action": "none" }{ "signal": "default", "blocked": false, "action": "none" }{ "signal": "…token…", "blocked": true, "action": "wipe_out" }{ "signal": "xK9mP2rT...", "blocked": false, "action": "none" }stolen_domains.cache_signal নতুন random token পায়। পরের বার ক্লায়েন্ট poll করলে signal পরিবর্তিত দেখে cache মুছবে।
POST /api/check-updateনতুন লাইসেন্স: license_key + signature (LICENSE_SIGNATURE) বাধ্যতামূলক। বৈধ হলে script_updates থেকে version_compare।
| ফিল্ড | বাধ্যতা | বিবরণ |
|---|---|---|
domain | Required | Normalize হয়। |
license_key | Required | LICENSE_KEY |
signature | Required | LICENSE_SIGNATURE — DB signature_key |
script_name | Required | Admin আপলোডের নামের সাথে হুবহু মিল। |
current_version | Optional | Default 1.0.0। |
লাইসেন্স/সিগনেচার ব্যর্থ → HTTP 403। আপডেট থাকলে download_url = POST /api/download-update।
POST /api/download-updateZIP ডাউনলোড — একই domain, license_key, signature, script_name।
সফল → ZIP স্ট্রিম। Rate limit: 30/min।
POST /api/log-unauthorizedবডিতে reason ও attempted_at উভয়ই থাকলে নতুন ফরম্যাট (unauthorized_logs)। নইলে Legacy (stolen_domains updateOrCreate)।
Legacy বডি
| ফিল্ড | বাধ্যতা |
|---|---|
domain | Required |
ip | Optional |
url | Optional |
{ "status": "success" }নতুন বডি (UnauthorizedLog)
| ফিল্ড | বাধ্যতা |
|---|---|
domain | Required |
reason | Required |
attempted_at | Required |
ip | Optional |
url | Optional |
{ "status": "ok", "message": "লগ সংরক্ষিত হয়েছে।" }action: wipe_outUnauthorizedLog::killSwitchWantsWipeOut($domain) — unauthorized_logs এ ওই ডোমেইনের kill_switch = true সারি থাকলে সব API response এ "action": "wipe_out"।
| API | kill_switch চেক হয় |
|---|---|
POST /api/verify-license | হ্যাঁ — সব মোডে |
GET /api/cache-signal | হ্যাঁ — সব response এ |
Admin থেকে চালু/বন্ধ
Stolen Reports → ☠️ Kill Switch বাটন → Toggle on/off।
→ Admin Stolen Reports খুলুনaction: wipe_out পেলে ক্লায়েন্ট সাইট নিজেকে disable করবে। এটি অপরিবর্তনীয় ক্ষতি করতে পারে। শুধু বিশ্বস্ত পরিস্থিতিতে চালু করুন।
Invalid লাইসেন্স দিয়ে প্রবেশের চেষ্টা স্বয়ংক্রিয়ভাবে log হয় এবং ডোমেইন block হয়।
is_blocked = 1, নতুন cache_signalis_blocked toggleis_blocked = 0 (auto-unblock)Admin Action বাটনসমূহ
| বাটন | কাজ |
|---|---|
| ⚡ Cache Clear | নতুন cache_signal পাঠানো — ক্লায়েন্ট ৩০ মিনিটের মধ্যে cache মুছবে |
| 🔒 Block | is_blocked = 1 → ক্লায়েন্ট সাইট বন্ধ |
| 🔓 Unblock | is_blocked = 0 → ক্লায়েন্ট সাইট চালু |
| ☠️ Kill Switch | unauthorized_logs.kill_switch toggle → action: wipe_out |
| 🔄 Status | Pending ↔ Resolved |
| 🗑️ Delete | রিপোর্ট মুছা |
URL: /admin/licenses — ইউজার + ডোমেইন + মেয়াদ (১/২/৩ বছর) → LIC-XXXX-XXXX-XXXX তৈরি হয়।
URL: /admin/license-renewal-orders
| Action | ফলাফল |
|---|---|
| Mark as Paid | লাইসেন্স +১ বছর extend, status = paid |
| Mark as Unpaid | পূর্বের অবস্থায় ফেরানো |
-- phpMyAdmin এ: UPDATE users SET free_license_generation = 1 WHERE id = USER_ID;
ক্লায়েন্ট নিজে লাইসেন্স কিনতে ও নবায়ন করতে পারে।
| URL | কাজ |
|---|---|
/my-licenses | সকল লাইসেন্স দেখা |
/license/generate | নতুন লাইসেন্স কেনা (UddoktaPay বা বিনামূল্যে) |
/license/renew/{id} | মেয়াদ শেষে নবায়ন করা |
/license/update/{id} | ডোমেইন পরিবর্তন |
/license/delete/{id} | লাইসেন্স মুছা |
license_generation_price × মেয়াদ (বছর)।
উদাহরণ: ১০০০ টাকা × ২ বছর = ২০০০ টাকা।
SSH না থাকলে নিচের SQL গুলো phpMyAdmin → SQL ট্যাবে paste করে Execute করুন।
-- ১. stolen_domains এ নতুন column ALTER TABLE `stolen_domains` ADD COLUMN IF NOT EXISTS `cache_signal` varchar(255) DEFAULT NULL, ADD COLUMN IF NOT EXISTS `is_blocked` tinyint(1) NOT NULL DEFAULT 0; -- ২. users এ free_license_generation ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `free_license_generation` tinyint(1) NOT NULL DEFAULT 0; -- ৩. generalsettings এ license price ALTER TABLE `generalsettings` ADD COLUMN IF NOT EXISTS `license_generation_price` decimal(10,2) DEFAULT 1000.00; -- ৪. unauthorized_logs টেবিল CREATE TABLE IF NOT EXISTS `unauthorized_logs` ( `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, `domain` varchar(255) NOT NULL, `ip` varchar(255) DEFAULT NULL, `reason` text NOT NULL, `url` text DEFAULT NULL, `attempted_at` timestamp NOT NULL, `kill_switch` tinyint(1) NOT NULL DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ৫. license_renewal_orders টেবিল CREATE TABLE IF NOT EXISTS `license_renewal_orders` ( `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, `user_id` bigint(20) UNSIGNED NOT NULL, `license_id` bigint(20) UNSIGNED NOT NULL, `amount` decimal(10,2) NOT NULL DEFAULT 1000.00, `invoice_id` varchar(255) DEFAULT NULL, `status` varchar(255) NOT NULL DEFAULT 'pending', `paid_at` timestamp NULL DEFAULT NULL, `notes` text DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
SSH থাকলে
php artisan migrate php artisan config:cache php artisan route:cache php artisan cache:clear
<?php
function verifyLicense($domain, $licenseKey, $serverUrl) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $serverUrl . '/api/verify-license',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'domain' => $domain,
'license_key' => $licenseKey,
'url' => (isset($_SERVER['HTTPS']) ? 'https' : 'http')
. '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
]),
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
$result = verifyLicense('example.com', 'LIC-ABCD-EFGH-IJKL', '{{ url('/') }}');
if ($result['status'] === 'valid') {
if ($result['action'] === 'wipe_out') {
die('Site disabled by administrator.');
}
// cache_signal চেক
$saved = @file_get_contents('license_signal.txt');
if ($saved !== ($result['cache_signal'] ?? '')) {
// local cache মুছুন
file_put_contents('license_signal.txt', $result['cache_signal'] ?? '');
}
} else {
die('License invalid or domain blocked.');
}<?php
define('LICENSE_SERVER', '{{ url('/') }}');
define('LICENSE_DOMAIN', 'example.com');
define('LICENSE_KEY', 'LIC-ABCD-EFGH-IJKL');
define('HMAC_SALT', 'your_secret_salt_key'); // server এর HMAC_SALT
function verifyLicenseHmac() {
$domain = LICENSE_DOMAIN;
$signature = hash_hmac('sha256', $domain, HMAC_SALT);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => LICENSE_SERVER . '/api/verify-license',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'domain' => $domain,
'license_key' => LICENSE_KEY,
'signature' => $signature,
]),
CURLOPT_TIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => false,
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
$result = verifyLicenseHmac();
if (!$result || $result['status'] !== 'valid') {
die('License verification failed.');
}
if ($result['action'] === 'wipe_out') {
die('This installation has been remotely deactivated.');
}
// Cache signal চেক (background polling)
$cacheFile = __DIR__ . '/.license_signal';
$savedSignal = file_exists($cacheFile) ? trim(file_get_contents($cacheFile)) : '';
if (!empty($result['cache_signal']) && $savedSignal !== $result['cache_signal']) {
if (function_exists('opcache_reset')) opcache_reset();
file_put_contents($cacheFile, $result['cache_signal']);
}
// ✅ License validকারণ: unauthorized_logs টেবিল নেই। উপরের Deploy SQL এ ৪ নম্বর SQL রান করুন।
UPDATE stolen_domains SET is_blocked = 0 WHERE domain_name = 'example.com';
অথবা Admin → Stolen Reports → 🔓 Unblock।
UPDATE unauthorized_logs SET kill_switch = 0 WHERE domain = 'example.com';
ALTER TABLE `unauthorized_logs` ADD COLUMN `kill_switch` tinyint(1) NOT NULL DEFAULT 0;
License edit করে domain আবার Save করুন — স্বয়ংক্রিয়ভাবে normalize হবে।
Public
| Method | URL | কাজ |
|---|---|---|
| POST | /api/verify-license | লাইসেন্স ভেরিফিকেশন |
| GET | /api/cache-signal | Cache Signal চেক |
| POST | /api/log-unauthorized | Unauthorized লগ |
| POST | /api/check-update | আপডেট চেক (license + signature) |
| POST | /api/download-update | আপডেট ZIP |
Admin (auth:admin required)
| Method | URL | কাজ |
|---|---|---|
| GET | /admin/licenses | লাইসেন্স লিস্ট |
| POST | /admin/licenses/store | নতুন লাইসেন্স তৈরি |
| POST | /admin/licenses/update/{id} | লাইসেন্স আপডেট |
| GET | /admin/licenses/delete/{id} | লাইসেন্স মুছা |
| GET | /admin/license-renewal-orders | রিনিউয়াল অর্ডার লিস্ট |
| POST | /admin/license-renewal-orders/{id}/mark-paid | পেইড মার্ক |
| GET | /admin/stolen-reports | চুরির রিপোর্ট |
| POST | /admin/stolen-reports/clear-cache/{id} | Cache Signal আপডেট |
| POST | /admin/stolen-reports/block/{id} | Block/Unblock টগল |
| POST | /admin/stolen-reports/kill-switch/{id} | Kill Switch টগল |
User (auth required)
| Method | URL | কাজ |
|---|---|---|
| GET | /my-licenses | আমার লাইসেন্সসমূহ |
| GET | /license/generate | নতুন লাইসেন্স ফর্ম |
| POST | /license/generate | লাইসেন্স জেনারেট |
| GET | /license/renew/{id} | রিনিউ পেজ |
| POST | /license/update/{id} | লাইসেন্স আপডেট |
| POST | /license/delete/{id} | লাইসেন্স মুছা |
| POST | /license/renew/webhook | UddoktaPay Webhook |
POST /api/verify-license — পুরনো: শুধু domain + license_key। নতুন: normalize করা domain এ HMAC + signature।action পড়ুন — wipe_out হলে নীতি প্রয়োগ করুন।GET /api/cache-signal?domain=… poll (৩০ মিনিট) — signal বদলালে local cache মুছে পুনরায় verify।POST /api/log-unauthorized — legacy বা নতুন ফরম্যাটে।POST /api/check-update — LICENSE_KEY + LICENSE_SIGNATURE পাঠান; গাইড: Script Update System।LICENSE_REMOTE_BASE_URL = লাইসেন্স সার্ভারের root URL।