Update skills: add website-creator, mql-developer, ecommerce-astro

Changes:
- Add FAL_KEY and GEMINI_API_KEY to .env.example
- Update picture-it to use ~/.config/opencode/.env (unified creds)
- Remove shodh-memory skill (no longer used)
- Remove alphaear-* skills (deprecated)
- Remove thai-frontend-dev skill (replaced by website-creator)
- Remove theme-factory skill
- Add mql-developer skill (MQL5 trading)
- Add ecommerce-astro skill (Astro e-commerce)
- Add website-creator skill (Next.js + Payload CMS)
- Update install script for new skills
This commit is contained in:
2026-04-16 17:40:27 +07:00
parent 5053ccdba2
commit b26c8199a5
562 changed files with 59030 additions and 37600 deletions

View File

@@ -0,0 +1,259 @@
# Security & Licensing
## Table of Contents
- [Account-Based Licensing](#account-based-licensing)
- [Server-Side License Validation](#server-side-license-validation)
- [Expiration Date Checks](#expiration-date-checks)
- [Anti-Decompilation Techniques](#anti-decompilation-techniques)
- [Distribution Best Practices](#distribution-best-practices)
- [Node.js License Server Example](#nodejs-license-server-example)
- [MQL4 Specific Considerations](#mql4-specific-considerations)
- [Complete License System Architecture](#complete-license-system-architecture)
---
## Account-Based Licensing
### Simple Multi-Account Check
```mql5
bool CheckAccountLicense() {
long currentAccount = AccountInfoInteger(ACCOUNT_LOGIN);
long authorizedAccounts[] = {12345678, 87654321, 11223344};
for(int i = 0; i < ArraySize(authorizedAccounts); i++) {
if(currentAccount == authorizedAccounts[i]) return true;
}
Alert("Account ", currentAccount, " is not authorized.");
return false;
}
```
### Account + Broker Hash Check (More Secure)
Don't store plain account numbers in code. Hash account + broker + salt:
```mql5
ulong HashString(string s) {
ulong hash = 5381;
for(int i = 0; i < StringLen(s); i++)
hash = ((hash << 5) + hash) + StringGetCharacter(s, i);
return hash;
}
bool ValidateLicense() {
long account = AccountInfoInteger(ACCOUNT_LOGIN);
string server = AccountInfoString(ACCOUNT_SERVER);
string raw = IntegerToString(account) + "|" + server + "|SECRET_SALT";
ulong hash = HashString(raw);
ulong validHashes[] = {0xA1B2C3D4E5F6, 0x1234567890AB};
for(int i = 0; i < ArraySize(validHashes); i++)
if(hash == validHashes[i]) return true;
return false;
}
```
---
## Server-Side License Validation
### Implementation Pattern
```mql5
input string InpLicenseKey = "";
bool ValidateLicenseOnServer() {
long account = AccountInfoInteger(ACCOUNT_LOGIN);
string broker = AccountInfoString(ACCOUNT_SERVER);
string eaName = MQLInfoString(MQL_PROGRAM_NAME);
string json = "{";
json += "\"license_key\":\"" + InpLicenseKey + "\",";
json += "\"account\":" + IntegerToString(account) + ",";
json += "\"broker\":\"" + broker + "\",";
json += "\"ea\":\"" + eaName + "\"";
json += "}";
// Send to server, check response
// Handle network failures (fail-open vs fail-closed decision)
}
```
### Fail-Open vs Fail-Closed
- **Fail-open:** Allow trading if server unreachable (better UX, weaker security)
- **Fail-closed:** Block trading if server unreachable (stronger security, risk of false blocks)
- **Recommended:** Fail-open with cached expiry and offline grace period
### Caching License Locally
Use `GlobalVariableSet` to cache server expiry:
```mql5
GlobalVariableSet("EA_LICENSE_EXPIRY", (double)StringToTime(expiry));
```
On next check, verify cached expiry first, re-validate with server periodically.
---
## Expiration Date Checks
### Hardcoded Expiration
```mql5
datetime expiryDate = D'2025.12.31 23:59:59';
if(TimeCurrent() > expiryDate) {
Alert("EA has expired. Please renew.");
return INIT_FAILED;
}
```
### Server-Cached Expiration
Check `GlobalVariable` cache first, re-validate on timer.
### Demo Mode with Limited Features
After trial period, restrict to demo accounts or limited lot sizes.
### Integration in OnInit
```mql5
int OnInit() {
if(!CheckAccountLicense()) return INIT_FAILED;
if(!CheckExpiration()) return INIT_FAILED;
EventSetTimer(3600); // Re-check every hour
return INIT_SUCCEEDED;
}
void OnTimer() {
if(!CheckExpiration()) ExpertRemove();
}
```
---
## Anti-Decompilation Techniques
### Protection Layers Table
| Layer | Technique | Protection Level | Notes |
|-------|-----------|-----------------|-------|
| 1 | Compile to .ex4/.ex5 | Basic | Strips variable/function names |
| 2 | Code obfuscation | Low | Complex expressions, dummy code |
| 3 | String encryption | Medium | Hide URLs, keys, constants |
| 4 | MQL5 Cloud Protector | High | Asymmetric encryption, native code |
| 5 | DLL offloading | High | Core logic in C++ DLL |
| 6 | Server-side logic | Highest | Core signals never leave server |
### String Obfuscation Example
```mql5
string DecryptString(int key) {
uchar encrypted[] = {104,116,116,112,115,58,47,47};
string result = "";
for(int i = 0; i < ArraySize(encrypted); i++)
result += CharToString((uchar)(encrypted[i] ^ (key % 256)));
return result;
}
```
### MQL5 Cloud Protector
- Available in MetaEditor: Tools > MQL5 Cloud Protector
- Sends compiled .ex5 to MetaQuotes cloud
- Applies asymmetric encryption + unique key signing
- Source code never leaves your machine
- Same protection as MQL5 Market store
- Files NOT bound to specific computer (unlike Market)
- Free to use
---
## Distribution Best Practices
### Do's
- Distribute only .ex4/.ex5 compiled files
- Use MQL5 Market for built-in DRM (hardware + account binding)
- Combine account binding + server validation + Cloud Protector
- Include version checking in licensing server to force updates
- Use unique magic numbers or comments to identify your EA
### Don'ts
- Never distribute .mq4/.mq5 source files
- Never hardcode API keys or server passwords in source
- Don't rely on a single protection layer
- Don't use simple string comparisons for license keys
---
## Node.js License Server Example
### Endpoint: POST /api/validate
```javascript
app.post('/api/validate', (req, res) => {
const { license_key, account, broker, ea, version } = req.body;
// Look up license in database
const license = db.findLicense(license_key);
if(!license) return res.json({ valid: false, message: "Invalid key" });
if(license.expired) return res.json({ valid: false, message: "License expired" });
if(license.maxAccounts && license.accounts.length >= license.maxAccounts) {
if(!license.accounts.includes(account))
return res.json({ valid: false, message: "Max accounts reached" });
}
// Register account if new
if(!license.accounts.includes(account)) {
license.accounts.push(account);
db.updateLicense(license);
}
res.json({
valid: true,
expiry: license.expiryDate,
features: license.features
});
});
```
---
## MQL4 Specific Considerations
### Account Number Check (MQL4)
```mql4
bool CheckLicense() {
int account = AccountNumber(); // MQL4 function
// ... same logic but with int instead of long
}
```
### Hardware ID (MQL4 via DLL)
Can use Windows API via DLL to get hardware identifiers for machine-specific licensing.
---
## Complete License System Architecture
```
EA (MQL5) License Server (Node.js)
| |
|-- POST /validate ----------->|
| {key, account, broker} |-- Check DB
| |-- Validate
|<-- {valid, expiry, features}-|
| |
|-- Cache expiry locally |
|-- Re-check every hour |
| |
|-- POST /heartbeat ---------->| (optional)
| {account, balance, status} |-- Track usage
```