UNPKG

google-authenticator-util

Version:

Google authenticator tool for backend development / automation

289 lines (288 loc) 14.4 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.GoogleAuthenticator = void 0; const fs_1 = require("fs"); const dmock_server_1 = require("dmock-server"); const googleapis_1 = require("googleapis"); const puppeteer = require("puppeteer-core"); class GoogleAuthenticator { /** * Initializing the Google Authenticator object * @param authenticationOptions The parameters to configure the authentication to Google * @param authenticationOptions.clientId The authentication client ID * @param authenticationOptions.clientSecret The authentication client secret * @param debugOptions Optional. The parameters to configure the debug logging * @param debugOptions.debug Is debug enabled * @param debugOptions.debugger The debugger of the debug printing */ constructor(authenticationOptions, debugOptions) { this.isTokenGenerated = true; this.debugOptions = { debug: false, debugger: console.log }; this.clientId = authenticationOptions.clientId; this.clientSecret = authenticationOptions.clientSecret; this.api = googleapis_1.google.gmail('v1'); this.oAuth2Client = new googleapis_1.google.auth.OAuth2(this.clientId, this.clientSecret); if (debugOptions !== undefined && debugOptions.debug !== undefined) this.debugOptions.debug = debugOptions.debug; if (debugOptions !== undefined && debugOptions.debugger !== undefined) this.debugOptions.debugger = debugOptions.debugger; } /** * Authorizing a google account with a new token * @param options The new token authentication parameters * @param options.username Required. The gmail username * @param options.password Required. The gmail password * @param options.scope Required. The authentication scope * @param options.redirectURI Optional. The configuration of the redirect URI * @param options.tokenName Optional. The name of the token * @param options.tokenDirectory Optional. The directory of the token */ authorizeWithNewToken(options) { return __awaiter(this, void 0, void 0, function* () { try { this.debug('Configurating given parameters'); const configuratedOptions = this.configure(options); this.debug('==== Configurated parameters ===='); this.debug(`Username: ${(configuratedOptions.username !== undefined) ? '***' : 'undefined'}`); this.debug(`Password: ${(configuratedOptions.password !== undefined) ? '***' : 'undefined'}`); this.debug(`Token Name: ${(configuratedOptions.tokenName.indexOf(this.clientId) !== -1) ? configuratedOptions.tokenName.replace(this.clientId, '*****') : configuratedOptions.tokenName}`); this.debug(`Token Directory: ${configuratedOptions.tokenDirectory}`); this.debug(`Scope: ${configuratedOptions.scope}`); this.debug('================================='); this.oAuth2Client = new googleapis_1.google.auth.OAuth2(this.clientId, this.clientSecret, configuratedOptions.redirectURI); yield this.generateToken(configuratedOptions); return this.oAuth2Client; } catch (error) { throw new Error(error); } }); } /** * Authorizing a google account with an existing token file * @param options The token file authentication parameters * @param options.name The name of the token * @param options.directory The directory of the token */ authorizeWithTokenFile(options) { return __awaiter(this, void 0, void 0, function* () { try { let directory = options.directory; if (directory[directory.length - 1] !== '/') directory += '/'; const name = (options.name.indexOf('.json') !== -1) ? options.name : `${options.name}.json`; const tokenFullPath = directory + name; this.debug(`Retrieving token from file: ${tokenFullPath}`); const token = JSON.parse(yield fs_1.promises.readFile(tokenFullPath, 'utf8')); this.debug('Setting the token'); this.oAuth2Client.setCredentials(token); return this.oAuth2Client; } catch (error) { throw new Error(error); } }); } /** * Authorizing using a token * @param token The token */ authorizeWithToken(token) { return __awaiter(this, void 0, void 0, function* () { try { this.debug('Setting the token'); this.oAuth2Client.setCredentials(token); return this.oAuth2Client; } catch (error) { throw new Error(error); } }); } /** * Generating a new oAuth2 token * @param options The generate token parameters * @param options.username The email address username * @param options.password The email address password * @param options.scope The authorization scope * @param options.tokenName The name of the token * @param options.tokenDirectory The directory of the token * @param options.redirectURI The full redirectURI * @param options.redirectURIOptions The redirectURI options */ generateToken(options) { return __awaiter(this, void 0, void 0, function* () { this.debug('Retrieving a new token'); const authUrl = this.oAuth2Client.generateAuthUrl({ access_type: 'offline', scope: options.scope, }); this.debug(`Generating new auth URL: ${authUrl}`); this.isTokenGenerated = false; this.debug(`Creating a mock server on port ${options.redirectURIOptions.port}, domain: ${options.redirectURIOptions.domain}`); this.authServer = new dmock_server_1.MockServer({ hostname: options.redirectURIOptions.domain, port: options.redirectURIOptions.port, routes: [{ path: options.redirectURIOptions.path, method: 'get', response: (res, req) => this.retrieveToken(req.req.query.code, options) }] }); this.authServer.start(); this.debug('Authenticating to get the first token'); yield this.authenticateToken(authUrl, options.username, options.password); this.debug(`Token generation process is ${this.isTokenGenerated}`); return this.oAuth2Client; }); } /** * Authenticating the first token using the google UI * @param authUrl The authentication URL * @param username The email address username * @param password The email address password */ authenticateToken(authUrl, username, password) { return __awaiter(this, void 0, void 0, function* () { const browserFetcher = puppeteer.createBrowserFetcher(); const revisionInfo = yield browserFetcher.download('737027'); const browser = yield puppeteer.launch({ executablePath: revisionInfo.executablePath, headless: false, args: ['--no-sandbox'] }); const page = yield browser.newPage(); yield page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'); //UI authentication when there is no access token. yield page.goto(authUrl, { waitUntil: 'networkidle2' }); yield page.waitForSelector('input[type=email]', { visible: true }); this.debug('Filling the username'); yield page.type('input[type=email]', username); yield page.click('#identifierNext'); yield page.waitForSelector('input[type=password]', { visible: true }); this.debug('Filling the password'); yield page.type('input[type=password]', password); yield page.click('#passwordNext'); this.debug(`Waiting for token generation process to be finished`); while (!this.isTokenGenerated) yield this.sleep(0.5); yield browser.close(); yield browserFetcher.remove(revisionInfo.revision); }); } /** * Configurating the Google Authenticator class properties * @param options Required. The configuration options * @param username Required. The gmail username * @param password Required. The gmail password * @param scope Required. The authentication scope * @param redirectURI Optional. The configuration of the redirect URI * @param tokenName Optional. The name of the token * @param tokenDirectory Optional. The directory of the token */ configure(options) { let tokenDirectory = './tokens/'; let tokenName = `${this.clientId}-token`; let tmpRedirectURIOptions = { protocol: 'http', domain: 'localhost', path: '/oauth2callback' }; //token related options this.debug('Configurating token path'); if (options.tokenDirectory !== undefined) tokenDirectory = options.tokenDirectory; if (tokenDirectory[tokenDirectory.length - 1] !== '/') tokenDirectory += '/'; if (options.tokenName !== undefined) tokenName = options.tokenName; //redirect URI related options this.debug('Configurating redirect URI options'); if (options.redirectURIOptions !== undefined && options.redirectURIOptions.protocol !== undefined) tmpRedirectURIOptions.protocol = options.redirectURIOptions.protocol; if (options.redirectURIOptions !== undefined && options.redirectURIOptions.domain !== undefined) tmpRedirectURIOptions.domain = options.redirectURIOptions.domain; if (options.redirectURIOptions !== undefined && options.redirectURIOptions.path !== undefined) tmpRedirectURIOptions.path = options.redirectURIOptions.path; if (options.redirectURIOptions !== undefined && options.redirectURIOptions.port !== undefined) tmpRedirectURIOptions.port = options.redirectURIOptions.port; else tmpRedirectURIOptions.port = 3000; //building the redirect URI this.debug('Configurating the redirect URI'); let redirectURI = `${tmpRedirectURIOptions.protocol}://${tmpRedirectURIOptions.domain}`; if (tmpRedirectURIOptions.port !== undefined) redirectURI += `:${tmpRedirectURIOptions.port}`; redirectURI += tmpRedirectURIOptions.path; this.debug('Configurating scope'); if (options.scope === undefined) options.scope = ['https://www.googleapis.com/auth/gmail.readonly']; const redirectURIOptions = tmpRedirectURIOptions; return { username: options.username, password: options.password, scope: options.scope, tokenName: tokenName, tokenDirectory: tokenDirectory, tokenFullPath: `${tokenDirectory}${tokenName}.json`, redirectURI: redirectURI, redirectURIOptions: redirectURIOptions }; } /** * Retrieving a token * @param code The code of the authentication * @param options The options of token generation */ retrieveToken(code, options) { return __awaiter(this, void 0, void 0, function* () { this.debug(`Getting a new token with code ${code}`); const { tokens } = yield this.oAuth2Client.getToken(code); console.log(`token: ${JSON.stringify(tokens)}`); this.debug('Setting the token'); this.oAuth2Client.setCredentials(tokens); // Store the token to disk for later program executions yield this.createDirectory(options.tokenDirectory); yield fs_1.promises.writeFile(options.tokenFullPath, JSON.stringify(tokens)); this.isTokenGenerated = true; this.authServer.stop(); this.debug('Stopping the authentication server'); }); } /** * Printing a message based on configured logger * @param message The printed message */ debug(message) { if (this.debugOptions.debug) this.debugOptions.debugger(`DEBUG: ${message}`); } /** * Creating the token's directory * @param directory The directory to verify */ createDirectory(directory) { return __awaiter(this, void 0, void 0, function* () { try { this.debug(`Verifying directory ${directory} exists`); yield fs_1.promises.access(directory); } catch (error) { this.debug(`Directory ${directory} doesn't exist, creating it.`); yield fs_1.promises.mkdir(directory); } }); } /** * Sleeping for X seconds in the code * @param seconds The sleep seconds */ sleep(seconds) { return __awaiter(this, void 0, void 0, function* () { yield new Promise(resolve => setTimeout(resolve, seconds * 1000)); }); } } exports.GoogleAuthenticator = GoogleAuthenticator;