mrpc-js 0.1.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/README.md +0 -0
- package/package.json +26 -0
- package/src/index.js +1 -0
- package/src/mrpc.js +102 -0
package/README.md
ADDED
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mrpc-js",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Zero-dependency mRPC request encoder and HMAC-SHA256 response validator",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mrpc",
|
|
7
|
+
"mouse-php",
|
|
8
|
+
"hmac",
|
|
9
|
+
"crypto",
|
|
10
|
+
"rpc"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "Kurtis Milliren",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./src/index.js",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./src/index.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"src",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "node --test"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { create_mRPC, validate_mRPC_response } from './mrpc.js';
|
package/src/mrpc.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates an mRPC request
|
|
3
|
+
*
|
|
4
|
+
* @param {number} user_id
|
|
5
|
+
* @param {string} token
|
|
6
|
+
* @param {string} version
|
|
7
|
+
* @param {Record<string, any>} payload
|
|
8
|
+
* @returns {Promise<{user_id: number, version: string, request_tag: string, seed: string, data: Record<string, any>, auth: string}>}
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export async function create_mRPC(user_id, token, version, payload) {
|
|
12
|
+
const seed = random_hex();
|
|
13
|
+
const tag = random_hex();
|
|
14
|
+
|
|
15
|
+
const json_out = {
|
|
16
|
+
user_id: user_id,
|
|
17
|
+
version: version,
|
|
18
|
+
request_tag: tag,
|
|
19
|
+
seed: seed,
|
|
20
|
+
data: payload,
|
|
21
|
+
auth: await auth_hash(seed, token, payload)
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return json_out;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validates a mRPC response from a server
|
|
29
|
+
*
|
|
30
|
+
* @param {Record<string, any>} response
|
|
31
|
+
* @param {string} tag
|
|
32
|
+
* @param {string} token
|
|
33
|
+
* @returns {Promise<boolean>}
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
export async function validate_mRPC_response(response, tag, token) {
|
|
37
|
+
const valid_keys = ['success', 'request_tag', 'seed', 'data', 'auth'];
|
|
38
|
+
const key_types = {
|
|
39
|
+
success: 'boolean',
|
|
40
|
+
request_tag: 'string',
|
|
41
|
+
seed: 'string',
|
|
42
|
+
data: 'object',
|
|
43
|
+
auth: 'string'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const keys = Object.keys(response);
|
|
47
|
+
|
|
48
|
+
if (keys.length !== 5) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const key of keys) {
|
|
53
|
+
if (!valid_keys.includes(key)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (typeof response[key] !== key_types[key]) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if(response.request_tag !== tag) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const hash = await auth_hash(response.seed, token, response.data);
|
|
66
|
+
|
|
67
|
+
return hash === response.auth;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function random_hex(length=32) {
|
|
71
|
+
const crypto_api = globalThis.crypto;
|
|
72
|
+
const buf = crypto_api.getRandomValues(new Uint8Array(length));
|
|
73
|
+
return Array.from(buf).map(byte => byte.toString(16).padStart(2, '0')).join('');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function auth_hash(seed, token, payload) {
|
|
77
|
+
const crypto_api = globalThis.crypto;
|
|
78
|
+
const data_string = JSON.stringify(payload) + seed;
|
|
79
|
+
|
|
80
|
+
const encoder = new TextEncoder();
|
|
81
|
+
const key_bytes = encoder.encode(token);
|
|
82
|
+
const data_bytes = encoder.encode(data_string);
|
|
83
|
+
|
|
84
|
+
const crypto_key = await crypto_api.subtle.importKey(
|
|
85
|
+
'raw',
|
|
86
|
+
key_bytes,
|
|
87
|
+
{ name: 'HMAC', hash: 'SHA-256' },
|
|
88
|
+
false,
|
|
89
|
+
['sign']
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const signature_buffer = await crypto_api.subtle.sign(
|
|
93
|
+
'HMAC',
|
|
94
|
+
crypto_key,
|
|
95
|
+
data_bytes
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
return Array.from(new Uint8Array(signature_buffer))
|
|
99
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
100
|
+
.join('');
|
|
101
|
+
}
|
|
102
|
+
|