Search code examples
node.jsfirebasegoogle-apigoogle-oauthgoogle-api-nodejs-client

Google auth hanging waiting for response


Log of execution:

inside authorize() function ------------------------
inside readAuth2TokensFromFile() function ------------------------
Error reading Tokens from a file:./credentials/token.json inside getNewTokensFromGoogleCLI() function ------------------------
Authorize this app by visiting this (Google login) url: https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&scope=...
inside getAnswerOn(question) function ------------------------

...waiting for response from Google CLI...

The issue in this function calling readLineInterface:

/**
 * @param {string} query The question to ask.
 */
function getAnswerOn(question) {
  console.log("inside getAnswerOn(question) function ------------------------");
  return new Promise((resolve) => {
    //https://millermedeiros.github.io/mdoc/examples/node_api/doc/readline.html
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });

    rl.question(question, (answer) => {
      console.log("Answer received succesfully!");
      rl.close();
      process.stdin.destroy();
      resolve(answer);
    });

  })
}

Full source code of OAuth2Authorizer.js file:

//Based on https://stackoverflow.com/questions/54882145/is-there-an-async-await-node-js-google-auth-module
//OAuth2Authorizer with Promises
'use strict'
const fs = require('fs');
const readline = require('readline');

const functions = require('firebase-functions');
const { google } = require('googleapis');

//Go to the Google Cloud Dashboard: APIs and Services/ Credentials panel: 
//https://console.cloud.google.com/apis/credentials
//OAuth2 Client Credentials (Other):
const KEY = require("./credentials/oauth2.key.json").web;

/**
* Create a new OAuth2 client with the configured keys.
* https://github.com/googleapis/google-api-nodejs-client
*/
const oauth2Client = new google.auth.OAuth2(
  KEY.client_id,
  KEY.client_secret,
  KEY.redirect_uris[0]
);

function getOAuth2Client() {
  console.log("inside getOAuth2Client ----------------------------");
  return oauth2Client
}


/**
 * reading Tokens from a file:
 * @param {string} path Path to a JSON file where the tokens is stored in.
 */
function readAuth2TokensFromFile(path) {
  console.log("inside readAuth2TokensFromFile() function ------------------------");
  return new Promise((resolve, reject) => {
    try {
      resolve(JSON.parse(fs.readFileSync(path).toString()));
    } catch (error) {
      console.log("Error reading Tokens from a file:" + path);
      resolve(undefined)
    }
  })
}


/**
 * @param {string} query The question to ask.
 */
function getAnswerOn(question) {
  console.log("inside getAnswerOn(question) function ------------------------");
  return new Promise((resolve) => {
    //https://millermedeiros.github.io/mdoc/examples/node_api/doc/readline.html
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });

    rl.question(question, (answer) => {
      console.log("Answer received succesfully!");
      rl.close();
      process.stdin.destroy();
      resolve(answer);
    });

  })
}



/**
 * Get and store new token after prompting for user authorization, and then
 * execute the given callback with the authorized OAuth2 client.
 * @param {OAuth2Client} oAuth2Client The OAuth2 client to get token for.
 * @param {string[]} scopes Scopes to get authorization for.
 * @param {string} tokenFilePath Path to a JSON file to store the tokens in.
 * @returns {OAuth2Client} terurns updated oAuth2Client
 */
function getNewTokensFromGoogleCLI(oAuth2Client, scopes, tokenFilePath) {
  console.log("inside getNewTokensFromGoogleCLI() function ------------------------");
  return new Promise((resolve, reject) => {

    const authUrl = oAuth2Client.generateAuthUrl({
      access_type: 'offline',
      scope: scopes,
    });

    console.log('Authorize this app by visiting this (Google login) url: ', authUrl);

    getAnswerOn("Enter the long code from that page here:").then(answer => {
      try {
        console.log("trying to get new token (before getToken) ------------------")
        oAuth2Client.getToken(answer, (err, tokens) => {
          if (!err) {
            // Now tokens contains an access_token and an optional refresh_token. Save them.
            oauth2Client.setCredentials(tokens);
            resolve(oauth2Client) //return updated oauth2Client <-----------------------------
            try {
              // Store the token to disk for later program executions
              fs.writeFileSync(tokenFilePath, JSON.stringify(tokens));
              console.log('Token stored to', tokenFilePath);
            } catch (error) {
              console.error("Error while storing tokens in a file", error);
              reject(new functions.https.HttpsError(error.code, error.message));
            }
          } else {
            console.error('Error in oAuth2Client.getToken() function', err);
            reject(new functions.https.HttpsError(err.code, err.message));
          }
        })
      } catch (error) {
        console.error('Error while trying to retrieve access token', error);
        reject(new functions.https.HttpsError(error.code, error.message));
      }
    })
  })
}


function authorize(key, scopes, tokenPath) {
  console.log("inside authorize() function ------------------------");
  return new Promise((resolve, reject) => {
    try {

      let REQ_SCOPES = scopes;
      if (Array.isArray(scopes)) {
        REQ_SCOPES = scopes.join(" "); //convert an array to the string  
      }

      /*
      * Create a new OAuth2 client with the configured keys.
      * https://github.com/googleapis/google-api-nodejs-client
      */
      const oauth2Client = new google.auth.OAuth2(
        key.client_id,
        key.client_secret,
        key.redirect_uris[0]
      );

      readAuth2TokensFromFile(tokenPath).then(tokens => {
        if (tokens) {
          oauth2Client.setCredentials(auth2tokensTokens);
          resolve(oauth2Client);
        } else {
          getNewTokensFromGoogleCLI(oauth2Client, REQ_SCOPES, tokenPath).then(updatedOAauth2Client => {
            resolve(updatedOAauth2Client)
          })
        }
      })
    } catch (error) {
      console.error('Error while getting a new oauth2Client from key: ', error);
      reject(new functions.https.HttpsError(error.code, error.message));
    }
  })
}

//Use: const { authorize } = require('./OAuth2Authorizer.js');
exports.authorize = authorize;

Solution

  • The way Oauth2 works your application needs to request permission of a user to access their data. To do that we show them a consent form. Your script is telling you that

    console.log('Authorize this app by visiting this (Google login) url: ', authUrl);
    getAnswerOn("Enter the long code from that page here:").then(answer => {
    

    As seen here

    Authorize this app by visiting this (Google login) url: https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&scope=...

    Take the full URL it gives you. Put it in a web browser consent to the access it will give you an authorization code. Take this code back and place it in your application where it is waiting for it.

    Your application isnt hanging its waiting for you to respond to its request.