nodejs-insta-private-api-mqt 1.3.87 → 1.3.89
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.
|
@@ -45,7 +45,7 @@ class AccountRepository extends Repository {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
//
|
|
48
|
+
// 1) Try straight headers for IG-Set-Authorization
|
|
49
49
|
for (const key of Object.keys(headers)) {
|
|
50
50
|
const val = headers[key];
|
|
51
51
|
if (!val) continue;
|
|
@@ -60,7 +60,6 @@ class AccountRepository extends Repository {
|
|
|
60
60
|
return token;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
// Some servers include the header value inside a JSON-like string in other headers
|
|
64
63
|
if (typeof val === 'string' && val.includes('Bearer IGT:2:')) {
|
|
65
64
|
const token = this._findBearerInString(val);
|
|
66
65
|
if (token) {
|
|
@@ -120,10 +119,9 @@ class AccountRepository extends Repository {
|
|
|
120
119
|
}
|
|
121
120
|
}
|
|
122
121
|
|
|
123
|
-
// 2) Check response.body for common fields
|
|
122
|
+
// 2) Check response.body for common fields / embedded layout/login_response
|
|
124
123
|
const body = response.body;
|
|
125
124
|
if (body) {
|
|
126
|
-
// If body has headers string (some responses embed headers JSON as a string)
|
|
127
125
|
if (body.headers && typeof body.headers === 'string') {
|
|
128
126
|
const token = this._findBearerInString(body.headers);
|
|
129
127
|
if (token) {
|
|
@@ -135,22 +133,20 @@ class AccountRepository extends Repository {
|
|
|
135
133
|
}
|
|
136
134
|
}
|
|
137
135
|
|
|
138
|
-
// If body contains a layout object or string, stringify and search it
|
|
139
136
|
try {
|
|
140
137
|
let layoutStr = null;
|
|
141
138
|
if (body.layout) {
|
|
142
|
-
layoutStr =
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
: JSON.stringify(body.layout);
|
|
139
|
+
layoutStr = typeof body.layout === 'string'
|
|
140
|
+
? body.layout
|
|
141
|
+
: JSON.stringify(body.layout);
|
|
146
142
|
} else if (body?.layout?.bloks_payload) {
|
|
147
143
|
layoutStr = JSON.stringify(body.layout.bloks_payload);
|
|
148
144
|
} else if (body?.bloks_payload) {
|
|
149
145
|
layoutStr = JSON.stringify(body.bloks_payload);
|
|
150
|
-
}
|
|
151
|
-
if (!layoutStr && typeof body === 'string') {
|
|
146
|
+
} else if (typeof body === 'string') {
|
|
152
147
|
layoutStr = body;
|
|
153
148
|
}
|
|
149
|
+
|
|
154
150
|
if (layoutStr) {
|
|
155
151
|
const token = this._findBearerInString(layoutStr);
|
|
156
152
|
if (token) {
|
|
@@ -161,12 +157,11 @@ class AccountRepository extends Repository {
|
|
|
161
157
|
return token;
|
|
162
158
|
}
|
|
163
159
|
|
|
164
|
-
//
|
|
160
|
+
// Embedded login_response JSON string
|
|
165
161
|
const loginResponseMatch = layoutStr.match(
|
|
166
162
|
/"login_response"\s*:\s*"(\\?{.*?\\?})"/s
|
|
167
163
|
);
|
|
168
164
|
if (loginResponseMatch) {
|
|
169
|
-
// Unescape JSON string
|
|
170
165
|
let jsonStr = loginResponseMatch[1];
|
|
171
166
|
try {
|
|
172
167
|
jsonStr = jsonStr
|
|
@@ -174,23 +169,19 @@ class AccountRepository extends Repository {
|
|
|
174
169
|
.replace(/\\n/g, '');
|
|
175
170
|
const parsed = JSON.parse(jsonStr);
|
|
176
171
|
if (parsed && parsed.headers) {
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (token) {
|
|
181
|
-
this.client.state.authorization = token;
|
|
172
|
+
const t = this._findBearerInString(parsed.headers);
|
|
173
|
+
if (t) {
|
|
174
|
+
this.client.state.authorization = t;
|
|
182
175
|
try {
|
|
183
176
|
this.client.state.updateAuthorization();
|
|
184
177
|
} catch (e) {}
|
|
185
|
-
return
|
|
178
|
+
return t;
|
|
186
179
|
}
|
|
187
180
|
}
|
|
188
|
-
} catch (e) {
|
|
189
|
-
// ignore parse errors
|
|
190
|
-
}
|
|
181
|
+
} catch (e) {}
|
|
191
182
|
}
|
|
192
183
|
|
|
193
|
-
//
|
|
184
|
+
// Encryption key id + pub key in layout
|
|
194
185
|
const encKeyIdMatch =
|
|
195
186
|
layoutStr.match(
|
|
196
187
|
/IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i
|
|
@@ -202,6 +193,7 @@ class AccountRepository extends Repository {
|
|
|
202
193
|
this.client.state.passwordEncryptionKeyId =
|
|
203
194
|
parseInt(encKeyIdMatch[1]);
|
|
204
195
|
}
|
|
196
|
+
|
|
205
197
|
const encPubKeyMatch =
|
|
206
198
|
layoutStr.match(
|
|
207
199
|
/IG-Set-Password-Encryption-Pub-Key[\\"\s:]+([A-Za-z0-9+\/=]+)/i
|
|
@@ -214,15 +206,13 @@ class AccountRepository extends Repository {
|
|
|
214
206
|
encPubKeyMatch[1];
|
|
215
207
|
}
|
|
216
208
|
|
|
217
|
-
//
|
|
209
|
+
// User / pk id
|
|
218
210
|
const dsIdMatch =
|
|
219
211
|
layoutStr.match(
|
|
220
212
|
/ig-set-ig-u-ds-user-id[\\"\s:]+(\d+)/i
|
|
221
213
|
) ||
|
|
222
214
|
layoutStr.match(/ig-u-ds-user-id[\\"\s:]+(\d+)/i) ||
|
|
223
|
-
layoutStr.match(
|
|
224
|
-
/"strong_id__"\s*:\s*"(\d+)"/i
|
|
225
|
-
) ||
|
|
215
|
+
layoutStr.match(/"strong_id__"\s*:\s*"(\d+)"/i) ||
|
|
226
216
|
layoutStr.match(/"pk_id"\s*:\s*"(\d+)"/i) ||
|
|
227
217
|
layoutStr.match(/"pk"\s*:\s*(\d+)/i);
|
|
228
218
|
if (dsIdMatch) {
|
|
@@ -254,11 +244,8 @@ class AccountRepository extends Repository {
|
|
|
254
244
|
this.client.state.mid = midMatch[1];
|
|
255
245
|
}
|
|
256
246
|
}
|
|
257
|
-
} catch (e) {
|
|
258
|
-
// ignore
|
|
259
|
-
}
|
|
247
|
+
} catch (e) {}
|
|
260
248
|
|
|
261
|
-
// 3) If body.logged_in_user exists and we have authorization already, return it
|
|
262
249
|
if (body.logged_in_user && this.client.state.authorization) {
|
|
263
250
|
return this.client.state.authorization;
|
|
264
251
|
}
|
|
@@ -267,15 +254,12 @@ class AccountRepository extends Repository {
|
|
|
267
254
|
return null;
|
|
268
255
|
}
|
|
269
256
|
|
|
270
|
-
// Normalize token string: accept either raw token or "Bearer IGT:2:..." and return trimmed token string
|
|
271
257
|
_normalizeTokenString(val) {
|
|
272
258
|
if (!val || typeof val !== 'string') return null;
|
|
273
259
|
|
|
274
|
-
// If header already contains "Bearer IGT:2:..."
|
|
275
260
|
const bearer = this._findBearerInString(val);
|
|
276
261
|
if (bearer) return bearer;
|
|
277
262
|
|
|
278
|
-
// If header contains JSON with IG-Set-Authorization field
|
|
279
263
|
try {
|
|
280
264
|
const maybeJson = JSON.parse(val);
|
|
281
265
|
if (maybeJson['IG-Set-Authorization']) {
|
|
@@ -283,19 +267,14 @@ class AccountRepository extends Repository {
|
|
|
283
267
|
maybeJson['IG-Set-Authorization']
|
|
284
268
|
);
|
|
285
269
|
}
|
|
286
|
-
} catch (e) {
|
|
287
|
-
// not JSON
|
|
288
|
-
}
|
|
270
|
+
} catch (e) {}
|
|
289
271
|
|
|
290
|
-
// fallback: return trimmed value
|
|
291
272
|
return val.trim();
|
|
292
273
|
}
|
|
293
274
|
|
|
294
|
-
// Find bearer token in arbitrary string using multiple patterns
|
|
295
275
|
_findBearerInString(str) {
|
|
296
276
|
if (!str || typeof str !== 'string') return null;
|
|
297
277
|
|
|
298
|
-
// Patterns to match the Bearer token in many possible encodings/escapes
|
|
299
278
|
const bearerPatterns = [
|
|
300
279
|
/Bearer IGT:2:[A-Za-z0-9+\/=]+/,
|
|
301
280
|
/Bearer\s+IGT:2:[A-Za-z0-9+\/=]+/,
|
|
@@ -309,14 +288,12 @@ class AccountRepository extends Repository {
|
|
|
309
288
|
for (const pattern of bearerPatterns) {
|
|
310
289
|
const m = str.match(pattern);
|
|
311
290
|
if (m) {
|
|
312
|
-
// If capturing group present, prefer it
|
|
313
291
|
if (m[1]) {
|
|
314
292
|
const token = m[1].includes('Bearer IGT:2:')
|
|
315
293
|
? m[1]
|
|
316
294
|
: `Bearer IGT:2:${m[1]}`;
|
|
317
295
|
return token.replace(/\?"/g, '').trim();
|
|
318
296
|
}
|
|
319
|
-
// Otherwise m[0] contains the full token
|
|
320
297
|
return m[0].replace(/\?"/g, '').trim();
|
|
321
298
|
}
|
|
322
299
|
}
|
|
@@ -324,8 +301,35 @@ class AccountRepository extends Repository {
|
|
|
324
301
|
return null;
|
|
325
302
|
}
|
|
326
303
|
|
|
304
|
+
/**
|
|
305
|
+
* NEW helper: resolve a valid User-Agent from state / request / deviceString
|
|
306
|
+
* so it works even if scriptul tău nu setează explicit state.userAgent.
|
|
307
|
+
*/
|
|
308
|
+
_resolveUserAgent() {
|
|
309
|
+
const state = this.client.state || {};
|
|
310
|
+
const req = this.client.request || {};
|
|
311
|
+
|
|
312
|
+
const candidates = [
|
|
313
|
+
state.userAgent,
|
|
314
|
+
state.appUserAgent,
|
|
315
|
+
req.userAgent,
|
|
316
|
+
state.deviceString,
|
|
317
|
+
].filter(Boolean);
|
|
318
|
+
|
|
319
|
+
let userAgent = candidates.length > 0 ? candidates[0] : null;
|
|
320
|
+
|
|
321
|
+
if (!userAgent) {
|
|
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)';
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Sincronizăm în state ca să fie disponibil peste tot
|
|
328
|
+
this.client.state.userAgent = userAgent;
|
|
329
|
+
return userAgent;
|
|
330
|
+
}
|
|
331
|
+
|
|
327
332
|
// Ensure we have a valid csrftoken cookie (and mid) before sensitive endpoints.
|
|
328
|
-
// Instagram typically sets csrftoken via /api/v1/si/fetch_headers/.
|
|
329
333
|
async ensureCsrfToken() {
|
|
330
334
|
const cookieToken = this.client.state.cookieCsrfToken;
|
|
331
335
|
if (
|
|
@@ -339,15 +343,12 @@ class AccountRepository extends Repository {
|
|
|
339
343
|
return cookieToken;
|
|
340
344
|
}
|
|
341
345
|
|
|
342
|
-
// Warmup endpoint that returns/set cookies (csrftoken, mid, etc.)
|
|
343
346
|
try {
|
|
344
347
|
await this.client.request.send({
|
|
345
348
|
method: 'GET',
|
|
346
349
|
url: `/api/v1/si/fetch_headers/?challenge_type=signup&guid=${this.client.state.uuid}`,
|
|
347
350
|
});
|
|
348
|
-
} catch (e) {
|
|
349
|
-
// ignore warmup failures
|
|
350
|
-
}
|
|
351
|
+
} catch (e) {}
|
|
351
352
|
|
|
352
353
|
const token = this.client.state.cookieCsrfToken;
|
|
353
354
|
if (
|
|
@@ -365,9 +366,8 @@ class AccountRepository extends Repository {
|
|
|
365
366
|
}
|
|
366
367
|
|
|
367
368
|
/**
|
|
368
|
-
* Internal:
|
|
369
|
-
*
|
|
370
|
-
* This is best-effort only and never throws to the caller.
|
|
369
|
+
* Internal: pre-login Bloks OAuth token fetch, imită exact apelul oficial
|
|
370
|
+
* com.bloks.www.caa.login.oauth.token.fetch.async
|
|
371
371
|
*/
|
|
372
372
|
async _prefetchOauthTokenForLogin(username) {
|
|
373
373
|
if (!username) return null;
|
|
@@ -375,7 +375,6 @@ class AccountRepository extends Repository {
|
|
|
375
375
|
try {
|
|
376
376
|
const nowSec = Math.floor(Date.now() / 1000);
|
|
377
377
|
|
|
378
|
-
// Device identifiers – keep them consistent with the main login flow
|
|
379
378
|
const androidDeviceId =
|
|
380
379
|
this.client.state.androidDeviceId ||
|
|
381
380
|
this.client.state.deviceId ||
|
|
@@ -404,7 +403,6 @@ class AccountRepository extends Repository {
|
|
|
404
403
|
`${Date.now()}${Math.floor(Math.random() * 1000)}`
|
|
405
404
|
);
|
|
406
405
|
|
|
407
|
-
// AAC object as seen in real traffic: timestamp + UUID + random token
|
|
408
406
|
const aacInitTimestamp =
|
|
409
407
|
nowSec - Math.floor(Math.random() * 50);
|
|
410
408
|
const aacjid = crypto.randomUUID
|
|
@@ -460,9 +458,7 @@ class AccountRepository extends Repository {
|
|
|
460
458
|
|
|
461
459
|
const lang = this.client.state.language || 'ro_RO';
|
|
462
460
|
const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
|
|
463
|
-
const userAgent =
|
|
464
|
-
this.client.state.userAgent ||
|
|
465
|
-
'Instagram 371.0.0.0.23 Android (30/11; 320dpi; 720x1449; Xiaomi/Redmi; M2006C3MNG; angelican; mt6765; ro_RO; 703217507)';
|
|
461
|
+
const userAgent = this._resolveUserAgent();
|
|
466
462
|
|
|
467
463
|
const timezoneOffset =
|
|
468
464
|
typeof this.client.state.timezoneOffset === 'number'
|
|
@@ -474,7 +470,6 @@ class AccountRepository extends Repository {
|
|
|
474
470
|
'content-type':
|
|
475
471
|
'application/x-www-form-urlencoded; charset=UTF-8',
|
|
476
472
|
'ig-intended-user-id': '0',
|
|
477
|
-
// Critical: matches the captured request header
|
|
478
473
|
priority: 'u=3',
|
|
479
474
|
|
|
480
475
|
'x-bloks-is-layout-rtl': 'false',
|
|
@@ -556,7 +551,6 @@ class AccountRepository extends Repository {
|
|
|
556
551
|
headers: bloksHeaders,
|
|
557
552
|
});
|
|
558
553
|
|
|
559
|
-
// Optional debug dump so you can inspect the exact server response.
|
|
560
554
|
try {
|
|
561
555
|
const fs = require('fs');
|
|
562
556
|
const path = require('path');
|
|
@@ -584,13 +578,466 @@ class AccountRepository extends Repository {
|
|
|
584
578
|
JSON.stringify(debugPayload, null, 2),
|
|
585
579
|
'utf8'
|
|
586
580
|
);
|
|
587
|
-
} catch (e) {
|
|
588
|
-
|
|
589
|
-
|
|
581
|
+
} catch (e) {}
|
|
582
|
+
|
|
583
|
+
return response.body;
|
|
584
|
+
} catch (e) {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Step 1: launcher/mobileconfig to fetch mobile config + encryption keys.
|
|
591
|
+
* Best effort – errors are ignored.
|
|
592
|
+
*/
|
|
593
|
+
async _launcherMobileConfig(preLogin = true) {
|
|
594
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
595
|
+
const state = this.client.state || {};
|
|
596
|
+
|
|
597
|
+
const androidDeviceId =
|
|
598
|
+
state.androidDeviceId ||
|
|
599
|
+
state.deviceId ||
|
|
600
|
+
`android-${crypto.randomBytes(8).toString('hex')}`;
|
|
601
|
+
|
|
602
|
+
const familyDeviceId =
|
|
603
|
+
state.phoneId ||
|
|
604
|
+
state.familyDeviceId ||
|
|
605
|
+
(crypto.randomUUID
|
|
606
|
+
? crypto.randomUUID()
|
|
607
|
+
: require('uuid').v4());
|
|
608
|
+
|
|
609
|
+
const qeDeviceId =
|
|
610
|
+
state.deviceId ||
|
|
611
|
+
state.qeDeviceId ||
|
|
612
|
+
(crypto.randomUUID
|
|
613
|
+
? crypto.randomUUID()
|
|
614
|
+
: require('uuid').v4());
|
|
615
|
+
|
|
616
|
+
const deviceUUID = state.uuid || qeDeviceId;
|
|
617
|
+
const userId = state.cookieUserId || '0';
|
|
618
|
+
|
|
619
|
+
const bloksVersionId =
|
|
620
|
+
state.bloksVersionId ||
|
|
621
|
+
'5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
|
|
622
|
+
|
|
623
|
+
const lang = state.language || 'ro_RO';
|
|
624
|
+
const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
|
|
625
|
+
const userAgent = this._resolveUserAgent();
|
|
626
|
+
|
|
627
|
+
const timezoneOffset =
|
|
628
|
+
typeof state.timezoneOffset === 'number'
|
|
629
|
+
? state.timezoneOffset
|
|
630
|
+
: 7200;
|
|
631
|
+
|
|
632
|
+
const params = {
|
|
633
|
+
bool_opt_policy: '0',
|
|
634
|
+
mobileconfig: '',
|
|
635
|
+
api_version: '9',
|
|
636
|
+
client_context: '["opt,value_hash"]',
|
|
637
|
+
unit_type: '2',
|
|
638
|
+
use_case: 'STANDARD',
|
|
639
|
+
query_hash:
|
|
640
|
+
'f00b9d0869db3969378d8d06bfccb24b5ef078012c8e199cba961cd5dfedaa88',
|
|
641
|
+
ts: String(nowSec),
|
|
642
|
+
_uid: userId,
|
|
643
|
+
device_id: deviceUUID,
|
|
644
|
+
_uuid: deviceUUID,
|
|
645
|
+
fetch_mode: 'CONFIG_SYNC_ONLY',
|
|
646
|
+
fetch_type: 'ASYNC_FULL',
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
const headers = {
|
|
650
|
+
'accept-language': acceptLanguage,
|
|
651
|
+
'content-type':
|
|
652
|
+
'application/x-www-form-urlencoded; charset=UTF-8',
|
|
653
|
+
priority: 'u=3',
|
|
654
|
+
|
|
655
|
+
'x-bloks-is-layout-rtl': 'false',
|
|
656
|
+
'x-bloks-prism-ax-base-colors-enabled': 'false',
|
|
657
|
+
'x-bloks-prism-button-version': 'CONTROL',
|
|
658
|
+
'x-bloks-prism-colors-enabled': 'true',
|
|
659
|
+
'x-bloks-prism-font-enabled': 'false',
|
|
660
|
+
'x-bloks-prism-indigo-link-version': '0',
|
|
661
|
+
'x-bloks-version-id': bloksVersionId,
|
|
662
|
+
|
|
663
|
+
'x-fb-client-ip': 'True',
|
|
664
|
+
'x-fb-connection-type': 'WIFI',
|
|
665
|
+
'x-fb-server-cluster': 'True',
|
|
666
|
+
'x-fb-network-properties':
|
|
667
|
+
'VPN;Validated;LocalAddrs=/10.0.0.2,;',
|
|
668
|
+
'x-fb-http-engine': 'MNS/TCP',
|
|
669
|
+
'x-fb-rmd': 'state=URL_ELIGIBLE',
|
|
670
|
+
|
|
671
|
+
'x-ig-android-id': androidDeviceId,
|
|
672
|
+
'x-ig-app-id': String(
|
|
673
|
+
state.fbAnalyticsApplicationId || '567067343352427'
|
|
674
|
+
),
|
|
675
|
+
'x-ig-app-locale': lang,
|
|
676
|
+
'x-ig-bandwidth-speed-kbps': (
|
|
677
|
+
Math.random() * 1500 +
|
|
678
|
+
800
|
|
679
|
+
).toFixed(3),
|
|
680
|
+
'x-ig-bandwidth-totalbytes-b': '0',
|
|
681
|
+
'x-ig-bandwidth-totaltime-ms': '0',
|
|
682
|
+
'x-ig-client-endpoint':
|
|
683
|
+
'LockoutFragment:dogfooding_lockout',
|
|
684
|
+
'x-ig-capabilities': '3brTv10=',
|
|
685
|
+
'x-ig-connection-type': 'WIFI',
|
|
686
|
+
'x-ig-device-id': deviceUUID,
|
|
687
|
+
'x-ig-device-languages': '{"system_languages":"ro-RO"}',
|
|
688
|
+
'x-ig-device-locale': lang,
|
|
689
|
+
'x-ig-family-device-id': familyDeviceId,
|
|
690
|
+
'x-ig-mapped-locale': lang,
|
|
691
|
+
'x-ig-nav-chain':
|
|
692
|
+
'LockoutFragment:dogfooding_lockout:1:cold_start',
|
|
693
|
+
'x-ig-salt-ids':
|
|
694
|
+
'220140399,332020310,974466465,974460658',
|
|
695
|
+
'x-ig-timezone-offset': String(timezoneOffset),
|
|
696
|
+
'x-ig-www-claim': state.igWWWClaim || '0',
|
|
697
|
+
'x-mid':
|
|
698
|
+
state.mid ||
|
|
699
|
+
state.machineId ||
|
|
700
|
+
`aZ${crypto.randomBytes(8).toString('hex')}`,
|
|
701
|
+
'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(
|
|
702
|
+
Math.random() * 1000
|
|
703
|
+
)
|
|
704
|
+
.toString()
|
|
705
|
+
.padStart(3, '0')}`,
|
|
706
|
+
'x-pigeon-session-id':
|
|
707
|
+
state.pigeonSessionId ||
|
|
708
|
+
`UFS-${crypto.randomBytes(16).toString('hex')}-M`,
|
|
709
|
+
'x-tigon-is-retry': 'False',
|
|
710
|
+
|
|
711
|
+
'accept-encoding': 'gzip, deflate, br',
|
|
712
|
+
'user-agent': userAgent,
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
if (state.cookieUserId) {
|
|
716
|
+
headers['ig-intended-user-id'] = String(state.cookieUserId);
|
|
717
|
+
headers['ig-u-ds-user-id'] = String(state.cookieUserId);
|
|
718
|
+
}
|
|
719
|
+
if (state.igURur) {
|
|
720
|
+
headers['ig-u-rur'] = state.igURur;
|
|
721
|
+
}
|
|
722
|
+
if (state.authorization) {
|
|
723
|
+
headers['authorization'] = state.authorization;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
try {
|
|
727
|
+
const response = await this.client.request.send({
|
|
728
|
+
method: 'POST',
|
|
729
|
+
url: '/api/v1/launcher/mobileconfig/',
|
|
730
|
+
form: this.client.request.sign(params),
|
|
731
|
+
headers,
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
try {
|
|
735
|
+
const fs = require('fs');
|
|
736
|
+
const path = require('path');
|
|
737
|
+
const debugDir = path.join(
|
|
738
|
+
process.cwd(),
|
|
739
|
+
'authinfo_instagram'
|
|
740
|
+
);
|
|
741
|
+
const debugFile = path.join(
|
|
742
|
+
debugDir,
|
|
743
|
+
'launcher-mobileconfig-debug.json'
|
|
744
|
+
);
|
|
745
|
+
try {
|
|
746
|
+
fs.mkdirSync(debugDir, { recursive: true });
|
|
747
|
+
} catch (e) {}
|
|
748
|
+
const debugPayload = {
|
|
749
|
+
at: new Date().toISOString(),
|
|
750
|
+
statusCode:
|
|
751
|
+
response.statusCode || response.status || null,
|
|
752
|
+
headers: response.headers || null,
|
|
753
|
+
body: response.body || null,
|
|
754
|
+
};
|
|
755
|
+
fs.writeFileSync(
|
|
756
|
+
debugFile,
|
|
757
|
+
JSON.stringify(debugPayload, null, 2),
|
|
758
|
+
'utf8'
|
|
759
|
+
);
|
|
760
|
+
} catch (e) {}
|
|
761
|
+
|
|
762
|
+
return response.body;
|
|
763
|
+
} catch (e) {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Step 2: Android Keystore attestation – moves x-ig-attest-params
|
|
770
|
+
* payload to dedicated endpoint /api/v1/attestation/create_android_keystore/.
|
|
771
|
+
*/
|
|
772
|
+
async _createAndroidKeystoreAttestation() {
|
|
773
|
+
const state = this.client.state || {};
|
|
774
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
775
|
+
|
|
776
|
+
const androidDeviceId =
|
|
777
|
+
state.androidDeviceId ||
|
|
778
|
+
state.deviceId ||
|
|
779
|
+
`android-${crypto.randomBytes(8).toString('hex')}`;
|
|
780
|
+
|
|
781
|
+
const familyDeviceId =
|
|
782
|
+
state.phoneId ||
|
|
783
|
+
state.familyDeviceId ||
|
|
784
|
+
(crypto.randomUUID
|
|
785
|
+
? crypto.randomUUID()
|
|
786
|
+
: require('uuid').v4());
|
|
787
|
+
|
|
788
|
+
const qeDeviceId =
|
|
789
|
+
state.deviceId ||
|
|
790
|
+
state.qeDeviceId ||
|
|
791
|
+
(crypto.randomUUID
|
|
792
|
+
? crypto.randomUUID()
|
|
793
|
+
: require('uuid').v4());
|
|
794
|
+
|
|
795
|
+
const deviceUUID = state.uuid || qeDeviceId;
|
|
796
|
+
const userId = state.cookieUserId || '0';
|
|
797
|
+
|
|
798
|
+
const attestParams =
|
|
799
|
+
AccountRepository.generateAttestParams(state);
|
|
800
|
+
|
|
801
|
+
const lang = state.language || 'ro_RO';
|
|
802
|
+
const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
|
|
803
|
+
const userAgent = this._resolveUserAgent();
|
|
804
|
+
const timezoneOffset =
|
|
805
|
+
typeof state.timezoneOffset === 'number'
|
|
806
|
+
? state.timezoneOffset
|
|
807
|
+
: 7200;
|
|
808
|
+
|
|
809
|
+
const params = {
|
|
810
|
+
_uid: userId,
|
|
811
|
+
_uuid: deviceUUID,
|
|
812
|
+
device_id: androidDeviceId,
|
|
813
|
+
family_device_id: familyDeviceId,
|
|
814
|
+
ts: String(nowSec),
|
|
815
|
+
...attestParams,
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
const headers = {
|
|
819
|
+
'accept-language': acceptLanguage,
|
|
820
|
+
'content-type':
|
|
821
|
+
'application/x-www-form-urlencoded; charset=UTF-8',
|
|
822
|
+
priority: 'u=3',
|
|
823
|
+
|
|
824
|
+
'x-ig-android-id': androidDeviceId,
|
|
825
|
+
'x-ig-device-id': qeDeviceId,
|
|
826
|
+
'x-ig-family-device-id': familyDeviceId,
|
|
827
|
+
'x-ig-timezone-offset': String(timezoneOffset),
|
|
828
|
+
'x-ig-app-id': String(
|
|
829
|
+
state.fbAnalyticsApplicationId || '567067343352427'
|
|
830
|
+
),
|
|
831
|
+
'x-ig-app-locale': lang,
|
|
832
|
+
'x-ig-device-locale': lang,
|
|
833
|
+
'x-ig-mapped-locale': lang,
|
|
834
|
+
'x-ig-connection-type': 'WIFI',
|
|
835
|
+
'x-ig-capabilities': '3brTv10=',
|
|
836
|
+
'x-ig-www-claim': state.igWWWClaim || '0',
|
|
837
|
+
|
|
838
|
+
'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(
|
|
839
|
+
Math.random() * 1000
|
|
840
|
+
)
|
|
841
|
+
.toString()
|
|
842
|
+
.padStart(3, '0')}`,
|
|
843
|
+
'x-pigeon-session-id':
|
|
844
|
+
state.pigeonSessionId ||
|
|
845
|
+
`UFS-${crypto.randomBytes(16).toString('hex')}-A`,
|
|
846
|
+
'x-mid':
|
|
847
|
+
state.mid ||
|
|
848
|
+
state.machineId ||
|
|
849
|
+
`aZ${crypto.randomBytes(8).toString('hex')}`,
|
|
850
|
+
|
|
851
|
+
'x-fb-client-ip': 'True',
|
|
852
|
+
'x-fb-connection-type': 'WIFI',
|
|
853
|
+
'x-fb-server-cluster': 'True',
|
|
854
|
+
'x-fb-http-engine': 'MNS/TCP',
|
|
855
|
+
'x-fb-network-properties':
|
|
856
|
+
'VPN;Validated;LocalAddrs=/10.0.0.2,;',
|
|
857
|
+
'x-fb-rmd': 'state=URL_ELIGIBLE',
|
|
858
|
+
|
|
859
|
+
'accept-encoding': 'gzip, deflate, br',
|
|
860
|
+
'user-agent': userAgent,
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
if (state.cookieUserId) {
|
|
864
|
+
headers['ig-intended-user-id'] = String(state.cookieUserId);
|
|
865
|
+
}
|
|
866
|
+
if (state.authorization) {
|
|
867
|
+
headers['authorization'] = state.authorization;
|
|
868
|
+
}
|
|
869
|
+
if (state.igURur) {
|
|
870
|
+
headers['ig-u-rur'] = state.igURur;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
try {
|
|
874
|
+
const response = await this.client.request.send({
|
|
875
|
+
method: 'POST',
|
|
876
|
+
url: '/api/v1/attestation/create_android_keystore/',
|
|
877
|
+
form: this.client.request.sign(params),
|
|
878
|
+
headers,
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
try {
|
|
882
|
+
const fs = require('fs');
|
|
883
|
+
const path = require('path');
|
|
884
|
+
const debugDir = path.join(
|
|
885
|
+
process.cwd(),
|
|
886
|
+
'authinfo_instagram'
|
|
887
|
+
);
|
|
888
|
+
const debugFile = path.join(
|
|
889
|
+
debugDir,
|
|
890
|
+
'attestation-debug.json'
|
|
891
|
+
);
|
|
892
|
+
try {
|
|
893
|
+
fs.mkdirSync(debugDir, { recursive: true });
|
|
894
|
+
} catch (e) {}
|
|
895
|
+
const debugPayload = {
|
|
896
|
+
at: new Date().toISOString(),
|
|
897
|
+
statusCode:
|
|
898
|
+
response.statusCode || response.status || null,
|
|
899
|
+
headers: response.headers || null,
|
|
900
|
+
body: response.body || null,
|
|
901
|
+
};
|
|
902
|
+
fs.writeFileSync(
|
|
903
|
+
debugFile,
|
|
904
|
+
JSON.stringify(debugPayload, null, 2),
|
|
905
|
+
'utf8'
|
|
906
|
+
);
|
|
907
|
+
} catch (e) {}
|
|
908
|
+
|
|
909
|
+
return response.body;
|
|
910
|
+
} catch (e) {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* Step 3 & 4 & 5 helpers: Terms of service preload, process client data,
|
|
917
|
+
* and (optional) phone number prefill.
|
|
918
|
+
* These are best-effort calls; failures are ignored.
|
|
919
|
+
*/
|
|
920
|
+
async _preloadTermsOfService() {
|
|
921
|
+
const state = this.client.state || {};
|
|
922
|
+
const lang = state.language || 'ro_RO';
|
|
923
|
+
const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
|
|
924
|
+
const userAgent = this._resolveUserAgent();
|
|
925
|
+
const bloksVersionId =
|
|
926
|
+
state.bloksVersionId ||
|
|
927
|
+
'5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
|
|
928
|
+
|
|
929
|
+
const paramsJson = JSON.stringify({});
|
|
930
|
+
const bkClientContext = JSON.stringify({
|
|
931
|
+
bloks_version: bloksVersionId,
|
|
932
|
+
styles_id: 'instagram',
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
const headers = {
|
|
936
|
+
'accept-language': acceptLanguage,
|
|
937
|
+
'content-type':
|
|
938
|
+
'application/x-www-form-urlencoded; charset=UTF-8',
|
|
939
|
+
priority: 'u=3',
|
|
940
|
+
'x-bloks-version-id': bloksVersionId,
|
|
941
|
+
'user-agent': userAgent,
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
try {
|
|
945
|
+
const response = await this.client.request.send({
|
|
946
|
+
method: 'POST',
|
|
947
|
+
url: '/api/v1/bloks/apps/com.bloks.www.caa.login.oxygen_preloads_terms_of_service/',
|
|
948
|
+
form: {
|
|
949
|
+
params: paramsJson,
|
|
950
|
+
bk_client_context: bkClientContext,
|
|
951
|
+
bloks_versioning_id: bloksVersionId,
|
|
952
|
+
},
|
|
953
|
+
headers,
|
|
954
|
+
});
|
|
955
|
+
return response.body;
|
|
956
|
+
} catch (e) {
|
|
957
|
+
return null;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
async _processClientDataAndRedirect(username) {
|
|
962
|
+
const state = this.client.state || {};
|
|
963
|
+
const lang = state.language || 'ro_RO';
|
|
964
|
+
const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
|
|
965
|
+
const userAgent = this._resolveUserAgent();
|
|
966
|
+
const bloksVersionId =
|
|
967
|
+
state.bloksVersionId ||
|
|
968
|
+
'5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
|
|
969
|
+
|
|
970
|
+
const paramsJson = JSON.stringify({
|
|
971
|
+
username_input: username || '',
|
|
972
|
+
});
|
|
973
|
+
const bkClientContext = JSON.stringify({
|
|
974
|
+
bloks_version: bloksVersionId,
|
|
975
|
+
styles_id: 'instagram',
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
const headers = {
|
|
979
|
+
'accept-language': acceptLanguage,
|
|
980
|
+
'content-type':
|
|
981
|
+
'application/x-www-form-urlencoded; charset=UTF-8',
|
|
982
|
+
priority: 'u=3',
|
|
983
|
+
'x-bloks-version-id': bloksVersionId,
|
|
984
|
+
'user-agent': userAgent,
|
|
985
|
+
};
|
|
986
|
+
|
|
987
|
+
try {
|
|
988
|
+
const response = await this.client.request.send({
|
|
989
|
+
method: 'POST',
|
|
990
|
+
url: '/api/v1/bloks/async_action/com.bloks.www.bloks.caa.login.process_client_data_and_redirect/',
|
|
991
|
+
form: {
|
|
992
|
+
params: paramsJson,
|
|
993
|
+
bk_client_context: bkClientContext,
|
|
994
|
+
bloks_versioning_id: bloksVersionId,
|
|
995
|
+
},
|
|
996
|
+
headers,
|
|
997
|
+
});
|
|
998
|
+
return response.body;
|
|
999
|
+
} catch (e) {
|
|
1000
|
+
return null;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
590
1003
|
|
|
1004
|
+
async _phoneNumberPrefill() {
|
|
1005
|
+
const state = this.client.state || {};
|
|
1006
|
+
const lang = state.language || 'ro_RO';
|
|
1007
|
+
const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
|
|
1008
|
+
const userAgent = this._resolveUserAgent();
|
|
1009
|
+
const bloksVersionId =
|
|
1010
|
+
state.bloksVersionId ||
|
|
1011
|
+
'5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
|
|
1012
|
+
|
|
1013
|
+
const paramsJson = JSON.stringify({});
|
|
1014
|
+
const bkClientContext = JSON.stringify({
|
|
1015
|
+
bloks_version: bloksVersionId,
|
|
1016
|
+
styles_id: 'instagram',
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
const headers = {
|
|
1020
|
+
'accept-language': acceptLanguage,
|
|
1021
|
+
'content-type':
|
|
1022
|
+
'application/x-www-form-urlencoded; charset=UTF-8',
|
|
1023
|
+
priority: 'u=3',
|
|
1024
|
+
'x-bloks-version-id': bloksVersionId,
|
|
1025
|
+
'user-agent': userAgent,
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
try {
|
|
1029
|
+
const response = await this.client.request.send({
|
|
1030
|
+
method: 'POST',
|
|
1031
|
+
url: '/api/v1/bloks/async_action/com.bloks.www.bloks.caa.phone.number.prefill.async.controller/',
|
|
1032
|
+
form: {
|
|
1033
|
+
params: paramsJson,
|
|
1034
|
+
bk_client_context: bkClientContext,
|
|
1035
|
+
bloks_versioning_id: bloksVersionId,
|
|
1036
|
+
},
|
|
1037
|
+
headers,
|
|
1038
|
+
});
|
|
591
1039
|
return response.body;
|
|
592
1040
|
} catch (e) {
|
|
593
|
-
// Completely swallow any errors; this step should never break login.
|
|
594
1041
|
return null;
|
|
595
1042
|
}
|
|
596
1043
|
}
|
|
@@ -618,19 +1065,32 @@ class AccountRepository extends Repository {
|
|
|
618
1065
|
|
|
619
1066
|
const { encrypted, time } = this.encryptPassword(password);
|
|
620
1067
|
|
|
621
|
-
// optional CSRF warmup (like app does before login)
|
|
622
1068
|
await this.ensureCsrfToken();
|
|
623
1069
|
|
|
624
|
-
//
|
|
625
|
-
|
|
1070
|
+
// Step 1–5: mobileconfig, keystore attestation, TOS preload, process client data, phone prefill
|
|
1071
|
+
try {
|
|
1072
|
+
await this._launcherMobileConfig(true);
|
|
1073
|
+
} catch (e) {}
|
|
1074
|
+
try {
|
|
1075
|
+
await this._createAndroidKeystoreAttestation();
|
|
1076
|
+
} catch (e) {}
|
|
1077
|
+
try {
|
|
1078
|
+
await this._preloadTermsOfService();
|
|
1079
|
+
} catch (e) {}
|
|
1080
|
+
try {
|
|
1081
|
+
await this._processClientDataAndRedirect(username);
|
|
1082
|
+
} catch (e) {}
|
|
1083
|
+
try {
|
|
1084
|
+
await this._phoneNumberPrefill();
|
|
1085
|
+
} catch (e) {}
|
|
1086
|
+
|
|
1087
|
+
// Step 6: OAuth token fetch
|
|
626
1088
|
try {
|
|
627
1089
|
await this._prefetchOauthTokenForLogin(username);
|
|
628
|
-
} catch (e) {
|
|
629
|
-
// Ignore any errors from the prefetch step.
|
|
630
|
-
}
|
|
1090
|
+
} catch (e) {}
|
|
631
1091
|
|
|
1092
|
+
// Step 7: real login
|
|
632
1093
|
return this.requestWithRetry(async () => {
|
|
633
|
-
// ====== client_input_params (mirror of real traffic with password) ======
|
|
634
1094
|
const nowSec = Math.floor(Date.now() / 1000);
|
|
635
1095
|
const aacInitTimestamp =
|
|
636
1096
|
nowSec - Math.floor(Math.random() * 50);
|
|
@@ -717,7 +1177,6 @@ class AccountRepository extends Repository {
|
|
|
717
1177
|
contact_point: username,
|
|
718
1178
|
};
|
|
719
1179
|
|
|
720
|
-
// ====== server_params (mirror after reverse engineering) ======
|
|
721
1180
|
const waterfallId = crypto.randomUUID
|
|
722
1181
|
? crypto.randomUUID()
|
|
723
1182
|
: require('uuid').v4();
|
|
@@ -770,10 +1229,6 @@ class AccountRepository extends Repository {
|
|
|
770
1229
|
server_params: serverParams,
|
|
771
1230
|
});
|
|
772
1231
|
|
|
773
|
-
const attestParams =
|
|
774
|
-
AccountRepository.generateAttestParams(
|
|
775
|
-
this.client.state
|
|
776
|
-
);
|
|
777
1232
|
const bloksVersionId =
|
|
778
1233
|
this.client.state.bloksVersionId ||
|
|
779
1234
|
'5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
|
|
@@ -783,12 +1238,9 @@ class AccountRepository extends Repository {
|
|
|
783
1238
|
styles_id: 'instagram',
|
|
784
1239
|
});
|
|
785
1240
|
|
|
786
|
-
// ====== HEADERS – modelled after real traffic ======
|
|
787
1241
|
const lang = this.client.state.language || 'ro_RO';
|
|
788
1242
|
const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
|
|
789
|
-
const userAgent =
|
|
790
|
-
this.client.state.userAgent ||
|
|
791
|
-
'Instagram 371.0.0.0.23 Android (30/11; 320dpi; 720x1449; Xiaomi/Redmi; M2006C3MNG; angelican; mt6765; ro_RO; 703217507)';
|
|
1243
|
+
const userAgent = this._resolveUserAgent();
|
|
792
1244
|
|
|
793
1245
|
const bloksHeaders = {
|
|
794
1246
|
'accept-language': acceptLanguage,
|
|
@@ -831,7 +1283,6 @@ class AccountRepository extends Repository {
|
|
|
831
1283
|
'567067343352427'
|
|
832
1284
|
),
|
|
833
1285
|
'x-ig-app-locale': lang,
|
|
834
|
-
'x-ig-attest-params': JSON.stringify(attestParams),
|
|
835
1286
|
'x-ig-bandwidth-speed-kbps': (
|
|
836
1287
|
Math.random() * 1500 +
|
|
837
1288
|
800
|
|
@@ -887,7 +1338,7 @@ class AccountRepository extends Repository {
|
|
|
887
1338
|
headers: bloksHeaders,
|
|
888
1339
|
});
|
|
889
1340
|
|
|
890
|
-
//
|
|
1341
|
+
// DEBUG login response
|
|
891
1342
|
try {
|
|
892
1343
|
const fs = require('fs');
|
|
893
1344
|
const path = require('path');
|
|
@@ -915,14 +1366,10 @@ class AccountRepository extends Repository {
|
|
|
915
1366
|
JSON.stringify(debugPayload, null, 2),
|
|
916
1367
|
'utf8'
|
|
917
1368
|
);
|
|
918
|
-
} catch (e) {
|
|
919
|
-
// don't break login on debug failure
|
|
920
|
-
}
|
|
921
|
-
// === END DEBUG LOGIN ===
|
|
1369
|
+
} catch (e) {}
|
|
922
1370
|
|
|
923
1371
|
const body = response.body;
|
|
924
1372
|
|
|
925
|
-
// Immediately attempt to extract and save authorization token from the response
|
|
926
1373
|
this._extractAndSaveAuthorization(response);
|
|
927
1374
|
|
|
928
1375
|
if (body && body.two_factor_required) {
|
|
@@ -956,7 +1403,6 @@ class AccountRepository extends Repository {
|
|
|
956
1403
|
? body.layout
|
|
957
1404
|
: JSON.stringify(body.layout);
|
|
958
1405
|
|
|
959
|
-
// If layout contains two-factor markers, parse and throw
|
|
960
1406
|
if (
|
|
961
1407
|
layoutStr.includes('two_factor_required') ||
|
|
962
1408
|
layoutStr.includes('"two_factor_info"')
|
|
@@ -1000,7 +1446,6 @@ class AccountRepository extends Repository {
|
|
|
1000
1446
|
throw err;
|
|
1001
1447
|
}
|
|
1002
1448
|
|
|
1003
|
-
// Additional attempt to extract token from layout string
|
|
1004
1449
|
const tokenFromLayout =
|
|
1005
1450
|
this._findBearerInString(layoutStr);
|
|
1006
1451
|
if (tokenFromLayout) {
|
|
@@ -1011,7 +1456,6 @@ class AccountRepository extends Repository {
|
|
|
1011
1456
|
} catch (e) {}
|
|
1012
1457
|
}
|
|
1013
1458
|
|
|
1014
|
-
// Extract pk (user id) from layout if present
|
|
1015
1459
|
const pkPatterns = [
|
|
1016
1460
|
/\\"pk\\":\s*(\d+)/,
|
|
1017
1461
|
/"pk":\s*(\d+)/,
|
|
@@ -1032,7 +1476,6 @@ class AccountRepository extends Repository {
|
|
|
1032
1476
|
}
|
|
1033
1477
|
}
|
|
1034
1478
|
|
|
1035
|
-
// Extract IG-Set-WWW-Claim if present
|
|
1036
1479
|
const wwwClaimPatterns = [
|
|
1037
1480
|
/IG-Set-WWW-Claim[\\"\s:]+([a-f0-9]+)/i,
|
|
1038
1481
|
/ig-set-www-claim[\\"\s:]+([a-f0-9]+)/i,
|
|
@@ -1048,43 +1491,41 @@ class AccountRepository extends Repository {
|
|
|
1048
1491
|
}
|
|
1049
1492
|
}
|
|
1050
1493
|
|
|
1051
|
-
|
|
1052
|
-
const encKeyIdMatch =
|
|
1494
|
+
const encKeyIdMatch2 =
|
|
1053
1495
|
layoutStr.match(
|
|
1054
1496
|
/IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i
|
|
1055
1497
|
) ||
|
|
1056
1498
|
layoutStr.match(
|
|
1057
1499
|
/password.encryption.key.id[\\"\s:]+(\d+)/i
|
|
1058
1500
|
);
|
|
1059
|
-
if (
|
|
1501
|
+
if (encKeyIdMatch2) {
|
|
1060
1502
|
this.client.state.passwordEncryptionKeyId =
|
|
1061
|
-
parseInt(
|
|
1503
|
+
parseInt(encKeyIdMatch2[1]);
|
|
1062
1504
|
}
|
|
1063
1505
|
|
|
1064
|
-
const
|
|
1506
|
+
const encPubKeyMatch2 =
|
|
1065
1507
|
layoutStr.match(
|
|
1066
1508
|
/IG-Set-Password-Encryption-Pub-Key[\\"\s:]+([A-Za-z0-9+\/=]+)/i
|
|
1067
1509
|
) ||
|
|
1068
1510
|
layoutStr.match(
|
|
1069
1511
|
/password.encryption.pub.key[\\"\s:]+([A-Za-z0-9+\/=]+)/i
|
|
1070
1512
|
);
|
|
1071
|
-
if (
|
|
1513
|
+
if (encPubKeyMatch2) {
|
|
1072
1514
|
this.client.state.passwordEncryptionPubKey =
|
|
1073
|
-
|
|
1515
|
+
encPubKeyMatch2[1];
|
|
1074
1516
|
}
|
|
1075
1517
|
|
|
1076
|
-
const
|
|
1518
|
+
const midMatch2 =
|
|
1077
1519
|
layoutStr.match(
|
|
1078
1520
|
/ig-set-x-mid[\\"\s:]+([A-Za-z0-9+\/=_-]+)/i
|
|
1079
1521
|
) ||
|
|
1080
1522
|
layoutStr.match(
|
|
1081
1523
|
/\\"x-mid\\"[\\"\s:]+([A-Za-z0-9+\/=_-]+)/i
|
|
1082
1524
|
);
|
|
1083
|
-
if (
|
|
1084
|
-
this.client.state.mid =
|
|
1525
|
+
if (midMatch2) {
|
|
1526
|
+
this.client.state.mid = midMatch2[1];
|
|
1085
1527
|
}
|
|
1086
1528
|
|
|
1087
|
-
// If layout contains a direct logged_in_user JSON, extract and return it
|
|
1088
1529
|
const loginResponsePatterns = [
|
|
1089
1530
|
/\\"logged_in_user\\":\{[^}]*\\"pk\\":(\d+)[^}]*\\"username\\":\\"([^"\\]+)\\"/,
|
|
1090
1531
|
/"logged_in_user":\{[^}]*"pk":(\d+)[^}]*"username":"([^"]+)"/,
|
|
@@ -1106,7 +1547,6 @@ class AccountRepository extends Repository {
|
|
|
1106
1547
|
}
|
|
1107
1548
|
}
|
|
1108
1549
|
|
|
1109
|
-
// If we have extracted a pk and we also have authorization, try to fetch current user
|
|
1110
1550
|
if (
|
|
1111
1551
|
extractedPk &&
|
|
1112
1552
|
this.client.state.authorization
|
|
@@ -1130,19 +1570,16 @@ class AccountRepository extends Repository {
|
|
|
1130
1570
|
}
|
|
1131
1571
|
}
|
|
1132
1572
|
|
|
1133
|
-
// If server returned logged_in_user directly in body
|
|
1134
1573
|
if (body && body.logged_in_user) {
|
|
1135
1574
|
const lu = body.logged_in_user;
|
|
1136
1575
|
if (lu.pk) {
|
|
1137
1576
|
this.client.state.cookieUserId = String(lu.pk);
|
|
1138
1577
|
this.client.state._userId = String(lu.pk);
|
|
1139
1578
|
}
|
|
1140
|
-
// Ensure authorization is saved if present in response headers
|
|
1141
1579
|
this._extractAndSaveAuthorization(response);
|
|
1142
1580
|
return lu;
|
|
1143
1581
|
}
|
|
1144
1582
|
|
|
1145
|
-
// If we already have authorization, try to fetch current user to populate state
|
|
1146
1583
|
if (this.client.state.authorization) {
|
|
1147
1584
|
try {
|
|
1148
1585
|
const userInfo = await this.currentUser();
|
|
@@ -1154,7 +1591,6 @@ class AccountRepository extends Repository {
|
|
|
1154
1591
|
}
|
|
1155
1592
|
return user;
|
|
1156
1593
|
} catch (e) {
|
|
1157
|
-
// fallback: return raw body
|
|
1158
1594
|
return body;
|
|
1159
1595
|
}
|
|
1160
1596
|
}
|
|
@@ -1184,7 +1620,6 @@ class AccountRepository extends Repository {
|
|
|
1184
1620
|
phone_id: this.client.state.phoneId,
|
|
1185
1621
|
}),
|
|
1186
1622
|
});
|
|
1187
|
-
// Save authorization if present in two-factor response
|
|
1188
1623
|
this._extractAndSaveAuthorization(response);
|
|
1189
1624
|
return response.body;
|
|
1190
1625
|
});
|
|
@@ -1572,18 +2007,6 @@ class AccountRepository extends Repository {
|
|
|
1572
2007
|
}
|
|
1573
2008
|
|
|
1574
2009
|
static generateAttestParams(state) {
|
|
1575
|
-
// Emulate Instagram's keystore attestation as closely as possible:
|
|
1576
|
-
// - version: 2
|
|
1577
|
-
// - type: "keystore"
|
|
1578
|
-
// - errors: [0]
|
|
1579
|
-
// - challenge_nonce: random base64url
|
|
1580
|
-
// - signed_nonce: ECDSA signature over the nonce
|
|
1581
|
-
// - key_hash: sha256(spki(publicKey))
|
|
1582
|
-
// - certificate_chain: 4 PEM certificates concatenated (leaf + 2 intermediates + root)
|
|
1583
|
-
//
|
|
1584
|
-
// NOTE: This is *not* a real hardware-backed attestation chain, but it mirrors
|
|
1585
|
-
// the structure of the official app very closely so the server sees the same
|
|
1586
|
-
// shape: a single attestation object with a 4-certificate chain.
|
|
1587
2010
|
const challengeNonce = crypto
|
|
1588
2011
|
.randomBytes(24)
|
|
1589
2012
|
.toString('base64url');
|
|
@@ -1593,7 +2016,6 @@ class AccountRepository extends Repository {
|
|
|
1593
2016
|
namedCurve: 'prime256v1',
|
|
1594
2017
|
}
|
|
1595
2018
|
);
|
|
1596
|
-
// Sign the challenge nonce with the private key (simulating TEE signing).
|
|
1597
2019
|
const signedData = crypto.sign(
|
|
1598
2020
|
null,
|
|
1599
2021
|
Buffer.from(challengeNonce),
|
|
@@ -1609,7 +2031,6 @@ class AccountRepository extends Repository {
|
|
|
1609
2031
|
.update(publicKeyDer)
|
|
1610
2032
|
.digest('hex');
|
|
1611
2033
|
|
|
1612
|
-
// Build a 4-certificate chain (leaf + 2 intermediates + root)
|
|
1613
2034
|
const leafCertPem =
|
|
1614
2035
|
AccountRepository._generateSelfSignedCert(
|
|
1615
2036
|
privateKey,
|
|
@@ -1876,6 +2297,4 @@ class AccountRepository extends Repository {
|
|
|
1876
2297
|
return response.body;
|
|
1877
2298
|
});
|
|
1878
2299
|
}
|
|
1879
|
-
}
|
|
1880
|
-
|
|
1881
|
-
module.exports = AccountRepository;
|
|
2300
|
+
}module.exports = AccountRepository;
|
package/package.json
CHANGED