This JavaScript module handles OAuth2 authentication with Google Calendar using various external libraries and provides error handling and fallback scenarios to recover from connection issues. The module defines four primary functions: storing tokens, exchanging codes for OAuth2 tokens, error fallback, and renewing existing tokens, with the last function having incomplete documentation and implementation.
npm run import -- "google oauth token client"
var readline = require('readline');
var process = require('process');
var path = require('path');
var fs = require('fs');
var util = require('util');
var {OAuth2Client} = require('google-auth-library');
var importer = require('../Core');
//var runSeleniumCell = importer.import("selenium cell");
// Load client secrets from a local file.
var SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'];
var SECRET_PATH, credentials;
var TOKEN_DIR = path.join(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE || '', '.credentials');
try {
SECRET_PATH = path.join(TOKEN_DIR, 'client_secret.json');
credentials = JSON.parse(fs.readFileSync(SECRET_PATH).toString());
} catch(e) {
console.log(e);
}
function storeToken(token, tokenPath) {
fs.writeFileSync(tokenPath, JSON.stringify(token, null, 4));
console.log('Token stored to ' + tokenPath);
}
function receiveCode(code, oauth2Client, tokenPath) {
return util.promisify(oauth2Client.getToken.bind(oauth2Client))(code)
.then(token => {
console.log('recieved token: ' + token.access_token);
oauth2Client.setCredentials(token);
storeToken(token, tokenPath);
return oauth2Client;
})
}
async function errorFallback(up, authUrl, oauth2Client, tokenPath) {
if(up.message.includes('ECONNREFUSED')
|| up.message.includes('find module')
|| up.message.includes('runSeleniumCell is not defined')) {
console.log('can\'t authenticate with selenium, waiting for user input.');
console.log('Authorize this app by visiting this url:', authUrl);
//if (process.stdout.isTTY) {
//}
var interface = readline.createInterface(process.stdin, process.stdout);
var code = await new Promise(resolve => interface.question(
'Enter the code from that page here: ', resolve))
interface.close();
return receiveCode(code, oauth2Client, tokenPath)
} else {
throw up;
}
}
async function renewToken(oauth2Client, scopes, tokenPath) {
var authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes
})
try {
var {authorizeSelenium} = await runSeleniumCell(['log in Google', 'selenium authorize google access'])
console.log('using selenium to authorize: ' + authUrl)
var code = await authorizeSelenium(authUrl)
return receiveCode(code, oauth2Client, tokenPath)
} catch (up) {
return errorFallback(up, authUrl, oauth2Client, tokenPath)
}
}
async function authorize(scopes = SCOPES) {
const tokenPath = path.join(TOKEN_DIR, scopes.join('')
.replace(/[^a-z]+/ig, '_') + '.json')
var oauth2Client = new OAuth2Client(
credentials.web.client_id,
credentials.web.client_secret,
credentials.web.redirect_uris[0])
try {
// Check if we have previously stored a token.
oauth2Client.setCredentials(JSON.parse(fs.readFileSync(tokenPath)));
await oauth2Client.getAccessToken()
return oauth2Client
} catch (up) {
// if the token file isn't found start a new auth
if(up.message == 'invalid_token'
|| up.code === 'ENOENT') {
return renewToken(oauth2Client, scopes, tokenPath);
} else {
throw up;
}
}
}
module.exports = authorize;
const readline = require('readline');
const { OAuth2Client } = require('google-auth-library');
const fs = require('fs').promises;
const path = require('path');
const { sep } = require('path');
const util = require('util');
const console = console;
const importer = require('../Core');
const { runSeleniumCell } = importer.import('selenium cell');
const SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'];
const SECRET_PATH = path.join(path.dirname(require.main.filename), 'client_secret.json');
const TOKEN_DIR = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE || '', '.credentials';
async function authorize(scopes = SCOPES) {
const tokenPath = path.join(TOKEN_DIR, scopes.join('')
.replace(/[^a-z]+/ig, '_') + '.json');
const oauth2Client = new OAuth2Client(
await getCredentials().web.client_id,
await getCredentials().web.client_secret,
await getCredentials().web.redirect_uris[0]);
try {
// Check if we have previously stored a token.
const storedToken = await fs.readFile(tokenPath);
oauth2Client.setCredentials(JSON.parse(storedToken));
await oauth2Client.getAccessToken();
return oauth2Client;
} catch (error) {
try {
await renewToken(oauth2Client, scopes, tokenPath);
} catch (error) {
throw error;
}
}
}
async function renewToken(oauth2Client, scopes, tokenPath) {
try {
const authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes
});
const authorizeSelenium = await runSeleniumCell(['log in Google','selenium authorize google access']);
console.log('using selenium to authorize:'+ authUrl);
const code = await authorizeSelenium(authUrl);
return receiveCode(code, oauth2Client, tokenPath);
} catch (error) {
await errorFallback(error, oauth2Client, tokenPath);
}
}
async function errorFallback(up, oauth2Client, tokenPath) {
if (up.message.includes('ECONNREFUSED') || up.message.includes('find module') || up.message.includes('runSeleniumCell is not defined')) {
console.log('can\'t authenticate with selenium, waiting for user input.');
console.log('Authorize this app by visiting this url:', oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
}));
const interface = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const code = await new Promise(resolve => interface.question(
'Enter the code from that page here: ', resolve));
interface.close();
return receiveCode(code, oauth2Client, tokenPath);
} else {
throw up;
}
}
async function receiveCode(code, oauth2Client, tokenPath) {
const token = await util.promisify(oauth2Client.getToken.bind(oauth2Client))(code);
console.log('recieved token:'+ token.access_token);
oauth2Client.setCredentials(token);
await fs.writeFile(tokenPath, JSON.stringify(token, null, 4));
console.log('Token stored to'+ tokenPath);
return oauth2Client;
}
async function getCredentials() {
try {
const secretPath = path.join(TOKEN_DIR, 'client_secret.json');
const secret = await fs.readFile(secretPath).then(data => JSON.parse(data).toString());
return secret;
} catch (error) {
throw new Error('Unable to load client secret json');
}
}
module.exports = authorize;
Overview
This code is a JavaScript module that utilizes various external libraries to handle OAuth2 authentication with Google Calendar. The module appears to be designed to handle errors and fallback scenarios, allowing the application to recover from connection issues.
Breakdown
The code begins by importing various Node.js libraries:
readline
: for reading user inputprocess
: for accessing environmental variablespath
: for working with file pathsfs
: for interacting with the file systemutil
: for utilizing utility functionsgoogle-auth-library
: for OAuth2 authentication with GoogleThe code attempts to load client secrets from a local file (client_secret.json
) located in the user's home directory. If the file does not exist, an error is caught and logged to the console.
There are four main functions in this code:
storeToken(token, tokenPath)
: writes a token to a file at the specified tokenPath
.receiveCode(code, oauth2Client, tokenPath)
: exchanges a code for an OAuth2 token using the Google authentication library. It stores the token and sets the oauth2Client
credentials.errorFallback(up, authUrl, oauth2Client, tokenPath)
: a function that handles errors and triggers a fallback scenario when OAuth2 authentication fails.renewToken(oauth2Client, tokenPath)
: a function that renews an existing OAuth2 token. The documentation and implementation for this function are incomplete, so it is unclear how it works.The errorFallback
function is triggered when an error occurs during OAuth2 authentication. It checks if the error message indicates a connection issue or a problem with importing Selenium cells. If so, it prompts the user to visit the authorization URL and enter the code received. Once the code is entered, the receiveCode
function is called to exchange the code for a token.
The code is written in a asynchronous manner, using async/await
syntax to handle promises. The process.stdout.isTTY
property is used to detect if the console is a terminal, but it is commented out. The importer
variable is imported from another module, but the runSeleniumCell
import is commented out.