11
votes

I'm attempting to build an email parser that responds to a gmail pubsub message. I am currently able to receive and parse the pubsub message and extract the historyId, but I'm having trouble authenticating my request to the gmail api. Here's what I have up to now:

  //function defined outside the scope of the main function
  //used to create auth client
  function getAuthClient(event, callback) {
    var GoogleAuth = require('google-auth-library');

    var authFactory = new GoogleAuth();

    authFactory.getApplicationDefault(function (err, authClient) {
     if (err) {
       console.log('Unable to get application default credentials.');
       callback(err);
       return;
     }

     // Create the oAuth scopes you need
     if (authClient.createScopedRequired && authClient.createScopedRequired()) {
    console.log('creating scoped client');
      authClient = authClient.createScoped([
          'https://mail.google.com/'
      ]);
    }
   callback(null, authClient);
  });
}

exports.gmailParser = function gmailParser (event, callback) {
 var path = require('path');
 var base64 = require('base-64');

 // Set your service account key file into the environment for the auth lib 
to pick up
 process.env['GOOGLE_APPLICATION_CREDENTIALS'] = path.resolve(__dirname, 
'auth_credentials.json');
 console.log(process.env['GOOGLE_APPLICATION_CREDENTIALS']);


 console.log(__dirname);

 //parse pubsub message
 var pubsubMessage = event.data;
 var baseMessage = pubsubMessage.data;
 var decodedMessage = base64.decode(baseMessage);
 var messageJSON = JSON.parse(decodedMessage);

 // Extract emailAddress and historyId from gmail pubsub message
 var historyId = messageJSON.historyId;
 var email = messageJSON.emailAddress;

 getAuthClient(null, function(err, authClient) {

   //import necessary libraries
   var google = require('googleapis');
   var gmail = google.gmail('v1');

   if(err) {
     callback(err);
   }

  // Construct a params object
  var params = {
    userId: email,
    startHistoryId: historyId
 };


//Attempt to call gmail api. This is where the error occurs.
gmail.users.history.list(params, function(error, response) {
  if (error) {  
    console.log('Encountered error', error);
    callback(error);
  } else {
    console.log('Response', response);
    callback(response);
  }
});
 });
};

The code runs successfully but I get the following error:

"Error: Login Required
at Request._callback (/user_code/node_modules/googleapis/node_modules/google-auth-library/lib/transporters.js:85:15)
at Request.self.callback (/user_code/node_modules/googleapis/node_modules/request/request.js:188:22)
at emitTwo (events.js:106:13)
at Request.emit (events.js:191:7)
at Request.<anonymous> (/user_code/node_modules/googleapis/node_modules/request/request.js:1171:10)
at emitOne (events.js:96:13)
at Request.emit (events.js:188:7)
at IncomingMessage.<anonymous> (/user_code/node_modules/googleapis/node_modules/request/request.js:1091:12)
at IncomingMessage.g (events.js:291:16)
at emitNone (events.js:91:20)"   

I've tried adding the authClient to the param object but it returns a "Bad request" error. I've tried changing the order of the imports, created new credentials, but I haven't been able to get anywhere. If anyone has any pointers it would be greatly appreciated.

1
Since you'ure using Gmail API and NodeJS, did you try using the Login flow in Gmail Node.js Quickstart - noogui
I did, but when I try using the json file, I get the following error: Error: The incoming JSON object does not contain a client_email field - kevinivan05
@kevinivan05 That means there is something wrong with your json file. Can you make sure you are using the correct file, attained by following the steps in the quickstart? The json file should look like this {"installed":{"client_id":"your_id","project_id":"your_project_name","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"your_secret","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}} - Jon Church
Have you got the solution? - Ajay Sivan
It looks like your callback never used its authClient. Did you try replacing google.gmail('v1'); with something like google.gmail({version:'v1', auth: authClient);? - Adam Bliss

1 Answers

2
votes

This blogpost might help. It has details regarding getting OAuth tokens using nodejs and Cloud Functions. I tried it and it worked fine.

https://cloud.google.com/blog/products/application-development/adding-custom-intelligence-to-gmail-with-serverless-on-gcp

Code: https://github.com/GoogleCloudPlatform/cloud-functions-gmail-nodejs

The below code snippet requests an OAuth 2.0 authorisation code

exports.oauth2init = (req, res) => {
  // Define OAuth2 scopes
  const scopes = [
    'https://www.googleapis.com/auth/gmail.modify'
  ];

  // Generate + redirect to OAuth2 consent form URL
  const authUrl = oauth.client.generateAuthUrl({
    access_type: 'offline',
    scope: scopes,
    prompt: 'consent' // Required in order to receive a refresh token every time
  });
  return res.redirect(authUrl);
};

The below code snippet gets an access token from the OAuth authorisation code

exports.oauth2callback = (req, res) => {
  // Get authorization code from request
  const code = req.query.code;

  // OAuth2: Exchange authorization code for access token
  return new Promise((resolve, reject) => {
    oauth.client.getToken(code, (err, token) =>
      (err ? reject(err) : resolve(token))
    );
  })