google-authenticator-util
Version:
Google authenticator tool for backend development / automation
289 lines (288 loc) • 14.4 kB
JavaScript
;
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;