discord.js-selfbot-v13/src/util/RemoteAuth.js

283 lines
9.3 KiB
JavaScript
Raw Normal View History

2022-05-03 05:29:10 +00:00
'use strict';
const { Buffer } = require('buffer');
const crypto = require('crypto');
2022-07-07 17:23:41 +00:00
const EventEmitter = require('node:events');
2022-05-03 05:29:10 +00:00
const { setInterval, clearInterval, setTimeout, clearTimeout } = require('node:timers');
const { StringDecoder } = require('string_decoder');
const { encode: urlsafe_b64encode } = require('safe-base64');
const WebSocket = require('ws');
const { randomUA } = require('./Constants');
var Messages = {
HEARTBEAT: 'heartbeat',
HEARTBEAT_ACK: 'heartbeat_ack',
HELLO: 'hello',
INIT: 'init',
NONCE_PROOF: 'nonce_proof',
PENDING_REMOTE_INIT: 'pending_remote_init',
PENDING_FINISH: 'pending_finish',
FINISH: 'finish',
CANCEL: 'cancel',
};
class DiscordUser_FromPayload {
constructor(payload, debug = false) {
let values = payload.split(':');
this.id = values[0];
this.username = values[3];
this.discriminator = values[1];
this.avatar = values[2];
this.tag = `${this.username}#${this.discriminator}`;
this.avatarURL = `https://cdn.discordapp.com/avatars/${this.id}/${this.avatar}.${
this.avatar.startsWith('a_') ? 'gif' : 'png'
}`;
2022-05-03 05:29:10 +00:00
this.debug = debug;
return this;
}
pretty_print() {
let out = '';
out += `User: ${this.tag} (${this.id})\n`;
out += `Avatar URL: ${this.avatarURL}\n`;
2022-05-03 05:29:10 +00:00
if (this.debug) out += `Token: ${this.token}\n`;
return out;
}
}
2022-07-07 17:23:41 +00:00
/**
2022-07-10 12:28:47 +00:00
* Discord Auth QR (Discord.RemoteAuth will be removed in the future, v13.9.0 release)
2022-07-07 17:23:41 +00:00
* @extends {EventEmitter}
* @abstract
*/
class DiscordAuthWebsocket extends EventEmitter {
/**
* Creates a new DiscordAuthWebsocket instance.
* @param {?Client} client Discord.Client (Login)
* @param {?boolean} debug Log debug info
* @param {?boolean} hideLog Hide log ?
2022-07-07 17:23:41 +00:00
*/
constructor(client, debug = false, hideLog = false) {
2022-07-07 17:23:41 +00:00
super();
2022-05-03 05:29:10 +00:00
this.debug = debug;
this.client = client;
this.hideLog = hideLog;
2022-07-07 17:23:41 +00:00
this.ws = new WebSocket(client?.options?.http?.remoteAuth || 'wss://remote-auth-gateway.discord.gg/?v=1', {
2022-05-03 05:29:10 +00:00
headers: {
Origin: 'https://discord.com',
'User-Agent': randomUA(),
},
});
this.key = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem',
},
privateKeyEncoding: {
type: 'pkcs1',
format: 'pem',
},
});
this.heartbeat_interval = null;
this.connectionDestroy = null;
this.missQR = null;
this.login_state = false;
2022-07-07 17:23:41 +00:00
/**
* User login with QR
* @type {?object}
*/
2022-05-03 05:29:10 +00:00
this.user = null;
2022-07-07 17:23:41 +00:00
/**
* Discord Auth URL (QR Code decoded)
* @type {?string}
*/
this.authURL = null;
/**
* Discord Token
* @type {?string}
*/
this.token = null;
2022-05-03 05:29:10 +00:00
this.ws.on('error', error => {
if (this.debug) console.log(error);
});
this.ws.on('open', () => {
if (this.debug) console.log('[WebSocket] Client Connected');
});
this.ws.on('message', message => {
let data = JSON.parse(message);
if (this.debug) console.log(`[WebSocket] Packet receive`, data);
let op = data.op;
if (op == Messages.HELLO) {
2022-06-26 02:40:04 +00:00
if (this.debug) console.log('[WebSocket] Attempting server handshake...');
2022-05-03 05:29:10 +00:00
this.heartbeat_interval = setInterval(() => {
this.heartbeat_sender();
}, data.heartbeat_interval);
this.connectionDestroy = setTimeout(() => {
this.destroy();
}, data.timeout_ms);
this.missQR = new Date(Date.now() + data.timeout_ms);
let publickey = this.public_key();
this.send(Messages.INIT, { encoded_public_key: publickey });
if (this.debug) console.log('[WebSocket] Sent PEM');
} else if (op == Messages.HEARTBEAT_ACK) {
if (this.debug) console.log('[WebSocket] Heartbeat acknowledged');
} else if (op == Messages.NONCE_PROOF) {
let nonce = data.encrypted_nonce;
let decrypted_nonce = this.decrypt_payload(nonce);
let proof = crypto.createHash('sha256').update(decrypted_nonce).digest();
proof = urlsafe_b64encode(proof);
proof = proof.replace(/\s+$/, '');
this.send(Messages.NONCE_PROOF, { proof: proof });
if (this.debug) console.log('[WebSocket] Nonce proof decrypted');
} else if (op == Messages.PENDING_REMOTE_INIT) {
let fingerprint = data.fingerprint;
2022-07-07 17:23:41 +00:00
this.authURL = `https://discord.com/ra/${fingerprint}`;
/**
* Emitted whenever a url is created.
* @event DiscordAuthWebsocket#ready
* @param {string} url DiscordAuthWebsocket
*/
this.emit('ready', this.authURL);
if (!this.hideLog) this.generate_qr_code(fingerprint);
2022-05-03 05:29:10 +00:00
if (this.debug) console.log('[WebSocket] QR Code generated');
if (!this.hideLog) {
console.log(
`Please scan the QR code to continue.\nQR Code will expire in ${this.missQR.toLocaleString('vi-VN')}`,
);
}
2022-05-03 05:29:10 +00:00
} else if (op == Messages.PENDING_FINISH) {
let encrypted_payload = data.encrypted_user_payload;
let payload = this.decrypt_payload(encrypted_payload);
const decoder = new StringDecoder('utf-8');
this.user = new DiscordUser_FromPayload(decoder.write(payload), this.debug);
if (!this.hideLog) console.log('\n');
if (!this.hideLog) console.log(this.user.pretty_print());
/**
* Emitted whenever a user is scan QR Code.
* @event DiscordAuthWebsocket#pending
* @param {object} user Discord User Raw
*/
this.emit('pending', this.user);
2022-05-03 05:29:10 +00:00
if (this.debug) console.log('[WebSocket] Waiting for user to finish login...');
if (!this.hideLog) console.log('\n');
if (!this.hideLog) console.log('Please check your phone again to confirm login.');
2022-05-03 05:29:10 +00:00
} else if (op == Messages.FINISH) {
this.login_state = true;
let encrypted_token = data.encrypted_token;
let token = this.decrypt_payload(encrypted_token);
const decoder = new StringDecoder('utf-8');
this.user.token = decoder.write(token);
if (this.debug) console.log(this.user.pretty_print());
2022-07-07 17:23:41 +00:00
this.token = this.user.token;
/**
* Emitted whenever a token is created.
* @event DiscordAuthWebsocket#success
* @param {object} user Discord User (Raw)
2022-07-07 17:23:41 +00:00
* @param {string} token Discord Token
*/
this.emit('success', this.user, this.token);
2022-07-07 17:23:41 +00:00
this.client?.login(this.user.token);
2022-05-03 05:29:10 +00:00
this.destroy();
} else if (op == Messages.CANCEL) {
2022-07-07 17:23:41 +00:00
/**
* Emitted whenever a user cancels the login process.
* @event DiscordAuthWebsocket#cancel
* @param {object} user User (Raw)
2022-07-07 17:23:41 +00:00
*/
this.emit('cancel', this.user);
2022-05-03 05:29:10 +00:00
this.destroy();
}
});
this.ws.on('close', () => {
if (this.debug) {
console.log('[WebSocket] Connection closed.');
}
});
if (this.debug) console.log('[WebSocket] Setup passed');
}
2022-07-07 17:23:41 +00:00
/**
* Destroy WebSocket connection
* @returns {void}
*/
2022-05-03 05:29:10 +00:00
destroy() {
this.ws.close();
clearInterval(this.heartbeat_interval);
clearTimeout(this.connectionDestroy);
/**
* Emitted whenever a connection is closed.
* @event DiscordAuthWebsocket#closed
* @param {boolean} loginState Login state
*/
this.emit('closed', this.login_state);
2022-05-03 05:29:10 +00:00
if (this.debug) {
console.log(`[WebSocket] Connection Destroyed, User login state: ${this.login_state ? 'success' : 'failure'}`);
}
if (!this.login_state && this.client) throw new Error('Login failed');
2022-05-03 05:29:10 +00:00
}
public_key() {
if (this.debug) console.log('[WebSocket] Generating public key...');
const decoder = new StringDecoder('utf-8');
let pub_key = this.key.publicKey;
if (this.debug) console.log(pub_key);
pub_key = decoder.write(pub_key);
if (this.debug) console.log(pub_key);
pub_key = pub_key.split('\n').slice(1, -2).join('');
if (this.debug) console.log(pub_key);
if (this.debug) console.log('[WebSocket] Public key generated');
return pub_key;
}
heartbeat_sender() {
if (this.ws.readyState === this.ws.OPEN) {
this.send(Messages.HEARTBEAT);
if (this.debug) console.log('[WebSocket] Heartbeat sent');
} else if (this.debug) {
console.log('[WebSocket] Heartbeat not sent');
}
}
send(op, data = null) {
let payload = { op: op };
if (data !== null) payload = { ...payload, ...data };
if (this.debug) {
console.log(`Send:`, payload);
console.log(payload);
}
this.ws.send(JSON.stringify(payload));
}
decrypt_payload(encrypted_payload) {
let payload = Buffer.from(encrypted_payload, 'base64');
if (this.debug) {
console.log(payload);
console.log(this.key.privateKey);
}
const decoder = new StringDecoder('utf-8');
let private_key = this.key.privateKey;
private_key = decoder.write(private_key);
let decrypted = crypto.privateDecrypt(
{
key: private_key,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
},
payload,
);
return decrypted;
}
2022-07-07 17:23:41 +00:00
/**
* Generate QR code for user to scan (Terminal)
* @param {string} fingerprint Auth URL
*/
2022-05-03 05:29:10 +00:00
generate_qr_code(fingerprint) {
require('@aikochan2k6/qrcode-terminal').generate(`https://discord.com/ra/${fingerprint}`, {
2022-05-03 05:29:10 +00:00
small: true,
});
}
}
module.exports = DiscordAuthWebsocket;