atm-droid 1.0.0 → 1.0.2
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/package.json +7 -2
- package/src/api.js +83 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atm-droid",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "ATM Token Manager CLI - 跨平台 Factory Token 管理工具",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/atm.js"
|
|
11
11
|
},
|
|
12
|
-
"keywords": [
|
|
12
|
+
"keywords": [
|
|
13
|
+
"factory",
|
|
14
|
+
"token",
|
|
15
|
+
"manager",
|
|
16
|
+
"cli"
|
|
17
|
+
],
|
|
13
18
|
"author": "ATM Team",
|
|
14
19
|
"license": "MIT",
|
|
15
20
|
"dependencies": {
|
package/src/api.js
CHANGED
|
@@ -1,15 +1,64 @@
|
|
|
1
1
|
const fetch = require('node-fetch');
|
|
2
|
+
const crypto = require('crypto');
|
|
2
3
|
const { config, getDeviceId } = require('./config');
|
|
3
4
|
|
|
4
5
|
const getServerUrl = () => config.get('serverUrl');
|
|
5
6
|
|
|
7
|
+
// 生成加密密钥(与服务器一致)
|
|
8
|
+
function getEncryptionKey() {
|
|
9
|
+
const key = Buffer.alloc(32);
|
|
10
|
+
const base = Buffer.from([
|
|
11
|
+
0x4a, 0x7b, 0x2c, 0x9d, 0x1e, 0x5f, 0x8a, 0x3b,
|
|
12
|
+
0x6c, 0x0d, 0x4e, 0x9f, 0x2a, 0x7b, 0x1c, 0x5d,
|
|
13
|
+
0x8e, 0x3f, 0x6a, 0x0b, 0x4c, 0x9d, 0x2e, 0x7f,
|
|
14
|
+
0x1a, 0x5b, 0x8c, 0x3d, 0x6e, 0x0f, 0x4a, 0x9b,
|
|
15
|
+
]);
|
|
16
|
+
for (let i = 0; i < 32; i++) {
|
|
17
|
+
key[i] = base[i] ^ ((i * 0x17) & 0xff);
|
|
18
|
+
}
|
|
19
|
+
return key;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 生成签名
|
|
23
|
+
function generateSignature(data, timestamp, deviceId) {
|
|
24
|
+
const message = `${data}|${timestamp}|${deviceId}`;
|
|
25
|
+
const key = getEncryptionKey();
|
|
26
|
+
const hmac = crypto.createHmac('sha256', key);
|
|
27
|
+
hmac.update(message);
|
|
28
|
+
return hmac.digest('hex');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 解密服务器返回的数据
|
|
32
|
+
function decryptPayload(encryptedData, ivHex, tagHex) {
|
|
33
|
+
try {
|
|
34
|
+
const key = getEncryptionKey();
|
|
35
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
36
|
+
const tag = Buffer.from(tagHex, 'hex');
|
|
37
|
+
const encrypted = Buffer.from(encryptedData, 'hex');
|
|
38
|
+
|
|
39
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
|
|
40
|
+
decipher.setAuthTag(tag);
|
|
41
|
+
|
|
42
|
+
let decrypted = decipher.update(encrypted, null, 'utf8');
|
|
43
|
+
decrypted += decipher.final('utf8');
|
|
44
|
+
|
|
45
|
+
return JSON.parse(decrypted);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.error('解密失败:', e.message);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
6
52
|
// 激活码登录
|
|
7
53
|
async function activate(code) {
|
|
8
54
|
const deviceId = getDeviceId();
|
|
9
|
-
const
|
|
55
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
56
|
+
const signature = generateSignature(code, timestamp, deviceId);
|
|
57
|
+
|
|
58
|
+
const response = await fetch(`${getServerUrl()}/api/v1/auth/activate`, {
|
|
10
59
|
method: 'POST',
|
|
11
60
|
headers: { 'Content-Type': 'application/json' },
|
|
12
|
-
body: JSON.stringify({ code, device_id: deviceId })
|
|
61
|
+
body: JSON.stringify({ code, device_id: deviceId, timestamp, signature })
|
|
13
62
|
});
|
|
14
63
|
return response.json();
|
|
15
64
|
}
|
|
@@ -23,19 +72,27 @@ async function getTokens() {
|
|
|
23
72
|
throw new Error('未登录');
|
|
24
73
|
}
|
|
25
74
|
|
|
26
|
-
const
|
|
75
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
76
|
+
const signature = generateSignature(sessionToken, timestamp, deviceId);
|
|
77
|
+
|
|
78
|
+
const response = await fetch(`${getServerUrl()}/api/v1/tokens`, {
|
|
27
79
|
headers: {
|
|
28
80
|
'Authorization': `Bearer ${sessionToken}`,
|
|
29
|
-
'X-Device-ID': deviceId
|
|
81
|
+
'X-Device-ID': deviceId,
|
|
82
|
+
'X-Timestamp': timestamp.toString(),
|
|
83
|
+
'X-Signature': signature
|
|
30
84
|
}
|
|
31
85
|
});
|
|
32
86
|
|
|
33
87
|
const result = await response.json();
|
|
34
88
|
|
|
35
89
|
// 如果返回加密数据,需要解密
|
|
36
|
-
if (result.success && result.data && result.iv) {
|
|
37
|
-
|
|
38
|
-
|
|
90
|
+
if (result.success && result.data && result.iv && result.tag) {
|
|
91
|
+
const decrypted = decryptPayload(result.data, result.iv, result.tag);
|
|
92
|
+
if (decrypted) {
|
|
93
|
+
return { success: true, data: decrypted };
|
|
94
|
+
}
|
|
95
|
+
return { success: false, error: '解密失败' };
|
|
39
96
|
}
|
|
40
97
|
|
|
41
98
|
return result;
|
|
@@ -45,13 +102,17 @@ async function getTokens() {
|
|
|
45
102
|
async function activateToken(tokenId) {
|
|
46
103
|
const sessionToken = config.get('sessionToken');
|
|
47
104
|
const deviceId = getDeviceId();
|
|
105
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
106
|
+
const signature = generateSignature(sessionToken, timestamp, deviceId);
|
|
48
107
|
|
|
49
|
-
const response = await fetch(`${getServerUrl()}/api/tokens/activate`, {
|
|
108
|
+
const response = await fetch(`${getServerUrl()}/api/v1/tokens/activate`, {
|
|
50
109
|
method: 'POST',
|
|
51
110
|
headers: {
|
|
52
111
|
'Content-Type': 'application/json',
|
|
53
112
|
'Authorization': `Bearer ${sessionToken}`,
|
|
54
|
-
'X-Device-ID': deviceId
|
|
113
|
+
'X-Device-ID': deviceId,
|
|
114
|
+
'X-Timestamp': timestamp.toString(),
|
|
115
|
+
'X-Signature': signature
|
|
55
116
|
},
|
|
56
117
|
body: JSON.stringify({ token_id: tokenId })
|
|
57
118
|
});
|
|
@@ -66,12 +127,17 @@ async function heartbeat() {
|
|
|
66
127
|
|
|
67
128
|
if (!sessionToken) return { valid: false };
|
|
68
129
|
|
|
69
|
-
const
|
|
130
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
131
|
+
const signature = generateSignature(sessionToken, timestamp, deviceId);
|
|
132
|
+
|
|
133
|
+
const response = await fetch(`${getServerUrl()}/api/v1/auth/heartbeat`, {
|
|
70
134
|
method: 'POST',
|
|
71
135
|
headers: {
|
|
72
136
|
'Content-Type': 'application/json',
|
|
73
137
|
'Authorization': `Bearer ${sessionToken}`,
|
|
74
|
-
'X-Device-ID': deviceId
|
|
138
|
+
'X-Device-ID': deviceId,
|
|
139
|
+
'X-Timestamp': timestamp.toString(),
|
|
140
|
+
'X-Signature': signature
|
|
75
141
|
}
|
|
76
142
|
});
|
|
77
143
|
|
|
@@ -82,13 +148,17 @@ async function heartbeat() {
|
|
|
82
148
|
async function unbind() {
|
|
83
149
|
const sessionToken = config.get('sessionToken');
|
|
84
150
|
const deviceId = getDeviceId();
|
|
151
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
152
|
+
const signature = generateSignature(sessionToken, timestamp, deviceId);
|
|
85
153
|
|
|
86
|
-
const response = await fetch(`${getServerUrl()}/api/unbind`, {
|
|
154
|
+
const response = await fetch(`${getServerUrl()}/api/v1/auth/unbind`, {
|
|
87
155
|
method: 'POST',
|
|
88
156
|
headers: {
|
|
89
157
|
'Content-Type': 'application/json',
|
|
90
158
|
'Authorization': `Bearer ${sessionToken}`,
|
|
91
|
-
'X-Device-ID': deviceId
|
|
159
|
+
'X-Device-ID': deviceId,
|
|
160
|
+
'X-Timestamp': timestamp.toString(),
|
|
161
|
+
'X-Signature': signature
|
|
92
162
|
}
|
|
93
163
|
});
|
|
94
164
|
|