nodejs-insta-private-api-mqt 1.3.86 → 1.3.87
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,18 +1,20 @@
|
|
|
1
1
|
const Repository = require('../core/repository');
|
|
2
2
|
const crypto = require('crypto');
|
|
3
|
+
|
|
3
4
|
class AccountRepository extends Repository {
|
|
4
5
|
constructor(client) {
|
|
5
6
|
super(client);
|
|
6
7
|
this.maxRetries = 3;
|
|
7
8
|
}
|
|
9
|
+
|
|
8
10
|
async requestWithRetry(requestFn, retries = 0) {
|
|
9
11
|
try {
|
|
10
12
|
const result = await requestFn();
|
|
11
13
|
return result;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
} catch (error) {
|
|
15
|
+
const shouldRetry =
|
|
16
|
+
(error.data?.error_type === 'server_error' ||
|
|
17
|
+
error.data?.error_type === 'rate_limited') &&
|
|
16
18
|
retries < this.maxRetries;
|
|
17
19
|
if (shouldRetry) {
|
|
18
20
|
const delay = 1000 * (retries + 1);
|
|
@@ -22,6 +24,7 @@ class AccountRepository extends Repository {
|
|
|
22
24
|
throw error;
|
|
23
25
|
}
|
|
24
26
|
}
|
|
27
|
+
|
|
25
28
|
/**
|
|
26
29
|
* Helper: try to extract an IGT bearer token from various sources:
|
|
27
30
|
* - response.headers (IG-Set-Authorization or ig-set-authorization)
|
|
@@ -32,8 +35,8 @@ class AccountRepository extends Repository {
|
|
|
32
35
|
* When found, sets this.client.state.authorization and calls updateAuthorization().
|
|
33
36
|
*/
|
|
34
37
|
_extractAndSaveAuthorization(response) {
|
|
35
|
-
if (!response)
|
|
36
|
-
|
|
38
|
+
if (!response) return null;
|
|
39
|
+
|
|
37
40
|
// Normalize headers (case-insensitive)
|
|
38
41
|
const headers = {};
|
|
39
42
|
if (response.headers && typeof response.headers === 'object') {
|
|
@@ -41,19 +44,19 @@ class AccountRepository extends Repository {
|
|
|
41
44
|
headers[k.toLowerCase()] = response.headers[k];
|
|
42
45
|
}
|
|
43
46
|
}
|
|
47
|
+
|
|
44
48
|
// Simpler: look for any header that contains ig-set-authorization
|
|
45
49
|
for (const key of Object.keys(headers)) {
|
|
46
50
|
const val = headers[key];
|
|
47
|
-
if (!val)
|
|
48
|
-
|
|
51
|
+
if (!val) continue;
|
|
52
|
+
|
|
49
53
|
if (key.includes('ig-set-authorization')) {
|
|
50
54
|
const token = this._normalizeTokenString(val);
|
|
51
55
|
if (token) {
|
|
52
56
|
this.client.state.authorization = token;
|
|
53
57
|
try {
|
|
54
58
|
this.client.state.updateAuthorization();
|
|
55
|
-
}
|
|
56
|
-
catch (e) { }
|
|
59
|
+
} catch (e) {}
|
|
57
60
|
return token;
|
|
58
61
|
}
|
|
59
62
|
}
|
|
@@ -64,14 +67,15 @@ class AccountRepository extends Repository {
|
|
|
64
67
|
this.client.state.authorization = token;
|
|
65
68
|
try {
|
|
66
69
|
this.client.state.updateAuthorization();
|
|
67
|
-
}
|
|
68
|
-
catch (e) { }
|
|
70
|
+
} catch (e) {}
|
|
69
71
|
return token;
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
}
|
|
75
|
+
|
|
73
76
|
// Extract IG-U headers (user id, region, claim) when present
|
|
74
|
-
const dsUserIdHeader =
|
|
77
|
+
const dsUserIdHeader =
|
|
78
|
+
headers['ig-set-ig-u-ds-user-id'] ||
|
|
75
79
|
headers['ig-u-ds-user-id'] ||
|
|
76
80
|
headers['x-ig-set-ig-u-ds-user-id'];
|
|
77
81
|
if (dsUserIdHeader) {
|
|
@@ -82,7 +86,9 @@ class AccountRepository extends Repository {
|
|
|
82
86
|
this.client.state._userId = uid;
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
|
-
|
|
89
|
+
|
|
90
|
+
const rurHeader =
|
|
91
|
+
headers['ig-set-ig-u-rur'] ||
|
|
86
92
|
headers['ig-u-rur'] ||
|
|
87
93
|
headers['x-ig-set-ig-u-rur'];
|
|
88
94
|
if (rurHeader) {
|
|
@@ -91,7 +97,9 @@ class AccountRepository extends Repository {
|
|
|
91
97
|
this.client.state.igURur = rur;
|
|
92
98
|
}
|
|
93
99
|
}
|
|
94
|
-
|
|
100
|
+
|
|
101
|
+
const wwwClaimHeader =
|
|
102
|
+
headers['x-ig-set-www-claim'] ||
|
|
95
103
|
headers['ig-set-www-claim'] ||
|
|
96
104
|
headers['ig-u-www-claim'];
|
|
97
105
|
if (wwwClaimHeader) {
|
|
@@ -100,7 +108,9 @@ class AccountRepository extends Repository {
|
|
|
100
108
|
this.client.state.igWWWClaim = claim;
|
|
101
109
|
}
|
|
102
110
|
}
|
|
103
|
-
|
|
111
|
+
|
|
112
|
+
const midHeader =
|
|
113
|
+
headers['ig-set-x-mid'] ||
|
|
104
114
|
headers['x-mid'] ||
|
|
105
115
|
headers['ig-set-mid'];
|
|
106
116
|
if (midHeader) {
|
|
@@ -109,6 +119,7 @@ class AccountRepository extends Repository {
|
|
|
109
119
|
this.client.state.mid = mid;
|
|
110
120
|
}
|
|
111
121
|
}
|
|
122
|
+
|
|
112
123
|
// 2) Check response.body for common fields
|
|
113
124
|
const body = response.body;
|
|
114
125
|
if (body) {
|
|
@@ -119,21 +130,22 @@ class AccountRepository extends Repository {
|
|
|
119
130
|
this.client.state.authorization = token;
|
|
120
131
|
try {
|
|
121
132
|
this.client.state.updateAuthorization();
|
|
122
|
-
}
|
|
123
|
-
catch (e) { }
|
|
133
|
+
} catch (e) {}
|
|
124
134
|
return token;
|
|
125
135
|
}
|
|
126
136
|
}
|
|
137
|
+
|
|
127
138
|
// If body contains a layout object or string, stringify and search it
|
|
128
139
|
try {
|
|
129
140
|
let layoutStr = null;
|
|
130
141
|
if (body.layout) {
|
|
131
|
-
layoutStr =
|
|
132
|
-
|
|
133
|
-
|
|
142
|
+
layoutStr =
|
|
143
|
+
typeof body.layout === 'string'
|
|
144
|
+
? body.layout
|
|
145
|
+
: JSON.stringify(body.layout);
|
|
146
|
+
} else if (body?.layout?.bloks_payload) {
|
|
134
147
|
layoutStr = JSON.stringify(body.layout.bloks_payload);
|
|
135
|
-
}
|
|
136
|
-
else if (body?.bloks_payload) {
|
|
148
|
+
} else if (body?.bloks_payload) {
|
|
137
149
|
layoutStr = JSON.stringify(body.bloks_payload);
|
|
138
150
|
}
|
|
139
151
|
if (!layoutStr && typeof body === 'string') {
|
|
@@ -145,49 +157,72 @@ class AccountRepository extends Repository {
|
|
|
145
157
|
this.client.state.authorization = token;
|
|
146
158
|
try {
|
|
147
159
|
this.client.state.updateAuthorization();
|
|
148
|
-
}
|
|
149
|
-
catch (e) { }
|
|
160
|
+
} catch (e) {}
|
|
150
161
|
return token;
|
|
151
162
|
}
|
|
163
|
+
|
|
152
164
|
// Some responses embed a login_response JSON string inside layout; try to extract it
|
|
153
|
-
const loginResponseMatch = layoutStr.match(
|
|
165
|
+
const loginResponseMatch = layoutStr.match(
|
|
166
|
+
/"login_response"\s*:\s*"(\\?{.*?\\?})"/s
|
|
167
|
+
);
|
|
154
168
|
if (loginResponseMatch) {
|
|
155
169
|
// Unescape JSON string
|
|
156
170
|
let jsonStr = loginResponseMatch[1];
|
|
157
171
|
try {
|
|
158
|
-
jsonStr = jsonStr
|
|
172
|
+
jsonStr = jsonStr
|
|
173
|
+
.replace(/\\"/g, '"')
|
|
174
|
+
.replace(/\\n/g, '');
|
|
159
175
|
const parsed = JSON.parse(jsonStr);
|
|
160
176
|
if (parsed && parsed.headers) {
|
|
161
|
-
const token = this._findBearerInString(
|
|
177
|
+
const token = this._findBearerInString(
|
|
178
|
+
parsed.headers
|
|
179
|
+
);
|
|
162
180
|
if (token) {
|
|
163
181
|
this.client.state.authorization = token;
|
|
164
182
|
try {
|
|
165
183
|
this.client.state.updateAuthorization();
|
|
166
|
-
}
|
|
167
|
-
catch (e) { }
|
|
184
|
+
} catch (e) {}
|
|
168
185
|
return token;
|
|
169
186
|
}
|
|
170
187
|
}
|
|
171
|
-
}
|
|
172
|
-
catch (e) {
|
|
188
|
+
} catch (e) {
|
|
173
189
|
// ignore parse errors
|
|
174
190
|
}
|
|
175
191
|
}
|
|
192
|
+
|
|
176
193
|
// Also try to find IG-Set-Password-Encryption headers (not token but useful)
|
|
177
|
-
const encKeyIdMatch =
|
|
178
|
-
layoutStr.match(
|
|
194
|
+
const encKeyIdMatch =
|
|
195
|
+
layoutStr.match(
|
|
196
|
+
/IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i
|
|
197
|
+
) ||
|
|
198
|
+
layoutStr.match(
|
|
199
|
+
/password.encryption.key.id[\\"\s:]+(\d+)/i
|
|
200
|
+
);
|
|
179
201
|
if (encKeyIdMatch) {
|
|
180
|
-
this.client.state.passwordEncryptionKeyId =
|
|
202
|
+
this.client.state.passwordEncryptionKeyId =
|
|
203
|
+
parseInt(encKeyIdMatch[1]);
|
|
181
204
|
}
|
|
182
|
-
const encPubKeyMatch =
|
|
183
|
-
layoutStr.match(
|
|
205
|
+
const encPubKeyMatch =
|
|
206
|
+
layoutStr.match(
|
|
207
|
+
/IG-Set-Password-Encryption-Pub-Key[\\"\s:]+([A-Za-z0-9+\/=]+)/i
|
|
208
|
+
) ||
|
|
209
|
+
layoutStr.match(
|
|
210
|
+
/password.encryption.pub.key[\\"\s:]+([A-Za-z0-9+\/=]+)/i
|
|
211
|
+
);
|
|
184
212
|
if (encPubKeyMatch) {
|
|
185
|
-
this.client.state.passwordEncryptionPubKey =
|
|
213
|
+
this.client.state.passwordEncryptionPubKey =
|
|
214
|
+
encPubKeyMatch[1];
|
|
186
215
|
}
|
|
216
|
+
|
|
187
217
|
// Extract user/account identifiers from layout string
|
|
188
|
-
const dsIdMatch =
|
|
218
|
+
const dsIdMatch =
|
|
219
|
+
layoutStr.match(
|
|
220
|
+
/ig-set-ig-u-ds-user-id[\\"\s:]+(\d+)/i
|
|
221
|
+
) ||
|
|
189
222
|
layoutStr.match(/ig-u-ds-user-id[\\"\s:]+(\d+)/i) ||
|
|
190
|
-
layoutStr.match(
|
|
223
|
+
layoutStr.match(
|
|
224
|
+
/"strong_id__"\s*:\s*"(\d+)"/i
|
|
225
|
+
) ||
|
|
191
226
|
layoutStr.match(/"pk_id"\s*:\s*"(\d+)"/i) ||
|
|
192
227
|
layoutStr.match(/"pk"\s*:\s*(\d+)/i);
|
|
193
228
|
if (dsIdMatch) {
|
|
@@ -195,56 +230,71 @@ class AccountRepository extends Repository {
|
|
|
195
230
|
this.client.state.cookieUserId = uid;
|
|
196
231
|
this.client.state._userId = uid;
|
|
197
232
|
}
|
|
198
|
-
|
|
233
|
+
|
|
234
|
+
const rurMatch =
|
|
235
|
+
layoutStr.match(
|
|
236
|
+
/ig-set-ig-u-rur[\\"\s:]+"([^"\\]+)/i
|
|
237
|
+
) ||
|
|
199
238
|
layoutStr.match(/ig-u-rur[\\"\s:]+"([^"\\]+)/i);
|
|
200
239
|
if (rurMatch) {
|
|
201
240
|
this.client.state.igURur = rurMatch[1];
|
|
202
241
|
}
|
|
203
|
-
|
|
242
|
+
|
|
243
|
+
const wwwClaimMatch = layoutStr.match(
|
|
244
|
+
/x-ig-set-www-claim[\\"\s:]+"([^"\\]+)/i
|
|
245
|
+
);
|
|
204
246
|
if (wwwClaimMatch) {
|
|
205
247
|
this.client.state.igWWWClaim = wwwClaimMatch[1];
|
|
206
248
|
}
|
|
207
|
-
|
|
249
|
+
|
|
250
|
+
const midMatch = layoutStr.match(
|
|
251
|
+
/"mid"\s*:\s*"([^"\\]+)"/i
|
|
252
|
+
);
|
|
208
253
|
if (midMatch) {
|
|
209
254
|
this.client.state.mid = midMatch[1];
|
|
210
255
|
}
|
|
211
256
|
}
|
|
212
|
-
}
|
|
213
|
-
catch (e) {
|
|
257
|
+
} catch (e) {
|
|
214
258
|
// ignore
|
|
215
259
|
}
|
|
260
|
+
|
|
216
261
|
// 3) If body.logged_in_user exists and we have authorization already, return it
|
|
217
262
|
if (body.logged_in_user && this.client.state.authorization) {
|
|
218
263
|
return this.client.state.authorization;
|
|
219
264
|
}
|
|
220
265
|
}
|
|
266
|
+
|
|
221
267
|
return null;
|
|
222
268
|
}
|
|
269
|
+
|
|
223
270
|
// Normalize token string: accept either raw token or "Bearer IGT:2:..." and return trimmed token string
|
|
224
271
|
_normalizeTokenString(val) {
|
|
225
|
-
if (!val || typeof val !== 'string')
|
|
226
|
-
|
|
272
|
+
if (!val || typeof val !== 'string') return null;
|
|
273
|
+
|
|
227
274
|
// If header already contains "Bearer IGT:2:..."
|
|
228
275
|
const bearer = this._findBearerInString(val);
|
|
229
|
-
if (bearer)
|
|
230
|
-
|
|
276
|
+
if (bearer) return bearer;
|
|
277
|
+
|
|
231
278
|
// If header contains JSON with IG-Set-Authorization field
|
|
232
279
|
try {
|
|
233
280
|
const maybeJson = JSON.parse(val);
|
|
234
281
|
if (maybeJson['IG-Set-Authorization']) {
|
|
235
|
-
return this._findBearerInString(
|
|
282
|
+
return this._findBearerInString(
|
|
283
|
+
maybeJson['IG-Set-Authorization']
|
|
284
|
+
);
|
|
236
285
|
}
|
|
237
|
-
}
|
|
238
|
-
catch (e) {
|
|
286
|
+
} catch (e) {
|
|
239
287
|
// not JSON
|
|
240
288
|
}
|
|
289
|
+
|
|
241
290
|
// fallback: return trimmed value
|
|
242
291
|
return val.trim();
|
|
243
292
|
}
|
|
293
|
+
|
|
244
294
|
// Find bearer token in arbitrary string using multiple patterns
|
|
245
295
|
_findBearerInString(str) {
|
|
246
|
-
if (!str || typeof str !== 'string')
|
|
247
|
-
|
|
296
|
+
if (!str || typeof str !== 'string') return null;
|
|
297
|
+
|
|
248
298
|
// Patterns to match the Bearer token in many possible encodings/escapes
|
|
249
299
|
const bearerPatterns = [
|
|
250
300
|
/Bearer IGT:2:[A-Za-z0-9+\/=]+/,
|
|
@@ -255,98 +305,372 @@ class AccountRepository extends Repository {
|
|
|
255
305
|
/IG-Set-Authorization[\s:]+(Bearer IGT:2:[A-Za-z0-9+\/=]+)/i,
|
|
256
306
|
/Bearer IGT:2:([A-Za-z0-9+\/=]+)/,
|
|
257
307
|
];
|
|
308
|
+
|
|
258
309
|
for (const pattern of bearerPatterns) {
|
|
259
310
|
const m = str.match(pattern);
|
|
260
311
|
if (m) {
|
|
261
312
|
// If capturing group present, prefer it
|
|
262
313
|
if (m[1]) {
|
|
263
|
-
const token = m[1].includes('Bearer IGT:2:')
|
|
314
|
+
const token = m[1].includes('Bearer IGT:2:')
|
|
315
|
+
? m[1]
|
|
316
|
+
: `Bearer IGT:2:${m[1]}`;
|
|
264
317
|
return token.replace(/\?"/g, '').trim();
|
|
265
318
|
}
|
|
266
319
|
// Otherwise m[0] contains the full token
|
|
267
320
|
return m[0].replace(/\?"/g, '').trim();
|
|
268
321
|
}
|
|
269
322
|
}
|
|
323
|
+
|
|
270
324
|
return null;
|
|
271
325
|
}
|
|
326
|
+
|
|
272
327
|
// Ensure we have a valid csrftoken cookie (and mid) before sensitive endpoints.
|
|
273
328
|
// Instagram typically sets csrftoken via /api/v1/si/fetch_headers/.
|
|
274
329
|
async ensureCsrfToken() {
|
|
275
330
|
const cookieToken = this.client.state.cookieCsrfToken;
|
|
276
|
-
if (
|
|
331
|
+
if (
|
|
332
|
+
cookieToken &&
|
|
333
|
+
cookieToken !== 'missing' &&
|
|
334
|
+
cookieToken !== 'pending'
|
|
335
|
+
) {
|
|
277
336
|
try {
|
|
278
337
|
this.client.state.csrfToken = cookieToken;
|
|
279
|
-
}
|
|
280
|
-
catch { }
|
|
338
|
+
} catch {}
|
|
281
339
|
return cookieToken;
|
|
282
340
|
}
|
|
341
|
+
|
|
283
342
|
// Warmup endpoint that returns/set cookies (csrftoken, mid, etc.)
|
|
284
343
|
try {
|
|
285
344
|
await this.client.request.send({
|
|
286
345
|
method: 'GET',
|
|
287
346
|
url: `/api/v1/si/fetch_headers/?challenge_type=signup&guid=${this.client.state.uuid}`,
|
|
288
347
|
});
|
|
289
|
-
}
|
|
290
|
-
catch (e) {
|
|
348
|
+
} catch (e) {
|
|
291
349
|
// ignore warmup failures
|
|
292
350
|
}
|
|
351
|
+
|
|
293
352
|
const token = this.client.state.cookieCsrfToken;
|
|
294
|
-
if (
|
|
353
|
+
if (
|
|
354
|
+
token &&
|
|
355
|
+
token !== 'missing' &&
|
|
356
|
+
token !== 'pending'
|
|
357
|
+
) {
|
|
295
358
|
try {
|
|
296
359
|
this.client.state.csrfToken = token;
|
|
297
|
-
}
|
|
298
|
-
catch { }
|
|
360
|
+
} catch {}
|
|
299
361
|
return token;
|
|
300
362
|
}
|
|
363
|
+
|
|
301
364
|
return null;
|
|
302
365
|
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Internal: perform the Bloks OAuth token fetch step that the official app does
|
|
369
|
+
* just before password login (com.bloks.www.caa.login.oauth.token.fetch.async).
|
|
370
|
+
* This is best-effort only and never throws to the caller.
|
|
371
|
+
*/
|
|
372
|
+
async _prefetchOauthTokenForLogin(username) {
|
|
373
|
+
if (!username) return null;
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
377
|
+
|
|
378
|
+
// Device identifiers – keep them consistent with the main login flow
|
|
379
|
+
const androidDeviceId =
|
|
380
|
+
this.client.state.androidDeviceId ||
|
|
381
|
+
this.client.state.deviceId ||
|
|
382
|
+
`android-${crypto.randomBytes(8).toString('hex')}`;
|
|
383
|
+
|
|
384
|
+
const familyDeviceId =
|
|
385
|
+
this.client.state.phoneId ||
|
|
386
|
+
this.client.state.familyDeviceId ||
|
|
387
|
+
(crypto.randomUUID
|
|
388
|
+
? crypto.randomUUID()
|
|
389
|
+
: require('uuid').v4());
|
|
390
|
+
|
|
391
|
+
const qeDeviceId =
|
|
392
|
+
this.client.state.deviceId ||
|
|
393
|
+
this.client.state.qeDeviceId ||
|
|
394
|
+
(crypto.randomUUID
|
|
395
|
+
? crypto.randomUUID()
|
|
396
|
+
: require('uuid').v4());
|
|
397
|
+
|
|
398
|
+
const waterfallId = crypto.randomUUID
|
|
399
|
+
? crypto.randomUUID()
|
|
400
|
+
: require('uuid').v4();
|
|
401
|
+
|
|
402
|
+
const latencyMarkerId = 36707139;
|
|
403
|
+
const latencyInstanceId = Number(
|
|
404
|
+
`${Date.now()}${Math.floor(Math.random() * 1000)}`
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
// AAC object as seen in real traffic: timestamp + UUID + random token
|
|
408
|
+
const aacInitTimestamp =
|
|
409
|
+
nowSec - Math.floor(Math.random() * 50);
|
|
410
|
+
const aacjid = crypto.randomUUID
|
|
411
|
+
? crypto.randomUUID()
|
|
412
|
+
: require('uuid').v4();
|
|
413
|
+
const aaccs = crypto
|
|
414
|
+
.randomBytes(32)
|
|
415
|
+
.toString('base64')
|
|
416
|
+
.replace(/=/g, '');
|
|
417
|
+
|
|
418
|
+
const clientInputParams = {
|
|
419
|
+
username_input: username,
|
|
420
|
+
aac: JSON.stringify({
|
|
421
|
+
aac_init_timestamp: aacInitTimestamp,
|
|
422
|
+
aacjid,
|
|
423
|
+
aaccs,
|
|
424
|
+
}),
|
|
425
|
+
lois_settings: { lois_token: '' },
|
|
426
|
+
cloud_trust_token: null,
|
|
427
|
+
zero_balance_state: '',
|
|
428
|
+
network_bssid: null,
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const serverParams = {
|
|
432
|
+
is_from_logged_out: 0,
|
|
433
|
+
layered_homepage_experiment_group: null,
|
|
434
|
+
device_id: androidDeviceId,
|
|
435
|
+
login_surface: 'switcher',
|
|
436
|
+
waterfall_id: waterfallId,
|
|
437
|
+
INTERNAL__latency_qpl_instance_id: latencyInstanceId,
|
|
438
|
+
is_platform_login: 0,
|
|
439
|
+
INTERNAL__latency_qpl_marker_id: latencyMarkerId,
|
|
440
|
+
family_device_id: familyDeviceId,
|
|
441
|
+
offline_experiment_group: 'caa_iteration_v3_perf_ig_4',
|
|
442
|
+
access_flow_version: 'pre_mt_behavior',
|
|
443
|
+
is_from_logged_in_switcher: 1,
|
|
444
|
+
qe_device_id: qeDeviceId,
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
const paramsJson = JSON.stringify({
|
|
448
|
+
client_input_params: clientInputParams,
|
|
449
|
+
server_params: serverParams,
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const bloksVersionId =
|
|
453
|
+
this.client.state.bloksVersionId ||
|
|
454
|
+
'5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
|
|
455
|
+
|
|
456
|
+
const bkClientContext = JSON.stringify({
|
|
457
|
+
bloks_version: bloksVersionId,
|
|
458
|
+
styles_id: 'instagram',
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const lang = this.client.state.language || 'ro_RO';
|
|
462
|
+
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)';
|
|
466
|
+
|
|
467
|
+
const timezoneOffset =
|
|
468
|
+
typeof this.client.state.timezoneOffset === 'number'
|
|
469
|
+
? this.client.state.timezoneOffset
|
|
470
|
+
: 7200;
|
|
471
|
+
|
|
472
|
+
const bloksHeaders = {
|
|
473
|
+
'accept-language': acceptLanguage,
|
|
474
|
+
'content-type':
|
|
475
|
+
'application/x-www-form-urlencoded; charset=UTF-8',
|
|
476
|
+
'ig-intended-user-id': '0',
|
|
477
|
+
// Critical: matches the captured request header
|
|
478
|
+
priority: 'u=3',
|
|
479
|
+
|
|
480
|
+
'x-bloks-is-layout-rtl': 'false',
|
|
481
|
+
'x-bloks-prism-ax-base-colors-enabled': 'false',
|
|
482
|
+
'x-bloks-prism-button-version': 'CONTROL',
|
|
483
|
+
'x-bloks-prism-colors-enabled': 'true',
|
|
484
|
+
'x-bloks-prism-font-enabled': 'false',
|
|
485
|
+
'x-bloks-prism-indigo-link-version': '0',
|
|
486
|
+
'x-bloks-version-id': bloksVersionId,
|
|
487
|
+
|
|
488
|
+
'x-ig-android-id': androidDeviceId,
|
|
489
|
+
'x-ig-device-id': qeDeviceId,
|
|
490
|
+
'x-ig-family-device-id': familyDeviceId,
|
|
491
|
+
'x-ig-timezone-offset': String(timezoneOffset),
|
|
492
|
+
'x-ig-app-id': String(
|
|
493
|
+
this.client.state.fbAnalyticsApplicationId ||
|
|
494
|
+
'567067343352427'
|
|
495
|
+
),
|
|
496
|
+
'x-ig-app-locale': lang,
|
|
497
|
+
'x-ig-device-locale': lang,
|
|
498
|
+
'x-ig-mapped-locale': lang,
|
|
499
|
+
|
|
500
|
+
'x-ig-client-endpoint':
|
|
501
|
+
'com.bloks.www.caa.login.login_homepage',
|
|
502
|
+
'x-ig-nav-chain':
|
|
503
|
+
'com.bloks.www.caa.login.login_homepage:com.bloks.www.caa.login.login_homepage:1:button:0:::0',
|
|
504
|
+
|
|
505
|
+
'x-ig-connection-type': 'WIFI',
|
|
506
|
+
'x-ig-capabilities': '3brTv10=',
|
|
507
|
+
'x-ig-www-claim': this.client.state.igWWWClaim || '0',
|
|
508
|
+
|
|
509
|
+
'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(
|
|
510
|
+
Math.random() * 1000
|
|
511
|
+
)
|
|
512
|
+
.toString()
|
|
513
|
+
.padStart(3, '0')}`,
|
|
514
|
+
'x-pigeon-session-id':
|
|
515
|
+
this.client.state.pigeonSessionId ||
|
|
516
|
+
`UFS-${crypto.randomBytes(16).toString('hex')}-2`,
|
|
517
|
+
|
|
518
|
+
'x-mid':
|
|
519
|
+
this.client.state.mid ||
|
|
520
|
+
`aZ${crypto.randomBytes(8).toString('hex')}`,
|
|
521
|
+
'x-tigon-is-retry': 'False',
|
|
522
|
+
|
|
523
|
+
'x-fb-client-ip': 'True',
|
|
524
|
+
'x-fb-connection-type': 'WIFI',
|
|
525
|
+
'x-fb-server-cluster': 'True',
|
|
526
|
+
'x-fb-network-properties':
|
|
527
|
+
'VPN;Validated;LocalAddrs=/10.0.0.2,;',
|
|
528
|
+
'x-fb-request-analytics-tags': JSON.stringify({
|
|
529
|
+
network_tags: {
|
|
530
|
+
product: String(
|
|
531
|
+
this.client.state.fbAnalyticsApplicationId ||
|
|
532
|
+
'567067343352427'
|
|
533
|
+
),
|
|
534
|
+
purpose: 'fetch',
|
|
535
|
+
surface: 'undefined',
|
|
536
|
+
request_category: 'api',
|
|
537
|
+
retry_attempt: '0',
|
|
538
|
+
},
|
|
539
|
+
}),
|
|
540
|
+
'x-fb-friendly-name':
|
|
541
|
+
'IgApi: bloks/async_action/com.bloks.www.caa.login.oauth.token.fetch.async/',
|
|
542
|
+
'x-fb-http-engine': 'MNS/TCP',
|
|
543
|
+
'x-fb-rmd': 'state=URL_ELIGIBLE',
|
|
544
|
+
|
|
545
|
+
'user-agent': userAgent,
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
const response = await this.client.request.send({
|
|
549
|
+
method: 'POST',
|
|
550
|
+
url: '/api/v1/bloks/async_action/com.bloks.www.caa.login.oauth.token.fetch.async/',
|
|
551
|
+
form: {
|
|
552
|
+
params: paramsJson,
|
|
553
|
+
bk_client_context: bkClientContext,
|
|
554
|
+
bloks_versioning_id: bloksVersionId,
|
|
555
|
+
},
|
|
556
|
+
headers: bloksHeaders,
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
// Optional debug dump so you can inspect the exact server response.
|
|
560
|
+
try {
|
|
561
|
+
const fs = require('fs');
|
|
562
|
+
const path = require('path');
|
|
563
|
+
const debugDir = path.join(
|
|
564
|
+
process.cwd(),
|
|
565
|
+
'authinfo_instagram'
|
|
566
|
+
);
|
|
567
|
+
const debugFile = path.join(
|
|
568
|
+
debugDir,
|
|
569
|
+
'oauth-token-debug.json'
|
|
570
|
+
);
|
|
571
|
+
try {
|
|
572
|
+
fs.mkdirSync(debugDir, { recursive: true });
|
|
573
|
+
} catch (e) {}
|
|
574
|
+
|
|
575
|
+
const debugPayload = {
|
|
576
|
+
at: new Date().toISOString(),
|
|
577
|
+
statusCode:
|
|
578
|
+
response.statusCode || response.status || null,
|
|
579
|
+
headers: response.headers || null,
|
|
580
|
+
body: response.body || null,
|
|
581
|
+
};
|
|
582
|
+
fs.writeFileSync(
|
|
583
|
+
debugFile,
|
|
584
|
+
JSON.stringify(debugPayload, null, 2),
|
|
585
|
+
'utf8'
|
|
586
|
+
);
|
|
587
|
+
} catch (e) {
|
|
588
|
+
// Never break login on debug error
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return response.body;
|
|
592
|
+
} catch (e) {
|
|
593
|
+
// Completely swallow any errors; this step should never break login.
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
303
598
|
async login(credentialsOrUsername, passwordArg) {
|
|
304
599
|
let username, password;
|
|
305
|
-
if (
|
|
600
|
+
if (
|
|
601
|
+
typeof credentialsOrUsername === 'object' &&
|
|
602
|
+
credentialsOrUsername !== null
|
|
603
|
+
) {
|
|
306
604
|
username = credentialsOrUsername.username;
|
|
307
605
|
password = credentialsOrUsername.password;
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
606
|
+
} else {
|
|
310
607
|
username = credentialsOrUsername;
|
|
311
608
|
password = passwordArg;
|
|
312
609
|
}
|
|
610
|
+
|
|
313
611
|
if (!username || !password) {
|
|
314
612
|
throw new Error('Username and password are required');
|
|
315
613
|
}
|
|
614
|
+
|
|
316
615
|
if (!this.client.state.passwordEncryptionPubKey) {
|
|
317
616
|
await this.syncLoginExperiments();
|
|
318
617
|
}
|
|
618
|
+
|
|
319
619
|
const { encrypted, time } = this.encryptPassword(password);
|
|
620
|
+
|
|
320
621
|
// optional CSRF warmup (like app does before login)
|
|
321
622
|
await this.ensureCsrfToken();
|
|
623
|
+
|
|
624
|
+
// Pre-login Bloks OAuth token fetch (mirrors captured request).
|
|
625
|
+
// This helps the flow look closer to the real app, but is best-effort only.
|
|
626
|
+
try {
|
|
627
|
+
await this._prefetchOauthTokenForLogin(username);
|
|
628
|
+
} catch (e) {
|
|
629
|
+
// Ignore any errors from the prefetch step.
|
|
630
|
+
}
|
|
631
|
+
|
|
322
632
|
return this.requestWithRetry(async () => {
|
|
323
|
-
// ====== client_input_params (
|
|
633
|
+
// ====== client_input_params (mirror of real traffic with password) ======
|
|
324
634
|
const nowSec = Math.floor(Date.now() / 1000);
|
|
325
|
-
const aacInitTimestamp =
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
635
|
+
const aacInitTimestamp =
|
|
636
|
+
nowSec - Math.floor(Math.random() * 50);
|
|
637
|
+
const aacjid = crypto.randomUUID
|
|
638
|
+
? crypto.randomUUID()
|
|
639
|
+
: require('uuid').v4();
|
|
640
|
+
const aaccs = crypto
|
|
641
|
+
.randomBytes(32)
|
|
642
|
+
.toString('base64')
|
|
643
|
+
.replace(/=/g, '');
|
|
644
|
+
|
|
645
|
+
const androidDeviceId =
|
|
646
|
+
this.client.state.androidDeviceId ||
|
|
329
647
|
this.client.state.deviceId ||
|
|
330
648
|
`android-${crypto.randomBytes(8).toString('hex')}`;
|
|
331
|
-
const machineId =
|
|
649
|
+
const machineId =
|
|
650
|
+
this.client.state.mid ||
|
|
332
651
|
this.client.state.machineId ||
|
|
333
652
|
`aZ${crypto.randomBytes(8).toString('hex')}`;
|
|
334
|
-
const familyDeviceId =
|
|
653
|
+
const familyDeviceId =
|
|
654
|
+
this.client.state.phoneId ||
|
|
335
655
|
this.client.state.familyDeviceId ||
|
|
336
|
-
crypto.randomUUID
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const qeDeviceId =
|
|
656
|
+
(crypto.randomUUID
|
|
657
|
+
? crypto.randomUUID()
|
|
658
|
+
: require('uuid').v4());
|
|
659
|
+
const qeDeviceId =
|
|
660
|
+
this.client.state.deviceId ||
|
|
340
661
|
this.client.state.qeDeviceId ||
|
|
341
|
-
crypto.randomUUID
|
|
342
|
-
|
|
343
|
-
|
|
662
|
+
(crypto.randomUUID
|
|
663
|
+
? crypto.randomUUID()
|
|
664
|
+
: require('uuid').v4());
|
|
665
|
+
|
|
344
666
|
const accountsList = this.client.state.accountsList || [];
|
|
667
|
+
|
|
345
668
|
const flashCallPermissionStatus = {
|
|
346
669
|
READ_PHONE_STATE: 'GRANTED',
|
|
347
670
|
READ_CALL_LOG: 'GRANTED',
|
|
348
671
|
ANSWER_PHONE_CALLS: 'GRANTED',
|
|
349
672
|
};
|
|
673
|
+
|
|
350
674
|
const clientInputParams = {
|
|
351
675
|
aac: JSON.stringify({
|
|
352
676
|
aac_init_timestamp: aacInitTimestamp,
|
|
@@ -380,7 +704,8 @@ class AccountRepository extends Repository {
|
|
|
380
704
|
machine_id: machineId,
|
|
381
705
|
flash_call_permission_status: flashCallPermissionStatus,
|
|
382
706
|
accounts_list: accountsList,
|
|
383
|
-
gms_incoming_call_retriever_eligibility:
|
|
707
|
+
gms_incoming_call_retriever_eligibility:
|
|
708
|
+
'client_not_supported',
|
|
384
709
|
family_device_id: familyDeviceId,
|
|
385
710
|
fb_ig_device_id: [],
|
|
386
711
|
device_emails: [],
|
|
@@ -391,10 +716,16 @@ class AccountRepository extends Repository {
|
|
|
391
716
|
openid_tokens: {},
|
|
392
717
|
contact_point: username,
|
|
393
718
|
};
|
|
394
|
-
|
|
395
|
-
|
|
719
|
+
|
|
720
|
+
// ====== server_params (mirror after reverse engineering) ======
|
|
721
|
+
const waterfallId = crypto.randomUUID
|
|
722
|
+
? crypto.randomUUID()
|
|
723
|
+
: require('uuid').v4();
|
|
396
724
|
const latencyMarkerId = 36707139;
|
|
397
|
-
const latencyInstanceId = Number(
|
|
725
|
+
const latencyInstanceId = Number(
|
|
726
|
+
`${Date.now()}${Math.floor(Math.random() * 1000)}`
|
|
727
|
+
);
|
|
728
|
+
|
|
398
729
|
const serverParams = {
|
|
399
730
|
should_trigger_override_login_2fa_action: 0,
|
|
400
731
|
is_vanilla_password_page_empty_password: 0,
|
|
@@ -408,7 +739,8 @@ class AccountRepository extends Repository {
|
|
|
408
739
|
is_platform_login: 0,
|
|
409
740
|
INTERNAL__latency_qpl_marker_id: latencyMarkerId,
|
|
410
741
|
is_from_aymh: 0,
|
|
411
|
-
offline_experiment_group:
|
|
742
|
+
offline_experiment_group:
|
|
743
|
+
'caa_iteration_v3_perf_ig_4',
|
|
412
744
|
is_from_landing_page: 0,
|
|
413
745
|
left_nav_button_action: 'NONE',
|
|
414
746
|
password_text_input_id: 'z0jejq:194',
|
|
@@ -421,7 +753,8 @@ class AccountRepository extends Repository {
|
|
|
421
753
|
device_id: androidDeviceId,
|
|
422
754
|
login_surface: 'switcher',
|
|
423
755
|
INTERNAL__latency_qpl_instance_id: latencyInstanceId,
|
|
424
|
-
reg_flow_source:
|
|
756
|
+
reg_flow_source:
|
|
757
|
+
'login_home_native_integration_point',
|
|
425
758
|
is_caa_perf_enabled: 1,
|
|
426
759
|
credential_type: 'password',
|
|
427
760
|
is_from_password_entry_page: 0,
|
|
@@ -431,39 +764,59 @@ class AccountRepository extends Repository {
|
|
|
431
764
|
access_flow_version: 'pre_mt_behavior',
|
|
432
765
|
is_from_logged_in_switcher: 1,
|
|
433
766
|
};
|
|
767
|
+
|
|
434
768
|
const paramsJson = JSON.stringify({
|
|
435
769
|
client_input_params: clientInputParams,
|
|
436
770
|
server_params: serverParams,
|
|
437
771
|
});
|
|
438
|
-
|
|
772
|
+
|
|
773
|
+
const attestParams =
|
|
774
|
+
AccountRepository.generateAttestParams(
|
|
775
|
+
this.client.state
|
|
776
|
+
);
|
|
777
|
+
const bloksVersionId =
|
|
778
|
+
this.client.state.bloksVersionId ||
|
|
779
|
+
'5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
|
|
780
|
+
|
|
439
781
|
const bkClientContext = JSON.stringify({
|
|
440
|
-
bloks_version:
|
|
782
|
+
bloks_version: bloksVersionId,
|
|
441
783
|
styles_id: 'instagram',
|
|
442
784
|
});
|
|
443
|
-
|
|
444
|
-
|
|
785
|
+
|
|
786
|
+
// ====== HEADERS – modelled after real traffic ======
|
|
787
|
+
const lang = this.client.state.language || 'ro_RO';
|
|
445
788
|
const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
|
|
446
|
-
const userAgent =
|
|
789
|
+
const userAgent =
|
|
790
|
+
this.client.state.userAgent ||
|
|
447
791
|
'Instagram 371.0.0.0.23 Android (30/11; 320dpi; 720x1449; Xiaomi/Redmi; M2006C3MNG; angelican; mt6765; ro_RO; 703217507)';
|
|
792
|
+
|
|
448
793
|
const bloksHeaders = {
|
|
449
794
|
'accept-language': acceptLanguage,
|
|
450
|
-
'content-type':
|
|
795
|
+
'content-type':
|
|
796
|
+
'application/x-www-form-urlencoded; charset=UTF-8',
|
|
451
797
|
'ig-intended-user-id': '0',
|
|
452
|
-
|
|
798
|
+
priority: 'u=3',
|
|
799
|
+
|
|
453
800
|
'x-bloks-is-layout-rtl': 'false',
|
|
454
801
|
'x-bloks-prism-ax-base-colors-enabled': 'false',
|
|
455
802
|
'x-bloks-prism-button-version': 'CONTROL',
|
|
456
803
|
'x-bloks-prism-colors-enabled': 'true',
|
|
457
804
|
'x-bloks-prism-font-enabled': 'false',
|
|
458
805
|
'x-bloks-prism-indigo-link-version': '0',
|
|
459
|
-
'x-bloks-version-id':
|
|
806
|
+
'x-bloks-version-id': bloksVersionId,
|
|
807
|
+
|
|
460
808
|
'x-fb-client-ip': 'True',
|
|
461
809
|
'x-fb-connection-type': 'WIFI',
|
|
462
|
-
'x-fb-friendly-name':
|
|
463
|
-
|
|
810
|
+
'x-fb-friendly-name':
|
|
811
|
+
'IgApi: bloks/async_action/com.bloks.www.bloks.caa.login.async.send_login_request/',
|
|
812
|
+
'x-fb-network-properties':
|
|
813
|
+
'VPN;Validated;LocalAddrs=/10.0.0.2,;',
|
|
464
814
|
'x-fb-request-analytics-tags': JSON.stringify({
|
|
465
815
|
network_tags: {
|
|
466
|
-
product: String(
|
|
816
|
+
product: String(
|
|
817
|
+
this.client.state.fbAnalyticsApplicationId ||
|
|
818
|
+
'567067343352427'
|
|
819
|
+
),
|
|
467
820
|
purpose: 'fetch',
|
|
468
821
|
surface: 'undefined',
|
|
469
822
|
request_category: 'api',
|
|
@@ -471,78 +824,111 @@ class AccountRepository extends Repository {
|
|
|
471
824
|
},
|
|
472
825
|
}),
|
|
473
826
|
'x-fb-server-cluster': 'True',
|
|
827
|
+
|
|
474
828
|
'x-ig-android-id': androidDeviceId,
|
|
475
|
-
'x-ig-app-id': String(
|
|
829
|
+
'x-ig-app-id': String(
|
|
830
|
+
this.client.state.fbAnalyticsApplicationId ||
|
|
831
|
+
'567067343352427'
|
|
832
|
+
),
|
|
476
833
|
'x-ig-app-locale': lang,
|
|
477
834
|
'x-ig-attest-params': JSON.stringify(attestParams),
|
|
478
|
-
'x-ig-bandwidth-speed-kbps': (
|
|
835
|
+
'x-ig-bandwidth-speed-kbps': (
|
|
836
|
+
Math.random() * 1500 +
|
|
837
|
+
800
|
|
838
|
+
).toFixed(3),
|
|
479
839
|
'x-ig-bandwidth-totalbytes-b': '0',
|
|
480
840
|
'x-ig-bandwidth-totaltime-ms': '0',
|
|
481
|
-
'x-ig-client-endpoint':
|
|
841
|
+
'x-ig-client-endpoint':
|
|
842
|
+
'com.bloks.www.caa.login.aymh_single_profile_screen_entry',
|
|
482
843
|
'x-ig-capabilities': '3brTv10=',
|
|
483
844
|
'x-ig-connection-type': 'WIFI',
|
|
484
845
|
'x-ig-device-id': qeDeviceId,
|
|
485
846
|
'x-ig-device-locale': lang,
|
|
486
847
|
'x-ig-family-device-id': familyDeviceId,
|
|
487
848
|
'x-ig-mapped-locale': lang,
|
|
488
|
-
'x-ig-nav-chain':
|
|
489
|
-
'com.bloks.www.caa.login.aymh_single_profile_screen_entry:com.bloks.www.caa.login.aymh_single_profile_screen_entry:14:button:0:::0',
|
|
490
|
-
'x-ig-timezone-offset': String(
|
|
491
|
-
|
|
492
|
-
|
|
849
|
+
'x-ig-nav-chain':
|
|
850
|
+
'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',
|
|
851
|
+
'x-ig-timezone-offset': String(
|
|
852
|
+
typeof this.client.state.timezoneOffset ===
|
|
853
|
+
'number'
|
|
854
|
+
? this.client.state.timezoneOffset
|
|
855
|
+
: 7200
|
|
856
|
+
),
|
|
493
857
|
'x-ig-www-claim': this.client.state.igWWWClaim || '0',
|
|
494
858
|
'x-mid': machineId,
|
|
495
|
-
'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(
|
|
859
|
+
'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(
|
|
860
|
+
Math.random() * 1000
|
|
861
|
+
)
|
|
496
862
|
.toString()
|
|
497
863
|
.padStart(3, '0')}`,
|
|
498
|
-
'x-pigeon-session-id':
|
|
499
|
-
|
|
864
|
+
'x-pigeon-session-id':
|
|
865
|
+
this.client.state.pigeonSessionId ||
|
|
866
|
+
`UFS-${crypto.randomBytes(16).toString(
|
|
867
|
+
'hex'
|
|
868
|
+
)}-1`,
|
|
500
869
|
'x-tigon-is-retry': 'False',
|
|
501
|
-
|
|
870
|
+
|
|
502
871
|
'accept-encoding': 'gzip, deflate, br',
|
|
503
|
-
// ...
|
|
504
872
|
'user-agent': userAgent,
|
|
505
|
-
'x-fb-conn-uuid-client':
|
|
873
|
+
'x-fb-conn-uuid-client':
|
|
874
|
+
crypto.randomBytes(16).toString('hex'),
|
|
506
875
|
'x-fb-http-engine': 'MNS/TCP',
|
|
507
876
|
'x-fb-rmd': 'state=URL_ELIGIBLE',
|
|
508
877
|
};
|
|
878
|
+
|
|
509
879
|
const response = await this.client.request.send({
|
|
510
880
|
method: 'POST',
|
|
511
881
|
url: '/api/v1/bloks/async_action/com.bloks.www.bloks.caa.login.async.send_login_request/',
|
|
512
882
|
form: {
|
|
513
883
|
params: paramsJson,
|
|
514
884
|
bk_client_context: bkClientContext,
|
|
515
|
-
bloks_versioning_id:
|
|
885
|
+
bloks_versioning_id: bloksVersionId,
|
|
516
886
|
},
|
|
517
887
|
headers: bloksHeaders,
|
|
518
888
|
});
|
|
519
|
-
|
|
889
|
+
|
|
890
|
+
// === DEBUG LOGIN: save response for offline analysis ===
|
|
520
891
|
try {
|
|
521
892
|
const fs = require('fs');
|
|
522
893
|
const path = require('path');
|
|
523
|
-
const debugDir = path.join(
|
|
524
|
-
|
|
894
|
+
const debugDir = path.join(
|
|
895
|
+
process.cwd(),
|
|
896
|
+
'authinfo_instagram'
|
|
897
|
+
);
|
|
898
|
+
const debugFile = path.join(
|
|
899
|
+
debugDir,
|
|
900
|
+
'login-debug.json'
|
|
901
|
+
);
|
|
525
902
|
try {
|
|
526
903
|
fs.mkdirSync(debugDir, { recursive: true });
|
|
527
|
-
}
|
|
528
|
-
|
|
904
|
+
} catch (e) {}
|
|
905
|
+
|
|
529
906
|
const debugPayload = {
|
|
530
907
|
at: new Date().toISOString(),
|
|
531
|
-
statusCode:
|
|
908
|
+
statusCode:
|
|
909
|
+
response.statusCode || response.status || null,
|
|
532
910
|
headers: response.headers || null,
|
|
533
911
|
body: response.body || null,
|
|
534
912
|
};
|
|
535
|
-
fs.writeFileSync(
|
|
913
|
+
fs.writeFileSync(
|
|
914
|
+
debugFile,
|
|
915
|
+
JSON.stringify(debugPayload, null, 2),
|
|
916
|
+
'utf8'
|
|
917
|
+
);
|
|
918
|
+
} catch (e) {
|
|
919
|
+
// don't break login on debug failure
|
|
536
920
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
}
|
|
540
|
-
// === SFÂRȘIT DEBUG LOGIN ===
|
|
921
|
+
// === END DEBUG LOGIN ===
|
|
922
|
+
|
|
541
923
|
const body = response.body;
|
|
924
|
+
|
|
542
925
|
// Immediately attempt to extract and save authorization token from the response
|
|
543
926
|
this._extractAndSaveAuthorization(response);
|
|
927
|
+
|
|
544
928
|
if (body && body.two_factor_required) {
|
|
545
|
-
const err = new Error(
|
|
929
|
+
const err = new Error(
|
|
930
|
+
'Two factor authentication required'
|
|
931
|
+
);
|
|
546
932
|
err.name = 'IgLoginTwoFactorRequiredError';
|
|
547
933
|
err.twoFactorInfo = body.two_factor_info;
|
|
548
934
|
throw err;
|
|
@@ -563,28 +949,46 @@ class AccountRepository extends Repository {
|
|
|
563
949
|
err.challengeInfo = body.challenge;
|
|
564
950
|
throw err;
|
|
565
951
|
}
|
|
952
|
+
|
|
566
953
|
if (body && body.layout) {
|
|
567
|
-
const layoutStr =
|
|
954
|
+
const layoutStr =
|
|
955
|
+
typeof body.layout === 'string'
|
|
956
|
+
? body.layout
|
|
957
|
+
: JSON.stringify(body.layout);
|
|
958
|
+
|
|
568
959
|
// If layout contains two-factor markers, parse and throw
|
|
569
|
-
if (
|
|
960
|
+
if (
|
|
961
|
+
layoutStr.includes('two_factor_required') ||
|
|
962
|
+
layoutStr.includes('"two_factor_info"')
|
|
963
|
+
) {
|
|
570
964
|
let twoFactorInfo = null;
|
|
571
965
|
try {
|
|
572
|
-
const match = layoutStr.match(
|
|
966
|
+
const match = layoutStr.match(
|
|
967
|
+
/"two_factor_info"\s*:\s*(\{[^}]+\})/
|
|
968
|
+
);
|
|
573
969
|
if (match)
|
|
574
970
|
twoFactorInfo = JSON.parse(match[1]);
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
|
|
971
|
+
} catch (e) {}
|
|
972
|
+
const err = new Error(
|
|
973
|
+
'Two factor authentication required'
|
|
974
|
+
);
|
|
578
975
|
err.name = 'IgLoginTwoFactorRequiredError';
|
|
579
976
|
err.twoFactorInfo = twoFactorInfo;
|
|
580
977
|
throw err;
|
|
581
978
|
}
|
|
582
|
-
|
|
979
|
+
|
|
980
|
+
if (
|
|
981
|
+
layoutStr.includes('bad_password') ||
|
|
982
|
+
layoutStr.includes('incorrect_password')
|
|
983
|
+
) {
|
|
583
984
|
const err = new Error('Bad password');
|
|
584
985
|
err.name = 'IgLoginBadPasswordError';
|
|
585
986
|
throw err;
|
|
586
987
|
}
|
|
587
|
-
if (
|
|
988
|
+
if (
|
|
989
|
+
layoutStr.includes('invalid_user') ||
|
|
990
|
+
layoutStr.includes('user_not_found')
|
|
991
|
+
) {
|
|
588
992
|
const err = new Error('Invalid user');
|
|
589
993
|
err.name = 'IgLoginInvalidUserError';
|
|
590
994
|
throw err;
|
|
@@ -595,15 +999,18 @@ class AccountRepository extends Repository {
|
|
|
595
999
|
err.challengeInfo = body.challenge || null;
|
|
596
1000
|
throw err;
|
|
597
1001
|
}
|
|
598
|
-
|
|
599
|
-
|
|
1002
|
+
|
|
1003
|
+
// Additional attempt to extract token from layout string
|
|
1004
|
+
const tokenFromLayout =
|
|
1005
|
+
this._findBearerInString(layoutStr);
|
|
600
1006
|
if (tokenFromLayout) {
|
|
601
|
-
this.client.state.authorization =
|
|
1007
|
+
this.client.state.authorization =
|
|
1008
|
+
tokenFromLayout;
|
|
602
1009
|
try {
|
|
603
1010
|
this.client.state.updateAuthorization();
|
|
604
|
-
}
|
|
605
|
-
catch (e) { }
|
|
1011
|
+
} catch (e) {}
|
|
606
1012
|
}
|
|
1013
|
+
|
|
607
1014
|
// Extract pk (user id) from layout if present
|
|
608
1015
|
const pkPatterns = [
|
|
609
1016
|
/\\"pk\\":\s*(\d+)/,
|
|
@@ -618,11 +1025,13 @@ class AccountRepository extends Repository {
|
|
|
618
1025
|
const pkMatch = layoutStr.match(pattern);
|
|
619
1026
|
if (pkMatch) {
|
|
620
1027
|
extractedPk = pkMatch[1];
|
|
621
|
-
this.client.state.cookieUserId =
|
|
1028
|
+
this.client.state.cookieUserId =
|
|
1029
|
+
extractedPk;
|
|
622
1030
|
this.client.state._userId = extractedPk;
|
|
623
1031
|
break;
|
|
624
1032
|
}
|
|
625
1033
|
}
|
|
1034
|
+
|
|
626
1035
|
// Extract IG-Set-WWW-Claim if present
|
|
627
1036
|
const wwwClaimPatterns = [
|
|
628
1037
|
/IG-Set-WWW-Claim[\\"\s:]+([a-f0-9]+)/i,
|
|
@@ -633,26 +1042,48 @@ class AccountRepository extends Repository {
|
|
|
633
1042
|
for (const pattern of wwwClaimPatterns) {
|
|
634
1043
|
const wwwClaimMatch = layoutStr.match(pattern);
|
|
635
1044
|
if (wwwClaimMatch) {
|
|
636
|
-
this.client.state.igWWWClaim =
|
|
1045
|
+
this.client.state.igWWWClaim =
|
|
1046
|
+
wwwClaimMatch[1];
|
|
637
1047
|
break;
|
|
638
1048
|
}
|
|
639
1049
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
1050
|
+
|
|
1051
|
+
// Extract encryption key id and pub key (already attempted earlier)
|
|
1052
|
+
const encKeyIdMatch =
|
|
1053
|
+
layoutStr.match(
|
|
1054
|
+
/IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i
|
|
1055
|
+
) ||
|
|
1056
|
+
layoutStr.match(
|
|
1057
|
+
/password.encryption.key.id[\\"\s:]+(\d+)/i
|
|
1058
|
+
);
|
|
643
1059
|
if (encKeyIdMatch) {
|
|
644
|
-
this.client.state.passwordEncryptionKeyId =
|
|
1060
|
+
this.client.state.passwordEncryptionKeyId =
|
|
1061
|
+
parseInt(encKeyIdMatch[1]);
|
|
645
1062
|
}
|
|
646
|
-
|
|
647
|
-
|
|
1063
|
+
|
|
1064
|
+
const encPubKeyMatch =
|
|
1065
|
+
layoutStr.match(
|
|
1066
|
+
/IG-Set-Password-Encryption-Pub-Key[\\"\s:]+([A-Za-z0-9+\/=]+)/i
|
|
1067
|
+
) ||
|
|
1068
|
+
layoutStr.match(
|
|
1069
|
+
/password.encryption.pub.key[\\"\s:]+([A-Za-z0-9+\/=]+)/i
|
|
1070
|
+
);
|
|
648
1071
|
if (encPubKeyMatch) {
|
|
649
|
-
this.client.state.passwordEncryptionPubKey =
|
|
1072
|
+
this.client.state.passwordEncryptionPubKey =
|
|
1073
|
+
encPubKeyMatch[1];
|
|
650
1074
|
}
|
|
651
|
-
|
|
652
|
-
|
|
1075
|
+
|
|
1076
|
+
const midMatch =
|
|
1077
|
+
layoutStr.match(
|
|
1078
|
+
/ig-set-x-mid[\\"\s:]+([A-Za-z0-9+\/=_-]+)/i
|
|
1079
|
+
) ||
|
|
1080
|
+
layoutStr.match(
|
|
1081
|
+
/\\"x-mid\\"[\\"\s:]+([A-Za-z0-9+\/=_-]+)/i
|
|
1082
|
+
);
|
|
653
1083
|
if (midMatch) {
|
|
654
1084
|
this.client.state.mid = midMatch[1];
|
|
655
1085
|
}
|
|
1086
|
+
|
|
656
1087
|
// If layout contains a direct logged_in_user JSON, extract and return it
|
|
657
1088
|
const loginResponsePatterns = [
|
|
658
1089
|
/\\"logged_in_user\\":\{[^}]*\\"pk\\":(\d+)[^}]*\\"username\\":\\"([^"\\]+)\\"/,
|
|
@@ -660,10 +1091,13 @@ class AccountRepository extends Repository {
|
|
|
660
1091
|
/\\"logged_in_user\\".*?\\"pk\\":\s*(\d+).*?\\"username\\":\s*\\"([^"\\]+)\\"/s,
|
|
661
1092
|
];
|
|
662
1093
|
for (const pattern of loginResponsePatterns) {
|
|
663
|
-
const loginResponseMatch =
|
|
1094
|
+
const loginResponseMatch =
|
|
1095
|
+
layoutStr.match(pattern);
|
|
664
1096
|
if (loginResponseMatch) {
|
|
665
|
-
this.client.state.cookieUserId =
|
|
666
|
-
|
|
1097
|
+
this.client.state.cookieUserId =
|
|
1098
|
+
loginResponseMatch[1];
|
|
1099
|
+
this.client.state._userId =
|
|
1100
|
+
loginResponseMatch[1];
|
|
667
1101
|
return {
|
|
668
1102
|
pk: parseInt(loginResponseMatch[1]),
|
|
669
1103
|
pk_id: loginResponseMatch[1],
|
|
@@ -671,22 +1105,31 @@ class AccountRepository extends Repository {
|
|
|
671
1105
|
};
|
|
672
1106
|
}
|
|
673
1107
|
}
|
|
1108
|
+
|
|
674
1109
|
// If we have extracted a pk and we also have authorization, try to fetch current user
|
|
675
|
-
if (
|
|
1110
|
+
if (
|
|
1111
|
+
extractedPk &&
|
|
1112
|
+
this.client.state.authorization
|
|
1113
|
+
) {
|
|
676
1114
|
try {
|
|
677
1115
|
const userInfo = await this.currentUser();
|
|
678
1116
|
const user = userInfo.user || userInfo;
|
|
679
1117
|
if (user && user.pk) {
|
|
680
|
-
this.client.state.cookieUserId =
|
|
681
|
-
|
|
1118
|
+
this.client.state.cookieUserId =
|
|
1119
|
+
String(user.pk);
|
|
1120
|
+
this.client.state._userId =
|
|
1121
|
+
String(user.pk);
|
|
682
1122
|
}
|
|
683
1123
|
return user;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
|
|
1124
|
+
} catch (e) {
|
|
1125
|
+
return {
|
|
1126
|
+
pk: parseInt(extractedPk),
|
|
1127
|
+
pk_id: extractedPk,
|
|
1128
|
+
};
|
|
687
1129
|
}
|
|
688
1130
|
}
|
|
689
1131
|
}
|
|
1132
|
+
|
|
690
1133
|
// If server returned logged_in_user directly in body
|
|
691
1134
|
if (body && body.logged_in_user) {
|
|
692
1135
|
const lu = body.logged_in_user;
|
|
@@ -698,26 +1141,34 @@ class AccountRepository extends Repository {
|
|
|
698
1141
|
this._extractAndSaveAuthorization(response);
|
|
699
1142
|
return lu;
|
|
700
1143
|
}
|
|
1144
|
+
|
|
701
1145
|
// If we already have authorization, try to fetch current user to populate state
|
|
702
1146
|
if (this.client.state.authorization) {
|
|
703
1147
|
try {
|
|
704
1148
|
const userInfo = await this.currentUser();
|
|
705
1149
|
const user = userInfo.user || userInfo;
|
|
706
1150
|
if (user && user.pk) {
|
|
707
|
-
this.client.state.cookieUserId =
|
|
1151
|
+
this.client.state.cookieUserId =
|
|
1152
|
+
String(user.pk);
|
|
708
1153
|
this.client.state._userId = String(user.pk);
|
|
709
1154
|
}
|
|
710
1155
|
return user;
|
|
711
|
-
}
|
|
712
|
-
catch (e) {
|
|
1156
|
+
} catch (e) {
|
|
713
1157
|
// fallback: return raw body
|
|
714
1158
|
return body;
|
|
715
1159
|
}
|
|
716
1160
|
}
|
|
1161
|
+
|
|
717
1162
|
return body;
|
|
718
1163
|
});
|
|
719
1164
|
}
|
|
720
|
-
|
|
1165
|
+
|
|
1166
|
+
async twoFactorLogin(
|
|
1167
|
+
username,
|
|
1168
|
+
verificationCode,
|
|
1169
|
+
twoFactorIdentifier,
|
|
1170
|
+
verificationMethod = '1'
|
|
1171
|
+
) {
|
|
721
1172
|
return this.requestWithRetry(async () => {
|
|
722
1173
|
const response = await this.client.request.send({
|
|
723
1174
|
method: 'POST',
|
|
@@ -738,6 +1189,7 @@ class AccountRepository extends Repository {
|
|
|
738
1189
|
return response.body;
|
|
739
1190
|
});
|
|
740
1191
|
}
|
|
1192
|
+
|
|
741
1193
|
async logout() {
|
|
742
1194
|
return this.requestWithRetry(async () => {
|
|
743
1195
|
const response = await this.client.request.send({
|
|
@@ -750,6 +1202,7 @@ class AccountRepository extends Repository {
|
|
|
750
1202
|
return response.body;
|
|
751
1203
|
});
|
|
752
1204
|
}
|
|
1205
|
+
|
|
753
1206
|
async currentUser() {
|
|
754
1207
|
return this.requestWithRetry(async () => {
|
|
755
1208
|
const response = await this.client.request.send({
|
|
@@ -760,10 +1213,19 @@ class AccountRepository extends Repository {
|
|
|
760
1213
|
return response.body;
|
|
761
1214
|
});
|
|
762
1215
|
}
|
|
1216
|
+
|
|
763
1217
|
async accountInfo() {
|
|
764
1218
|
return this.currentUser();
|
|
765
1219
|
}
|
|
766
|
-
|
|
1220
|
+
|
|
1221
|
+
async editProfile({
|
|
1222
|
+
externalUrl,
|
|
1223
|
+
phoneNumber,
|
|
1224
|
+
username,
|
|
1225
|
+
fullName,
|
|
1226
|
+
biography,
|
|
1227
|
+
email,
|
|
1228
|
+
} = {}) {
|
|
767
1229
|
return this.requestWithRetry(async () => {
|
|
768
1230
|
const current = await this.currentUser();
|
|
769
1231
|
const user = current.user || current;
|
|
@@ -772,26 +1234,48 @@ class AccountRepository extends Repository {
|
|
|
772
1234
|
url: '/api/v1/accounts/edit_profile/',
|
|
773
1235
|
form: this.client.request.sign({
|
|
774
1236
|
_uuid: this.client.state.uuid,
|
|
775
|
-
external_url:
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1237
|
+
external_url:
|
|
1238
|
+
externalUrl !== undefined
|
|
1239
|
+
? externalUrl
|
|
1240
|
+
: user.external_url || '',
|
|
1241
|
+
phone_number:
|
|
1242
|
+
phoneNumber !== undefined
|
|
1243
|
+
? phoneNumber
|
|
1244
|
+
: user.phone_number || '',
|
|
1245
|
+
username:
|
|
1246
|
+
username !== undefined
|
|
1247
|
+
? username
|
|
1248
|
+
: user.username,
|
|
1249
|
+
full_name:
|
|
1250
|
+
fullName !== undefined
|
|
1251
|
+
? fullName
|
|
1252
|
+
: user.full_name || '',
|
|
1253
|
+
biography:
|
|
1254
|
+
biography !== undefined
|
|
1255
|
+
? biography
|
|
1256
|
+
: user.biography || '',
|
|
1257
|
+
email:
|
|
1258
|
+
email !== undefined
|
|
1259
|
+
? email
|
|
1260
|
+
: user.email || '',
|
|
781
1261
|
}),
|
|
782
1262
|
});
|
|
783
1263
|
return response.body;
|
|
784
1264
|
});
|
|
785
1265
|
}
|
|
1266
|
+
|
|
786
1267
|
async setBiography(biography) {
|
|
787
1268
|
return this.editProfile({ biography });
|
|
788
1269
|
}
|
|
1270
|
+
|
|
789
1271
|
async setExternalUrl(url) {
|
|
790
1272
|
return this.editProfile({ externalUrl: url });
|
|
791
1273
|
}
|
|
1274
|
+
|
|
792
1275
|
async removeBioLinks() {
|
|
793
1276
|
return this.editProfile({ externalUrl: '' });
|
|
794
1277
|
}
|
|
1278
|
+
|
|
795
1279
|
async setGender(gender) {
|
|
796
1280
|
return this.requestWithRetry(async () => {
|
|
797
1281
|
const response = await this.client.request.send({
|
|
@@ -805,6 +1289,7 @@ class AccountRepository extends Repository {
|
|
|
805
1289
|
return response.body;
|
|
806
1290
|
});
|
|
807
1291
|
}
|
|
1292
|
+
|
|
808
1293
|
async setPrivate() {
|
|
809
1294
|
return this.requestWithRetry(async () => {
|
|
810
1295
|
const response = await this.client.request.send({
|
|
@@ -817,6 +1302,7 @@ class AccountRepository extends Repository {
|
|
|
817
1302
|
return response.body;
|
|
818
1303
|
});
|
|
819
1304
|
}
|
|
1305
|
+
|
|
820
1306
|
async setPublic() {
|
|
821
1307
|
return this.requestWithRetry(async () => {
|
|
822
1308
|
const response = await this.client.request.send({
|
|
@@ -829,6 +1315,7 @@ class AccountRepository extends Repository {
|
|
|
829
1315
|
return response.body;
|
|
830
1316
|
});
|
|
831
1317
|
}
|
|
1318
|
+
|
|
832
1319
|
async changePassword(oldPassword, newPassword) {
|
|
833
1320
|
const oldEnc = this.encryptPassword(oldPassword);
|
|
834
1321
|
const newEnc = this.encryptPassword(newPassword);
|
|
@@ -846,6 +1333,7 @@ class AccountRepository extends Repository {
|
|
|
846
1333
|
return response.body;
|
|
847
1334
|
});
|
|
848
1335
|
}
|
|
1336
|
+
|
|
849
1337
|
async sendConfirmEmail() {
|
|
850
1338
|
return this.requestWithRetry(async () => {
|
|
851
1339
|
const response = await this.client.request.send({
|
|
@@ -859,6 +1347,7 @@ class AccountRepository extends Repository {
|
|
|
859
1347
|
return response.body;
|
|
860
1348
|
});
|
|
861
1349
|
}
|
|
1350
|
+
|
|
862
1351
|
async sendConfirmPhoneNumber(phoneNumber) {
|
|
863
1352
|
return this.requestWithRetry(async () => {
|
|
864
1353
|
const response = await this.client.request.send({
|
|
@@ -872,6 +1361,7 @@ class AccountRepository extends Repository {
|
|
|
872
1361
|
return response.body;
|
|
873
1362
|
});
|
|
874
1363
|
}
|
|
1364
|
+
|
|
875
1365
|
async profilePictureChange(uploadId) {
|
|
876
1366
|
return this.requestWithRetry(async () => {
|
|
877
1367
|
const response = await this.client.request.send({
|
|
@@ -886,6 +1376,7 @@ class AccountRepository extends Repository {
|
|
|
886
1376
|
return response.body;
|
|
887
1377
|
});
|
|
888
1378
|
}
|
|
1379
|
+
|
|
889
1380
|
async profilePictureRemove() {
|
|
890
1381
|
return this.requestWithRetry(async () => {
|
|
891
1382
|
const response = await this.client.request.send({
|
|
@@ -898,6 +1389,7 @@ class AccountRepository extends Repository {
|
|
|
898
1389
|
return response.body;
|
|
899
1390
|
});
|
|
900
1391
|
}
|
|
1392
|
+
|
|
901
1393
|
async newsInbox() {
|
|
902
1394
|
return this.requestWithRetry(async () => {
|
|
903
1395
|
const response = await this.client.request.send({
|
|
@@ -907,6 +1399,7 @@ class AccountRepository extends Repository {
|
|
|
907
1399
|
return response.body;
|
|
908
1400
|
});
|
|
909
1401
|
}
|
|
1402
|
+
|
|
910
1403
|
async syncLoginExperiments() {
|
|
911
1404
|
return this.requestWithRetry(async () => {
|
|
912
1405
|
const response = await this.client.request.send({
|
|
@@ -915,20 +1408,23 @@ class AccountRepository extends Repository {
|
|
|
915
1408
|
form: this.client.request.sign({
|
|
916
1409
|
id: this.client.state.uuid,
|
|
917
1410
|
server_config_retrieval: '1',
|
|
918
|
-
experiments:
|
|
1411
|
+
experiments:
|
|
1412
|
+
this.client.state.constants
|
|
1413
|
+
.LOGIN_EXPERIMENTS,
|
|
919
1414
|
}),
|
|
920
1415
|
});
|
|
921
1416
|
return response.body;
|
|
922
1417
|
});
|
|
923
1418
|
}
|
|
1419
|
+
|
|
924
1420
|
async syncPostLoginExperiments() {
|
|
925
1421
|
let userId;
|
|
926
1422
|
try {
|
|
927
1423
|
userId = this.client.state.cookieUserId;
|
|
928
|
-
}
|
|
929
|
-
catch {
|
|
1424
|
+
} catch {
|
|
930
1425
|
userId = '0';
|
|
931
1426
|
}
|
|
1427
|
+
|
|
932
1428
|
return this.requestWithRetry(async () => {
|
|
933
1429
|
const response = await this.client.request.send({
|
|
934
1430
|
method: 'POST',
|
|
@@ -938,12 +1434,14 @@ class AccountRepository extends Repository {
|
|
|
938
1434
|
_uid: userId,
|
|
939
1435
|
_uuid: this.client.state.uuid,
|
|
940
1436
|
server_config_retrieval: '1',
|
|
941
|
-
experiments:
|
|
1437
|
+
experiments:
|
|
1438
|
+
this.client.state.constants.EXPERIMENTS,
|
|
942
1439
|
}),
|
|
943
1440
|
});
|
|
944
1441
|
return response.body;
|
|
945
1442
|
});
|
|
946
1443
|
}
|
|
1444
|
+
|
|
947
1445
|
async syncLauncher(preLogin = true) {
|
|
948
1446
|
const data = {
|
|
949
1447
|
id: this.client.state.uuid,
|
|
@@ -953,9 +1451,9 @@ class AccountRepository extends Repository {
|
|
|
953
1451
|
try {
|
|
954
1452
|
data._uid = this.client.state.cookieUserId;
|
|
955
1453
|
data._uuid = this.client.state.uuid;
|
|
956
|
-
}
|
|
957
|
-
catch { }
|
|
1454
|
+
} catch {}
|
|
958
1455
|
}
|
|
1456
|
+
|
|
959
1457
|
return this.requestWithRetry(async () => {
|
|
960
1458
|
const response = await this.client.request.send({
|
|
961
1459
|
method: 'POST',
|
|
@@ -965,6 +1463,7 @@ class AccountRepository extends Repository {
|
|
|
965
1463
|
return response.body;
|
|
966
1464
|
});
|
|
967
1465
|
}
|
|
1466
|
+
|
|
968
1467
|
async syncDeviceFeatures() {
|
|
969
1468
|
return this.requestWithRetry(async () => {
|
|
970
1469
|
const response = await this.client.request.send({
|
|
@@ -978,6 +1477,7 @@ class AccountRepository extends Repository {
|
|
|
978
1477
|
return response.body;
|
|
979
1478
|
});
|
|
980
1479
|
}
|
|
1480
|
+
|
|
981
1481
|
async getPrefillCandidates() {
|
|
982
1482
|
return this.requestWithRetry(async () => {
|
|
983
1483
|
const response = await this.client.request.send({
|
|
@@ -993,6 +1493,7 @@ class AccountRepository extends Repository {
|
|
|
993
1493
|
return response.body;
|
|
994
1494
|
});
|
|
995
1495
|
}
|
|
1496
|
+
|
|
996
1497
|
async contactPointPrefill(usage = 'prefill') {
|
|
997
1498
|
return this.requestWithRetry(async () => {
|
|
998
1499
|
const response = await this.client.request.send({
|
|
@@ -1006,6 +1507,7 @@ class AccountRepository extends Repository {
|
|
|
1006
1507
|
return response.body;
|
|
1007
1508
|
});
|
|
1008
1509
|
}
|
|
1510
|
+
|
|
1009
1511
|
async getZrToken(params = {}) {
|
|
1010
1512
|
return this.requestWithRetry(async () => {
|
|
1011
1513
|
const response = await this.client.request.send({
|
|
@@ -1022,6 +1524,7 @@ class AccountRepository extends Repository {
|
|
|
1022
1524
|
return response.body;
|
|
1023
1525
|
});
|
|
1024
1526
|
}
|
|
1527
|
+
|
|
1025
1528
|
async getConsentSignupConfig() {
|
|
1026
1529
|
return this.requestWithRetry(async () => {
|
|
1027
1530
|
const response = await this.client.request.send({
|
|
@@ -1035,6 +1538,7 @@ class AccountRepository extends Repository {
|
|
|
1035
1538
|
return response.body;
|
|
1036
1539
|
});
|
|
1037
1540
|
}
|
|
1541
|
+
|
|
1038
1542
|
async sendRecoveryFlowEmail(query) {
|
|
1039
1543
|
return this.requestWithRetry(async () => {
|
|
1040
1544
|
const response = await this.client.request.send({
|
|
@@ -1050,6 +1554,7 @@ class AccountRepository extends Repository {
|
|
|
1050
1554
|
return response.body;
|
|
1051
1555
|
});
|
|
1052
1556
|
}
|
|
1557
|
+
|
|
1053
1558
|
async sendRecoveryFlowSms(query) {
|
|
1054
1559
|
return this.requestWithRetry(async () => {
|
|
1055
1560
|
const response = await this.client.request.send({
|
|
@@ -1065,6 +1570,7 @@ class AccountRepository extends Repository {
|
|
|
1065
1570
|
return response.body;
|
|
1066
1571
|
});
|
|
1067
1572
|
}
|
|
1573
|
+
|
|
1068
1574
|
static generateAttestParams(state) {
|
|
1069
1575
|
// Emulate Instagram's keystore attestation as closely as possible:
|
|
1070
1576
|
// - version: 2
|
|
@@ -1077,28 +1583,65 @@ class AccountRepository extends Repository {
|
|
|
1077
1583
|
//
|
|
1078
1584
|
// NOTE: This is *not* a real hardware-backed attestation chain, but it mirrors
|
|
1079
1585
|
// the structure of the official app very closely so the server sees the same
|
|
1080
|
-
// shape: a single attestation object with a 4
|
|
1081
|
-
const challengeNonce = crypto
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
}
|
|
1586
|
+
// shape: a single attestation object with a 4-certificate chain.
|
|
1587
|
+
const challengeNonce = crypto
|
|
1588
|
+
.randomBytes(24)
|
|
1589
|
+
.toString('base64url');
|
|
1590
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync(
|
|
1591
|
+
'ec',
|
|
1592
|
+
{
|
|
1593
|
+
namedCurve: 'prime256v1',
|
|
1594
|
+
}
|
|
1595
|
+
);
|
|
1085
1596
|
// Sign the challenge nonce with the private key (simulating TEE signing).
|
|
1086
|
-
const signedData = crypto.sign(
|
|
1597
|
+
const signedData = crypto.sign(
|
|
1598
|
+
null,
|
|
1599
|
+
Buffer.from(challengeNonce),
|
|
1600
|
+
privateKey
|
|
1601
|
+
);
|
|
1087
1602
|
const signedNonce = signedData.toString('base64');
|
|
1088
|
-
const publicKeyDer = publicKey.export({
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
const
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1603
|
+
const publicKeyDer = publicKey.export({
|
|
1604
|
+
type: 'spki',
|
|
1605
|
+
format: 'der',
|
|
1606
|
+
});
|
|
1607
|
+
const keyHash = crypto
|
|
1608
|
+
.createHash('sha256')
|
|
1609
|
+
.update(publicKeyDer)
|
|
1610
|
+
.digest('hex');
|
|
1611
|
+
|
|
1612
|
+
// Build a 4-certificate chain (leaf + 2 intermediates + root)
|
|
1613
|
+
const leafCertPem =
|
|
1614
|
+
AccountRepository._generateSelfSignedCert(
|
|
1615
|
+
privateKey,
|
|
1616
|
+
publicKey,
|
|
1617
|
+
'Android Keystore Key'
|
|
1618
|
+
);
|
|
1619
|
+
const intermediate1Pem =
|
|
1620
|
+
AccountRepository._generateSelfSignedCert(
|
|
1621
|
+
privateKey,
|
|
1622
|
+
publicKey,
|
|
1623
|
+
'Android Keystore Key Attestation'
|
|
1624
|
+
);
|
|
1625
|
+
const intermediate2Pem =
|
|
1626
|
+
AccountRepository._generateSelfSignedCert(
|
|
1627
|
+
privateKey,
|
|
1628
|
+
publicKey,
|
|
1629
|
+
'Android Hardware Keystore'
|
|
1630
|
+
);
|
|
1631
|
+
const rootCertPem =
|
|
1632
|
+
AccountRepository._generateSelfSignedCert(
|
|
1633
|
+
privateKey,
|
|
1634
|
+
publicKey,
|
|
1635
|
+
'Android Keystore Root'
|
|
1636
|
+
);
|
|
1637
|
+
|
|
1096
1638
|
const certificateChain = [
|
|
1097
1639
|
leafCertPem,
|
|
1098
1640
|
intermediate1Pem,
|
|
1099
1641
|
intermediate2Pem,
|
|
1100
1642
|
rootCertPem,
|
|
1101
1643
|
].join('\n');
|
|
1644
|
+
|
|
1102
1645
|
return {
|
|
1103
1646
|
attestation: [
|
|
1104
1647
|
{
|
|
@@ -1113,12 +1656,20 @@ class AccountRepository extends Repository {
|
|
|
1113
1656
|
],
|
|
1114
1657
|
};
|
|
1115
1658
|
}
|
|
1659
|
+
|
|
1116
1660
|
static _generateSelfSignedCert(privateKey, publicKey, cn) {
|
|
1117
|
-
const publicKeyDer = publicKey.export({
|
|
1661
|
+
const publicKeyDer = publicKey.export({
|
|
1662
|
+
type: 'spki',
|
|
1663
|
+
format: 'der',
|
|
1664
|
+
});
|
|
1118
1665
|
const serialNumber = crypto.randomBytes(8);
|
|
1119
1666
|
const now = new Date();
|
|
1120
|
-
const notBefore = new Date(
|
|
1121
|
-
|
|
1667
|
+
const notBefore = new Date(
|
|
1668
|
+
now.getTime() - 365 * 24 * 60 * 60 * 1000
|
|
1669
|
+
);
|
|
1670
|
+
const notAfter = new Date(
|
|
1671
|
+
now.getTime() + 10 * 365 * 24 * 60 * 60 * 1000
|
|
1672
|
+
);
|
|
1122
1673
|
const cnBytes = Buffer.from(cn, 'utf8');
|
|
1123
1674
|
const cnSeq = Buffer.concat([
|
|
1124
1675
|
Buffer.from([0x30, cnBytes.length + 13]),
|
|
@@ -1128,39 +1679,75 @@ class AccountRepository extends Repository {
|
|
|
1128
1679
|
Buffer.from([0x0c, cnBytes.length]),
|
|
1129
1680
|
cnBytes,
|
|
1130
1681
|
]);
|
|
1682
|
+
|
|
1131
1683
|
function encodeTime(date) {
|
|
1132
|
-
const str =
|
|
1133
|
-
|
|
1684
|
+
const str =
|
|
1685
|
+
date
|
|
1686
|
+
.toISOString()
|
|
1687
|
+
.replace(/[-:T]/g, '')
|
|
1688
|
+
.slice(2, 14) + 'Z';
|
|
1689
|
+
return Buffer.concat([
|
|
1690
|
+
Buffer.from([0x17, str.length]),
|
|
1691
|
+
Buffer.from(str),
|
|
1692
|
+
]);
|
|
1134
1693
|
}
|
|
1694
|
+
|
|
1135
1695
|
const validityBuf = Buffer.concat([
|
|
1136
1696
|
encodeTime(notBefore),
|
|
1137
1697
|
encodeTime(notAfter),
|
|
1138
1698
|
]);
|
|
1139
|
-
const validity = Buffer.concat([
|
|
1699
|
+
const validity = Buffer.concat([
|
|
1700
|
+
Buffer.from([0x30, validityBuf.length]),
|
|
1701
|
+
validityBuf,
|
|
1702
|
+
]);
|
|
1703
|
+
|
|
1140
1704
|
const tbs = Buffer.concat([
|
|
1141
1705
|
Buffer.from([0xa0, 0x03, 0x02, 0x01, 0x02]),
|
|
1142
|
-
Buffer.from([0x02, serialNumber.length]),
|
|
1143
|
-
|
|
1706
|
+
Buffer.from([0x02, serialNumber.length]),
|
|
1707
|
+
serialNumber,
|
|
1708
|
+
Buffer.from([
|
|
1709
|
+
0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d,
|
|
1710
|
+
0x04, 0x03, 0x02,
|
|
1711
|
+
]),
|
|
1144
1712
|
cnSeq,
|
|
1145
1713
|
validity,
|
|
1146
1714
|
cnSeq,
|
|
1147
1715
|
publicKeyDer,
|
|
1148
1716
|
]);
|
|
1149
|
-
|
|
1717
|
+
|
|
1718
|
+
const tbsSeq = Buffer.concat([
|
|
1719
|
+
Buffer.from([0x30, 0x82]),
|
|
1720
|
+
Buffer.alloc(2),
|
|
1721
|
+
tbs,
|
|
1722
|
+
]);
|
|
1150
1723
|
tbsSeq.writeUInt16BE(tbs.length, 2);
|
|
1724
|
+
|
|
1151
1725
|
const signature = crypto.sign(null, tbsSeq, privateKey);
|
|
1152
1726
|
const sigBitString = Buffer.concat([
|
|
1153
1727
|
Buffer.from([0x03, signature.length + 1, 0x00]),
|
|
1154
1728
|
signature,
|
|
1155
1729
|
]);
|
|
1156
|
-
const algId = Buffer.from([
|
|
1730
|
+
const algId = Buffer.from([
|
|
1731
|
+
0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d,
|
|
1732
|
+
0x04, 0x03, 0x02,
|
|
1733
|
+
]);
|
|
1157
1734
|
const certBody = Buffer.concat([tbsSeq, algId, sigBitString]);
|
|
1158
|
-
const cert = Buffer.concat([
|
|
1735
|
+
const cert = Buffer.concat([
|
|
1736
|
+
Buffer.from([0x30, 0x82]),
|
|
1737
|
+
Buffer.alloc(2),
|
|
1738
|
+
certBody,
|
|
1739
|
+
]);
|
|
1159
1740
|
cert.writeUInt16BE(certBody.length, 2);
|
|
1741
|
+
|
|
1160
1742
|
const b64 = cert.toString('base64');
|
|
1161
1743
|
const lines = b64.match(/.{1,76}/g) || [b64];
|
|
1162
|
-
return
|
|
1744
|
+
return (
|
|
1745
|
+
'-----BEGIN CERTIFICATE-----\n' +
|
|
1746
|
+
lines.join('\n') +
|
|
1747
|
+
'\n-----END CERTIFICATE-----'
|
|
1748
|
+
);
|
|
1163
1749
|
}
|
|
1750
|
+
|
|
1164
1751
|
static createJazoest(input) {
|
|
1165
1752
|
const buf = Buffer.from(input || '', 'ascii');
|
|
1166
1753
|
let sum = 0;
|
|
@@ -1169,45 +1756,70 @@ class AccountRepository extends Repository {
|
|
|
1169
1756
|
}
|
|
1170
1757
|
return `2${sum}`;
|
|
1171
1758
|
}
|
|
1759
|
+
|
|
1172
1760
|
encryptPassword(password) {
|
|
1173
1761
|
if (!this.client.state.passwordEncryptionPubKey) {
|
|
1174
|
-
return {
|
|
1762
|
+
return {
|
|
1763
|
+
time: Math.floor(Date.now() / 1000).toString(),
|
|
1764
|
+
encrypted: password,
|
|
1765
|
+
};
|
|
1175
1766
|
}
|
|
1176
1767
|
const randKey = crypto.randomBytes(32);
|
|
1177
1768
|
const iv = crypto.randomBytes(12);
|
|
1178
|
-
const rsaEncrypted = crypto.publicEncrypt(
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1769
|
+
const rsaEncrypted = crypto.publicEncrypt(
|
|
1770
|
+
{
|
|
1771
|
+
key: Buffer.from(
|
|
1772
|
+
this.client.state.passwordEncryptionPubKey,
|
|
1773
|
+
'base64'
|
|
1774
|
+
).toString(),
|
|
1775
|
+
padding: crypto.constants.RSA_PKCS1_PADDING,
|
|
1776
|
+
},
|
|
1777
|
+
randKey
|
|
1778
|
+
);
|
|
1779
|
+
const cipher = crypto.createCipheriv(
|
|
1780
|
+
'aes-256-gcm',
|
|
1781
|
+
randKey,
|
|
1782
|
+
iv
|
|
1783
|
+
);
|
|
1183
1784
|
const time = Math.floor(Date.now() / 1000).toString();
|
|
1184
1785
|
cipher.setAAD(Buffer.from(time));
|
|
1185
|
-
const aesEncrypted = Buffer.concat([
|
|
1786
|
+
const aesEncrypted = Buffer.concat([
|
|
1787
|
+
cipher.update(password, 'utf8'),
|
|
1788
|
+
cipher.final(),
|
|
1789
|
+
]);
|
|
1186
1790
|
const sizeBuffer = Buffer.alloc(2, 0);
|
|
1187
1791
|
sizeBuffer.writeInt16LE(rsaEncrypted.byteLength, 0);
|
|
1188
1792
|
const authTag = cipher.getAuthTag();
|
|
1189
1793
|
return {
|
|
1190
1794
|
time,
|
|
1191
1795
|
encrypted: Buffer.concat([
|
|
1192
|
-
Buffer.from([
|
|
1796
|
+
Buffer.from([
|
|
1797
|
+
1,
|
|
1798
|
+
this.client.state.passwordEncryptionKeyId || 0,
|
|
1799
|
+
]),
|
|
1193
1800
|
iv,
|
|
1194
1801
|
sizeBuffer,
|
|
1195
1802
|
rsaEncrypted,
|
|
1196
1803
|
authTag,
|
|
1197
|
-
aesEncrypted
|
|
1198
|
-
]).toString('base64')
|
|
1804
|
+
aesEncrypted,
|
|
1805
|
+
]).toString('base64'),
|
|
1199
1806
|
};
|
|
1200
1807
|
}
|
|
1808
|
+
|
|
1201
1809
|
async passwordPublicKeys() {
|
|
1202
1810
|
const response = await this.client.request.send({
|
|
1203
1811
|
method: 'GET',
|
|
1204
1812
|
url: '/api/v1/qe/sync/',
|
|
1205
1813
|
});
|
|
1206
1814
|
const headers = response.headers || {};
|
|
1207
|
-
const keyId = parseInt(
|
|
1208
|
-
|
|
1815
|
+
const keyId = parseInt(
|
|
1816
|
+
headers['ig-set-password-encryption-key-id'] || '0'
|
|
1817
|
+
);
|
|
1818
|
+
const pubKey =
|
|
1819
|
+
headers['ig-set-password-encryption-pub-key'] || '';
|
|
1209
1820
|
return { keyId, pubKey };
|
|
1210
1821
|
}
|
|
1822
|
+
|
|
1211
1823
|
async setPresenceDisabled(disabled = true) {
|
|
1212
1824
|
return this.requestWithRetry(async () => {
|
|
1213
1825
|
const response = await this.client.request.send({
|
|
@@ -1221,6 +1833,7 @@ class AccountRepository extends Repository {
|
|
|
1221
1833
|
return response.body;
|
|
1222
1834
|
});
|
|
1223
1835
|
}
|
|
1836
|
+
|
|
1224
1837
|
async getCommentFilter() {
|
|
1225
1838
|
return this.requestWithRetry(async () => {
|
|
1226
1839
|
const response = await this.client.request.send({
|
|
@@ -1230,6 +1843,7 @@ class AccountRepository extends Repository {
|
|
|
1230
1843
|
return response.body;
|
|
1231
1844
|
});
|
|
1232
1845
|
}
|
|
1846
|
+
|
|
1233
1847
|
async setCommentFilter(configValue = 0) {
|
|
1234
1848
|
return this.requestWithRetry(async () => {
|
|
1235
1849
|
const response = await this.client.request.send({
|
|
@@ -1243,6 +1857,7 @@ class AccountRepository extends Repository {
|
|
|
1243
1857
|
return response.body;
|
|
1244
1858
|
});
|
|
1245
1859
|
}
|
|
1860
|
+
|
|
1246
1861
|
async pushPreferences(preferences = 'default') {
|
|
1247
1862
|
return this.requestWithRetry(async () => {
|
|
1248
1863
|
const response = await this.client.request.send({
|
|
@@ -1262,4 +1877,5 @@ class AccountRepository extends Repository {
|
|
|
1262
1877
|
});
|
|
1263
1878
|
}
|
|
1264
1879
|
}
|
|
1880
|
+
|
|
1265
1881
|
module.exports = AccountRepository;
|