7

I want to sign-in my meteor server program without manual browser authentication. So the authentication must all be automated; I don't want user intervention/interaction at all. I have come across the answer here but it is in java and I don't know whether it works or not. Can someone please provide the answer in meteor.js. If the answer is in node.js and can work in meteor that is also fine.

Preferably I want to run this meteor code in the backend as part of a job using the msavin /SteveJobs pacjage.

yusha uzumo
  • 211
  • 1
  • 4
  • 15
  • Please add some code of what you already achieved. If you don't know at all where to start, you may look into the code of the accounts packages, which are part of the Meteor repo on GitHub. – Jankapunkt Jan 15 '20 at 11:32
  • @Jankapunkt, please note I am talking about logging into and using gmail-api. An example would be https://github.com/Slava/meteor-gmail . I am not dealing with the accounts packages which would include accounts-google package. I want to use gmail api which provide a whole host of functions I won't get from accounts-google package. Let me know if I am misunderstanding you. Thanks for your reply. – yusha uzumo Jan 15 '20 at 12:16
  • It is all the same oauth workflow with some more or less different configurations. The Meteor server is also just a "client" from an oauth-perspective. So, where you exactly get stuck? Why not just store your credentials in settings.json and load them on startup using the API from the meteor-gmail link? – Jankapunkt Jan 15 '20 at 12:17
  • So I've got https://github.com/meteor/meteor/tree/devel/packages/; where in the above link do I look? Do I have the wrong link above? – yusha uzumo Jan 15 '20 at 12:50
  • What operations do want to use inside the Gmail API? are you reading/writing user data? – KLiFF Jan 21 '20 at 15:58
  • @yushauzumo I have similar snippet, but it is done with plain `node.js`, it works with `Gmail API`, get email, send email, get labels. Is it interesting to you? – Jeff Rush Jan 22 '20 at 11:16
  • @KLiFF, yes reading and backing up emails in other folders – yusha uzumo Jan 23 '20 at 12:40
  • @yushauzumo check an updated answer with Service Account – Jeff Rush Jan 24 '20 at 13:25
  • @yushauzumo Is there a solution for this? I have the same problem, if I am not wrong. You want your account accessible from backend without manually authorizing from a browser, yes? Kindly let me know. Thanks. – ashwin mahajan May 23 '21 at 18:53

1 Answers1

0

Below is the working snippet in plain node.js

Functionality:

You can adapt it for your needs using the Gmail API

If you struggle with this example try Node.js Quickstart Guide

Here is the snippet:

const fs = require('fs');
const readline = require('readline');
const { google } = require('googleapis');

// If modifying these scopes, delete token.json.
const SCOPES = [
  'https://mail.google.com/',
  'https://www.googleapis.com/auth/gmail.modify',
  'https://www.googleapis.com/auth/gmail.compose',
  'https://www.googleapis.com/auth/gmail.send'
];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';

/**
 * Create an OAuth2 client with the given credentials, and then execute the
 * given callback function.
 * @param {Object} credentials The authorization client credentials.
 * @param {function} callback The callback to call with the authorized client.
 */
function authorize(credentials, callback) {
  const { client_secret, client_id, redirect_uris } = credentials.installed;
  const oAuth2Client = new google.auth.OAuth2(
    client_id, client_secret, redirect_uris[0]);

  // Check if we have previously stored a token.
  fs.readFile(TOKEN_PATH, (err, token) => {
    if (err) return getNewToken(oAuth2Client, callback);
    oAuth2Client.setCredentials(JSON.parse(token));
    callback(oAuth2Client);
  });
}

/**
 * Get and store new token after prompting for user authorization, and then
 * execute the given callback with the authorized OAuth2 client.
 * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
 * @param {getEventsCallback} callback The callback for the authorized client.
 */
