feat: auto MFA handling
Co-Authored-By: Yellowy <64450187+TheDevYellowy@users.noreply.github.com>
This commit is contained in:
@@ -15,13 +15,9 @@ const client = new Discord.Client({
|
|||||||
})
|
})
|
||||||
.then(res => res.data);
|
.then(res => res.data);
|
||||||
},
|
},
|
||||||
|
TOTPKey: '<string>',
|
||||||
});
|
});
|
||||||
|
|
||||||
async function getMFACode() {
|
|
||||||
// This is just an example, you should implement your own MFA code retrieval
|
|
||||||
return '123456';
|
|
||||||
}
|
|
||||||
|
|
||||||
client.on('ready', async () => {
|
client.on('ready', async () => {
|
||||||
console.log('Ready!', client.user.tag);
|
console.log('Ready!', client.user.tag);
|
||||||
// Note
|
// Note
|
||||||
@@ -32,7 +28,6 @@ client.on('ready', async () => {
|
|||||||
{
|
{
|
||||||
guild_id: 'guild id',
|
guild_id: 'guild id',
|
||||||
},
|
},
|
||||||
await getMFACode(), // Optional
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -710,7 +710,6 @@ class Client extends BaseClient {
|
|||||||
* Authorize an application.
|
* Authorize an application.
|
||||||
* @param {string} urlOAuth2 Discord Auth URL
|
* @param {string} urlOAuth2 Discord Auth URL
|
||||||
* @param {OAuth2AuthorizeOptions} [options] Oauth2 options
|
* @param {OAuth2AuthorizeOptions} [options] Oauth2 options
|
||||||
* @param {string|number} [mfaCode = null] The mfa code if you have it enabled
|
|
||||||
* @returns {Promise<{ location: string }>}
|
* @returns {Promise<{ location: string }>}
|
||||||
* @example
|
* @example
|
||||||
* client.authorizeURL(`https://discord.com/api/oauth2/authorize?client_id=botID&permissions=8&scope=applications.commands%20bot`, {
|
* client.authorizeURL(`https://discord.com/api/oauth2/authorize?client_id=botID&permissions=8&scope=applications.commands%20bot`, {
|
||||||
@@ -719,7 +718,7 @@ class Client extends BaseClient {
|
|||||||
authorize: true
|
authorize: true
|
||||||
})
|
})
|
||||||
*/
|
*/
|
||||||
authorizeURL(urlOAuth2, options = {}, mfaCode = null) {
|
authorizeURL(urlOAuth2, options = {}) {
|
||||||
// ! throw new Error('METHOD_WARNING');
|
// ! throw new Error('METHOD_WARNING');
|
||||||
const url = new URL(urlOAuth2);
|
const url = new URL(urlOAuth2);
|
||||||
if (!/https:\/\/(canary\.|ptb\.)?discord.com\/api(\/v\d{1,2})?\/oauth2\/authorize\?/.test(urlOAuth2)) {
|
if (!/https:\/\/(canary\.|ptb\.)?discord.com\/api(\/v\d{1,2})?\/oauth2\/authorize\?/.test(urlOAuth2)) {
|
||||||
@@ -745,7 +744,6 @@ class Client extends BaseClient {
|
|||||||
return this.api.oauth2.authorize.post({
|
return this.api.oauth2.authorize.post({
|
||||||
query: searchParams,
|
query: searchParams,
|
||||||
data: options,
|
data: options,
|
||||||
mfaCode,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const RateLimitError = require('./RateLimitError');
|
|||||||
const {
|
const {
|
||||||
Events: { DEBUG, RATE_LIMIT, INVALID_REQUEST_WARNING, API_RESPONSE, API_REQUEST },
|
Events: { DEBUG, RATE_LIMIT, INVALID_REQUEST_WARNING, API_RESPONSE, API_REQUEST },
|
||||||
} = require('../util/Constants');
|
} = require('../util/Constants');
|
||||||
|
const TOTP = require('../util/Totp');
|
||||||
|
|
||||||
const captchaMessage = [
|
const captchaMessage = [
|
||||||
'incorrect-captcha',
|
'incorrect-captcha',
|
||||||
@@ -385,22 +386,31 @@ class RequestHandler {
|
|||||||
request.retries++;
|
request.retries++;
|
||||||
return this.execute(request, captcha, data.captcha_rqtoken);
|
return this.execute(request, captcha, data.captcha_rqtoken);
|
||||||
}
|
}
|
||||||
// Two factor
|
// Two factor handling
|
||||||
if (data?.code && data.code == 60003 && request.options.mfaCode && request.retries < 1) {
|
if (
|
||||||
|
data?.code &&
|
||||||
|
data.code == 60003 && // Two factor is required for this operation
|
||||||
|
data.mfa.methods.find(o => o.type === 'totp') && // TOTP is available
|
||||||
|
typeof this.manager.client.options.TOTPKey === 'string' &&
|
||||||
|
request.options.auth !== false &&
|
||||||
|
request.retries < 1
|
||||||
|
) {
|
||||||
|
// Get mfa code
|
||||||
|
const { otp } = await TOTP.generate(this.options.TOTPKey);
|
||||||
this.manager.client.emit(
|
this.manager.client.emit(
|
||||||
DEBUG,
|
DEBUG,
|
||||||
`${data.message}
|
`${data.message}
|
||||||
Method : ${request.method}
|
Method : ${request.method}
|
||||||
Path : ${request.path}
|
Path : ${request.path}
|
||||||
Route : ${request.route}
|
Route : ${request.route}
|
||||||
mfaCode : ${request.options.mfaCode}`,
|
mfaCode : ${otp}`,
|
||||||
);
|
);
|
||||||
// Get ticket
|
// Get ticket
|
||||||
const mfaData = data.mfa;
|
const mfaData = data.mfa;
|
||||||
const mfaPost = await this.manager.client.api.mfa.finish.post({
|
const mfaPost = await this.manager.client.api.mfa.finish.post({
|
||||||
data: {
|
data: {
|
||||||
ticket: mfaData.ticket,
|
ticket: mfaData.ticket,
|
||||||
data: request.options.mfaCode,
|
data: otp,
|
||||||
mfa_type: 'totp',
|
mfa_type: 'totp',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1528,9 +1528,6 @@ class Guild extends AnonymousGuild {
|
|||||||
* Set the vanity URL to this guild.
|
* Set the vanity URL to this guild.
|
||||||
* Resolves with an object containing the vanity URL invite code and the use count.
|
* Resolves with an object containing the vanity URL invite code and the use count.
|
||||||
* @param {string} [code=''] Vanity URL code
|
* @param {string} [code=''] Vanity URL code
|
||||||
* @param {string|number} [mfaCode = null] Two-factor authentication code
|
|
||||||
* After you enter the mfaCode, you will receive a `__Secure-recent_mfa` cookie, which is valid for about
|
|
||||||
* 5 minutes, during which you won't need to enter MFA again.
|
|
||||||
* @returns {Promise<Vanity>}
|
* @returns {Promise<Vanity>}
|
||||||
* @example
|
* @example
|
||||||
* // Set invite code
|
* // Set invite code
|
||||||
@@ -1540,11 +1537,10 @@ class Guild extends AnonymousGuild {
|
|||||||
* })
|
* })
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
async setVanityCode(code = '', mfaCode = null) {
|
async setVanityCode(code = '') {
|
||||||
if (typeof code !== 'string') throw new TypeError('INVALID_VANITY_URL_CODE');
|
if (typeof code !== 'string') throw new TypeError('INVALID_VANITY_URL_CODE');
|
||||||
const data = await this.client.api.guilds(this.id, 'vanity-url').patch({
|
const data = await this.client.api.guilds(this.id, 'vanity-url').patch({
|
||||||
data: { code },
|
data: { code },
|
||||||
mfaCode,
|
|
||||||
});
|
});
|
||||||
this.vanityURLCode = data.code;
|
this.vanityURLCode = data.code;
|
||||||
this.vanityURLUses = data.uses;
|
this.vanityURLUses = data.uses;
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ const Intents = require('./Intents');
|
|||||||
* @property {number} [DMChannelVoiceStatusSync=0] The amount of time in milliseconds that the Client to register the event with each DM channel (0=Disable)
|
* @property {number} [DMChannelVoiceStatusSync=0] The amount of time in milliseconds that the Client to register the event with each DM channel (0=Disable)
|
||||||
* @property {number} [captchaRetryLimit=3] Captcha retry limit
|
* @property {number} [captchaRetryLimit=3] Captcha retry limit
|
||||||
* @property {CaptchaSolver} [captchaSolver] Captcha Solver
|
* @property {CaptchaSolver} [captchaSolver] Captcha Solver
|
||||||
|
* @property {string} [TOTPKey] TOTP key for two-factor authentication
|
||||||
* @property {number} [closeTimeout=5000] The amount of time in milliseconds to wait for the close frame to be received
|
* @property {number} [closeTimeout=5000] The amount of time in milliseconds to wait for the close frame to be received
|
||||||
* from the WebSocket.
|
* from the WebSocket.
|
||||||
* <info>Don't have this too high/low. It's best to have it between 2000-6000 ms.</info>
|
* <info>Don't have this too high/low. It's best to have it between 2000-6000 ms.</info>
|
||||||
@@ -160,6 +161,7 @@ class Options extends null {
|
|||||||
DMChannelVoiceStatusSync: 0,
|
DMChannelVoiceStatusSync: 0,
|
||||||
captchaRetryLimit: 3,
|
captchaRetryLimit: 3,
|
||||||
captchaSolver: () => Promise.reject(new Error('CAPTCHA_SOLVER_NOT_IMPLEMENTED')),
|
captchaSolver: () => Promise.reject(new Error('CAPTCHA_SOLVER_NOT_IMPLEMENTED')),
|
||||||
|
TOTPKey: null,
|
||||||
closeTimeout: 5_000,
|
closeTimeout: 5_000,
|
||||||
waitGuildTimeout: 15_000,
|
waitGuildTimeout: 15_000,
|
||||||
shardCount: 1,
|
shardCount: 1,
|
||||||
|
|||||||
10
typings/index.d.ts
vendored
10
typings/index.d.ts
vendored
@@ -794,7 +794,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
|
|||||||
options?: AcceptInviteOptions,
|
options?: AcceptInviteOptions,
|
||||||
): Promise<Guild | DMChannel | GroupDMChannel>;
|
): Promise<Guild | DMChannel | GroupDMChannel>;
|
||||||
public redeemNitro(nitro: string, channel?: TextChannelResolvable, paymentSourceId?: Snowflake): Promise<any>;
|
public redeemNitro(nitro: string, channel?: TextChannelResolvable, paymentSourceId?: Snowflake): Promise<any>;
|
||||||
public authorizeURL(urlOAuth2: string, options?: OAuth2AuthorizeOptions, mfaCode?: string | number): Promise<any>;
|
public authorizeURL(urlOAuth2: string, options?: OAuth2AuthorizeOptions): Promise<any>;
|
||||||
public installUserApps(applicationId: Snowflake): Promise<void>;
|
public installUserApps(applicationId: Snowflake): Promise<void>;
|
||||||
public deauthorize(applicationId: Snowflake): Promise<void>;
|
public deauthorize(applicationId: Snowflake): Promise<void>;
|
||||||
|
|
||||||
@@ -1553,7 +1553,7 @@ export class Guild extends AnonymousGuild {
|
|||||||
reason?: string,
|
reason?: string,
|
||||||
): Promise<this>;
|
): Promise<this>;
|
||||||
public topEmojis(): Promise<Collection<number, GuildEmoji>>;
|
public topEmojis(): Promise<Collection<number, GuildEmoji>>;
|
||||||
public setVanityCode(code?: string, mfaCode?: string | number): Promise<this>;
|
public setVanityCode(code?: string): Promise<this>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GuildAuditLogs<T extends GuildAuditLogsResolvable = 'ALL'> {
|
export class GuildAuditLogs<T extends GuildAuditLogsResolvable = 'ALL'> {
|
||||||
@@ -3437,11 +3437,11 @@ export class TOTP {
|
|||||||
private static buf2hex(buf: ArrayBuffer): string;
|
private static buf2hex(buf: ArrayBuffer): string;
|
||||||
private static readonly base32: { [key: number]: number };
|
private static readonly base32: { [key: number]: number };
|
||||||
private static readonly crypto: SubtleCrypto;
|
private static readonly crypto: SubtleCrypto;
|
||||||
public static generate(key: string, options?: generateOptions): Promise<{ otp: string; expires: number}>;
|
public static generate(key: string, options?: generateOptions): Promise<{ otp: string; expires: number }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TOTPAlgorithm = "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512";
|
export type TOTPAlgorithm = 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512';
|
||||||
export type TOTPEncoding = "hex" | "ascii";
|
export type TOTPEncoding = 'hex' | 'ascii';
|
||||||
|
|
||||||
export interface generateOptions {
|
export interface generateOptions {
|
||||||
digits: number;
|
digits: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user