Auth Strategies (#1257)

* auth strategies

* default to no auth

* rename base auth strategy

* rename base strategy cont.

* refactor auth strategy methods and LocalAuth

* activate old session options even if is falsy value

* move restartOnAuthFail to LegacyAuthStrategy option

* add link to guide item

* update example/shell

* types
This commit is contained in:
Pedro S. Lopez
2022-02-27 22:02:49 -04:00
committed by GitHub
parent 0d55d40885
commit f6de161c7d
12 changed files with 266 additions and 116 deletions

View File

@@ -1,7 +1,5 @@
'use strict';
const path = require('path');
const fs = require('fs');
const EventEmitter = require('events');
const puppeteer = require('puppeteer');
const moduleRaid = require('@pedroslopez/moduleraid/moduleraid');
@@ -13,23 +11,24 @@ const { ExposeStore, LoadUtils } = require('./util/Injected');
const ChatFactory = require('./factories/ChatFactory');
const ContactFactory = require('./factories/ContactFactory');
const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification, Label, Call, Buttons, List } = require('./structures');
const LegacySessionAuth = require('./authStrategies/LegacySessionAuth');
const NoAuth = require('./authStrategies/NoAuth');
/**
* Starting point for interacting with the WhatsApp Web API
* @extends {EventEmitter}
* @param {object} options - Client options
* @param {AuthStrategy} options.authStrategy - Determines how to save and restore sessions. Will use LegacySessionAuth if options.session is set. Otherwise, NoAuth will be used.
* @param {number} options.authTimeoutMs - Timeout for authentication selector in puppeteer
* @param {object} options.puppeteer - Puppeteer launch options. View docs here: https://github.com/puppeteer/puppeteer/
* @param {number} options.qrMaxRetries - How many times should the qrcode be refreshed before giving up
* @param {string} options.restartOnAuthFail - Restart client with a new session (i.e. use null 'session' var) if authentication fails
* @param {boolean} options.useDeprecatedSessionAuth - Enable JSON-based authentication. This is deprecated due to not being supported by MultiDevice, and will be removed in a future version.
* @param {object} options.session - This is deprecated due to not being supported by MultiDevice, and will be removed in a future version.
* @param {string} options.restartOnAuthFail - @deprecated This option should be set directly on the LegacySessionAuth.
* @param {object} options.session - @deprecated Only here for backwards-compatibility. You should move to using LocalAuth, or set the authStrategy to LegacySessionAuth explicitly.
* @param {number} options.takeoverOnConflict - If another whatsapp web session is detected (another browser), take over the session in the current browser
* @param {number} options.takeoverTimeoutMs - How much time to wait before taking over the session
* @param {string} options.dataPath - Change the default path for saving session files, default is: "./WWebJS/"
* @param {string} options.userAgent - User agent to use in puppeteer
* @param {string} options.ffmpegPath - Ffmpeg path to use when formating videos to webp while sending stickers
* @param {boolean} options.bypassCSP - Sets bypassing of page's Content-Security-Policy.
* @param {string} options.clientId - Client id to distinguish instances if you are using multiple, otherwise keep null if you are using only one instance
*
* @fires Client#qr
* @fires Client#authenticated
@@ -52,20 +51,28 @@ class Client extends EventEmitter {
super();
this.options = Util.mergeDefault(DefaultOptions, options);
if(!this.options.authStrategy) {
if(Object.prototype.hasOwnProperty.call(this.options, 'session')) {
process.emitWarning(
'options.session is deprecated and will be removed in a future release due to incompatibility with multi-device. ' +
'Use the LocalAuth authStrategy, don\'t pass in a session as an option, or suppress this warning by using the LegacySessionAuth strategy explicitly (see https://wwebjs.dev/guide/authentication.html#legacysessionauth-strategy).',
'DeprecationWarning'
);
this.id = this.options.clientId;
// eslint-disable-next-line no-useless-escape
const foldernameRegex = /^(?!.{256,})(?!(aux|clock\$|con|nul|prn|com[1-9]|lpt[1-9])(?:$|\.))[^ ][ \.\w-$()+=[\];#@~,&']+[^\. ]$/i;
if (this.id && !foldernameRegex.test(this.id)) throw Error('Invalid client ID. Make sure you abide by the folder naming rules of your operating system.');
if (!this.options.useDeprecatedSessionAuth) {
this.dataDir = this.options.puppeteer.userDataDir;
const dirPath = path.join(process.cwd(), this.options.dataPath, this.id ? 'session-' + this.id : 'session');
if (!this.dataDir) this.dataDir = dirPath;
fs.mkdirSync(this.dataDir, { recursive: true });
this.authStrategy = new LegacySessionAuth({
session: this.options.session,
restartOnAuthFail: this.options.restartOnAuthFail
});
} else {
this.authStrategy = new NoAuth();
}
} else {
this.authStrategy = this.options.authStrategy;
}
this.authStrategy.setup(this);
this.pupBrowser = null;
this.pupPage = null;
@@ -78,10 +85,9 @@ class Client extends EventEmitter {
async initialize() {
let [browser, page] = [null, null];
const puppeteerOpts = {
...this.options.puppeteer,
userDataDir: this.options.useDeprecatedSessionAuth ? undefined : this.dataDir
};
await this.authStrategy.beforeBrowserInitialized();
const puppeteerOpts = this.options.puppeteer;
if (puppeteerOpts && puppeteerOpts.browserWSEndpoint) {
browser = await puppeteer.connect(puppeteerOpts);
page = await browser.newPage();
@@ -91,27 +97,12 @@ class Client extends EventEmitter {
}
await page.setUserAgent(this.options.userAgent);
if (this.options.bypassCSP) await page.setBypassCSP(true);
this.pupBrowser = browser;
this.pupPage = page;
if (this.options.useDeprecatedSessionAuth && this.options.session) {
await page.evaluateOnNewDocument(session => {
if (document.referrer === 'https://whatsapp.com/') {
localStorage.clear();
localStorage.setItem('WABrowserId', session.WABrowserId);
localStorage.setItem('WASecretBundle', session.WASecretBundle);
localStorage.setItem('WAToken1', session.WAToken1);
localStorage.setItem('WAToken2', session.WAToken2);
}
localStorage.setItem('remember-me', 'true');
}, this.options.session);
}
if (this.options.bypassCSP) {
await page.setBypassCSP(true);
}
await this.authStrategy.afterBrowserInitialized();
await page.goto(WhatsWebURL, {
waitUntil: 'load',
@@ -141,18 +132,17 @@ class Client extends EventEmitter {
// Scan-qrcode selector was found. Needs authentication
if (needAuthentication) {
if(this.options.session) {
const { failed, failureEventPayload, restart } = await this.authStrategy.onAuthenticationNeeded();
if(failed) {
/**
* Emitted when there has been an error while trying to restore an existing session
* @event Client#auth_failure
* @param {string} message
* @deprecated
*/
this.emit(Events.AUTHENTICATION_FAILURE, 'Unable to log in. Are the session details valid?');
this.emit(Events.AUTHENTICATION_FAILURE, failureEventPayload);
await this.destroy();
if (this.options.restartOnAuthFail) {
if (restart) {
// session restore failed so try again but without session to force new authentication
this.options.session = null;
return this.initialize();
}
return;
@@ -224,20 +214,7 @@ class Client extends EventEmitter {
}
await page.evaluate(ExposeStore, moduleRaid.toString());
let authEventPayload = undefined;
if (this.options.useDeprecatedSessionAuth) {
// Get session tokens
const localStorage = JSON.parse(await page.evaluate(() => {
return JSON.stringify(window.localStorage);
}));
authEventPayload = {
WABrowserId: localStorage.WABrowserId,
WASecretBundle: localStorage.WASecretBundle,
WAToken1: localStorage.WAToken1,
WAToken2: localStorage.WAToken2
};
}
const authEventPayload = await this.authStrategy.getAuthEventPayload();
/**
* Emitted when authentication is successful
@@ -248,23 +225,14 @@ class Client extends EventEmitter {
// Check window.Store Injection
await page.waitForFunction('window.Store != undefined');
const isMD = await page.evaluate(() => {
return window.Store.Features.features.MD_BACKEND;
});
await page.evaluate(async () => {
// safely unregister service workers
const registrations = await navigator.serviceWorker.getRegistrations();
for (let registration of registrations) {
registration.unregister();
}
});
if (this.options.useDeprecatedSessionAuth && isMD) {
throw new Error('Authenticating via JSON session is not supported for MultiDevice-enabled WhatsApp accounts.');
}
//Load util functions (serializers, helper functions)
await page.evaluate(LoadUtils);
@@ -518,9 +486,7 @@ class Client extends EventEmitter {
return window.Store.AppState.logout();
});
if (this.dataDir) {
return (fs.rmSync ? fs.rmSync : fs.rmdirSync).call(this.dataDir, { recursive: true });
}
await this.authStrategy.logout();
}
/**