function getNewToken(oAuth2Client, callback) {
  const authUrl = oAuth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES,
  });
  console.log('Authorize this app by visiting this url:', authUrl);
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  rl.question('Enter the code from that page here: ', (code) => {
    rl.close();
    oAuth2Client.getToken(code, (err, token) => {
      if (err) return console.error('Error retrieving access token', err);
      oAuth2Client.setCredentials(token);
      // Store the token to disk for later program executions
      fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
        if (err) return console.error(err);
        console.log('Token stored to', TOKEN_PATH);
      });
      callback(oAuth2Client);
    });
  });
}

/********************************************************************************************
*********************************************************************************************
*********************************************************************************************
 * Custom edits here
 */

let labelList = [];

function listLabels(auth) {

  const gmail = google.gmail({ version: 'v1', auth });

  gmail.users.labels.list({
    userId: 'me',
  }, (err, res) => {

    if (err) return console.log('The API returned an error: ' + err);

    const labels = res.data.labels;

    if (labels.length) {
      labels.forEach((label) => {
        labelList.push(`${label.name}`);
      });
    } else {
      console.log('No labels found.');
    }

  });

  console.log('===listLabels finished===');

}

function makeEmailBody(to, from, subject, message) {

  let str = [
    "Content-Type: text/plain; charset=\"UTF-8\"\n",
    "MIME-Version: 1.0\n",
    "Content-Transfer-Encoding: 7bit\n",
    "to: ", to, "\n",
    "from: ", from, "\n",
    "subject: ", subject, "\n\n",
    message
  ].join('');

  let encodedMail = new Buffer(str).toString("base64").replace(/\+/g, '-').replace(/\//g, '_');

  return encodedMail;

}

function sendMessage(auth) {

  let raw = makeEmailBody(
    /* to */'email@gmail.com',
    /* from */'email@gmail.com',
    /* subject */'Subject',
    /* message */`Labels:
      ${labelList}
    `);

  const gmail = google.gmail({ version: 'v1', auth });

  gmail.users.messages.send({
    auth: auth,
    userId: 'me',
    resource: {
      raw: raw
    }
  }, (err, res) => {
    return (err || res)
  });

  console.log('===SendMessage finished===');

}

let userId = 'email@gmail.com';
let messageId = '__id__'; 

function getEmail(auth){

  const gmail = google.gmail({ version: 'v1', auth });

  gmail.users.messages.get({
    id: messageId,
    userId: userId,
    format: 'full'
  }, (err, res) => {
    if (err) return console.log('The API returned an error: ' + err);
    console.log(res.data);
  });

  console.log('===listLabels finished===');

}

// RUN script
fs.readFile('credentials.json', (err, content) => {

  if (err) {
    console.log('Error loading client secret file: ' + err);
    return;
  }

  authorize(JSON.parse(content), getEmail);

  // authorize(JSON.parse(content), listLabels);
  // authorize(JSON.parse(content), listFiles);
  // authorize(JSON.parse(content), sendMessage);

});

How to create service account:

  1. Create project in Admin Console
  2. Create service account
  3. Go to Admin Console > Security > Advanced settings > Manage API client access
  4. In Client Name put the full email of your created Service Account
  5. In One or More API Scopes put https://mail.google.com/ and click Authorize
  6. Come back to Service accounts, select your account, Enable G Suite Domain-wide Delegation
  7. Create Service Account KEY (download it as .json)
  8. Activate Gmail API for your project. Go to APIs & Services > Dashboard, click on ENABLE APIS AND SERVICES, search for Gmail and Enable it.

Now you got service account and you can use it in your project.

Jeff Rush
  • 874
  • 6
  • 13
  • It looks like you have user intervention when they authorize. I do not want that. Someone told me what I am looking for is using service accounts. I have not done so yet. If you have an example you are welcome to provide it – yusha uzumo Jan 23 '20 at 12:44
  • @yushauzumo if what you need is Service Account snippet its another story, take a look at my [answer here](https://stackoverflow.com/questions/59648637/google-api-drive-v3-retrieving-drive-storage-space-used/59661487#59661487) let me know if this is similar to what you want to achieve but with `node.js`. – Jeff Rush Jan 23 '20 at 13:59