nodejs-insta-private-api-mqt 1.4.4 → 1.4.6
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.
|
@@ -1,6 +1,55 @@
|
|
|
1
1
|
const Repository = require('../core/repository');
|
|
2
2
|
const crypto = require('crypto');
|
|
3
3
|
|
|
4
|
+
// ─── DEBUG helper ────────────────────────────────────────────────────────────
|
|
5
|
+
// Set env DEBUG_IG=true to enable disk writes.
|
|
6
|
+
// In production leave it undefined / false.
|
|
7
|
+
const DEBUG_IG = process.env.DEBUG_IG === 'true';
|
|
8
|
+
|
|
9
|
+
function debugWrite(filename, payload) {
|
|
10
|
+
if (!DEBUG_IG) return;
|
|
11
|
+
try {
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const debugDir = path.join(process.cwd(), 'authinfo_instagram');
|
|
15
|
+
try { fs.mkdirSync(debugDir, { recursive: true }); } catch (e) {}
|
|
16
|
+
fs.writeFileSync(
|
|
17
|
+
path.join(debugDir, filename),
|
|
18
|
+
JSON.stringify(payload, null, 2),
|
|
19
|
+
'utf8'
|
|
20
|
+
);
|
|
21
|
+
} catch (e) {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ─── Local IP helper ──────────────────────────────────────────────────────────
|
|
25
|
+
// Detects the first non-loopback IP of the machine for x-fb-network-properties.
|
|
26
|
+
function getLocalIp() {
|
|
27
|
+
try {
|
|
28
|
+
const os = require('os');
|
|
29
|
+
const ifaces = os.networkInterfaces();
|
|
30
|
+
for (const name of Object.keys(ifaces)) {
|
|
31
|
+
for (const iface of ifaces[name]) {
|
|
32
|
+
if (iface.family === 'IPv4' && !iface.internal) {
|
|
33
|
+
return iface.address;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} catch (e) {}
|
|
38
|
+
return '192.168.1.100'; // reasonable fallback
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ─── Network properties generator ────────────────────────────────────────────
|
|
42
|
+
// Dynamically generates x-fb-network-properties header, no hardcoded VPN.
|
|
43
|
+
function buildNetworkProperties() {
|
|
44
|
+
const localIp = getLocalIp();
|
|
45
|
+
// If IP is 10.x.x.x or 172.16-31.x.x.x it may be a real VPN — keep prefix
|
|
46
|
+
const isVpnRange =
|
|
47
|
+
/^10\./.test(localIp) ||
|
|
48
|
+
/^172\.(1[6-9]|2\d|3[01])\./.test(localIp);
|
|
49
|
+
const prefix = isVpnRange ? 'VPN;Validated' : 'Validated';
|
|
50
|
+
return `${prefix};LocalAddrs=/${localIp},;`;
|
|
51
|
+
}
|
|
52
|
+
|
|
4
53
|
class AccountRepository extends Repository {
|
|
5
54
|
constructor(client) {
|
|
6
55
|
super(client);
|
|
@@ -9,8 +58,7 @@ class AccountRepository extends Repository {
|
|
|
9
58
|
|
|
10
59
|
async requestWithRetry(requestFn, retries = 0) {
|
|
11
60
|
try {
|
|
12
|
-
|
|
13
|
-
return result;
|
|
61
|
+
return await requestFn();
|
|
14
62
|
} catch (error) {
|
|
15
63
|
const shouldRetry =
|
|
16
64
|
(error.data?.error_type === 'server_error' ||
|
|
@@ -26,18 +74,12 @@ class AccountRepository extends Repository {
|
|
|
26
74
|
}
|
|
27
75
|
|
|
28
76
|
/**
|
|
29
|
-
* Helper:
|
|
30
|
-
*
|
|
31
|
-
* - response.body top-level fields
|
|
32
|
-
* - body.layout (stringified layout)
|
|
33
|
-
* - any embedded login_response.headers JSON inside layout or body
|
|
34
|
-
*
|
|
35
|
-
* When found, sets this.client.state.authorization and calls updateAuthorization().
|
|
77
|
+
* Helper: extracts Bearer IGT token from headers / body / layout.
|
|
78
|
+
* When found, saves to state and calls updateAuthorization().
|
|
36
79
|
*/
|
|
37
80
|
_extractAndSaveAuthorization(response) {
|
|
38
81
|
if (!response) return null;
|
|
39
82
|
|
|
40
|
-
// Normalize headers (case-insensitive)
|
|
41
83
|
const headers = {};
|
|
42
84
|
if (response.headers && typeof response.headers === 'object') {
|
|
43
85
|
for (const k of Object.keys(response.headers)) {
|
|
@@ -45,7 +87,7 @@ class AccountRepository extends Repository {
|
|
|
45
87
|
}
|
|
46
88
|
}
|
|
47
89
|
|
|
48
|
-
// 1)
|
|
90
|
+
// 1) Header IG-Set-Authorization
|
|
49
91
|
for (const key of Object.keys(headers)) {
|
|
50
92
|
const val = headers[key];
|
|
51
93
|
if (!val) continue;
|
|
@@ -54,9 +96,7 @@ class AccountRepository extends Repository {
|
|
|
54
96
|
const token = this._normalizeTokenString(val);
|
|
55
97
|
if (token) {
|
|
56
98
|
this.client.state.authorization = token;
|
|
57
|
-
try {
|
|
58
|
-
this.client.state.updateAuthorization();
|
|
59
|
-
} catch (e) {}
|
|
99
|
+
try { this.client.state.updateAuthorization(); } catch (e) {}
|
|
60
100
|
return token;
|
|
61
101
|
}
|
|
62
102
|
}
|
|
@@ -64,15 +104,13 @@ class AccountRepository extends Repository {
|
|
|
64
104
|
const token = this._findBearerInString(val);
|
|
65
105
|
if (token) {
|
|
66
106
|
this.client.state.authorization = token;
|
|
67
|
-
try {
|
|
68
|
-
this.client.state.updateAuthorization();
|
|
69
|
-
} catch (e) {}
|
|
107
|
+
try { this.client.state.updateAuthorization(); } catch (e) {}
|
|
70
108
|
return token;
|
|
71
109
|
}
|
|
72
110
|
}
|
|
73
111
|
}
|
|
74
112
|
|
|
75
|
-
//
|
|
113
|
+
// IG-U headers
|
|
76
114
|
const dsUserIdHeader =
|
|
77
115
|
headers['ig-set-ig-u-ds-user-id'] ||
|
|
78
116
|
headers['ig-u-ds-user-id'] ||
|
|
@@ -80,9 +118,8 @@ class AccountRepository extends Repository {
|
|
|
80
118
|
if (dsUserIdHeader) {
|
|
81
119
|
const m = String(dsUserIdHeader).match(/(\d{3,})/);
|
|
82
120
|
if (m) {
|
|
83
|
-
|
|
84
|
-
this.client.state.
|
|
85
|
-
this.client.state._userId = uid;
|
|
121
|
+
this.client.state.cookieUserId = m[1];
|
|
122
|
+
this.client.state._userId = m[1];
|
|
86
123
|
}
|
|
87
124
|
}
|
|
88
125
|
|
|
@@ -92,9 +129,7 @@ class AccountRepository extends Repository {
|
|
|
92
129
|
headers['x-ig-set-ig-u-rur'];
|
|
93
130
|
if (rurHeader) {
|
|
94
131
|
const rur = String(rurHeader).trim().replace(/^"|"$/g, '');
|
|
95
|
-
if (rur)
|
|
96
|
-
this.client.state.igURur = rur;
|
|
97
|
-
}
|
|
132
|
+
if (rur) this.client.state.igURur = rur;
|
|
98
133
|
}
|
|
99
134
|
|
|
100
135
|
const wwwClaimHeader =
|
|
@@ -103,9 +138,7 @@ class AccountRepository extends Repository {
|
|
|
103
138
|
headers['ig-u-www-claim'];
|
|
104
139
|
if (wwwClaimHeader) {
|
|
105
140
|
const claim = String(wwwClaimHeader).trim().replace(/^"|"$/g, '');
|
|
106
|
-
if (claim)
|
|
107
|
-
this.client.state.igWWWClaim = claim;
|
|
108
|
-
}
|
|
141
|
+
if (claim) this.client.state.igWWWClaim = claim;
|
|
109
142
|
}
|
|
110
143
|
|
|
111
144
|
const midHeader =
|
|
@@ -114,21 +147,17 @@ class AccountRepository extends Repository {
|
|
|
114
147
|
headers['ig-set-mid'];
|
|
115
148
|
if (midHeader) {
|
|
116
149
|
const mid = String(midHeader).trim().replace(/^"|"$/g, '');
|
|
117
|
-
if (mid)
|
|
118
|
-
this.client.state.mid = mid;
|
|
119
|
-
}
|
|
150
|
+
if (mid) this.client.state.mid = mid;
|
|
120
151
|
}
|
|
121
152
|
|
|
122
|
-
// 2)
|
|
153
|
+
// 2) body
|
|
123
154
|
const body = response.body;
|
|
124
155
|
if (body) {
|
|
125
156
|
if (body.headers && typeof body.headers === 'string') {
|
|
126
157
|
const token = this._findBearerInString(body.headers);
|
|
127
158
|
if (token) {
|
|
128
159
|
this.client.state.authorization = token;
|
|
129
|
-
try {
|
|
130
|
-
this.client.state.updateAuthorization();
|
|
131
|
-
} catch (e) {}
|
|
160
|
+
try { this.client.state.updateAuthorization(); } catch (e) {}
|
|
132
161
|
return token;
|
|
133
162
|
}
|
|
134
163
|
}
|
|
@@ -139,8 +168,6 @@ class AccountRepository extends Repository {
|
|
|
139
168
|
layoutStr = typeof body.layout === 'string'
|
|
140
169
|
? body.layout
|
|
141
170
|
: JSON.stringify(body.layout);
|
|
142
|
-
} else if (body?.layout?.bloks_payload) {
|
|
143
|
-
layoutStr = JSON.stringify(body.layout.bloks_payload);
|
|
144
171
|
} else if (body?.bloks_payload) {
|
|
145
172
|
layoutStr = JSON.stringify(body.bloks_payload);
|
|
146
173
|
} else if (typeof body === 'string') {
|
|
@@ -151,9 +178,7 @@ class AccountRepository extends Repository {
|
|
|
151
178
|
const token = this._findBearerInString(layoutStr);
|
|
152
179
|
if (token) {
|
|
153
180
|
this.client.state.authorization = token;
|
|
154
|
-
try {
|
|
155
|
-
this.client.state.updateAuthorization();
|
|
156
|
-
} catch (e) {}
|
|
181
|
+
try { this.client.state.updateAuthorization(); } catch (e) {}
|
|
157
182
|
return token;
|
|
158
183
|
}
|
|
159
184
|
|
|
@@ -172,45 +197,31 @@ class AccountRepository extends Repository {
|
|
|
172
197
|
const t = this._findBearerInString(parsed.headers);
|
|
173
198
|
if (t) {
|
|
174
199
|
this.client.state.authorization = t;
|
|
175
|
-
try {
|
|
176
|
-
this.client.state.updateAuthorization();
|
|
177
|
-
} catch (e) {}
|
|
200
|
+
try { this.client.state.updateAuthorization(); } catch (e) {}
|
|
178
201
|
return t;
|
|
179
202
|
}
|
|
180
203
|
}
|
|
181
204
|
} catch (e) {}
|
|
182
205
|
}
|
|
183
206
|
|
|
184
|
-
// Encryption
|
|
207
|
+
// Encryption keys
|
|
185
208
|
const encKeyIdMatch =
|
|
186
|
-
layoutStr.match(
|
|
187
|
-
|
|
188
|
-
) ||
|
|
189
|
-
layoutStr.match(
|
|
190
|
-
/password.encryption.key.id[\\"\s:]+(\d+)/i
|
|
191
|
-
);
|
|
209
|
+
layoutStr.match(/IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i) ||
|
|
210
|
+
layoutStr.match(/password.encryption.key.id[\\"\s:]+(\d+)/i);
|
|
192
211
|
if (encKeyIdMatch) {
|
|
193
|
-
this.client.state.passwordEncryptionKeyId =
|
|
194
|
-
parseInt(encKeyIdMatch[1]);
|
|
212
|
+
this.client.state.passwordEncryptionKeyId = parseInt(encKeyIdMatch[1]);
|
|
195
213
|
}
|
|
196
214
|
|
|
197
215
|
const encPubKeyMatch =
|
|
198
|
-
layoutStr.match(
|
|
199
|
-
|
|
200
|
-
) ||
|
|
201
|
-
layoutStr.match(
|
|
202
|
-
/password.encryption.pub.key[\\"\s:]+([A-Za-z0-9+\/=]+)/i
|
|
203
|
-
);
|
|
216
|
+
layoutStr.match(/IG-Set-Password-Encryption-Pub-Key[\\"\s:]+([A-Za-z0-9+\/=]+)/i) ||
|
|
217
|
+
layoutStr.match(/password.encryption.pub.key[\\"\s:]+([A-Za-z0-9+\/=]+)/i);
|
|
204
218
|
if (encPubKeyMatch) {
|
|
205
|
-
this.client.state.passwordEncryptionPubKey =
|
|
206
|
-
encPubKeyMatch[1];
|
|
219
|
+
this.client.state.passwordEncryptionPubKey = encPubKeyMatch[1];
|
|
207
220
|
}
|
|
208
221
|
|
|
209
|
-
// User
|
|
222
|
+
// User ID
|
|
210
223
|
const dsIdMatch =
|
|
211
|
-
layoutStr.match(
|
|
212
|
-
/ig-set-ig-u-ds-user-id[\\"\s:]+(\d+)/i
|
|
213
|
-
) ||
|
|
224
|
+
layoutStr.match(/ig-set-ig-u-ds-user-id[\\"\s:]+(\d+)/i) ||
|
|
214
225
|
layoutStr.match(/ig-u-ds-user-id[\\"\s:]+(\d+)/i) ||
|
|
215
226
|
layoutStr.match(/"strong_id__"\s*:\s*"(\d+)"/i) ||
|
|
216
227
|
layoutStr.match(/"pk_id"\s*:\s*"(\d+)"/i) ||
|
|
@@ -222,27 +233,15 @@ class AccountRepository extends Repository {
|
|
|
222
233
|
}
|
|
223
234
|
|
|
224
235
|
const rurMatch =
|
|
225
|
-
layoutStr.match(
|
|
226
|
-
/ig-set-ig-u-rur[\\"\s:]+"([^"\\]+)/i
|
|
227
|
-
) ||
|
|
236
|
+
layoutStr.match(/ig-set-ig-u-rur[\\"\s:]+"([^"\\]+)/i) ||
|
|
228
237
|
layoutStr.match(/ig-u-rur[\\"\s:]+"([^"\\]+)/i);
|
|
229
|
-
if (rurMatch)
|
|
230
|
-
this.client.state.igURur = rurMatch[1];
|
|
231
|
-
}
|
|
238
|
+
if (rurMatch) this.client.state.igURur = rurMatch[1];
|
|
232
239
|
|
|
233
|
-
const wwwClaimMatch = layoutStr.match(
|
|
234
|
-
|
|
235
|
-
);
|
|
236
|
-
if (wwwClaimMatch) {
|
|
237
|
-
this.client.state.igWWWClaim = wwwClaimMatch[1];
|
|
238
|
-
}
|
|
240
|
+
const wwwClaimMatch = layoutStr.match(/x-ig-set-www-claim[\\"\s:]+"([^"\\]+)/i);
|
|
241
|
+
if (wwwClaimMatch) this.client.state.igWWWClaim = wwwClaimMatch[1];
|
|
239
242
|
|
|
240
|
-
const midMatch = layoutStr.match(
|
|
241
|
-
|
|
242
|
-
);
|
|
243
|
-
if (midMatch) {
|
|
244
|
-
this.client.state.mid = midMatch[1];
|
|
245
|
-
}
|
|
243
|
+
const midMatch = layoutStr.match(/"mid"\s*:\s*"([^"\\]+)"/i);
|
|
244
|
+
if (midMatch) this.client.state.mid = midMatch[1];
|
|
246
245
|
}
|
|
247
246
|
} catch (e) {}
|
|
248
247
|
|
|
@@ -256,19 +255,14 @@ class AccountRepository extends Repository {
|
|
|
256
255
|
|
|
257
256
|
_normalizeTokenString(val) {
|
|
258
257
|
if (!val || typeof val !== 'string') return null;
|
|
259
|
-
|
|
260
258
|
const bearer = this._findBearerInString(val);
|
|
261
259
|
if (bearer) return bearer;
|
|
262
|
-
|
|
263
260
|
try {
|
|
264
261
|
const maybeJson = JSON.parse(val);
|
|
265
262
|
if (maybeJson['IG-Set-Authorization']) {
|
|
266
|
-
return this._findBearerInString(
|
|
267
|
-
maybeJson['IG-Set-Authorization']
|
|
268
|
-
);
|
|
263
|
+
return this._findBearerInString(maybeJson['IG-Set-Authorization']);
|
|
269
264
|
}
|
|
270
265
|
} catch (e) {}
|
|
271
|
-
|
|
272
266
|
return val.trim();
|
|
273
267
|
}
|
|
274
268
|
|
|
@@ -297,18 +291,22 @@ class AccountRepository extends Repository {
|
|
|
297
291
|
return m[0].replace(/\?"/g, '').trim();
|
|
298
292
|
}
|
|
299
293
|
}
|
|
300
|
-
|
|
301
294
|
return null;
|
|
302
295
|
}
|
|
303
296
|
|
|
304
297
|
/**
|
|
305
|
-
*
|
|
306
|
-
*
|
|
298
|
+
* Resolves a valid User-Agent from state / dynamically generated fallback.
|
|
299
|
+
* Priority:
|
|
300
|
+
* 1. state.userAgent / state.appUserAgent / request.userAgent / state.deviceString
|
|
301
|
+
* 2. Built dynamically from state device parameters (androidRelease, manufacturer, model, etc.)
|
|
302
|
+
* 3. Last-resort realistic static fallback
|
|
303
|
+
* This way the user-agent always reflects the emulated device set in state.
|
|
307
304
|
*/
|
|
308
305
|
_resolveUserAgent() {
|
|
309
306
|
const state = this.client.state || {};
|
|
310
307
|
const req = this.client.request || {};
|
|
311
308
|
|
|
309
|
+
// 1) Explicit user-agent already set in state
|
|
312
310
|
const candidates = [
|
|
313
311
|
state.userAgent,
|
|
314
312
|
state.appUserAgent,
|
|
@@ -316,30 +314,41 @@ class AccountRepository extends Repository {
|
|
|
316
314
|
state.deviceString,
|
|
317
315
|
].filter(Boolean);
|
|
318
316
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
// Fallback – Pixel 9 Pro style UA (nu mai e Xiaomi)
|
|
323
|
-
userAgent =
|
|
324
|
-
'Instagram 371.0.0.0.23 Android (35/15; 505dpi; 1440x3120; google; Pixel 9 Pro; pixel9pro; qcom; ro_RO; 703217507)';
|
|
317
|
+
if (candidates.length > 0) {
|
|
318
|
+
this.client.state.userAgent = candidates[0];
|
|
319
|
+
return candidates[0];
|
|
325
320
|
}
|
|
326
321
|
|
|
327
|
-
//
|
|
328
|
-
|
|
329
|
-
|
|
322
|
+
// 2) Build dynamically from device parameters stored in state
|
|
323
|
+
try {
|
|
324
|
+
const igVersion = state.appVersion || state.igVersion || '415.0.0.36.76';
|
|
325
|
+
const buildNumber = state.appVersionCode || state.buildNumber || '580610226';
|
|
326
|
+
const androidApi = state.androidVersion || state.androidApiLevel || '35';
|
|
327
|
+
const androidRel = state.androidRelease || state.androidOsVersion || '15';
|
|
328
|
+
const dpi = state.dpi || state.screenDpi || '480dpi';
|
|
329
|
+
const resolution = state.resolution || state.screenResolution || '1080x2400';
|
|
330
|
+
const manufacturer = state.manufacturer || state.deviceManufacturer || 'Google';
|
|
331
|
+
const brand = state.brand || state.deviceBrand || manufacturer;
|
|
332
|
+
const model = state.model || state.deviceModel || 'Pixel 7';
|
|
333
|
+
const device = state.device || state.deviceName || 'panther';
|
|
334
|
+
const cpu = state.cpu || state.deviceCpu || 'gs201';
|
|
335
|
+
const lang = (state.language || 'en_US').replace('-', '_');
|
|
336
|
+
|
|
337
|
+
const ua = `Instagram ${igVersion} Android (${androidApi}/${androidRel}; ${dpi}; ${resolution}; ${brand}/${manufacturer}; ${model}; ${device}; ${cpu}; ${lang}; ${buildNumber})`;
|
|
338
|
+
this.client.state.userAgent = ua;
|
|
339
|
+
return ua;
|
|
340
|
+
} catch (e) {}
|
|
341
|
+
|
|
342
|
+
// 3) Last-resort static fallback
|
|
343
|
+
const fallback = 'Instagram 415.0.0.36.76 Android (35/15; 480dpi; 1280x2856; Google; Pixel 9 Pro; caiman; google; en_US; 580610226)';
|
|
344
|
+
this.client.state.userAgent = fallback;
|
|
345
|
+
return fallback;
|
|
330
346
|
}
|
|
331
347
|
|
|
332
|
-
// Ensure we have a valid csrftoken cookie (and mid) before sensitive endpoints.
|
|
333
348
|
async ensureCsrfToken() {
|
|
334
349
|
const cookieToken = this.client.state.cookieCsrfToken;
|
|
335
|
-
if (
|
|
336
|
-
cookieToken
|
|
337
|
-
cookieToken !== 'missing' &&
|
|
338
|
-
cookieToken !== 'pending'
|
|
339
|
-
) {
|
|
340
|
-
try {
|
|
341
|
-
this.client.state.csrfToken = cookieToken;
|
|
342
|
-
} catch {}
|
|
350
|
+
if (cookieToken && cookieToken !== 'missing' && cookieToken !== 'pending') {
|
|
351
|
+
try { this.client.state.csrfToken = cookieToken; } catch {}
|
|
343
352
|
return cookieToken;
|
|
344
353
|
}
|
|
345
354
|
|
|
@@ -351,24 +360,13 @@ class AccountRepository extends Repository {
|
|
|
351
360
|
} catch (e) {}
|
|
352
361
|
|
|
353
362
|
const token = this.client.state.cookieCsrfToken;
|
|
354
|
-
if (
|
|
355
|
-
token
|
|
356
|
-
token !== 'missing' &&
|
|
357
|
-
token !== 'pending'
|
|
358
|
-
) {
|
|
359
|
-
try {
|
|
360
|
-
this.client.state.csrfToken = token;
|
|
361
|
-
} catch {}
|
|
363
|
+
if (token && token !== 'missing' && token !== 'pending') {
|
|
364
|
+
try { this.client.state.csrfToken = token; } catch {}
|
|
362
365
|
return token;
|
|
363
366
|
}
|
|
364
|
-
|
|
365
367
|
return null;
|
|
366
368
|
}
|
|
367
369
|
|
|
368
|
-
/**
|
|
369
|
-
* Internal: pre-login Bloks OAuth token fetch, imită exact apelul oficial
|
|
370
|
-
* com.bloks.www.caa.login.oauth.token.fetch.async
|
|
371
|
-
*/
|
|
372
370
|
async _prefetchOauthTokenForLogin(username) {
|
|
373
371
|
if (!username) return null;
|
|
374
372
|
|
|
@@ -383,16 +381,12 @@ class AccountRepository extends Repository {
|
|
|
383
381
|
const familyDeviceId =
|
|
384
382
|
this.client.state.phoneId ||
|
|
385
383
|
this.client.state.familyDeviceId ||
|
|
386
|
-
(crypto.randomUUID
|
|
387
|
-
? crypto.randomUUID()
|
|
388
|
-
: require('uuid').v4());
|
|
384
|
+
(crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4());
|
|
389
385
|
|
|
390
386
|
const qeDeviceId =
|
|
391
387
|
this.client.state.deviceId ||
|
|
392
388
|
this.client.state.qeDeviceId ||
|
|
393
|
-
(crypto.randomUUID
|
|
394
|
-
? crypto.randomUUID()
|
|
395
|
-
: require('uuid').v4());
|
|
389
|
+
(crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4());
|
|
396
390
|
|
|
397
391
|
const waterfallId = crypto.randomUUID
|
|
398
392
|
? crypto.randomUUID()
|
|
@@ -403,23 +397,15 @@ class AccountRepository extends Repository {
|
|
|
403
397
|
`${Date.now()}${Math.floor(Math.random() * 1000)}`
|
|
404
398
|
);
|
|
405
399
|
|
|
406
|
-
const aacInitTimestamp =
|
|
407
|
-
nowSec - Math.floor(Math.random() * 50);
|
|
400
|
+
const aacInitTimestamp = nowSec - Math.floor(Math.random() * 50);
|
|
408
401
|
const aacjid = crypto.randomUUID
|
|
409
402
|
? crypto.randomUUID()
|
|
410
403
|
: require('uuid').v4();
|
|
411
|
-
const aaccs = crypto
|
|
412
|
-
.randomBytes(32)
|
|
413
|
-
.toString('base64')
|
|
414
|
-
.replace(/=/g, '');
|
|
404
|
+
const aaccs = crypto.randomBytes(32).toString('base64').replace(/=/g, '');
|
|
415
405
|
|
|
416
406
|
const clientInputParams = {
|
|
417
407
|
username_input: username,
|
|
418
|
-
aac: JSON.stringify({
|
|
419
|
-
aac_init_timestamp: aacInitTimestamp,
|
|
420
|
-
aacjid,
|
|
421
|
-
aaccs,
|
|
422
|
-
}),
|
|
408
|
+
aac: JSON.stringify({ aac_init_timestamp: aacInitTimestamp, aacjid, aaccs }),
|
|
423
409
|
lois_settings: { lois_token: '' },
|
|
424
410
|
cloud_trust_token: null,
|
|
425
411
|
zero_balance_state: '',
|
|
@@ -447,6 +433,7 @@ class AccountRepository extends Repository {
|
|
|
447
433
|
server_params: serverParams,
|
|
448
434
|
});
|
|
449
435
|
|
|
436
|
+
// FIX: bloksVersionId kept in sync with IG version from _resolveUserAgent
|
|
450
437
|
const bloksVersionId =
|
|
451
438
|
this.client.state.bloksVersionId ||
|
|
452
439
|
'5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
|
|
@@ -459,19 +446,19 @@ class AccountRepository extends Repository {
|
|
|
459
446
|
const lang = this.client.state.language || 'ro_RO';
|
|
460
447
|
const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
|
|
461
448
|
const userAgent = this._resolveUserAgent();
|
|
462
|
-
|
|
463
449
|
const timezoneOffset =
|
|
464
450
|
typeof this.client.state.timezoneOffset === 'number'
|
|
465
451
|
? this.client.state.timezoneOffset
|
|
466
452
|
: 7200;
|
|
467
453
|
|
|
454
|
+
// FIX: dynamic network properties
|
|
455
|
+
const networkProps = buildNetworkProperties();
|
|
456
|
+
|
|
468
457
|
const bloksHeaders = {
|
|
469
458
|
'accept-language': acceptLanguage,
|
|
470
|
-
'content-type':
|
|
471
|
-
'application/x-www-form-urlencoded; charset=UTF-8',
|
|
459
|
+
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
472
460
|
'ig-intended-user-id': '0',
|
|
473
461
|
priority: 'u=3',
|
|
474
|
-
|
|
475
462
|
'x-bloks-is-layout-rtl': 'false',
|
|
476
463
|
'x-bloks-prism-ax-base-colors-enabled': 'false',
|
|
477
464
|
'x-bloks-prism-button-version': 'CONTROL',
|
|
@@ -479,53 +466,35 @@ class AccountRepository extends Repository {
|
|
|
479
466
|
'x-bloks-prism-font-enabled': 'false',
|
|
480
467
|
'x-bloks-prism-indigo-link-version': '0',
|
|
481
468
|
'x-bloks-version-id': bloksVersionId,
|
|
482
|
-
|
|
483
469
|
'x-ig-android-id': androidDeviceId,
|
|
484
470
|
'x-ig-device-id': qeDeviceId,
|
|
485
471
|
'x-ig-family-device-id': familyDeviceId,
|
|
486
472
|
'x-ig-timezone-offset': String(timezoneOffset),
|
|
487
|
-
'x-ig-app-id': String(
|
|
488
|
-
this.client.state.fbAnalyticsApplicationId ||
|
|
489
|
-
'567067343352427'
|
|
490
|
-
),
|
|
473
|
+
'x-ig-app-id': String(this.client.state.fbAnalyticsApplicationId || '567067343352427'),
|
|
491
474
|
'x-ig-app-locale': lang,
|
|
492
475
|
'x-ig-device-locale': lang,
|
|
493
476
|
'x-ig-mapped-locale': lang,
|
|
494
|
-
|
|
495
|
-
'x-ig-client-endpoint':
|
|
496
|
-
'com.bloks.www.caa.login.login_homepage',
|
|
477
|
+
'x-ig-client-endpoint': 'com.bloks.www.caa.login.login_homepage',
|
|
497
478
|
'x-ig-nav-chain':
|
|
498
479
|
'com.bloks.www.caa.login.login_homepage:com.bloks.www.caa.login.login_homepage:1:button:0:::0',
|
|
499
|
-
|
|
500
480
|
'x-ig-connection-type': 'WIFI',
|
|
501
481
|
'x-ig-capabilities': '3brTv10=',
|
|
502
482
|
'x-ig-www-claim': this.client.state.igWWWClaim || '0',
|
|
503
|
-
|
|
504
|
-
'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(
|
|
505
|
-
Math.random() * 1000
|
|
506
|
-
)
|
|
507
|
-
.toString()
|
|
508
|
-
.padStart(3, '0')}`,
|
|
483
|
+
'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(Math.random() * 1000).toString().padStart(3, '0')}`,
|
|
509
484
|
'x-pigeon-session-id':
|
|
510
485
|
this.client.state.pigeonSessionId ||
|
|
511
486
|
`UFS-${crypto.randomBytes(16).toString('hex')}-2`,
|
|
512
|
-
|
|
513
487
|
'x-mid':
|
|
514
488
|
this.client.state.mid ||
|
|
515
489
|
`aZ${crypto.randomBytes(8).toString('hex')}`,
|
|
516
490
|
'x-tigon-is-retry': 'False',
|
|
517
|
-
|
|
518
491
|
'x-fb-client-ip': 'True',
|
|
519
492
|
'x-fb-connection-type': 'WIFI',
|
|
520
493
|
'x-fb-server-cluster': 'True',
|
|
521
|
-
'x-fb-network-properties':
|
|
522
|
-
'VPN;Validated;LocalAddrs=/10.0.0.2,;',
|
|
494
|
+
'x-fb-network-properties': networkProps, // FIX: dynamic
|
|
523
495
|
'x-fb-request-analytics-tags': JSON.stringify({
|
|
524
496
|
network_tags: {
|
|
525
|
-
product: String(
|
|
526
|
-
this.client.state.fbAnalyticsApplicationId ||
|
|
527
|
-
'567067343352427'
|
|
528
|
-
),
|
|
497
|
+
product: String(this.client.state.fbAnalyticsApplicationId || '567067343352427'),
|
|
529
498
|
purpose: 'fetch',
|
|
530
499
|
surface: 'undefined',
|
|
531
500
|
request_category: 'api',
|
|
@@ -536,7 +505,6 @@ class AccountRepository extends Repository {
|
|
|
536
505
|
'IgApi: bloks/async_action/com.bloks.www.caa.login.oauth.token.fetch.async/',
|
|
537
506
|
'x-fb-http-engine': 'MNS/TCP',
|
|
538
507
|
'x-fb-rmd': 'state=URL_ELIGIBLE',
|
|
539
|
-
|
|
540
508
|
'user-agent': userAgent,
|
|
541
509
|
};
|
|
542
510
|
|
|
@@ -551,34 +519,12 @@ class AccountRepository extends Repository {
|
|
|
551
519
|
headers: bloksHeaders,
|
|
552
520
|
});
|
|
553
521
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
);
|
|
561
|
-
const debugFile = path.join(
|
|
562
|
-
debugDir,
|
|
563
|
-
'oauth-token-debug.json'
|
|
564
|
-
);
|
|
565
|
-
try {
|
|
566
|
-
fs.mkdirSync(debugDir, { recursive: true });
|
|
567
|
-
} catch (e) {}
|
|
568
|
-
|
|
569
|
-
const debugPayload = {
|
|
570
|
-
at: new Date().toISOString(),
|
|
571
|
-
statusCode:
|
|
572
|
-
response.statusCode || response.status || null,
|
|
573
|
-
headers: response.headers || null,
|
|
574
|
-
body: response.body || null,
|
|
575
|
-
};
|
|
576
|
-
fs.writeFileSync(
|
|
577
|
-
debugFile,
|
|
578
|
-
JSON.stringify(debugPayload, null, 2),
|
|
579
|
-
'utf8'
|
|
580
|
-
);
|
|
581
|
-
} catch (e) {}
|
|
522
|
+
debugWrite('oauth-token-debug.json', {
|
|
523
|
+
at: new Date().toISOString(),
|
|
524
|
+
statusCode: response.statusCode || response.status || null,
|
|
525
|
+
headers: response.headers || null,
|
|
526
|
+
body: response.body || null,
|
|
527
|
+
});
|
|
582
528
|
|
|
583
529
|
return response.body;
|
|
584
530
|
} catch (e) {
|
|
@@ -586,10 +532,6 @@ class AccountRepository extends Repository {
|
|
|
586
532
|
}
|
|
587
533
|
}
|
|
588
534
|
|
|
589
|
-
/**
|
|
590
|
-
* Step 1: launcher/mobileconfig to fetch mobile config + encryption keys.
|
|
591
|
-
* Best effort – errors are ignored.
|
|
592
|
-
*/
|
|
593
535
|
async _launcherMobileConfig(preLogin = true) {
|
|
594
536
|
const nowSec = Math.floor(Date.now() / 1000);
|
|
595
537
|
const state = this.client.state || {};
|
|
@@ -602,16 +544,12 @@ class AccountRepository extends Repository {
|
|
|
602
544
|
const familyDeviceId =
|
|
603
545
|
state.phoneId ||
|
|
604
546
|
state.familyDeviceId ||
|
|
605
|
-
(crypto.randomUUID
|
|
606
|
-
? crypto.randomUUID()
|
|
607
|
-
: require('uuid').v4());
|
|
547
|
+
(crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4());
|
|
608
548
|
|
|
609
549
|
const qeDeviceId =
|
|
610
550
|
state.deviceId ||
|
|
611
551
|
state.qeDeviceId ||
|
|
612
|
-
(crypto.randomUUID
|
|
613
|
-
? crypto.randomUUID()
|
|
614
|
-
: require('uuid').v4());
|
|
552
|
+
(crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4());
|
|
615
553
|
|
|
616
554
|
const deviceUUID = state.uuid || qeDeviceId;
|
|
617
555
|
const userId = state.cookieUserId || '0';
|
|
@@ -623,11 +561,12 @@ class AccountRepository extends Repository {
|
|
|
623
561
|
const lang = state.language || 'ro_RO';
|
|
624
562
|
const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
|
|
625
563
|
const userAgent = this._resolveUserAgent();
|
|
626
|
-
|
|
627
564
|
const timezoneOffset =
|
|
628
|
-
typeof state.timezoneOffset === 'number'
|
|
629
|
-
|
|
630
|
-
|
|
565
|
+
typeof state.timezoneOffset === 'number' ? state.timezoneOffset : 7200;
|
|
566
|
+
|
|
567
|
+
// FIX: salt-ids from state or fallback
|
|
568
|
+
const saltIds = state.igSaltIds || '220140399,332020310,974466465,974460658';
|
|
569
|
+
const networkProps = buildNetworkProperties();
|
|
631
570
|
|
|
632
571
|
const params = {
|
|
633
572
|
bool_opt_policy: '0',
|
|
@@ -636,8 +575,7 @@ class AccountRepository extends Repository {
|
|
|
636
575
|
client_context: '["opt,value_hash"]',
|
|
637
576
|
unit_type: '2',
|
|
638
577
|
use_case: 'STANDARD',
|
|
639
|
-
query_hash:
|
|
640
|
-
'f00b9d0869db3969378d8d06bfccb24b5ef078012c8e199cba961cd5dfedaa88',
|
|
578
|
+
query_hash: 'f00b9d0869db3969378d8d06bfccb24b5ef078012c8e199cba961cd5dfedaa88',
|
|
641
579
|
ts: String(nowSec),
|
|
642
580
|
_uid: userId,
|
|
643
581
|
device_id: deviceUUID,
|
|
@@ -648,10 +586,8 @@ class AccountRepository extends Repository {
|
|
|
648
586
|
|
|
649
587
|
const headers = {
|
|
650
588
|
'accept-language': acceptLanguage,
|
|
651
|
-
'content-type':
|
|
652
|
-
'application/x-www-form-urlencoded; charset=UTF-8',
|
|
589
|
+
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
653
590
|
priority: 'u=3',
|
|
654
|
-
|
|
655
591
|
'x-bloks-is-layout-rtl': 'false',
|
|
656
592
|
'x-bloks-prism-ax-base-colors-enabled': 'false',
|
|
657
593
|
'x-bloks-prism-button-version': 'CONTROL',
|
|
@@ -659,28 +595,19 @@ class AccountRepository extends Repository {
|
|
|
659
595
|
'x-bloks-prism-font-enabled': 'false',
|
|
660
596
|
'x-bloks-prism-indigo-link-version': '0',
|
|
661
597
|
'x-bloks-version-id': bloksVersionId,
|
|
662
|
-
|
|
663
598
|
'x-fb-client-ip': 'True',
|
|
664
599
|
'x-fb-connection-type': 'WIFI',
|
|
665
600
|
'x-fb-server-cluster': 'True',
|
|
666
|
-
'x-fb-network-properties':
|
|
667
|
-
'VPN;Validated;LocalAddrs=/10.0.0.2,;',
|
|
601
|
+
'x-fb-network-properties': networkProps, // FIX: dynamic
|
|
668
602
|
'x-fb-http-engine': 'MNS/TCP',
|
|
669
603
|
'x-fb-rmd': 'state=URL_ELIGIBLE',
|
|
670
|
-
|
|
671
604
|
'x-ig-android-id': androidDeviceId,
|
|
672
|
-
'x-ig-app-id': String(
|
|
673
|
-
state.fbAnalyticsApplicationId || '567067343352427'
|
|
674
|
-
),
|
|
605
|
+
'x-ig-app-id': String(state.fbAnalyticsApplicationId || '567067343352427'),
|
|
675
606
|
'x-ig-app-locale': lang,
|
|
676
|
-
'x-ig-bandwidth-speed-kbps': (
|
|
677
|
-
Math.random() * 1500 +
|
|
678
|
-
800
|
|
679
|
-
).toFixed(3),
|
|
607
|
+
'x-ig-bandwidth-speed-kbps': (Math.random() * 1500 + 800).toFixed(3),
|
|
680
608
|
'x-ig-bandwidth-totalbytes-b': '0',
|
|
681
609
|
'x-ig-bandwidth-totaltime-ms': '0',
|
|
682
|
-
'x-ig-client-endpoint':
|
|
683
|
-
'LockoutFragment:dogfooding_lockout',
|
|
610
|
+
'x-ig-client-endpoint': 'LockoutFragment:dogfooding_lockout',
|
|
684
611
|
'x-ig-capabilities': '3brTv10=',
|
|
685
612
|
'x-ig-connection-type': 'WIFI',
|
|
686
613
|
'x-ig-device-id': deviceUUID,
|
|
@@ -688,26 +615,19 @@ class AccountRepository extends Repository {
|
|
|
688
615
|
'x-ig-device-locale': lang,
|
|
689
616
|
'x-ig-family-device-id': familyDeviceId,
|
|
690
617
|
'x-ig-mapped-locale': lang,
|
|
691
|
-
'x-ig-nav-chain':
|
|
692
|
-
|
|
693
|
-
'x-ig-salt-ids':
|
|
694
|
-
'220140399,332020310,974466465,974460658',
|
|
618
|
+
'x-ig-nav-chain': 'LockoutFragment:dogfooding_lockout:1:cold_start',
|
|
619
|
+
'x-ig-salt-ids': saltIds, // FIX: from state or fallback
|
|
695
620
|
'x-ig-timezone-offset': String(timezoneOffset),
|
|
696
621
|
'x-ig-www-claim': state.igWWWClaim || '0',
|
|
697
622
|
'x-mid':
|
|
698
623
|
state.mid ||
|
|
699
624
|
state.machineId ||
|
|
700
625
|
`aZ${crypto.randomBytes(8).toString('hex')}`,
|
|
701
|
-
'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(
|
|
702
|
-
Math.random() * 1000
|
|
703
|
-
)
|
|
704
|
-
.toString()
|
|
705
|
-
.padStart(3, '0')}`,
|
|
626
|
+
'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(Math.random() * 1000).toString().padStart(3, '0')}`,
|
|
706
627
|
'x-pigeon-session-id':
|
|
707
628
|
state.pigeonSessionId ||
|
|
708
629
|
`UFS-${crypto.randomBytes(16).toString('hex')}-M`,
|
|
709
630
|
'x-tigon-is-retry': 'False',
|
|
710
|
-
|
|
711
631
|
'accept-encoding': 'gzip, deflate, br',
|
|
712
632
|
'user-agent': userAgent,
|
|
713
633
|
};
|
|
@@ -716,12 +636,8 @@ class AccountRepository extends Repository {
|
|
|
716
636
|
headers['ig-intended-user-id'] = String(state.cookieUserId);
|
|
717
637
|
headers['ig-u-ds-user-id'] = String(state.cookieUserId);
|
|
718
638
|
}
|
|
719
|
-
if (state.igURur)
|
|
720
|
-
|
|
721
|
-
}
|
|
722
|
-
if (state.authorization) {
|
|
723
|
-
headers['authorization'] = state.authorization;
|
|
724
|
-
}
|
|
639
|
+
if (state.igURur) headers['ig-u-rur'] = state.igURur;
|
|
640
|
+
if (state.authorization) headers['authorization'] = state.authorization;
|
|
725
641
|
|
|
726
642
|
try {
|
|
727
643
|
const response = await this.client.request.send({
|
|
@@ -731,7 +647,6 @@ class AccountRepository extends Repository {
|
|
|
731
647
|
headers,
|
|
732
648
|
});
|
|
733
649
|
|
|
734
|
-
// NEW: salvăm cheia de criptare a parolei direct din headers de la mobileconfig
|
|
735
650
|
try {
|
|
736
651
|
const mcHeaders = response.headers || {};
|
|
737
652
|
const mcLower = {};
|
|
@@ -739,10 +654,8 @@ class AccountRepository extends Repository {
|
|
|
739
654
|
mcLower[k.toLowerCase()] = mcHeaders[k];
|
|
740
655
|
}
|
|
741
656
|
|
|
742
|
-
const mcKeyIdStr =
|
|
743
|
-
|
|
744
|
-
const mcPubKey =
|
|
745
|
-
mcLower['ig-set-password-encryption-pub-key'];
|
|
657
|
+
const mcKeyIdStr = mcLower['ig-set-password-encryption-key-id'];
|
|
658
|
+
const mcPubKey = mcLower['ig-set-password-encryption-pub-key'];
|
|
746
659
|
|
|
747
660
|
if (mcKeyIdStr) {
|
|
748
661
|
const parsedKeyId = parseInt(mcKeyIdStr, 10);
|
|
@@ -750,39 +663,15 @@ class AccountRepository extends Repository {
|
|
|
750
663
|
state.passwordEncryptionKeyId = parsedKeyId;
|
|
751
664
|
}
|
|
752
665
|
}
|
|
753
|
-
|
|
754
|
-
if (mcPubKey) {
|
|
755
|
-
state.passwordEncryptionPubKey = mcPubKey;
|
|
756
|
-
}
|
|
666
|
+
if (mcPubKey) state.passwordEncryptionPubKey = mcPubKey;
|
|
757
667
|
} catch (e) {}
|
|
758
668
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
);
|
|
766
|
-
const debugFile = path.join(
|
|
767
|
-
debugDir,
|
|
768
|
-
'launcher-mobileconfig-debug.json'
|
|
769
|
-
);
|
|
770
|
-
try {
|
|
771
|
-
fs.mkdirSync(debugDir, { recursive: true });
|
|
772
|
-
} catch (e) {}
|
|
773
|
-
const debugPayload = {
|
|
774
|
-
at: new Date().toISOString(),
|
|
775
|
-
statusCode:
|
|
776
|
-
response.statusCode || response.status || null,
|
|
777
|
-
headers: response.headers || null,
|
|
778
|
-
body: response.body || null,
|
|
779
|
-
};
|
|
780
|
-
fs.writeFileSync(
|
|
781
|
-
debugFile,
|
|
782
|
-
JSON.stringify(debugPayload, null, 2),
|
|
783
|
-
'utf8'
|
|
784
|
-
);
|
|
785
|
-
} catch (e) {}
|
|
669
|
+
debugWrite('launcher-mobileconfig-debug.json', {
|
|
670
|
+
at: new Date().toISOString(),
|
|
671
|
+
statusCode: response.statusCode || response.status || null,
|
|
672
|
+
headers: response.headers || null,
|
|
673
|
+
body: response.body || null,
|
|
674
|
+
});
|
|
786
675
|
|
|
787
676
|
return response.body;
|
|
788
677
|
} catch (e) {
|
|
@@ -790,10 +679,6 @@ class AccountRepository extends Repository {
|
|
|
790
679
|
}
|
|
791
680
|
}
|
|
792
681
|
|
|
793
|
-
/**
|
|
794
|
-
* Step 2: Android Keystore attestation – moves x-ig-attest-params
|
|
795
|
-
* payload to dedicated endpoint /api/v1/attestation/create_android_keystore/.
|
|
796
|
-
*/
|
|
797
682
|
async _createAndroidKeystoreAttestation() {
|
|
798
683
|
const state = this.client.state || {};
|
|
799
684
|
const nowSec = Math.floor(Date.now() / 1000);
|
|
@@ -806,30 +691,25 @@ class AccountRepository extends Repository {
|
|
|
806
691
|
const familyDeviceId =
|
|
807
692
|
state.phoneId ||
|
|
808
693
|
state.familyDeviceId ||
|
|
809
|
-
(crypto.randomUUID
|
|
810
|
-
? crypto.randomUUID()
|
|
811
|
-
: require('uuid').v4());
|
|
694
|
+
(crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4());
|
|
812
695
|
|
|
813
696
|
const qeDeviceId =
|
|
814
697
|
state.deviceId ||
|
|
815
698
|
state.qeDeviceId ||
|
|
816
|
-
(crypto.randomUUID
|
|
817
|
-
? crypto.randomUUID()
|
|
818
|
-
: require('uuid').v4());
|
|
699
|
+
(crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4());
|
|
819
700
|
|
|
820
701
|
const deviceUUID = state.uuid || qeDeviceId;
|
|
821
702
|
const userId = state.cookieUserId || '0';
|
|
822
703
|
|
|
823
|
-
|
|
824
|
-
|
|
704
|
+
// FIX: each cert in the chain has its own key pair
|
|
705
|
+
const attestParams = AccountRepository.generateAttestParams(state);
|
|
825
706
|
|
|
826
707
|
const lang = state.language || 'ro_RO';
|
|
827
708
|
const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
|
|
828
709
|
const userAgent = this._resolveUserAgent();
|
|
829
710
|
const timezoneOffset =
|
|
830
|
-
typeof state.timezoneOffset === 'number'
|
|
831
|
-
|
|
832
|
-
: 7200;
|
|
711
|
+
typeof state.timezoneOffset === 'number' ? state.timezoneOffset : 7200;
|
|
712
|
+
const networkProps = buildNetworkProperties();
|
|
833
713
|
|
|
834
714
|
const params = {
|
|
835
715
|
_uid: userId,
|
|
@@ -842,29 +722,20 @@ class AccountRepository extends Repository {
|
|
|
842
722
|
|
|
843
723
|
const headers = {
|
|
844
724
|
'accept-language': acceptLanguage,
|
|
845
|
-
'content-type':
|
|
846
|
-
'application/x-www-form-urlencoded; charset=UTF-8',
|
|
725
|
+
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
847
726
|
priority: 'u=3',
|
|
848
|
-
|
|
849
727
|
'x-ig-android-id': androidDeviceId,
|
|
850
728
|
'x-ig-device-id': qeDeviceId,
|
|
851
729
|
'x-ig-family-device-id': familyDeviceId,
|
|
852
730
|
'x-ig-timezone-offset': String(timezoneOffset),
|
|
853
|
-
'x-ig-app-id': String(
|
|
854
|
-
state.fbAnalyticsApplicationId || '567067343352427'
|
|
855
|
-
),
|
|
731
|
+
'x-ig-app-id': String(state.fbAnalyticsApplicationId || '567067343352427'),
|
|
856
732
|
'x-ig-app-locale': lang,
|
|
857
733
|
'x-ig-device-locale': lang,
|
|
858
734
|
'x-ig-mapped-locale': lang,
|
|
859
735
|
'x-ig-connection-type': 'WIFI',
|
|
860
736
|
'x-ig-capabilities': '3brTv10=',
|
|
861
737
|
'x-ig-www-claim': state.igWWWClaim || '0',
|
|
862
|
-
|
|
863
|
-
'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(
|
|
864
|
-
Math.random() * 1000
|
|
865
|
-
)
|
|
866
|
-
.toString()
|
|
867
|
-
.padStart(3, '0')}`,
|
|
738
|
+
'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(Math.random() * 1000).toString().padStart(3, '0')}`,
|
|
868
739
|
'x-pigeon-session-id':
|
|
869
740
|
state.pigeonSessionId ||
|
|
870
741
|
`UFS-${crypto.randomBytes(16).toString('hex')}-A`,
|
|
@@ -872,28 +743,19 @@ class AccountRepository extends Repository {
|
|
|
872
743
|
state.mid ||
|
|
873
744
|
state.machineId ||
|
|
874
745
|
`aZ${crypto.randomBytes(8).toString('hex')}`,
|
|
875
|
-
|
|
876
746
|
'x-fb-client-ip': 'True',
|
|
877
747
|
'x-fb-connection-type': 'WIFI',
|
|
878
748
|
'x-fb-server-cluster': 'True',
|
|
879
749
|
'x-fb-http-engine': 'MNS/TCP',
|
|
880
|
-
'x-fb-network-properties':
|
|
881
|
-
'VPN;Validated;LocalAddrs=/10.0.0.2,;',
|
|
750
|
+
'x-fb-network-properties': networkProps, // FIX: dynamic
|
|
882
751
|
'x-fb-rmd': 'state=URL_ELIGIBLE',
|
|
883
|
-
|
|
884
752
|
'accept-encoding': 'gzip, deflate, br',
|
|
885
753
|
'user-agent': userAgent,
|
|
886
754
|
};
|
|
887
755
|
|
|
888
|
-
if (state.cookieUserId)
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
if (state.authorization) {
|
|
892
|
-
headers['authorization'] = state.authorization;
|
|
893
|
-
}
|
|
894
|
-
if (state.igURur) {
|
|
895
|
-
headers['ig-u-rur'] = state.igURur;
|
|
896
|
-
}
|
|
756
|
+
if (state.cookieUserId) headers['ig-intended-user-id'] = String(state.cookieUserId);
|
|
757
|
+
if (state.authorization) headers['authorization'] = state.authorization;
|
|
758
|
+
if (state.igURur) headers['ig-u-rur'] = state.igURur;
|
|
897
759
|
|
|
898
760
|
try {
|
|
899
761
|
const response = await this.client.request.send({
|
|
@@ -903,33 +765,12 @@ class AccountRepository extends Repository {
|
|
|
903
765
|
headers,
|
|
904
766
|
});
|
|
905
767
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
);
|
|
913
|
-
const debugFile = path.join(
|
|
914
|
-
debugDir,
|
|
915
|
-
'attestation-debug.json'
|
|
916
|
-
);
|
|
917
|
-
try {
|
|
918
|
-
fs.mkdirSync(debugDir, { recursive: true });
|
|
919
|
-
} catch (e) {}
|
|
920
|
-
const debugPayload = {
|
|
921
|
-
at: new Date().toISOString(),
|
|
922
|
-
statusCode:
|
|
923
|
-
response.statusCode || response.status || null,
|
|
924
|
-
headers: response.headers || null,
|
|
925
|
-
body: response.body || null,
|
|
926
|
-
};
|
|
927
|
-
fs.writeFileSync(
|
|
928
|
-
debugFile,
|
|
929
|
-
JSON.stringify(debugPayload, null, 2),
|
|
930
|
-
'utf8'
|
|
931
|
-
);
|
|
932
|
-
} catch (e) {}
|
|
768
|
+
debugWrite('attestation-debug.json', {
|
|
769
|
+
at: new Date().toISOString(),
|
|
770
|
+
statusCode: response.statusCode || response.status || null,
|
|
771
|
+
headers: response.headers || null,
|
|
772
|
+
body: response.body || null,
|
|
773
|
+
});
|
|
933
774
|
|
|
934
775
|
return response.body;
|
|
935
776
|
} catch (e) {
|
|
@@ -937,11 +778,6 @@ class AccountRepository extends Repository {
|
|
|
937
778
|
}
|
|
938
779
|
}
|
|
939
780
|
|
|
940
|
-
/**
|
|
941
|
-
* Step 3 & 4 & 5 helpers: Terms of service preload, process client data,
|
|
942
|
-
* and (optional) phone number prefill.
|
|
943
|
-
* These are best-effort calls; failures are ignored.
|
|
944
|
-
*/
|
|
945
781
|
async _preloadTermsOfService() {
|
|
946
782
|
const state = this.client.state || {};
|
|
947
783
|
const lang = state.language || 'ro_RO';
|
|
@@ -952,29 +788,19 @@ class AccountRepository extends Repository {
|
|
|
952
788
|
'5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
|
|
953
789
|
|
|
954
790
|
const paramsJson = JSON.stringify({});
|
|
955
|
-
const bkClientContext = JSON.stringify({
|
|
956
|
-
bloks_version: bloksVersionId,
|
|
957
|
-
styles_id: 'instagram',
|
|
958
|
-
});
|
|
959
|
-
|
|
791
|
+
const bkClientContext = JSON.stringify({ bloks_version: bloksVersionId, styles_id: 'instagram' });
|
|
960
792
|
const headers = {
|
|
961
793
|
'accept-language': acceptLanguage,
|
|
962
|
-
'content-type':
|
|
963
|
-
'application/x-www-form-urlencoded; charset=UTF-8',
|
|
794
|
+
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
964
795
|
priority: 'u=3',
|
|
965
796
|
'x-bloks-version-id': bloksVersionId,
|
|
966
797
|
'user-agent': userAgent,
|
|
967
798
|
};
|
|
968
|
-
|
|
969
799
|
try {
|
|
970
800
|
const response = await this.client.request.send({
|
|
971
801
|
method: 'POST',
|
|
972
802
|
url: '/api/v1/bloks/apps/com.bloks.www.caa.login.oxygen_preloads_terms_of_service/',
|
|
973
|
-
form: {
|
|
974
|
-
params: paramsJson,
|
|
975
|
-
bk_client_context: bkClientContext,
|
|
976
|
-
bloks_versioning_id: bloksVersionId,
|
|
977
|
-
},
|
|
803
|
+
form: { params: paramsJson, bk_client_context: bkClientContext, bloks_versioning_id: bloksVersionId },
|
|
978
804
|
headers,
|
|
979
805
|
});
|
|
980
806
|
return response.body;
|
|
@@ -992,32 +818,20 @@ class AccountRepository extends Repository {
|
|
|
992
818
|
state.bloksVersionId ||
|
|
993
819
|
'5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
|
|
994
820
|
|
|
995
|
-
const paramsJson = JSON.stringify({
|
|
996
|
-
|
|
997
|
-
});
|
|
998
|
-
const bkClientContext = JSON.stringify({
|
|
999
|
-
bloks_version: bloksVersionId,
|
|
1000
|
-
styles_id: 'instagram',
|
|
1001
|
-
});
|
|
1002
|
-
|
|
821
|
+
const paramsJson = JSON.stringify({ username_input: username || '' });
|
|
822
|
+
const bkClientContext = JSON.stringify({ bloks_version: bloksVersionId, styles_id: 'instagram' });
|
|
1003
823
|
const headers = {
|
|
1004
824
|
'accept-language': acceptLanguage,
|
|
1005
|
-
'content-type':
|
|
1006
|
-
'application/x-www-form-urlencoded; charset=UTF-8',
|
|
825
|
+
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
1007
826
|
priority: 'u=3',
|
|
1008
827
|
'x-bloks-version-id': bloksVersionId,
|
|
1009
828
|
'user-agent': userAgent,
|
|
1010
829
|
};
|
|
1011
|
-
|
|
1012
830
|
try {
|
|
1013
831
|
const response = await this.client.request.send({
|
|
1014
832
|
method: 'POST',
|
|
1015
833
|
url: '/api/v1/bloks/async_action/com.bloks.www.bloks.caa.login.process_client_data_and_redirect/',
|
|
1016
|
-
form: {
|
|
1017
|
-
params: paramsJson,
|
|
1018
|
-
bk_client_context: bkClientContext,
|
|
1019
|
-
bloks_versioning_id: bloksVersionId,
|
|
1020
|
-
},
|
|
834
|
+
form: { params: paramsJson, bk_client_context: bkClientContext, bloks_versioning_id: bloksVersionId },
|
|
1021
835
|
headers,
|
|
1022
836
|
});
|
|
1023
837
|
return response.body;
|
|
@@ -1036,29 +850,19 @@ class AccountRepository extends Repository {
|
|
|
1036
850
|
'5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
|
|
1037
851
|
|
|
1038
852
|
const paramsJson = JSON.stringify({});
|
|
1039
|
-
const bkClientContext = JSON.stringify({
|
|
1040
|
-
bloks_version: bloksVersionId,
|
|
1041
|
-
styles_id: 'instagram',
|
|
1042
|
-
});
|
|
1043
|
-
|
|
853
|
+
const bkClientContext = JSON.stringify({ bloks_version: bloksVersionId, styles_id: 'instagram' });
|
|
1044
854
|
const headers = {
|
|
1045
855
|
'accept-language': acceptLanguage,
|
|
1046
|
-
'content-type':
|
|
1047
|
-
'application/x-www-form-urlencoded; charset=UTF-8',
|
|
856
|
+
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
1048
857
|
priority: 'u=3',
|
|
1049
858
|
'x-bloks-version-id': bloksVersionId,
|
|
1050
859
|
'user-agent': userAgent,
|
|
1051
860
|
};
|
|
1052
|
-
|
|
1053
861
|
try {
|
|
1054
862
|
const response = await this.client.request.send({
|
|
1055
863
|
method: 'POST',
|
|
1056
864
|
url: '/api/v1/bloks/async_action/com.bloks.www.bloks.caa.phone.number.prefill.async.controller/',
|
|
1057
|
-
form: {
|
|
1058
|
-
params: paramsJson,
|
|
1059
|
-
bk_client_context: bkClientContext,
|
|
1060
|
-
bloks_versioning_id: bloksVersionId,
|
|
1061
|
-
},
|
|
865
|
+
form: { params: paramsJson, bk_client_context: bkClientContext, bloks_versioning_id: bloksVersionId },
|
|
1062
866
|
headers,
|
|
1063
867
|
});
|
|
1064
868
|
return response.body;
|
|
@@ -1069,10 +873,7 @@ class AccountRepository extends Repository {
|
|
|
1069
873
|
|
|
1070
874
|
async login(credentialsOrUsername, passwordArg) {
|
|
1071
875
|
let username, password;
|
|
1072
|
-
if (
|
|
1073
|
-
typeof credentialsOrUsername === 'object' &&
|
|
1074
|
-
credentialsOrUsername !== null
|
|
1075
|
-
) {
|
|
876
|
+
if (typeof credentialsOrUsername === 'object' && credentialsOrUsername !== null) {
|
|
1076
877
|
username = credentialsOrUsername.username;
|
|
1077
878
|
password = credentialsOrUsername.password;
|
|
1078
879
|
} else {
|
|
@@ -1080,57 +881,37 @@ class AccountRepository extends Repository {
|
|
|
1080
881
|
password = passwordArg;
|
|
1081
882
|
}
|
|
1082
883
|
|
|
1083
|
-
if (!username || !password)
|
|
1084
|
-
throw new Error('Username and password are required');
|
|
1085
|
-
}
|
|
884
|
+
if (!username || !password) throw new Error('Username and password are required');
|
|
1086
885
|
|
|
1087
|
-
// 0: ne asigurăm că avem csrf + cookies de bază
|
|
1088
886
|
await this.ensureCsrfToken();
|
|
1089
887
|
|
|
1090
|
-
|
|
1091
|
-
try {
|
|
1092
|
-
await this._launcherMobileConfig(true);
|
|
1093
|
-
} catch (e) {}
|
|
888
|
+
try { await this._launcherMobileConfig(true); } catch (e) {}
|
|
1094
889
|
|
|
1095
|
-
// Fallback: dacă mobileconfig nu a setat cheia, folosim mecanismul vechi de LOGIN_EXPERIMENTS
|
|
1096
890
|
if (!this.client.state.passwordEncryptionPubKey) {
|
|
1097
891
|
await this.syncLoginExperiments();
|
|
1098
892
|
}
|
|
1099
893
|
|
|
1100
|
-
//
|
|
1101
|
-
|
|
894
|
+
// FIX: if we still have no public key, throw error — do not send password in plaintext
|
|
895
|
+
if (!this.client.state.passwordEncryptionPubKey) {
|
|
896
|
+
throw new Error(
|
|
897
|
+
'Could not obtain the password encryption public key. ' +
|
|
898
|
+
'Check that mobileconfig and syncLoginExperiments return valid data.'
|
|
899
|
+
);
|
|
900
|
+
}
|
|
1102
901
|
|
|
1103
|
-
|
|
1104
|
-
try {
|
|
1105
|
-
await this._createAndroidKeystoreAttestation();
|
|
1106
|
-
} catch (e) {}
|
|
1107
|
-
try {
|
|
1108
|
-
await this._preloadTermsOfService();
|
|
1109
|
-
} catch (e) {}
|
|
1110
|
-
try {
|
|
1111
|
-
await this._processClientDataAndRedirect(username);
|
|
1112
|
-
} catch (e) {}
|
|
1113
|
-
try {
|
|
1114
|
-
await this._phoneNumberPrefill();
|
|
1115
|
-
} catch (e) {}
|
|
902
|
+
const { encrypted, time } = this.encryptPassword(password);
|
|
1116
903
|
|
|
1117
|
-
|
|
1118
|
-
try {
|
|
1119
|
-
|
|
1120
|
-
} catch (e) {}
|
|
904
|
+
try { await this._createAndroidKeystoreAttestation(); } catch (e) {}
|
|
905
|
+
try { await this._preloadTermsOfService(); } catch (e) {}
|
|
906
|
+
try { await this._processClientDataAndRedirect(username); } catch (e) {}
|
|
907
|
+
try { await this._phoneNumberPrefill(); } catch (e) {}
|
|
908
|
+
try { await this._prefetchOauthTokenForLogin(username); } catch (e) {}
|
|
1121
909
|
|
|
1122
|
-
|
|
1123
|
-
return this.requestWithRetry(async () => {
|
|
910
|
+
const loginResult = await this.requestWithRetry(async () => {
|
|
1124
911
|
const nowSec = Math.floor(Date.now() / 1000);
|
|
1125
|
-
const aacInitTimestamp =
|
|
1126
|
-
|
|
1127
|
-
const
|
|
1128
|
-
? crypto.randomUUID()
|
|
1129
|
-
: require('uuid').v4();
|
|
1130
|
-
const aaccs = crypto
|
|
1131
|
-
.randomBytes(32)
|
|
1132
|
-
.toString('base64')
|
|
1133
|
-
.replace(/=/g, '');
|
|
912
|
+
const aacInitTimestamp = nowSec - Math.floor(Math.random() * 50);
|
|
913
|
+
const aacjid = crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4();
|
|
914
|
+
const aaccs = crypto.randomBytes(32).toString('base64').replace(/=/g, '');
|
|
1134
915
|
|
|
1135
916
|
const androidDeviceId =
|
|
1136
917
|
this.client.state.androidDeviceId ||
|
|
@@ -1143,18 +924,13 @@ class AccountRepository extends Repository {
|
|
|
1143
924
|
const familyDeviceId =
|
|
1144
925
|
this.client.state.phoneId ||
|
|
1145
926
|
this.client.state.familyDeviceId ||
|
|
1146
|
-
(crypto.randomUUID
|
|
1147
|
-
? crypto.randomUUID()
|
|
1148
|
-
: require('uuid').v4());
|
|
927
|
+
(crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4());
|
|
1149
928
|
const qeDeviceId =
|
|
1150
929
|
this.client.state.deviceId ||
|
|
1151
930
|
this.client.state.qeDeviceId ||
|
|
1152
|
-
(crypto.randomUUID
|
|
1153
|
-
? crypto.randomUUID()
|
|
1154
|
-
: require('uuid').v4());
|
|
931
|
+
(crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4());
|
|
1155
932
|
|
|
1156
933
|
const accountsList = this.client.state.accountsList || [];
|
|
1157
|
-
|
|
1158
934
|
const flashCallPermissionStatus = {
|
|
1159
935
|
READ_PHONE_STATE: 'GRANTED',
|
|
1160
936
|
READ_CALL_LOG: 'GRANTED',
|
|
@@ -1162,11 +938,7 @@ class AccountRepository extends Repository {
|
|
|
1162
938
|
};
|
|
1163
939
|
|
|
1164
940
|
const clientInputParams = {
|
|
1165
|
-
aac: JSON.stringify({
|
|
1166
|
-
aac_init_timestamp: aacInitTimestamp,
|
|
1167
|
-
aacjid,
|
|
1168
|
-
aaccs,
|
|
1169
|
-
}),
|
|
941
|
+
aac: JSON.stringify({ aac_init_timestamp: aacInitTimestamp, aacjid, aaccs }),
|
|
1170
942
|
sim_phones: [],
|
|
1171
943
|
aymh_accounts: [],
|
|
1172
944
|
network_bssid: null,
|
|
@@ -1194,8 +966,7 @@ class AccountRepository extends Repository {
|
|
|
1194
966
|
machine_id: machineId,
|
|
1195
967
|
flash_call_permission_status: flashCallPermissionStatus,
|
|
1196
968
|
accounts_list: accountsList,
|
|
1197
|
-
gms_incoming_call_retriever_eligibility:
|
|
1198
|
-
'client_not_supported',
|
|
969
|
+
gms_incoming_call_retriever_eligibility: 'client_not_supported',
|
|
1199
970
|
family_device_id: familyDeviceId,
|
|
1200
971
|
fb_ig_device_id: [],
|
|
1201
972
|
device_emails: [],
|
|
@@ -1207,13 +978,9 @@ class AccountRepository extends Repository {
|
|
|
1207
978
|
contact_point: username,
|
|
1208
979
|
};
|
|
1209
980
|
|
|
1210
|
-
const waterfallId = crypto.randomUUID
|
|
1211
|
-
? crypto.randomUUID()
|
|
1212
|
-
: require('uuid').v4();
|
|
981
|
+
const waterfallId = crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4();
|
|
1213
982
|
const latencyMarkerId = 36707139;
|
|
1214
|
-
const latencyInstanceId = Number(
|
|
1215
|
-
`${Date.now()}${Math.floor(Math.random() * 1000)}`
|
|
1216
|
-
);
|
|
983
|
+
const latencyInstanceId = Number(`${Date.now()}${Math.floor(Math.random() * 1000)}`);
|
|
1217
984
|
|
|
1218
985
|
const serverParams = {
|
|
1219
986
|
should_trigger_override_login_2fa_action: 0,
|
|
@@ -1228,8 +995,7 @@ class AccountRepository extends Repository {
|
|
|
1228
995
|
is_platform_login: 0,
|
|
1229
996
|
INTERNAL__latency_qpl_marker_id: latencyMarkerId,
|
|
1230
997
|
is_from_aymh: 0,
|
|
1231
|
-
offline_experiment_group:
|
|
1232
|
-
'caa_iteration_v3_perf_ig_4',
|
|
998
|
+
offline_experiment_group: 'caa_iteration_v3_perf_ig_4',
|
|
1233
999
|
is_from_landing_page: 0,
|
|
1234
1000
|
left_nav_button_action: 'NONE',
|
|
1235
1001
|
password_text_input_id: 'z0jejq:194',
|
|
@@ -1240,10 +1006,9 @@ class AccountRepository extends Repository {
|
|
|
1240
1006
|
username_text_input_id: 'z0jejq:193',
|
|
1241
1007
|
layered_homepage_experiment_group: null,
|
|
1242
1008
|
device_id: androidDeviceId,
|
|
1243
|
-
login_surface: '
|
|
1009
|
+
login_surface: 'login_home',
|
|
1244
1010
|
INTERNAL__latency_qpl_instance_id: latencyInstanceId,
|
|
1245
|
-
reg_flow_source:
|
|
1246
|
-
'login_home_native_integration_point',
|
|
1011
|
+
reg_flow_source: 'aymh_multi_profiles_native_integration_point',
|
|
1247
1012
|
is_caa_perf_enabled: 1,
|
|
1248
1013
|
credential_type: 'password',
|
|
1249
1014
|
is_from_password_entry_page: 0,
|
|
@@ -1263,22 +1028,17 @@ class AccountRepository extends Repository {
|
|
|
1263
1028
|
this.client.state.bloksVersionId ||
|
|
1264
1029
|
'5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
|
|
1265
1030
|
|
|
1266
|
-
const bkClientContext = JSON.stringify({
|
|
1267
|
-
bloks_version: bloksVersionId,
|
|
1268
|
-
styles_id: 'instagram',
|
|
1269
|
-
});
|
|
1270
|
-
|
|
1031
|
+
const bkClientContext = JSON.stringify({ bloks_version: bloksVersionId, styles_id: 'instagram' });
|
|
1271
1032
|
const lang = this.client.state.language || 'ro_RO';
|
|
1272
1033
|
const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
|
|
1273
1034
|
const userAgent = this._resolveUserAgent();
|
|
1035
|
+
const networkProps = buildNetworkProperties();
|
|
1274
1036
|
|
|
1275
1037
|
const bloksHeaders = {
|
|
1276
1038
|
'accept-language': acceptLanguage,
|
|
1277
|
-
'content-type':
|
|
1278
|
-
'application/x-www-form-urlencoded; charset=UTF-8',
|
|
1039
|
+
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
1279
1040
|
'ig-intended-user-id': '0',
|
|
1280
1041
|
priority: 'u=3',
|
|
1281
|
-
|
|
1282
1042
|
'x-bloks-is-layout-rtl': 'false',
|
|
1283
1043
|
'x-bloks-prism-ax-base-colors-enabled': 'false',
|
|
1284
1044
|
'x-bloks-prism-button-version': 'CONTROL',
|
|
@@ -1286,19 +1046,14 @@ class AccountRepository extends Repository {
|
|
|
1286
1046
|
'x-bloks-prism-font-enabled': 'false',
|
|
1287
1047
|
'x-bloks-prism-indigo-link-version': '0',
|
|
1288
1048
|
'x-bloks-version-id': bloksVersionId,
|
|
1289
|
-
|
|
1290
1049
|
'x-fb-client-ip': 'True',
|
|
1291
|
-
'x-fb-connection-type': '
|
|
1050
|
+
'x-fb-connection-type': 'MOBILE.UNKNOWN',
|
|
1292
1051
|
'x-fb-friendly-name':
|
|
1293
1052
|
'IgApi: bloks/async_action/com.bloks.www.bloks.caa.login.async.send_login_request/',
|
|
1294
|
-
'x-fb-network-properties':
|
|
1295
|
-
'VPN;Validated;LocalAddrs=/10.0.0.2,;',
|
|
1053
|
+
'x-fb-network-properties': networkProps, // FIX: dynamic
|
|
1296
1054
|
'x-fb-request-analytics-tags': JSON.stringify({
|
|
1297
1055
|
network_tags: {
|
|
1298
|
-
product: String(
|
|
1299
|
-
this.client.state.fbAnalyticsApplicationId ||
|
|
1300
|
-
'567067343352427'
|
|
1301
|
-
),
|
|
1056
|
+
product: String(this.client.state.fbAnalyticsApplicationId || '567067343352427'),
|
|
1302
1057
|
purpose: 'fetch',
|
|
1303
1058
|
surface: 'undefined',
|
|
1304
1059
|
request_category: 'api',
|
|
@@ -1306,23 +1061,16 @@ class AccountRepository extends Repository {
|
|
|
1306
1061
|
},
|
|
1307
1062
|
}),
|
|
1308
1063
|
'x-fb-server-cluster': 'True',
|
|
1309
|
-
|
|
1310
1064
|
'x-ig-android-id': androidDeviceId,
|
|
1311
|
-
'x-ig-app-id': String(
|
|
1312
|
-
this.client.state.fbAnalyticsApplicationId ||
|
|
1313
|
-
'567067343352427'
|
|
1314
|
-
),
|
|
1065
|
+
'x-ig-app-id': String(this.client.state.fbAnalyticsApplicationId || '567067343352427'),
|
|
1315
1066
|
'x-ig-app-locale': lang,
|
|
1316
|
-
'x-ig-bandwidth-speed-kbps': (
|
|
1317
|
-
Math.random() * 1500 +
|
|
1318
|
-
800
|
|
1319
|
-
).toFixed(3),
|
|
1067
|
+
'x-ig-bandwidth-speed-kbps': (Math.random() * 1500 + 800).toFixed(3),
|
|
1320
1068
|
'x-ig-bandwidth-totalbytes-b': '0',
|
|
1321
1069
|
'x-ig-bandwidth-totaltime-ms': '0',
|
|
1322
1070
|
'x-ig-client-endpoint':
|
|
1323
1071
|
'com.bloks.www.caa.login.aymh_single_profile_screen_entry',
|
|
1324
1072
|
'x-ig-capabilities': '3brTv10=',
|
|
1325
|
-
'x-ig-connection-type': '
|
|
1073
|
+
'x-ig-connection-type': 'MOBILE(UNKNOWN)',
|
|
1326
1074
|
'x-ig-device-id': qeDeviceId,
|
|
1327
1075
|
'x-ig-device-locale': lang,
|
|
1328
1076
|
'x-ig-family-device-id': familyDeviceId,
|
|
@@ -1330,29 +1078,20 @@ class AccountRepository extends Repository {
|
|
|
1330
1078
|
'x-ig-nav-chain':
|
|
1331
1079
|
'LockoutFragment:dogfooding_lockout:1:cold_start:...,com.bloks.www.caa.login.aymh_single_profile_screen_entry:com.bloks.www.caa.login.aymh_single_profile_screen_entry:14:button:0:::0',
|
|
1332
1080
|
'x-ig-timezone-offset': String(
|
|
1333
|
-
typeof this.client.state.timezoneOffset ===
|
|
1334
|
-
'number'
|
|
1081
|
+
typeof this.client.state.timezoneOffset === 'number'
|
|
1335
1082
|
? this.client.state.timezoneOffset
|
|
1336
1083
|
: 7200
|
|
1337
1084
|
),
|
|
1338
1085
|
'x-ig-www-claim': this.client.state.igWWWClaim || '0',
|
|
1339
1086
|
'x-mid': machineId,
|
|
1340
|
-
'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(
|
|
1341
|
-
Math.random() * 1000
|
|
1342
|
-
)
|
|
1343
|
-
.toString()
|
|
1344
|
-
.padStart(3, '0')}`,
|
|
1087
|
+
'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(Math.random() * 1000).toString().padStart(3, '0')}`,
|
|
1345
1088
|
'x-pigeon-session-id':
|
|
1346
1089
|
this.client.state.pigeonSessionId ||
|
|
1347
|
-
`UFS-${crypto.randomBytes(16).toString(
|
|
1348
|
-
'hex'
|
|
1349
|
-
)}-1`,
|
|
1090
|
+
`UFS-${crypto.randomBytes(16).toString('hex')}-1`,
|
|
1350
1091
|
'x-tigon-is-retry': 'False',
|
|
1351
|
-
|
|
1352
|
-
'accept-encoding': 'gzip, deflate, br',
|
|
1092
|
+
'accept-encoding': 'gzip, deflate',
|
|
1353
1093
|
'user-agent': userAgent,
|
|
1354
|
-
'x-fb-conn-uuid-client':
|
|
1355
|
-
crypto.randomBytes(16).toString('hex'),
|
|
1094
|
+
'x-fb-conn-uuid-client': crypto.randomBytes(16).toString('hex'),
|
|
1356
1095
|
'x-fb-http-engine': 'MNS/TCP',
|
|
1357
1096
|
'x-fb-rmd': 'state=URL_ELIGIBLE',
|
|
1358
1097
|
};
|
|
@@ -1368,44 +1107,18 @@ class AccountRepository extends Repository {
|
|
|
1368
1107
|
headers: bloksHeaders,
|
|
1369
1108
|
});
|
|
1370
1109
|
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
'authinfo_instagram'
|
|
1378
|
-
);
|
|
1379
|
-
const debugFile = path.join(
|
|
1380
|
-
debugDir,
|
|
1381
|
-
'login-debug.json'
|
|
1382
|
-
);
|
|
1383
|
-
try {
|
|
1384
|
-
fs.mkdirSync(debugDir, { recursive: true });
|
|
1385
|
-
} catch (e) {}
|
|
1386
|
-
|
|
1387
|
-
const debugPayload = {
|
|
1388
|
-
at: new Date().toISOString(),
|
|
1389
|
-
statusCode:
|
|
1390
|
-
response.statusCode || response.status || null,
|
|
1391
|
-
headers: response.headers || null,
|
|
1392
|
-
body: response.body || null,
|
|
1393
|
-
};
|
|
1394
|
-
fs.writeFileSync(
|
|
1395
|
-
debugFile,
|
|
1396
|
-
JSON.stringify(debugPayload, null, 2),
|
|
1397
|
-
'utf8'
|
|
1398
|
-
);
|
|
1399
|
-
} catch (e) {}
|
|
1110
|
+
debugWrite('login-debug.json', {
|
|
1111
|
+
at: new Date().toISOString(),
|
|
1112
|
+
statusCode: response.statusCode || response.status || null,
|
|
1113
|
+
headers: response.headers || null,
|
|
1114
|
+
body: response.body || null,
|
|
1115
|
+
});
|
|
1400
1116
|
|
|
1401
1117
|
const body = response.body;
|
|
1402
|
-
|
|
1403
1118
|
this._extractAndSaveAuthorization(response);
|
|
1404
1119
|
|
|
1405
1120
|
if (body && body.two_factor_required) {
|
|
1406
|
-
const err = new Error(
|
|
1407
|
-
'Two factor authentication required'
|
|
1408
|
-
);
|
|
1121
|
+
const err = new Error('Two factor authentication required');
|
|
1409
1122
|
err.name = 'IgLoginTwoFactorRequiredError';
|
|
1410
1123
|
err.twoFactorInfo = body.two_factor_info;
|
|
1411
1124
|
throw err;
|
|
@@ -1429,42 +1142,26 @@ class AccountRepository extends Repository {
|
|
|
1429
1142
|
|
|
1430
1143
|
if (body && body.layout) {
|
|
1431
1144
|
const layoutStr =
|
|
1432
|
-
typeof body.layout === 'string'
|
|
1433
|
-
? body.layout
|
|
1434
|
-
: JSON.stringify(body.layout);
|
|
1145
|
+
typeof body.layout === 'string' ? body.layout : JSON.stringify(body.layout);
|
|
1435
1146
|
|
|
1436
|
-
if (
|
|
1437
|
-
layoutStr.includes('two_factor_required') ||
|
|
1438
|
-
layoutStr.includes('"two_factor_info"')
|
|
1439
|
-
) {
|
|
1147
|
+
if (layoutStr.includes('two_factor_required') || layoutStr.includes('"two_factor_info"')) {
|
|
1440
1148
|
let twoFactorInfo = null;
|
|
1441
1149
|
try {
|
|
1442
|
-
const match = layoutStr.match(
|
|
1443
|
-
|
|
1444
|
-
);
|
|
1445
|
-
if (match)
|
|
1446
|
-
twoFactorInfo = JSON.parse(match[1]);
|
|
1150
|
+
const match = layoutStr.match(/"two_factor_info"\s*:\s*(\{[^}]+\})/);
|
|
1151
|
+
if (match) twoFactorInfo = JSON.parse(match[1]);
|
|
1447
1152
|
} catch (e) {}
|
|
1448
|
-
const err = new Error(
|
|
1449
|
-
'Two factor authentication required'
|
|
1450
|
-
);
|
|
1153
|
+
const err = new Error('Two factor authentication required');
|
|
1451
1154
|
err.name = 'IgLoginTwoFactorRequiredError';
|
|
1452
1155
|
err.twoFactorInfo = twoFactorInfo;
|
|
1453
1156
|
throw err;
|
|
1454
1157
|
}
|
|
1455
1158
|
|
|
1456
|
-
if (
|
|
1457
|
-
layoutStr.includes('bad_password') ||
|
|
1458
|
-
layoutStr.includes('incorrect_password')
|
|
1459
|
-
) {
|
|
1159
|
+
if (layoutStr.includes('bad_password') || layoutStr.includes('incorrect_password')) {
|
|
1460
1160
|
const err = new Error('Bad password');
|
|
1461
1161
|
err.name = 'IgLoginBadPasswordError';
|
|
1462
1162
|
throw err;
|
|
1463
1163
|
}
|
|
1464
|
-
if (
|
|
1465
|
-
layoutStr.includes('invalid_user') ||
|
|
1466
|
-
layoutStr.includes('user_not_found')
|
|
1467
|
-
) {
|
|
1164
|
+
if (layoutStr.includes('invalid_user') || layoutStr.includes('user_not_found')) {
|
|
1468
1165
|
const err = new Error('Invalid user');
|
|
1469
1166
|
err.name = 'IgLoginInvalidUserError';
|
|
1470
1167
|
throw err;
|
|
@@ -1476,14 +1173,10 @@ class AccountRepository extends Repository {
|
|
|
1476
1173
|
throw err;
|
|
1477
1174
|
}
|
|
1478
1175
|
|
|
1479
|
-
const tokenFromLayout =
|
|
1480
|
-
this._findBearerInString(layoutStr);
|
|
1176
|
+
const tokenFromLayout = this._findBearerInString(layoutStr);
|
|
1481
1177
|
if (tokenFromLayout) {
|
|
1482
|
-
this.client.state.authorization =
|
|
1483
|
-
|
|
1484
|
-
try {
|
|
1485
|
-
this.client.state.updateAuthorization();
|
|
1486
|
-
} catch (e) {}
|
|
1178
|
+
this.client.state.authorization = tokenFromLayout;
|
|
1179
|
+
try { this.client.state.updateAuthorization(); } catch (e) {}
|
|
1487
1180
|
}
|
|
1488
1181
|
|
|
1489
1182
|
const pkPatterns = [
|
|
@@ -1499,8 +1192,7 @@ class AccountRepository extends Repository {
|
|
|
1499
1192
|
const pkMatch = layoutStr.match(pattern);
|
|
1500
1193
|
if (pkMatch) {
|
|
1501
1194
|
extractedPk = pkMatch[1];
|
|
1502
|
-
this.client.state.cookieUserId =
|
|
1503
|
-
extractedPk;
|
|
1195
|
+
this.client.state.cookieUserId = extractedPk;
|
|
1504
1196
|
this.client.state._userId = extractedPk;
|
|
1505
1197
|
break;
|
|
1506
1198
|
}
|
|
@@ -1513,89 +1205,54 @@ class AccountRepository extends Repository {
|
|
|
1513
1205
|
/x-ig-set-www-claim[\\"\s:]+([a-f0-9]+)/i,
|
|
1514
1206
|
];
|
|
1515
1207
|
for (const pattern of wwwClaimPatterns) {
|
|
1516
|
-
const
|
|
1517
|
-
if (
|
|
1518
|
-
this.client.state.igWWWClaim =
|
|
1519
|
-
wwwClaimMatch[1];
|
|
1520
|
-
break;
|
|
1521
|
-
}
|
|
1208
|
+
const m = layoutStr.match(pattern);
|
|
1209
|
+
if (m) { this.client.state.igWWWClaim = m[1]; break; }
|
|
1522
1210
|
}
|
|
1523
1211
|
|
|
1524
1212
|
const encKeyIdMatch2 =
|
|
1525
|
-
layoutStr.match(
|
|
1526
|
-
|
|
1527
|
-
) ||
|
|
1528
|
-
layoutStr.match(
|
|
1529
|
-
/password.encryption.key.id[\\"\s:]+(\d+)/i
|
|
1530
|
-
);
|
|
1213
|
+
layoutStr.match(/IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i) ||
|
|
1214
|
+
layoutStr.match(/password.encryption.key.id[\\"\s:]+(\d+)/i);
|
|
1531
1215
|
if (encKeyIdMatch2) {
|
|
1532
|
-
this.client.state.passwordEncryptionKeyId =
|
|
1533
|
-
parseInt(encKeyIdMatch2[1]);
|
|
1216
|
+
this.client.state.passwordEncryptionKeyId = parseInt(encKeyIdMatch2[1]);
|
|
1534
1217
|
}
|
|
1535
1218
|
|
|
1536
1219
|
const encPubKeyMatch2 =
|
|
1537
|
-
layoutStr.match(
|
|
1538
|
-
|
|
1539
|
-
) ||
|
|
1540
|
-
layoutStr.match(
|
|
1541
|
-
/password.encryption.pub.key[\\"\s:]+([A-Za-z0-9+\/=]+)/i
|
|
1542
|
-
);
|
|
1220
|
+
layoutStr.match(/IG-Set-Password-Encryption-Pub-Key[\\"\s:]+([A-Za-z0-9+\/=]+)/i) ||
|
|
1221
|
+
layoutStr.match(/password.encryption.pub.key[\\"\s:]+([A-Za-z0-9+\/=]+)/i);
|
|
1543
1222
|
if (encPubKeyMatch2) {
|
|
1544
|
-
this.client.state.passwordEncryptionPubKey =
|
|
1545
|
-
encPubKeyMatch2[1];
|
|
1223
|
+
this.client.state.passwordEncryptionPubKey = encPubKeyMatch2[1];
|
|
1546
1224
|
}
|
|
1547
1225
|
|
|
1548
1226
|
const midMatch2 =
|
|
1549
|
-
layoutStr.match(
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
layoutStr.match(
|
|
1553
|
-
/\\"x-mid\\"[\\"\s:]+([A-Za-z0-9+\/=_-]+)/i
|
|
1554
|
-
);
|
|
1555
|
-
if (midMatch2) {
|
|
1556
|
-
this.client.state.mid = midMatch2[1];
|
|
1557
|
-
}
|
|
1227
|
+
layoutStr.match(/ig-set-x-mid[\\"\s:]+([A-Za-z0-9+\/=_-]+)/i) ||
|
|
1228
|
+
layoutStr.match(/\\"x-mid\\"[\\"\s:]+([A-Za-z0-9+\/=_-]+)/i);
|
|
1229
|
+
if (midMatch2) this.client.state.mid = midMatch2[1];
|
|
1558
1230
|
|
|
1559
1231
|
const loginResponsePatterns = [
|
|
1560
|
-
/\\"logged_in_user\\":\{[^}]*\\"pk\\":(
|
|
1232
|
+
/\\"logged_in_user\\":\{[^}]*\\"pk\\":(\\d+)[^}]*\\"username\\":\\"([^"\\]+)\\"/,
|
|
1561
1233
|
/"logged_in_user":\{[^}]*"pk":(\d+)[^}]*"username":"([^"]+)"/,
|
|
1562
1234
|
/\\"logged_in_user\\".*?\\"pk\\":\s*(\d+).*?\\"username\\":\s*\\"([^"\\]+)\\"/s,
|
|
1563
1235
|
];
|
|
1564
1236
|
for (const pattern of loginResponsePatterns) {
|
|
1565
|
-
const
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
this.client.state.
|
|
1569
|
-
|
|
1570
|
-
this.client.state._userId =
|
|
1571
|
-
loginResponseMatch[1];
|
|
1572
|
-
return {
|
|
1573
|
-
pk: parseInt(loginResponseMatch[1]),
|
|
1574
|
-
pk_id: loginResponseMatch[1],
|
|
1575
|
-
username: loginResponseMatch[2],
|
|
1576
|
-
};
|
|
1237
|
+
const m = layoutStr.match(pattern);
|
|
1238
|
+
if (m) {
|
|
1239
|
+
this.client.state.cookieUserId = m[1];
|
|
1240
|
+
this.client.state._userId = m[1];
|
|
1241
|
+
return { pk: parseInt(m[1]), pk_id: m[1], username: m[2] };
|
|
1577
1242
|
}
|
|
1578
1243
|
}
|
|
1579
1244
|
|
|
1580
|
-
if (
|
|
1581
|
-
extractedPk &&
|
|
1582
|
-
this.client.state.authorization
|
|
1583
|
-
) {
|
|
1245
|
+
if (extractedPk && this.client.state.authorization) {
|
|
1584
1246
|
try {
|
|
1585
1247
|
const userInfo = await this.currentUser();
|
|
1586
1248
|
const user = userInfo.user || userInfo;
|
|
1587
1249
|
if (user && user.pk) {
|
|
1588
|
-
this.client.state.cookieUserId =
|
|
1589
|
-
|
|
1590
|
-
this.client.state._userId =
|
|
1591
|
-
String(user.pk);
|
|
1250
|
+
this.client.state.cookieUserId = String(user.pk);
|
|
1251
|
+
this.client.state._userId = String(user.pk);
|
|
1592
1252
|
}
|
|
1593
1253
|
return user;
|
|
1594
1254
|
} catch (e) {
|
|
1595
|
-
return {
|
|
1596
|
-
pk: parseInt(extractedPk),
|
|
1597
|
-
pk_id: extractedPk,
|
|
1598
|
-
};
|
|
1255
|
+
return { pk: parseInt(extractedPk), pk_id: extractedPk };
|
|
1599
1256
|
}
|
|
1600
1257
|
}
|
|
1601
1258
|
}
|
|
@@ -1615,8 +1272,7 @@ class AccountRepository extends Repository {
|
|
|
1615
1272
|
const userInfo = await this.currentUser();
|
|
1616
1273
|
const user = userInfo.user || userInfo;
|
|
1617
1274
|
if (user && user.pk) {
|
|
1618
|
-
this.client.state.cookieUserId =
|
|
1619
|
-
String(user.pk);
|
|
1275
|
+
this.client.state.cookieUserId = String(user.pk);
|
|
1620
1276
|
this.client.state._userId = String(user.pk);
|
|
1621
1277
|
}
|
|
1622
1278
|
return user;
|
|
@@ -1627,14 +1283,129 @@ class AccountRepository extends Repository {
|
|
|
1627
1283
|
|
|
1628
1284
|
return body;
|
|
1629
1285
|
});
|
|
1286
|
+
|
|
1287
|
+
// Post-login: set E2EE eligibility (as real app does after login)
|
|
1288
|
+
try { await this._setE2eeEligibility(); } catch (e) {}
|
|
1289
|
+
|
|
1290
|
+
return loginResult;
|
|
1630
1291
|
}
|
|
1631
1292
|
|
|
1632
|
-
async
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1293
|
+
async _setE2eeEligibility(eligibility = 4) {
|
|
1294
|
+
const state = this.client.state || {};
|
|
1295
|
+
const lang = state.language || 'ro_RO';
|
|
1296
|
+
const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
|
|
1297
|
+
const userAgent = this._resolveUserAgent();
|
|
1298
|
+
const bloksVersionId =
|
|
1299
|
+
state.bloksVersionId ||
|
|
1300
|
+
'5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
|
|
1301
|
+
const networkProps = buildNetworkProperties();
|
|
1302
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
1303
|
+
|
|
1304
|
+
const androidDeviceId =
|
|
1305
|
+
state.androidDeviceId ||
|
|
1306
|
+
state.deviceId ||
|
|
1307
|
+
`android-${crypto.randomBytes(8).toString('hex')}`;
|
|
1308
|
+
const familyDeviceId =
|
|
1309
|
+
state.phoneId ||
|
|
1310
|
+
state.familyDeviceId ||
|
|
1311
|
+
(crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4());
|
|
1312
|
+
const qeDeviceId =
|
|
1313
|
+
state.deviceId ||
|
|
1314
|
+
state.qeDeviceId ||
|
|
1315
|
+
(crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4());
|
|
1316
|
+
const machineId =
|
|
1317
|
+
state.mid ||
|
|
1318
|
+
state.machineId ||
|
|
1319
|
+
`aZ${crypto.randomBytes(8).toString('hex')}`;
|
|
1320
|
+
|
|
1321
|
+
const headers = {
|
|
1322
|
+
'accept-language': acceptLanguage,
|
|
1323
|
+
'authorization': state.authorization || '',
|
|
1324
|
+
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
1325
|
+
'ig-intended-user-id': String(state.cookieUserId || '0'),
|
|
1326
|
+
'ig-u-ds-user-id': String(state.cookieUserId || '0'),
|
|
1327
|
+
'priority': 'u=3',
|
|
1328
|
+
'x-bloks-is-layout-rtl': 'false',
|
|
1329
|
+
'x-bloks-prism-ax-base-colors-enabled': 'false',
|
|
1330
|
+
'x-bloks-prism-button-version': 'CONTROL',
|
|
1331
|
+
'x-bloks-prism-colors-enabled': 'true',
|
|
1332
|
+
'x-bloks-prism-font-enabled': 'false',
|
|
1333
|
+
'x-bloks-prism-indigo-link-version': '0',
|
|
1334
|
+
'x-bloks-version-id': bloksVersionId,
|
|
1335
|
+
'x-fb-client-ip': 'True',
|
|
1336
|
+
'x-fb-connection-type': 'MOBILE.UNKNOWN',
|
|
1337
|
+
'x-fb-friendly-name': 'IgApi: direct_v2/set_e2ee_eligibility/',
|
|
1338
|
+
'x-fb-network-properties': networkProps,
|
|
1339
|
+
'x-fb-request-analytics-tags': JSON.stringify({
|
|
1340
|
+
network_tags: {
|
|
1341
|
+
product: String(state.fbAnalyticsApplicationId || '567067343352427'),
|
|
1342
|
+
purpose: 'fetch',
|
|
1343
|
+
surface: 'undefined',
|
|
1344
|
+
request_category: 'api',
|
|
1345
|
+
retry_attempt: '0',
|
|
1346
|
+
},
|
|
1347
|
+
}),
|
|
1348
|
+
'x-fb-server-cluster': 'True',
|
|
1349
|
+
'x-ig-android-id': androidDeviceId,
|
|
1350
|
+
'x-ig-app-id': String(state.fbAnalyticsApplicationId || '567067343352427'),
|
|
1351
|
+
'x-ig-app-locale': lang,
|
|
1352
|
+
'x-ig-bandwidth-speed-kbps': (Math.random() * 1500 + 800).toFixed(3),
|
|
1353
|
+
'x-ig-bandwidth-totalbytes-b': '0',
|
|
1354
|
+
'x-ig-bandwidth-totaltime-ms': '0',
|
|
1355
|
+
'x-ig-capabilities': '3brTv10=',
|
|
1356
|
+
'x-ig-client-endpoint': 'empty',
|
|
1357
|
+
'x-ig-connection-type': 'MOBILE(UNKNOWN)',
|
|
1358
|
+
'x-ig-device-id': qeDeviceId,
|
|
1359
|
+
'x-ig-device-languages': `{"system_languages":"${lang}"}`,
|
|
1360
|
+
'x-ig-device-locale': lang,
|
|
1361
|
+
'x-ig-family-device-id': familyDeviceId,
|
|
1362
|
+
'x-ig-mapped-locale': lang,
|
|
1363
|
+
'x-ig-nav-chain': 'LockoutFragment:dogfooding_lockout:1:cold_start',
|
|
1364
|
+
'x-ig-salt-ids': state.igSaltIds || '220140399,332020310,974466465,974460658',
|
|
1365
|
+
'x-ig-timezone-offset': String(
|
|
1366
|
+
typeof state.timezoneOffset === 'number' ? state.timezoneOffset : 7200
|
|
1367
|
+
),
|
|
1368
|
+
'x-ig-www-claim': state.igWWWClaim || '0',
|
|
1369
|
+
'x-mid': machineId,
|
|
1370
|
+
'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(Math.random() * 1000).toString().padStart(3, '0')}`,
|
|
1371
|
+
'x-pigeon-session-id':
|
|
1372
|
+
state.pigeonSessionId ||
|
|
1373
|
+
`UFS-${crypto.randomBytes(16).toString('hex')}-3`,
|
|
1374
|
+
'x-tigon-is-retry': 'False',
|
|
1375
|
+
'accept-encoding': 'gzip, deflate',
|
|
1376
|
+
'user-agent': userAgent,
|
|
1377
|
+
'x-fb-conn-uuid-client': crypto.randomBytes(16).toString('hex'),
|
|
1378
|
+
'x-fb-http-engine': 'MNS/TCP',
|
|
1379
|
+
'x-fb-rmd': 'state=URL_ELIGIBLE',
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1382
|
+
if (state.igURur) headers['ig-u-rur'] = state.igURur;
|
|
1383
|
+
|
|
1384
|
+
try {
|
|
1385
|
+
const response = await this.client.request.send({
|
|
1386
|
+
method: 'POST',
|
|
1387
|
+
url: '/api/v1/direct_v2/set_e2ee_eligibility/',
|
|
1388
|
+
form: {
|
|
1389
|
+
_uuid: qeDeviceId,
|
|
1390
|
+
e2ee_eligibility: String(eligibility),
|
|
1391
|
+
},
|
|
1392
|
+
headers,
|
|
1393
|
+
});
|
|
1394
|
+
|
|
1395
|
+
debugWrite('e2ee-eligibility-debug.json', {
|
|
1396
|
+
at: new Date().toISOString(),
|
|
1397
|
+
statusCode: response.statusCode || response.status || null,
|
|
1398
|
+
headers: response.headers || null,
|
|
1399
|
+
body: response.body || null,
|
|
1400
|
+
});
|
|
1401
|
+
|
|
1402
|
+
return response.body;
|
|
1403
|
+
} catch (e) {
|
|
1404
|
+
return null;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
async twoFactorLogin(username, verificationCode, twoFactorIdentifier, verificationMethod = '1') {
|
|
1638
1409
|
return this.requestWithRetry(async () => {
|
|
1639
1410
|
const response = await this.client.request.send({
|
|
1640
1411
|
method: 'POST',
|
|
@@ -1660,9 +1431,7 @@ class AccountRepository extends Repository {
|
|
|
1660
1431
|
const response = await this.client.request.send({
|
|
1661
1432
|
method: 'POST',
|
|
1662
1433
|
url: '/api/v1/accounts/logout/',
|
|
1663
|
-
form: this.client.request.sign({
|
|
1664
|
-
_uuid: this.client.state.uuid,
|
|
1665
|
-
}),
|
|
1434
|
+
form: this.client.request.sign({ _uuid: this.client.state.uuid }),
|
|
1666
1435
|
});
|
|
1667
1436
|
return response.body;
|
|
1668
1437
|
});
|
|
@@ -1683,14 +1452,7 @@ class AccountRepository extends Repository {
|
|
|
1683
1452
|
return this.currentUser();
|
|
1684
1453
|
}
|
|
1685
1454
|
|
|
1686
|
-
async editProfile({
|
|
1687
|
-
externalUrl,
|
|
1688
|
-
phoneNumber,
|
|
1689
|
-
username,
|
|
1690
|
-
fullName,
|
|
1691
|
-
biography,
|
|
1692
|
-
email,
|
|
1693
|
-
} = {}) {
|
|
1455
|
+
async editProfile({ externalUrl, phoneNumber, username, fullName, biography, email } = {}) {
|
|
1694
1456
|
return this.requestWithRetry(async () => {
|
|
1695
1457
|
const current = await this.currentUser();
|
|
1696
1458
|
const user = current.user || current;
|
|
@@ -1699,57 +1461,28 @@ class AccountRepository extends Repository {
|
|
|
1699
1461
|
url: '/api/v1/accounts/edit_profile/',
|
|
1700
1462
|
form: this.client.request.sign({
|
|
1701
1463
|
_uuid: this.client.state.uuid,
|
|
1702
|
-
external_url:
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
? phoneNumber
|
|
1709
|
-
: user.phone_number || '',
|
|
1710
|
-
username:
|
|
1711
|
-
username !== undefined
|
|
1712
|
-
? username
|
|
1713
|
-
: user.username,
|
|
1714
|
-
full_name:
|
|
1715
|
-
fullName !== undefined
|
|
1716
|
-
? fullName
|
|
1717
|
-
: user.full_name || '',
|
|
1718
|
-
biography:
|
|
1719
|
-
biography !== undefined
|
|
1720
|
-
? biography
|
|
1721
|
-
: user.biography || '',
|
|
1722
|
-
email:
|
|
1723
|
-
email !== undefined
|
|
1724
|
-
? email
|
|
1725
|
-
: user.email || '',
|
|
1464
|
+
external_url: externalUrl !== undefined ? externalUrl : user.external_url || '',
|
|
1465
|
+
phone_number: phoneNumber !== undefined ? phoneNumber : user.phone_number || '',
|
|
1466
|
+
username: username !== undefined ? username : user.username,
|
|
1467
|
+
full_name: fullName !== undefined ? fullName : user.full_name || '',
|
|
1468
|
+
biography: biography !== undefined ? biography : user.biography || '',
|
|
1469
|
+
email: email !== undefined ? email : user.email || '',
|
|
1726
1470
|
}),
|
|
1727
1471
|
});
|
|
1728
1472
|
return response.body;
|
|
1729
1473
|
});
|
|
1730
1474
|
}
|
|
1731
1475
|
|
|
1732
|
-
async setBiography(biography) {
|
|
1733
|
-
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
async setExternalUrl(url) {
|
|
1737
|
-
return this.editProfile({ externalUrl: url });
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
async removeBioLinks() {
|
|
1741
|
-
return this.editProfile({ externalUrl: '' });
|
|
1742
|
-
}
|
|
1476
|
+
async setBiography(biography) { return this.editProfile({ biography }); }
|
|
1477
|
+
async setExternalUrl(url) { return this.editProfile({ externalUrl: url }); }
|
|
1478
|
+
async removeBioLinks() { return this.editProfile({ externalUrl: '' }); }
|
|
1743
1479
|
|
|
1744
1480
|
async setGender(gender) {
|
|
1745
1481
|
return this.requestWithRetry(async () => {
|
|
1746
1482
|
const response = await this.client.request.send({
|
|
1747
1483
|
method: 'POST',
|
|
1748
1484
|
url: '/api/v1/accounts/set_gender/',
|
|
1749
|
-
form: this.client.request.sign({
|
|
1750
|
-
_uuid: this.client.state.uuid,
|
|
1751
|
-
gender,
|
|
1752
|
-
}),
|
|
1485
|
+
form: this.client.request.sign({ _uuid: this.client.state.uuid, gender }),
|
|
1753
1486
|
});
|
|
1754
1487
|
return response.body;
|
|
1755
1488
|
});
|
|
@@ -1760,9 +1493,7 @@ class AccountRepository extends Repository {
|
|
|
1760
1493
|
const response = await this.client.request.send({
|
|
1761
1494
|
method: 'POST',
|
|
1762
1495
|
url: '/api/v1/accounts/set_private/',
|
|
1763
|
-
form: this.client.request.sign({
|
|
1764
|
-
_uuid: this.client.state.uuid,
|
|
1765
|
-
}),
|
|
1496
|
+
form: this.client.request.sign({ _uuid: this.client.state.uuid }),
|
|
1766
1497
|
});
|
|
1767
1498
|
return response.body;
|
|
1768
1499
|
});
|
|
@@ -1773,9 +1504,7 @@ class AccountRepository extends Repository {
|
|
|
1773
1504
|
const response = await this.client.request.send({
|
|
1774
1505
|
method: 'POST',
|
|
1775
1506
|
url: '/api/v1/accounts/set_public/',
|
|
1776
|
-
form: this.client.request.sign({
|
|
1777
|
-
_uuid: this.client.state.uuid,
|
|
1778
|
-
}),
|
|
1507
|
+
form: this.client.request.sign({ _uuid: this.client.state.uuid }),
|
|
1779
1508
|
});
|
|
1780
1509
|
return response.body;
|
|
1781
1510
|
});
|
|
@@ -1804,10 +1533,7 @@ class AccountRepository extends Repository {
|
|
|
1804
1533
|
const response = await this.client.request.send({
|
|
1805
1534
|
method: 'POST',
|
|
1806
1535
|
url: '/api/v1/accounts/send_confirm_email/',
|
|
1807
|
-
form: this.client.request.sign({
|
|
1808
|
-
_uuid: this.client.state.uuid,
|
|
1809
|
-
send_source: 'edit_profile',
|
|
1810
|
-
}),
|
|
1536
|
+
form: this.client.request.sign({ _uuid: this.client.state.uuid, send_source: 'edit_profile' }),
|
|
1811
1537
|
});
|
|
1812
1538
|
return response.body;
|
|
1813
1539
|
});
|
|
@@ -1818,10 +1544,7 @@ class AccountRepository extends Repository {
|
|
|
1818
1544
|
const response = await this.client.request.send({
|
|
1819
1545
|
method: 'POST',
|
|
1820
1546
|
url: '/api/v1/accounts/send_confirm_phone_number/',
|
|
1821
|
-
form: this.client.request.sign({
|
|
1822
|
-
_uuid: this.client.state.uuid,
|
|
1823
|
-
phone_number: phoneNumber,
|
|
1824
|
-
}),
|
|
1547
|
+
form: this.client.request.sign({ _uuid: this.client.state.uuid, phone_number: phoneNumber }),
|
|
1825
1548
|
});
|
|
1826
1549
|
return response.body;
|
|
1827
1550
|
});
|
|
@@ -1847,9 +1570,7 @@ class AccountRepository extends Repository {
|
|
|
1847
1570
|
const response = await this.client.request.send({
|
|
1848
1571
|
method: 'POST',
|
|
1849
1572
|
url: '/api/v1/accounts/remove_profile_picture/',
|
|
1850
|
-
form: this.client.request.sign({
|
|
1851
|
-
_uuid: this.client.state.uuid,
|
|
1852
|
-
}),
|
|
1573
|
+
form: this.client.request.sign({ _uuid: this.client.state.uuid }),
|
|
1853
1574
|
});
|
|
1854
1575
|
return response.body;
|
|
1855
1576
|
});
|
|
@@ -1873,9 +1594,7 @@ class AccountRepository extends Repository {
|
|
|
1873
1594
|
form: this.client.request.sign({
|
|
1874
1595
|
id: this.client.state.uuid,
|
|
1875
1596
|
server_config_retrieval: '1',
|
|
1876
|
-
experiments:
|
|
1877
|
-
this.client.state.constants
|
|
1878
|
-
.LOGIN_EXPERIMENTS,
|
|
1597
|
+
experiments: this.client.state.constants.LOGIN_EXPERIMENTS,
|
|
1879
1598
|
}),
|
|
1880
1599
|
});
|
|
1881
1600
|
return response.body;
|
|
@@ -1884,12 +1603,7 @@ class AccountRepository extends Repository {
|
|
|
1884
1603
|
|
|
1885
1604
|
async syncPostLoginExperiments() {
|
|
1886
1605
|
let userId;
|
|
1887
|
-
try {
|
|
1888
|
-
userId = this.client.state.cookieUserId;
|
|
1889
|
-
} catch {
|
|
1890
|
-
userId = '0';
|
|
1891
|
-
}
|
|
1892
|
-
|
|
1606
|
+
try { userId = this.client.state.cookieUserId; } catch { userId = '0'; }
|
|
1893
1607
|
return this.requestWithRetry(async () => {
|
|
1894
1608
|
const response = await this.client.request.send({
|
|
1895
1609
|
method: 'POST',
|
|
@@ -1899,8 +1613,7 @@ class AccountRepository extends Repository {
|
|
|
1899
1613
|
_uid: userId,
|
|
1900
1614
|
_uuid: this.client.state.uuid,
|
|
1901
1615
|
server_config_retrieval: '1',
|
|
1902
|
-
experiments:
|
|
1903
|
-
this.client.state.constants.EXPERIMENTS,
|
|
1616
|
+
experiments: this.client.state.constants.EXPERIMENTS,
|
|
1904
1617
|
}),
|
|
1905
1618
|
});
|
|
1906
1619
|
return response.body;
|
|
@@ -1908,17 +1621,13 @@ class AccountRepository extends Repository {
|
|
|
1908
1621
|
}
|
|
1909
1622
|
|
|
1910
1623
|
async syncLauncher(preLogin = true) {
|
|
1911
|
-
const data = {
|
|
1912
|
-
id: this.client.state.uuid,
|
|
1913
|
-
server_config_retrieval: '1',
|
|
1914
|
-
};
|
|
1624
|
+
const data = { id: this.client.state.uuid, server_config_retrieval: '1' };
|
|
1915
1625
|
if (!preLogin) {
|
|
1916
1626
|
try {
|
|
1917
1627
|
data._uid = this.client.state.cookieUserId;
|
|
1918
1628
|
data._uuid = this.client.state.uuid;
|
|
1919
1629
|
} catch {}
|
|
1920
1630
|
}
|
|
1921
|
-
|
|
1922
1631
|
return this.requestWithRetry(async () => {
|
|
1923
1632
|
const response = await this.client.request.send({
|
|
1924
1633
|
method: 'POST',
|
|
@@ -1934,10 +1643,7 @@ class AccountRepository extends Repository {
|
|
|
1934
1643
|
const response = await this.client.request.send({
|
|
1935
1644
|
method: 'POST',
|
|
1936
1645
|
url: '/api/v1/devices/sync/',
|
|
1937
|
-
form: this.client.request.sign({
|
|
1938
|
-
id: this.client.state.uuid,
|
|
1939
|
-
server_config_retrieval: '1',
|
|
1940
|
-
}),
|
|
1646
|
+
form: this.client.request.sign({ id: this.client.state.uuid, server_config_retrieval: '1' }),
|
|
1941
1647
|
});
|
|
1942
1648
|
return response.body;
|
|
1943
1649
|
});
|
|
@@ -1964,10 +1670,7 @@ class AccountRepository extends Repository {
|
|
|
1964
1670
|
const response = await this.client.request.send({
|
|
1965
1671
|
method: 'POST',
|
|
1966
1672
|
url: '/api/v1/accounts/contact_point_prefill/',
|
|
1967
|
-
form: this.client.request.sign({
|
|
1968
|
-
phone_id: this.client.state.phoneId,
|
|
1969
|
-
usage,
|
|
1970
|
-
}),
|
|
1673
|
+
form: this.client.request.sign({ phone_id: this.client.state.phoneId, usage }),
|
|
1971
1674
|
});
|
|
1972
1675
|
return response.body;
|
|
1973
1676
|
});
|
|
@@ -1995,10 +1698,7 @@ class AccountRepository extends Repository {
|
|
|
1995
1698
|
const response = await this.client.request.send({
|
|
1996
1699
|
method: 'GET',
|
|
1997
1700
|
url: '/api/v1/consent/get_signup_config/',
|
|
1998
|
-
qs: {
|
|
1999
|
-
guid: this.client.state.uuid,
|
|
2000
|
-
main_account_selected: false,
|
|
2001
|
-
},
|
|
1701
|
+
qs: { guid: this.client.state.uuid, main_account_selected: false },
|
|
2002
1702
|
});
|
|
2003
1703
|
return response.body;
|
|
2004
1704
|
});
|
|
@@ -2036,91 +1736,70 @@ class AccountRepository extends Repository {
|
|
|
2036
1736
|
});
|
|
2037
1737
|
}
|
|
2038
1738
|
|
|
1739
|
+
/**
|
|
1740
|
+
* FIX MAJOR — generateAttestParams: each certificate in the chain
|
|
1741
|
+
* now has its own EC key pair, correctly simulating the
|
|
1742
|
+
* Android Keystore hierarchy (leaf → intermediate1 → intermediate2 → root).
|
|
1743
|
+
*/
|
|
2039
1744
|
static generateAttestParams(state) {
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
const
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
1745
|
+
// Key for leaf (the actually attested key)
|
|
1746
|
+
const leafKey = crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1' });
|
|
1747
|
+
|
|
1748
|
+
const challengeNonce = crypto.randomBytes(24).toString('base64url');
|
|
1749
|
+
const signedData = crypto.sign(null, Buffer.from(challengeNonce), leafKey.privateKey);
|
|
1750
|
+
const signedNonce = signedData.toString('base64');
|
|
1751
|
+
const publicKeyDer = leafKey.publicKey.export({ type: 'spki', format: 'der' });
|
|
1752
|
+
const keyHash = crypto.createHash('sha256').update(publicKeyDer).digest('hex');
|
|
1753
|
+
|
|
1754
|
+
// Separate keys for each level in the chain
|
|
1755
|
+
const int1Key = crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1' });
|
|
1756
|
+
const int2Key = crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1' });
|
|
1757
|
+
const rootKey = crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1' });
|
|
1758
|
+
|
|
1759
|
+
const leafCertPem = AccountRepository._generateSignedCert(
|
|
1760
|
+
leafKey.privateKey, leafKey.publicKey, 'Android Keystore Key',
|
|
1761
|
+
int1Key.privateKey // signed by intermediate 1
|
|
2048
1762
|
);
|
|
2049
|
-
const
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
1763
|
+
const intermediate1Pem = AccountRepository._generateSignedCert(
|
|
1764
|
+
int1Key.privateKey, int1Key.publicKey, 'Android Keystore Key Attestation',
|
|
1765
|
+
int2Key.privateKey // signed by intermediate 2
|
|
1766
|
+
);
|
|
1767
|
+
const intermediate2Pem = AccountRepository._generateSignedCert(
|
|
1768
|
+
int2Key.privateKey, int2Key.publicKey, 'Android Hardware Keystore',
|
|
1769
|
+
rootKey.privateKey // self-signed root
|
|
1770
|
+
);
|
|
1771
|
+
const rootCertPem = AccountRepository._generateSignedCert(
|
|
1772
|
+
rootKey.privateKey, rootKey.publicKey, 'Android Keystore Root',
|
|
1773
|
+
rootKey.privateKey // self-signed root
|
|
2053
1774
|
);
|
|
2054
|
-
const signedNonce = signedData.toString('base64');
|
|
2055
|
-
const publicKeyDer = publicKey.export({
|
|
2056
|
-
type: 'spki',
|
|
2057
|
-
format: 'der',
|
|
2058
|
-
});
|
|
2059
|
-
const keyHash = crypto
|
|
2060
|
-
.createHash('sha256')
|
|
2061
|
-
.update(publicKeyDer)
|
|
2062
|
-
.digest('hex');
|
|
2063
|
-
|
|
2064
|
-
const leafCertPem =
|
|
2065
|
-
AccountRepository._generateSelfSignedCert(
|
|
2066
|
-
privateKey,
|
|
2067
|
-
publicKey,
|
|
2068
|
-
'Android Keystore Key'
|
|
2069
|
-
);
|
|
2070
|
-
const intermediate1Pem =
|
|
2071
|
-
AccountRepository._generateSelfSignedCert(
|
|
2072
|
-
privateKey,
|
|
2073
|
-
publicKey,
|
|
2074
|
-
'Android Keystore Key Attestation'
|
|
2075
|
-
);
|
|
2076
|
-
const intermediate2Pem =
|
|
2077
|
-
AccountRepository._generateSelfSignedCert(
|
|
2078
|
-
privateKey,
|
|
2079
|
-
publicKey,
|
|
2080
|
-
'Android Hardware Keystore'
|
|
2081
|
-
);
|
|
2082
|
-
const rootCertPem =
|
|
2083
|
-
AccountRepository._generateSelfSignedCert(
|
|
2084
|
-
privateKey,
|
|
2085
|
-
publicKey,
|
|
2086
|
-
'Android Keystore Root'
|
|
2087
|
-
);
|
|
2088
1775
|
|
|
2089
|
-
const certificateChain = [
|
|
2090
|
-
leafCertPem,
|
|
2091
|
-
intermediate1Pem,
|
|
2092
|
-
intermediate2Pem,
|
|
2093
|
-
rootCertPem,
|
|
2094
|
-
].join('\n');
|
|
1776
|
+
const certificateChain = [leafCertPem, intermediate1Pem, intermediate2Pem, rootCertPem].join('\n');
|
|
2095
1777
|
|
|
2096
1778
|
return {
|
|
2097
|
-
attestation: [
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
},
|
|
2107
|
-
],
|
|
1779
|
+
attestation: [{
|
|
1780
|
+
version: 2,
|
|
1781
|
+
type: 'keystore',
|
|
1782
|
+
errors: [0],
|
|
1783
|
+
challenge_nonce: challengeNonce,
|
|
1784
|
+
signed_nonce: signedNonce,
|
|
1785
|
+
key_hash: keyHash,
|
|
1786
|
+
certificate_chain: certificateChain,
|
|
1787
|
+
}],
|
|
2108
1788
|
};
|
|
2109
1789
|
}
|
|
2110
1790
|
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
1791
|
+
/**
|
|
1792
|
+
* FIX: _generateSignedCert now accepts a separate signing key
|
|
1793
|
+
* (signerPrivateKey), enabling the correct certificate hierarchy.
|
|
1794
|
+
*/
|
|
1795
|
+
static _generateSignedCert(privateKey, publicKey, cn, signerPrivateKey) {
|
|
1796
|
+
if (!signerPrivateKey) signerPrivateKey = privateKey; // self-signed fallback
|
|
1797
|
+
|
|
1798
|
+
const publicKeyDer = publicKey.export({ type: 'spki', format: 'der' });
|
|
2116
1799
|
const serialNumber = crypto.randomBytes(8);
|
|
2117
1800
|
const now = new Date();
|
|
2118
|
-
const notBefore = new Date(
|
|
2119
|
-
|
|
2120
|
-
);
|
|
2121
|
-
const notAfter = new Date(
|
|
2122
|
-
now.getTime() + 10 * 365 * 24 * 60 * 60 * 1000
|
|
2123
|
-
);
|
|
1801
|
+
const notBefore = new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000);
|
|
1802
|
+
const notAfter = new Date(now.getTime() + 10 * 365 * 24 * 60 * 60 * 1000);
|
|
2124
1803
|
const cnBytes = Buffer.from(cn, 'utf8');
|
|
2125
1804
|
const cnSeq = Buffer.concat([
|
|
2126
1805
|
Buffer.from([0x30, cnBytes.length + 13]),
|
|
@@ -2132,122 +1811,83 @@ class AccountRepository extends Repository {
|
|
|
2132
1811
|
]);
|
|
2133
1812
|
|
|
2134
1813
|
function encodeTime(date) {
|
|
2135
|
-
const str =
|
|
2136
|
-
|
|
2137
|
-
.toISOString()
|
|
2138
|
-
.replace(/[-:T]/g, '')
|
|
2139
|
-
.slice(2, 14) + 'Z';
|
|
2140
|
-
return Buffer.concat([
|
|
2141
|
-
Buffer.from([0x17, str.length]),
|
|
2142
|
-
Buffer.from(str),
|
|
2143
|
-
]);
|
|
1814
|
+
const str = date.toISOString().replace(/[-:T]/g, '').slice(2, 14) + 'Z';
|
|
1815
|
+
return Buffer.concat([Buffer.from([0x17, str.length]), Buffer.from(str)]);
|
|
2144
1816
|
}
|
|
2145
1817
|
|
|
2146
|
-
const validityBuf = Buffer.concat([
|
|
2147
|
-
|
|
2148
|
-
encodeTime(notAfter),
|
|
2149
|
-
]);
|
|
2150
|
-
const validity = Buffer.concat([
|
|
2151
|
-
Buffer.from([0x30, validityBuf.length]),
|
|
2152
|
-
validityBuf,
|
|
2153
|
-
]);
|
|
1818
|
+
const validityBuf = Buffer.concat([encodeTime(notBefore), encodeTime(notAfter)]);
|
|
1819
|
+
const validity = Buffer.concat([Buffer.from([0x30, validityBuf.length]), validityBuf]);
|
|
2154
1820
|
|
|
2155
1821
|
const tbs = Buffer.concat([
|
|
2156
1822
|
Buffer.from([0xa0, 0x03, 0x02, 0x01, 0x02]),
|
|
2157
1823
|
Buffer.from([0x02, serialNumber.length]),
|
|
2158
1824
|
serialNumber,
|
|
2159
|
-
Buffer.from([
|
|
2160
|
-
0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d,
|
|
2161
|
-
0x04, 0x03, 0x02,
|
|
2162
|
-
]),
|
|
1825
|
+
Buffer.from([0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02]),
|
|
2163
1826
|
cnSeq,
|
|
2164
1827
|
validity,
|
|
2165
1828
|
cnSeq,
|
|
2166
1829
|
publicKeyDer,
|
|
2167
1830
|
]);
|
|
2168
1831
|
|
|
2169
|
-
const tbsSeq = Buffer.concat([
|
|
2170
|
-
Buffer.from([0x30, 0x82]),
|
|
2171
|
-
Buffer.alloc(2),
|
|
2172
|
-
tbs,
|
|
2173
|
-
]);
|
|
1832
|
+
const tbsSeq = Buffer.concat([Buffer.from([0x30, 0x82]), Buffer.alloc(2), tbs]);
|
|
2174
1833
|
tbsSeq.writeUInt16BE(tbs.length, 2);
|
|
2175
1834
|
|
|
2176
|
-
|
|
1835
|
+
// FIX: signed with signerPrivateKey (can be an upper-level key)
|
|
1836
|
+
const signature = crypto.sign(null, tbsSeq, signerPrivateKey);
|
|
2177
1837
|
const sigBitString = Buffer.concat([
|
|
2178
1838
|
Buffer.from([0x03, signature.length + 1, 0x00]),
|
|
2179
1839
|
signature,
|
|
2180
1840
|
]);
|
|
2181
|
-
const algId = Buffer.from([
|
|
2182
|
-
0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d,
|
|
2183
|
-
0x04, 0x03, 0x02,
|
|
2184
|
-
]);
|
|
1841
|
+
const algId = Buffer.from([0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02]);
|
|
2185
1842
|
const certBody = Buffer.concat([tbsSeq, algId, sigBitString]);
|
|
2186
|
-
const cert = Buffer.concat([
|
|
2187
|
-
Buffer.from([0x30, 0x82]),
|
|
2188
|
-
Buffer.alloc(2),
|
|
2189
|
-
certBody,
|
|
2190
|
-
]);
|
|
1843
|
+
const cert = Buffer.concat([Buffer.from([0x30, 0x82]), Buffer.alloc(2), certBody]);
|
|
2191
1844
|
cert.writeUInt16BE(certBody.length, 2);
|
|
2192
1845
|
|
|
2193
1846
|
const b64 = cert.toString('base64');
|
|
2194
1847
|
const lines = b64.match(/.{1,76}/g) || [b64];
|
|
2195
|
-
return (
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
1848
|
+
return '-----BEGIN CERTIFICATE-----\n' + lines.join('\n') + '\n-----END CERTIFICATE-----';
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
// Kept for compatibility — internally calls _generateSignedCert
|
|
1852
|
+
static _generateSelfSignedCert(privateKey, publicKey, cn) {
|
|
1853
|
+
return AccountRepository._generateSignedCert(privateKey, publicKey, cn, privateKey);
|
|
2200
1854
|
}
|
|
2201
1855
|
|
|
2202
1856
|
static createJazoest(input) {
|
|
2203
1857
|
const buf = Buffer.from(input || '', 'ascii');
|
|
2204
1858
|
let sum = 0;
|
|
2205
|
-
for (let i = 0; i < buf.byteLength; i++)
|
|
2206
|
-
sum += buf.readUInt8(i);
|
|
2207
|
-
}
|
|
1859
|
+
for (let i = 0; i < buf.byteLength; i++) sum += buf.readUInt8(i);
|
|
2208
1860
|
return `2${sum}`;
|
|
2209
1861
|
}
|
|
2210
1862
|
|
|
2211
1863
|
encryptPassword(password) {
|
|
2212
1864
|
if (!this.client.state.passwordEncryptionPubKey) {
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
1865
|
+
// FIX: no longer returning password in plaintext — throws a controlled error
|
|
1866
|
+
throw new Error(
|
|
1867
|
+
'passwordEncryptionPubKey missing from state. ' +
|
|
1868
|
+
'Cannot encrypt password safely.'
|
|
1869
|
+
);
|
|
2217
1870
|
}
|
|
2218
1871
|
const randKey = crypto.randomBytes(32);
|
|
2219
1872
|
const iv = crypto.randomBytes(12);
|
|
2220
1873
|
const rsaEncrypted = crypto.publicEncrypt(
|
|
2221
1874
|
{
|
|
2222
|
-
key: Buffer.from(
|
|
2223
|
-
this.client.state.passwordEncryptionPubKey,
|
|
2224
|
-
'base64'
|
|
2225
|
-
).toString(),
|
|
1875
|
+
key: Buffer.from(this.client.state.passwordEncryptionPubKey, 'base64').toString(),
|
|
2226
1876
|
padding: crypto.constants.RSA_PKCS1_PADDING,
|
|
2227
1877
|
},
|
|
2228
1878
|
randKey
|
|
2229
1879
|
);
|
|
2230
|
-
const cipher = crypto.createCipheriv(
|
|
2231
|
-
'aes-256-gcm',
|
|
2232
|
-
randKey,
|
|
2233
|
-
iv
|
|
2234
|
-
);
|
|
1880
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', randKey, iv);
|
|
2235
1881
|
const time = Math.floor(Date.now() / 1000).toString();
|
|
2236
1882
|
cipher.setAAD(Buffer.from(time));
|
|
2237
|
-
const aesEncrypted = Buffer.concat([
|
|
2238
|
-
cipher.update(password, 'utf8'),
|
|
2239
|
-
cipher.final(),
|
|
2240
|
-
]);
|
|
1883
|
+
const aesEncrypted = Buffer.concat([cipher.update(password, 'utf8'), cipher.final()]);
|
|
2241
1884
|
const sizeBuffer = Buffer.alloc(2, 0);
|
|
2242
1885
|
sizeBuffer.writeInt16LE(rsaEncrypted.byteLength, 0);
|
|
2243
1886
|
const authTag = cipher.getAuthTag();
|
|
2244
1887
|
return {
|
|
2245
1888
|
time,
|
|
2246
1889
|
encrypted: Buffer.concat([
|
|
2247
|
-
Buffer.from([
|
|
2248
|
-
1,
|
|
2249
|
-
this.client.state.passwordEncryptionKeyId || 0,
|
|
2250
|
-
]),
|
|
1890
|
+
Buffer.from([1, this.client.state.passwordEncryptionKeyId || 0]),
|
|
2251
1891
|
iv,
|
|
2252
1892
|
sizeBuffer,
|
|
2253
1893
|
rsaEncrypted,
|
|
@@ -2258,16 +1898,10 @@ class AccountRepository extends Repository {
|
|
|
2258
1898
|
}
|
|
2259
1899
|
|
|
2260
1900
|
async passwordPublicKeys() {
|
|
2261
|
-
const response = await this.client.request.send({
|
|
2262
|
-
method: 'GET',
|
|
2263
|
-
url: '/api/v1/qe/sync/',
|
|
2264
|
-
});
|
|
1901
|
+
const response = await this.client.request.send({ method: 'GET', url: '/api/v1/qe/sync/' });
|
|
2265
1902
|
const headers = response.headers || {};
|
|
2266
|
-
const keyId = parseInt(
|
|
2267
|
-
|
|
2268
|
-
);
|
|
2269
|
-
const pubKey =
|
|
2270
|
-
headers['ig-set-password-encryption-pub-key'] || '';
|
|
1903
|
+
const keyId = parseInt(headers['ig-set-password-encryption-key-id'] || '0');
|
|
1904
|
+
const pubKey = headers['ig-set-password-encryption-pub-key'] || '';
|
|
2271
1905
|
return { keyId, pubKey };
|
|
2272
1906
|
}
|
|
2273
1907
|
|
|
@@ -2309,6 +1943,7 @@ class AccountRepository extends Repository {
|
|
|
2309
1943
|
});
|
|
2310
1944
|
}
|
|
2311
1945
|
|
|
1946
|
+
// CRITICAL FIX: 'preferen1gces' → 'preferences' (typo → ReferenceError fix)
|
|
2312
1947
|
async pushPreferences(preferences = 'default') {
|
|
2313
1948
|
return this.requestWithRetry(async () => {
|
|
2314
1949
|
const response = await this.client.request.send({
|
|
@@ -2321,7 +1956,7 @@ class AccountRepository extends Repository {
|
|
|
2321
1956
|
phone_id: this.client.state.phoneId,
|
|
2322
1957
|
device_token: '',
|
|
2323
1958
|
guid: this.client.state.uuid,
|
|
2324
|
-
users: preferen1gces
|
|
1959
|
+
users: preferences, // FIX: was 'preferen1gces'
|
|
2325
1960
|
}),
|
|
2326
1961
|
});
|
|
2327
1962
|
return response.body;
|