diff --git a/src/client/Client.js b/src/client/Client.js index 69292d2..04224c2 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -229,7 +229,7 @@ class Client extends BaseClient { * Password cache * @type {?string} */ - this.password = null; + this.password = this.options.password; /** * Nitro cache @@ -392,6 +392,63 @@ class Client extends BaseClient { } } + /** + * Login Discord with Username and Password + * @param {string} username Email or Phone Number + * @param {?string} password Password + * @param {?string} mfaCode 2FA Code / Backup Code + * @returns {Promise} + */ + async normalLogin(username, password = this.password, mfaCode) { + if (!username || !password || typeof username !== 'string' || typeof password !== 'string') { + throw new Error('NORMAL_LOGIN'); + } + this.emit( + Events.DEBUG, + `Connecting to Discord with: + username: ${username} + password: ${password.replace(/./g, '*')}`, + ); + const data = await this.api.auth.login.post({ + data: { + login: username, + password: password, + undelete: false, + captcha_key: null, + login_source: null, + gift_code_sku_id: null, + }, + auth: false, + }); + this.password = password; + if (!data.token && data.ticket && data.mfa) { + this.emit(Events.DEBUG, `Using 2FA Code: ${mfaCode}`); + const normal2fa = /(\d{6})/g; + const backupCode = /([a-z0-9]{4})-([a-z0-9]{4})/g; + if (!mfaCode || typeof mfaCode !== 'string') { + throw new Error('LOGIN_FAILED_2FA'); + } + if (normal2fa.test(mfaCode) || backupCode.test(mfaCode)) { + const data2 = await this.api.auth.mfa.totp.post({ + data: { + code: mfaCode, + ticket: data.ticket, + login_source: null, + gift_code_sku_id: null, + }, + auth: false, + }); + return this.login(data2.token); + } else { + throw new Error('LOGIN_FAILED_2FA'); + } + } else if (data.token) { + return this.login(data.token); + } else { + throw new Error('LOGIN_FAILED_UNKNOWN'); + } + } + /** * Sign in with the QR code on your phone. * @param {boolean} debug Debug mode @@ -928,6 +985,9 @@ class Client extends BaseClient { if (options && typeof options.patchVoice !== 'boolean') { throw new TypeError('CLIENT_INVALID_OPTION', 'patchVoice', 'a boolean'); } + if (options && options.password && typeof options.password !== 'string') { + throw new TypeError('CLIENT_INVALID_OPTION', 'password', 'a string'); + } if (options && typeof options.proxy !== 'string') { throw new TypeError('CLIENT_INVALID_OPTION', 'proxy', 'a string'); } diff --git a/src/errors/Messages.js b/src/errors/Messages.js index 1efc427..8014bba 100644 --- a/src/errors/Messages.js +++ b/src/errors/Messages.js @@ -210,6 +210,9 @@ const Messages = { MISSING_CAPTCHA_SERVICE: 'This feature is only available for enabled captcha handler.', GUILD_FORUM_MESSAGE_REQUIRED: 'You must provide a message to create a guild forum thread', + NORMAL_LOGIN: 'Username and password are required for normal login', + LOGIN_FAILED_UNKNOWN: 'Login failed', + LOGIN_FAILED_2FA: 'Login failed, 2FA code is required', }; for (const [name, message] of Object.entries(Messages)) register(name, message); diff --git a/src/managers/SessionManager.js b/src/managers/SessionManager.js index 8746bef..b463189 100644 --- a/src/managers/SessionManager.js +++ b/src/managers/SessionManager.js @@ -33,17 +33,15 @@ class SessionManager extends CachedManager { /** * Logout the client (remote). - * @param {string} password User's password * @param {string | null} mfaCode MFA code (if 2FA is enabled) * @returns {Promise} */ - logoutAllDevices(password, mfaCode) { - password = password || this.client.password; - if (!password || typeof password !== 'string') throw new Error('REQUIRE_PASSWORD'); + logoutAllDevices(mfaCode) { + if (typeof this.client.password !== 'string') throw new Error('REQUIRE_PASSWORD'); return this.client.api.auth.sessions.logout({ data: { session_id_hashes: this.cache.map(session => session.id), - password, + password: this.client.password, code: typeof mfaCode === 'string' ? mfaCode : undefined, }, }); diff --git a/src/structures/Session.js b/src/structures/Session.js index 3b6fb92..1ec045e 100644 --- a/src/structures/Session.js +++ b/src/structures/Session.js @@ -59,17 +59,15 @@ class Session extends Base { /** * Logout the client (remote). - * @param {string} password User's password * @param {string | null} mfaCode MFA code (if 2FA is enabled) * @returns {Promise} */ - logout(password, mfaCode) { - password = password || this.client.password; - if (!password || typeof password !== 'string') throw new Error('REQUIRE_PASSWORD', 'You must provide a password.'); + logout(mfaCode) { + if (typeof this.client.password !== 'string') throw new Error('REQUIRE_PASSWORD', 'You must provide a password.'); return this.client.api.auth.sessions.logout({ data: { session_id_hashes: [this.id], - password, + password: this.client.password, code: typeof mfaCode === 'string' ? mfaCode : undefined, }, }); diff --git a/src/util/Options.js b/src/util/Options.js index 47dc935..8fbd02a 100644 --- a/src/util/Options.js +++ b/src/util/Options.js @@ -154,6 +154,7 @@ class Options extends null { captchaKey: null, DMSync: false, patchVoice: false, + password: null, waitGuildTimeout: 15_000, messageCreateEventGuildTimeout: 100, shardCount: 1, diff --git a/typings/index.d.ts b/typings/index.d.ts index 403f91b..430802b 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -164,7 +164,7 @@ import { export abstract class SessionManager extends CachedManager { constructor(client: Client, iterable?: Iterable); public fetch(): Promise; - public logoutAllDevices(password?: string, mfaCode?: string): Promise; + public logoutAllDevices(mfaCode?: string): Promise; } export abstract class Session extends Base { @@ -173,7 +173,7 @@ export abstract class Session extends Base { public clientInfo?: SessionClientInfo; public readonly createdTimestamp: number; public readonly createdAt: Date; - public logout(password?: string, mfaCode?: string): Promise; + public logout(mfaCode?: string): Promise; } export interface SessionClientInfo { @@ -865,6 +865,7 @@ export class Client extends BaseClient { public redeemNitro(code: string, channel?: TextChannelResolvable, failIfNotExists?: boolean): object; public generateInvite(options?: InviteGenerationOptions): string; public login(token?: string): Promise; + public normalLogin(username: string, password?: string, mfaCode?: string): Promise; public QRLogin(debug?: boolean): DiscordAuthWebsocket; public remoteAuth(url: string, forceAccept?: boolean): Promise; public createToken(): Promise; @@ -4705,6 +4706,7 @@ export interface ClientOptions { autoCookie?: boolean; autoRedeemNitro?: boolean; patchVoice?: boolean; + password?: string; DMSync?: boolean; proxy?: string; captchaService?: string;