nexus-fca 2.0.2 โ 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/index.js +82 -68
- package/nexloginsystem/NexusLoginSystem.js +611 -0
- package/nexloginsystem/README.md +510 -0
- package/nexloginsystem/examples.js +150 -0
- package/nexloginsystem/index.js +247 -0
- package/package.json +2 -1
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nexus Login System - Advanced & Safe Facebook Login
|
|
3
|
+
* Automatic appstate generation and management
|
|
4
|
+
* Built for Nexus-FCA with maximum account safety
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { v4: uuidv4 } = require('uuid');
|
|
10
|
+
const { TOTP } = require("totp-generator");
|
|
11
|
+
const axios = require("axios");
|
|
12
|
+
const crypto = require('crypto');
|
|
13
|
+
|
|
14
|
+
class NexusLoginSystem {
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.options = {
|
|
17
|
+
appstatePath: options.appstatePath || path.join(__dirname, 'appstate.json'),
|
|
18
|
+
credentialsPath: options.credentialsPath || path.join(__dirname, 'credentials.json'),
|
|
19
|
+
backupPath: options.backupPath || path.join(__dirname, 'backups'),
|
|
20
|
+
autoLogin: options.autoLogin !== false, // Default true
|
|
21
|
+
autoSave: options.autoSave !== false, // Default true
|
|
22
|
+
safeMode: options.safeMode !== false, // Default true
|
|
23
|
+
maxRetries: options.maxRetries || 3,
|
|
24
|
+
retryDelay: options.retryDelay || 5000,
|
|
25
|
+
...options
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
this.deviceCache = new Map();
|
|
29
|
+
this.loginAttempts = 0;
|
|
30
|
+
this.lastLoginTime = 0;
|
|
31
|
+
|
|
32
|
+
// Create directories
|
|
33
|
+
this.ensureDirectories();
|
|
34
|
+
|
|
35
|
+
this.logger('Nexus Login System initialized', '๐');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
logger(message, emoji = '๐') {
|
|
39
|
+
const timestamp = new Date().toLocaleString();
|
|
40
|
+
console.log(`${emoji} [${timestamp}] ${message}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
ensureDirectories() {
|
|
44
|
+
const dirs = [
|
|
45
|
+
path.dirname(this.options.appstatePath),
|
|
46
|
+
path.dirname(this.options.credentialsPath),
|
|
47
|
+
this.options.backupPath
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
dirs.forEach(dir => {
|
|
51
|
+
if (!fs.existsSync(dir)) {
|
|
52
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Enhanced device simulation with better fingerprinting
|
|
58
|
+
getRandomDevice() {
|
|
59
|
+
const devices = [
|
|
60
|
+
{ model: "Pixel 6", build: "SP2A.220505.002", sdk: "30", release: "11" },
|
|
61
|
+
{ model: "Pixel 5", build: "RQ3A.210805.001.A1", sdk: "30", release: "11" },
|
|
62
|
+
{ model: "Samsung Galaxy S21", build: "G991USQU4AUDA", sdk: "30", release: "11" },
|
|
63
|
+
{ model: "OnePlus 9", build: "LE2115_11_C.48", sdk: "30", release: "11" },
|
|
64
|
+
{ model: "Xiaomi Mi 11", build: "RKQ1.200826.002", sdk: "30", release: "11" },
|
|
65
|
+
{ model: "Pixel 7", build: "TD1A.220804.031", sdk: "33", release: "13" },
|
|
66
|
+
{ model: "Samsung Galaxy S22", build: "S901USQU2AVB3", sdk: "32", release: "12" }
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const device = devices[Math.floor(Math.random() * devices.length)];
|
|
70
|
+
const deviceId = this.generateConsistentDeviceId(device);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
userAgent: `Dalvik/2.1.0 (Linux; U; Android ${device.release}; ${device.model} Build/${device.build})`,
|
|
74
|
+
device,
|
|
75
|
+
deviceId,
|
|
76
|
+
familyDeviceId: uuidv4(),
|
|
77
|
+
androidId: this.generateAndroidId()
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
generateConsistentDeviceId(device) {
|
|
82
|
+
const key = `${device.model}_${device.build}`;
|
|
83
|
+
if (this.deviceCache.has(key)) {
|
|
84
|
+
return this.deviceCache.get(key);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const deviceId = uuidv4();
|
|
88
|
+
this.deviceCache.set(key, deviceId);
|
|
89
|
+
return deviceId;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
generateAndroidId() {
|
|
93
|
+
return crypto.randomBytes(8).toString('hex');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
randomString(length = 10) {
|
|
97
|
+
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
98
|
+
let result = 'abcdefghijklmnopqrstuvwxyz'.charAt(Math.floor(Math.random() * 26));
|
|
99
|
+
for (let i = 0; i < length - 1; i++) {
|
|
100
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
encodesig(object) {
|
|
106
|
+
let data = '';
|
|
107
|
+
Object.keys(object).forEach(key => {
|
|
108
|
+
data += `${key}=${object[key]}`;
|
|
109
|
+
});
|
|
110
|
+
return crypto.createHash('md5').update(data + '62f8ce9f74b12f84c123cc23437a4a32').digest('hex');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
sort(object) {
|
|
114
|
+
const sortedKeys = Object.keys(object).sort();
|
|
115
|
+
let sortedObject = {};
|
|
116
|
+
for (const key of sortedKeys) {
|
|
117
|
+
sortedObject[key] = object[key];
|
|
118
|
+
}
|
|
119
|
+
return sortedObject;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Safe delay between requests
|
|
123
|
+
async safeDelay(min = 1000, max = 3000) {
|
|
124
|
+
if (!this.options.safeMode) return;
|
|
125
|
+
|
|
126
|
+
const delay = Math.floor(Math.random() * (max - min + 1)) + min;
|
|
127
|
+
this.logger(`Safety delay: ${delay}ms`, 'โณ');
|
|
128
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check if appstate exists and is valid
|
|
132
|
+
hasValidAppstate() {
|
|
133
|
+
try {
|
|
134
|
+
if (!fs.existsSync(this.options.appstatePath)) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const appstate = JSON.parse(fs.readFileSync(this.options.appstatePath, 'utf8'));
|
|
139
|
+
|
|
140
|
+
if (!Array.isArray(appstate) || appstate.length === 0) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Check if cookies are not too old
|
|
145
|
+
const now = Date.now();
|
|
146
|
+
const validCookies = appstate.filter(cookie => {
|
|
147
|
+
if (!cookie.expires && !cookie.expirationDate) return true;
|
|
148
|
+
|
|
149
|
+
const expires = new Date(cookie.expires || cookie.expirationDate).getTime();
|
|
150
|
+
return expires > now;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return validCookies.length > appstate.length * 0.7; // 70% valid cookies required
|
|
154
|
+
} catch (error) {
|
|
155
|
+
this.logger(`Appstate validation failed: ${error.message}`, 'โ');
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Load existing appstate
|
|
161
|
+
loadAppstate() {
|
|
162
|
+
try {
|
|
163
|
+
if (!fs.existsSync(this.options.appstatePath)) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const appstate = JSON.parse(fs.readFileSync(this.options.appstatePath, 'utf8'));
|
|
168
|
+
this.logger(`Loaded appstate with ${appstate.length} cookies`, 'โ
');
|
|
169
|
+
return appstate;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
this.logger(`Failed to load appstate: ${error.message}`, 'โ');
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Save appstate with backup
|
|
177
|
+
saveAppstate(appstate, metadata = {}) {
|
|
178
|
+
try {
|
|
179
|
+
if (!this.options.autoSave) return;
|
|
180
|
+
|
|
181
|
+
// Create backup first
|
|
182
|
+
if (fs.existsSync(this.options.appstatePath)) {
|
|
183
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
184
|
+
const backupFile = path.join(this.options.backupPath, `appstate-backup-${timestamp}.json`);
|
|
185
|
+
fs.copyFileSync(this.options.appstatePath, backupFile);
|
|
186
|
+
this.logger(`Backup created: ${backupFile}`, '๐ฆ');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Save new appstate
|
|
190
|
+
const dataToSave = {
|
|
191
|
+
appstate,
|
|
192
|
+
metadata: {
|
|
193
|
+
generated: new Date().toISOString(),
|
|
194
|
+
deviceInfo: metadata.deviceInfo || {},
|
|
195
|
+
...metadata
|
|
196
|
+
},
|
|
197
|
+
version: '1.0'
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
fs.writeFileSync(this.options.appstatePath, JSON.stringify(appstate, null, 2));
|
|
201
|
+
fs.writeFileSync(
|
|
202
|
+
this.options.appstatePath.replace('.json', '-full.json'),
|
|
203
|
+
JSON.stringify(dataToSave, null, 2)
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
this.logger(`Appstate saved with ${appstate.length} cookies`, '๐พ');
|
|
207
|
+
return true;
|
|
208
|
+
} catch (error) {
|
|
209
|
+
this.logger(`Failed to save appstate: ${error.message}`, 'โ');
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Enhanced login with better error handling
|
|
215
|
+
async generateAppstate(credentials) {
|
|
216
|
+
try {
|
|
217
|
+
if (this.options.safeMode) {
|
|
218
|
+
// Rate limiting check
|
|
219
|
+
const timeSinceLastLogin = Date.now() - this.lastLoginTime;
|
|
220
|
+
if (timeSinceLastLogin < 30000) { // 30 seconds minimum
|
|
221
|
+
this.logger('Rate limiting: Please wait before next login attempt', 'โ ๏ธ');
|
|
222
|
+
await new Promise(resolve => setTimeout(resolve, 30000 - timeSinceLastLogin));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
this.lastLoginTime = Date.now();
|
|
227
|
+
this.loginAttempts++;
|
|
228
|
+
|
|
229
|
+
const androidDevice = this.getRandomDevice();
|
|
230
|
+
const machineId = this.randomString(24);
|
|
231
|
+
|
|
232
|
+
await this.safeDelay(1000, 2000);
|
|
233
|
+
|
|
234
|
+
const form = {
|
|
235
|
+
adid: uuidv4(),
|
|
236
|
+
email: credentials.username,
|
|
237
|
+
password: credentials.password,
|
|
238
|
+
format: 'json',
|
|
239
|
+
device_id: androidDevice.deviceId,
|
|
240
|
+
cpl: 'true',
|
|
241
|
+
family_device_id: androidDevice.familyDeviceId,
|
|
242
|
+
locale: 'en_US',
|
|
243
|
+
client_country_code: 'US',
|
|
244
|
+
credentials_type: 'device_based_login_password',
|
|
245
|
+
generate_session_cookies: '1',
|
|
246
|
+
generate_analytics_claim: '1',
|
|
247
|
+
generate_machine_id: '1',
|
|
248
|
+
currently_logged_in_userid: '0',
|
|
249
|
+
irisSeqID: 1,
|
|
250
|
+
try_num: "1",
|
|
251
|
+
enroll_misauth: "false",
|
|
252
|
+
meta_inf_fbmeta: "NO_FILE",
|
|
253
|
+
source: 'login',
|
|
254
|
+
machine_id: machineId,
|
|
255
|
+
fb_api_req_friendly_name: 'authenticate',
|
|
256
|
+
fb_api_caller_class: 'com.facebook.account.login.protocol.Fb4aAuthHandler',
|
|
257
|
+
api_key: '882a8490361da98702bf97a021ddc14d',
|
|
258
|
+
access_token: '350685531728|62f8ce9f74b12f84c123cc23437a4a32',
|
|
259
|
+
advertiser_id: uuidv4(),
|
|
260
|
+
device_platform: 'android',
|
|
261
|
+
app_version: '392.0.0.0.66',
|
|
262
|
+
network_type: 'WIFI'
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
form.sig = this.encodesig(this.sort(form));
|
|
266
|
+
|
|
267
|
+
const options = {
|
|
268
|
+
url: 'https://b-graph.facebook.com/auth/login',
|
|
269
|
+
method: 'post',
|
|
270
|
+
data: form,
|
|
271
|
+
transformRequest: [(data) => require('querystring').stringify(data)],
|
|
272
|
+
headers: {
|
|
273
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
274
|
+
'x-fb-friendly-name': form["fb_api_req_friendly_name"],
|
|
275
|
+
'x-fb-http-engine': 'Liger',
|
|
276
|
+
'user-agent': androidDevice.userAgent,
|
|
277
|
+
'x-fb-connection-type': 'WIFI',
|
|
278
|
+
'x-fb-net-hni': '',
|
|
279
|
+
'x-fb-sim-hni': '',
|
|
280
|
+
'x-fb-device-group': '5120',
|
|
281
|
+
'x-tigon-is-retry': 'False',
|
|
282
|
+
'x-fb-rmd': 'cached=0;state=NO_MATCH',
|
|
283
|
+
'x-fb-request-analytics-tags': 'unknown',
|
|
284
|
+
'authorization': `OAuth ${form.access_token}`,
|
|
285
|
+
'accept-language': 'en-US,en;q=0.9',
|
|
286
|
+
'x-fb-client-ip': 'True',
|
|
287
|
+
'x-fb-server-cluster': 'True'
|
|
288
|
+
},
|
|
289
|
+
timeout: 30000
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
this.logger('Attempting login with enhanced security...', '๐');
|
|
293
|
+
|
|
294
|
+
return new Promise((resolve) => {
|
|
295
|
+
axios.request(options).then(async (response) => {
|
|
296
|
+
try {
|
|
297
|
+
const appstate = response.data.session_cookies.map(cookie => ({
|
|
298
|
+
key: cookie.name,
|
|
299
|
+
value: cookie.value,
|
|
300
|
+
domain: cookie.domain,
|
|
301
|
+
path: cookie.path,
|
|
302
|
+
expires: cookie.expires,
|
|
303
|
+
httpOnly: cookie.httpOnly,
|
|
304
|
+
secure: cookie.secure
|
|
305
|
+
}));
|
|
306
|
+
|
|
307
|
+
if (credentials.i_user) {
|
|
308
|
+
appstate.push({
|
|
309
|
+
key: 'i_user',
|
|
310
|
+
value: credentials.i_user,
|
|
311
|
+
domain: '.facebook.com',
|
|
312
|
+
path: '/'
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
await this.safeDelay(500, 1500);
|
|
317
|
+
|
|
318
|
+
// Get additional token
|
|
319
|
+
const tokenOptions = {
|
|
320
|
+
url: `https://api.facebook.com/method/auth.getSessionforApp?format=json&access_token=${response.data.access_token}&new_app_id=275254692598279`,
|
|
321
|
+
method: 'get',
|
|
322
|
+
headers: {
|
|
323
|
+
'user-agent': androidDevice.userAgent,
|
|
324
|
+
'x-fb-connection-type': 'WIFI',
|
|
325
|
+
'authorization': `OAuth ${response.data.access_token}`
|
|
326
|
+
},
|
|
327
|
+
timeout: 15000
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
const tokenResponse = await axios.request(tokenOptions);
|
|
332
|
+
|
|
333
|
+
const result = {
|
|
334
|
+
success: true,
|
|
335
|
+
appstate: appstate,
|
|
336
|
+
access_token: response.data.access_token,
|
|
337
|
+
access_token_eaad6v7: tokenResponse.data.access_token,
|
|
338
|
+
device_info: {
|
|
339
|
+
model: androidDevice.device.model,
|
|
340
|
+
user_agent: androidDevice.userAgent,
|
|
341
|
+
device_id: androidDevice.deviceId
|
|
342
|
+
},
|
|
343
|
+
generated_at: new Date().toISOString()
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
this.saveAppstate(appstate, result);
|
|
347
|
+
this.logger('Login successful! Appstate generated and saved', '๐');
|
|
348
|
+
|
|
349
|
+
resolve(result);
|
|
350
|
+
} catch (tokenError) {
|
|
351
|
+
// Token fetch failed, but we still have appstate
|
|
352
|
+
const result = {
|
|
353
|
+
success: true,
|
|
354
|
+
appstate: appstate,
|
|
355
|
+
access_token: response.data.access_token,
|
|
356
|
+
device_info: {
|
|
357
|
+
model: androidDevice.device.model,
|
|
358
|
+
user_agent: androidDevice.userAgent
|
|
359
|
+
},
|
|
360
|
+
warning: 'Additional token generation failed',
|
|
361
|
+
generated_at: new Date().toISOString()
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
this.saveAppstate(appstate, result);
|
|
365
|
+
this.logger('Login successful! (Token generation had issues)', 'โ ๏ธ');
|
|
366
|
+
|
|
367
|
+
resolve(result);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
} catch (e) {
|
|
371
|
+
this.logger(`Login processing error: ${e.message}`, 'โ');
|
|
372
|
+
resolve({
|
|
373
|
+
success: false,
|
|
374
|
+
message: "Login processing failed. Please try again."
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}).catch(async (error) => {
|
|
378
|
+
// Handle 2FA requirement
|
|
379
|
+
try {
|
|
380
|
+
const errorData = error.response?.data?.error?.error_data;
|
|
381
|
+
if (!errorData) {
|
|
382
|
+
throw new Error('Unknown login error');
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
let twoFactorCode;
|
|
386
|
+
|
|
387
|
+
if (credentials._2fa && credentials._2fa !== "0") {
|
|
388
|
+
twoFactorCode = credentials._2fa;
|
|
389
|
+
} else if (credentials.twofactor && credentials.twofactor !== "0") {
|
|
390
|
+
try {
|
|
391
|
+
this.logger('Processing 2FA with TOTP...', '๐');
|
|
392
|
+
const cleanSecret = decodeURI(credentials.twofactor).replace(/\s+/g, '').toUpperCase();
|
|
393
|
+
const { otp } = TOTP.generate(cleanSecret);
|
|
394
|
+
twoFactorCode = otp;
|
|
395
|
+
this.logger(`Generated 2FA code: ${otp}`, '๐');
|
|
396
|
+
} catch (e) {
|
|
397
|
+
return resolve({
|
|
398
|
+
success: false,
|
|
399
|
+
message: 'Invalid 2FA secret key format'
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
} else {
|
|
403
|
+
return resolve({
|
|
404
|
+
success: false,
|
|
405
|
+
message: 'Two-factor authentication required. Please provide 2FA secret or code.'
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
await this.safeDelay(2000, 4000);
|
|
410
|
+
|
|
411
|
+
const twoFactorForm = {
|
|
412
|
+
...form,
|
|
413
|
+
twofactor_code: twoFactorCode,
|
|
414
|
+
encrypted_msisdn: "",
|
|
415
|
+
userid: errorData.uid,
|
|
416
|
+
machine_id: errorData.machine_id || machineId,
|
|
417
|
+
first_factor: errorData.login_first_factor,
|
|
418
|
+
credentials_type: "two_factor"
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
twoFactorForm.sig = this.encodesig(this.sort(twoFactorForm));
|
|
422
|
+
options.data = twoFactorForm;
|
|
423
|
+
|
|
424
|
+
this.logger('Attempting 2FA login...', '๐');
|
|
425
|
+
|
|
426
|
+
try {
|
|
427
|
+
const twoFactorResponse = await axios.request(options);
|
|
428
|
+
|
|
429
|
+
const appstate = twoFactorResponse.data.session_cookies.map(cookie => ({
|
|
430
|
+
key: cookie.name,
|
|
431
|
+
value: cookie.value,
|
|
432
|
+
domain: cookie.domain,
|
|
433
|
+
path: cookie.path,
|
|
434
|
+
expires: cookie.expires,
|
|
435
|
+
httpOnly: cookie.httpOnly,
|
|
436
|
+
secure: cookie.secure
|
|
437
|
+
}));
|
|
438
|
+
|
|
439
|
+
if (credentials.i_user) {
|
|
440
|
+
appstate.push({
|
|
441
|
+
key: 'i_user',
|
|
442
|
+
value: credentials.i_user,
|
|
443
|
+
domain: '.facebook.com',
|
|
444
|
+
path: '/'
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const result = {
|
|
449
|
+
success: true,
|
|
450
|
+
appstate: appstate,
|
|
451
|
+
access_token: twoFactorResponse.data.access_token,
|
|
452
|
+
device_info: {
|
|
453
|
+
model: androidDevice.device.model,
|
|
454
|
+
user_agent: androidDevice.userAgent
|
|
455
|
+
},
|
|
456
|
+
method: '2FA',
|
|
457
|
+
generated_at: new Date().toISOString()
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
this.saveAppstate(appstate, result);
|
|
461
|
+
this.logger('2FA login successful! Appstate saved', '๐');
|
|
462
|
+
|
|
463
|
+
resolve(result);
|
|
464
|
+
|
|
465
|
+
} catch (requestError) {
|
|
466
|
+
this.logger(`2FA request failed: ${requestError.message}`, 'โ');
|
|
467
|
+
resolve({
|
|
468
|
+
success: false,
|
|
469
|
+
message: '2FA verification failed. Check your code and try again.'
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
} catch (twoFactorError) {
|
|
474
|
+
this.logger(`2FA error: ${twoFactorError.message}`, 'โ');
|
|
475
|
+
resolve({
|
|
476
|
+
success: false,
|
|
477
|
+
message: 'Login failed. Check credentials and try again.'
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
} catch (e) {
|
|
484
|
+
this.logger(`Unexpected error: ${e.message}`, '๐ฅ');
|
|
485
|
+
return {
|
|
486
|
+
success: false,
|
|
487
|
+
message: 'Unexpected error occurred. Please try again.'
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Main login method with auto-detection
|
|
493
|
+
async login(credentials = null) {
|
|
494
|
+
try {
|
|
495
|
+
this.logger('Starting Nexus Login System...', '๐');
|
|
496
|
+
|
|
497
|
+
// Check for existing valid appstate first
|
|
498
|
+
if (this.options.autoLogin && this.hasValidAppstate()) {
|
|
499
|
+
this.logger('Valid appstate found, loading...', 'โ
');
|
|
500
|
+
const appstate = this.loadAppstate();
|
|
501
|
+
|
|
502
|
+
if (appstate) {
|
|
503
|
+
return {
|
|
504
|
+
success: true,
|
|
505
|
+
appstate: appstate,
|
|
506
|
+
method: 'existing_appstate',
|
|
507
|
+
message: 'Login successful using existing appstate'
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// No valid appstate, need credentials
|
|
513
|
+
if (!credentials) {
|
|
514
|
+
// Try to load from credentials file
|
|
515
|
+
if (fs.existsSync(this.options.credentialsPath)) {
|
|
516
|
+
try {
|
|
517
|
+
credentials = JSON.parse(fs.readFileSync(this.options.credentialsPath, 'utf8'));
|
|
518
|
+
this.logger('Credentials loaded from file', '๐');
|
|
519
|
+
} catch (error) {
|
|
520
|
+
this.logger('Failed to load credentials file', 'โ');
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (!credentials) {
|
|
525
|
+
return {
|
|
526
|
+
success: false,
|
|
527
|
+
message: 'No valid appstate found and no credentials provided'
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Validate credentials
|
|
533
|
+
if (!credentials.username || !credentials.password) {
|
|
534
|
+
return {
|
|
535
|
+
success: false,
|
|
536
|
+
message: 'Username and password are required'
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
this.logger('Generating new appstate...', '๐');
|
|
541
|
+
|
|
542
|
+
// Generate new appstate
|
|
543
|
+
const result = await this.generateAppstate(credentials);
|
|
544
|
+
|
|
545
|
+
if (result.success) {
|
|
546
|
+
// Save credentials for future use (optional)
|
|
547
|
+
if (this.options.autoSave && !fs.existsSync(this.options.credentialsPath)) {
|
|
548
|
+
try {
|
|
549
|
+
const credentialsToSave = { ...credentials };
|
|
550
|
+
delete credentialsToSave.password; // Don't save password for security
|
|
551
|
+
fs.writeFileSync(this.options.credentialsPath, JSON.stringify(credentialsToSave, null, 2));
|
|
552
|
+
} catch (error) {
|
|
553
|
+
this.logger('Failed to save credentials (non-critical)', 'โ ๏ธ');
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return result;
|
|
559
|
+
|
|
560
|
+
} catch (error) {
|
|
561
|
+
this.logger(`Login system error: ${error.message}`, '๐ฅ');
|
|
562
|
+
return {
|
|
563
|
+
success: false,
|
|
564
|
+
message: `System error: ${error.message}`
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Cleanup old backups
|
|
570
|
+
cleanupBackups(maxAge = 7 * 24 * 60 * 60 * 1000) { // 7 days
|
|
571
|
+
try {
|
|
572
|
+
if (!fs.existsSync(this.options.backupPath)) return;
|
|
573
|
+
|
|
574
|
+
const files = fs.readdirSync(this.options.backupPath);
|
|
575
|
+
const now = Date.now();
|
|
576
|
+
let cleaned = 0;
|
|
577
|
+
|
|
578
|
+
files.forEach(file => {
|
|
579
|
+
const filePath = path.join(this.options.backupPath, file);
|
|
580
|
+
const stats = fs.statSync(filePath);
|
|
581
|
+
|
|
582
|
+
if (now - stats.mtime.getTime() > maxAge) {
|
|
583
|
+
fs.unlinkSync(filePath);
|
|
584
|
+
cleaned++;
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
if (cleaned > 0) {
|
|
589
|
+
this.logger(`Cleaned up ${cleaned} old backup files`, '๐งน');
|
|
590
|
+
}
|
|
591
|
+
} catch (error) {
|
|
592
|
+
this.logger(`Backup cleanup failed: ${error.message}`, 'โ ๏ธ');
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Get login status
|
|
597
|
+
getStatus() {
|
|
598
|
+
const hasAppstate = this.hasValidAppstate();
|
|
599
|
+
const hasCredentials = fs.existsSync(this.options.credentialsPath);
|
|
600
|
+
|
|
601
|
+
return {
|
|
602
|
+
hasValidAppstate: hasAppstate,
|
|
603
|
+
hasCredentials: hasCredentials,
|
|
604
|
+
appstatePath: this.options.appstatePath,
|
|
605
|
+
lastLogin: hasAppstate ? fs.statSync(this.options.appstatePath).mtime : null,
|
|
606
|
+
loginAttempts: this.loginAttempts
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
module.exports = NexusLoginSystem;
|