Google calendar authentication on AWS Elastic beanstalk - node.js

I just moved my first mean app to aws and considering all things went pretty well. But my question is now that I have it up and running my google calendar api is not working. I checked the logs and noticed since I changed URL the api wanted to revalidate by visiting
Authorize this app by visiting this url: https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar&response_type=code&client_id=628160639508-njce0d3437e19gs8t4gn5at3surr3seu.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob
Enter the code from that page here:
Now from the logs I can easily grab the url and validate but then it gives a code and directs back the app to enter the code for validation. How can I do that when its running on aws elastic beanstalk?
Any help is greatly appreciated.
Thanks

As mentionned in this documentation: https://developers.google.com/calendar/quickstart/nodejs, you need to get a new token with this new code.
Here is a sample that might help you:
/**
* 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 getAccessToken(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 callback(err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}

According to my understanding, you have registered a redirect URL in the calendar API and once you start the validation process after the login page the control flow redirects to that URL with the all the credentials (tokens).
Here you can create an endpoint in the node application for example /token and register it in the calendar API. Once the redirection happens you can save the data inside a map in node application which can be used later on for all other calendar API calls.
Here is a small example of the token endpoint:
var google = require("googleapis");
var OAuth2 = google.auth.OAuth2;
var sessMap = require('./sessionMap');
var oauth2Client = new OAuth2(<client_id>, <access_token>, <domain>+"/token");
var app = require('express')();
app.all('/token', function(req, res){
var code = req.query.code;
oauth2Client.getToken(code, function(err, tokens) {
if (err) {
res.send(err);
return;
}
oauth2Client.setCredentials(tokens);
//updated after sessionMap
sessMap.set('googleAuth', tokens);
res.send("This page will now redirect you");
});
});
To save credentials you can create a session map as shown below:
sessionMap.js
var hashmapSession = {};
exports.sess = {
set : function(key, value){
hashmapSession[key] = value;
},
get : function(key){
return hashmapSession[key];
},
all : function(){
return hashmapSession;
},
delete : function(key){
delete hashmapSession[key];
}
};
The above code will have to be incorporated in your node application.

Related

Unauthorized Error when Fetching Google My Business accounts under an Authenticated Google Account

I am trying to list the businesses under an authenticated Google Account. I have done the Oauth2 flow and have gotten the object containing the access_token, id_token,refresh_token and expiry_date
Now when I tried listing the businesses under that account I keep getting Unauthorized Error.
Below is my flow:
initOAuth2Client() {
return new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
process.env.GOOGLE_REDIRECT_URL
);
}
//Authorization Code
async authenticateAgainstGoogleMyBusiness() {
let oauth2Client = module.exports.initOAuth2Client();
const scopes = [
'https://www.googleapis.com/auth/business.manage',
'https://www.googleapis.com/auth/userinfo.profile'
];
const state = ....
const url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes,
state: state,
prompt: 'consent'
});
}
and then for my Oauth Callback Handler I have this
async googleOAuthCallbackHandler(req, res) {
let oauth2Client = module.exports.initOAuth2Client();
let { code, state } = req.query;
const oauth2Client = module.exports.initOAuth2Client();
const { tokens } = await oauth2Client.getToken(code);
//this is where I saved the tokens exactly as received.
}
async fetchGoogleMyBusinessAccounts() {
let {access_token} = fetchTokenFromDatabase(); //This is how I retrieved the access tokens saved by the previous function.
console.log(`Fetching GMB Accounts`);
let accountsUrls = `https://mybusinessaccountmanagement.googleapis.com/v1/accounts`;
try {
let headers = {
'Authorization': `Bearer ${access_token}`
}
let response = axios.get(accountsUrls, {
headers: headers
});
let data = response.data;
console.log(`GMB Accounts response = ${JSON.stringify(data, null, 2)}`);
} catch (e) {
console.log('Error retrieving GMB Accounts:Full error below:');
console.log(e); //I keep getting Unauthorized even though I authorized the process on the oauth consent screen
}
}
How can I fix this?
I think you should have a look at the sample for Google drive I dont know why Google doesnt release samples for all the APIs but they dont. I put this together by altering the sample for google drive a bit. I dont have node installed right now but this should be close
Let me know if you have any issues and i can try and help you debug them.
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/business.manage'];
// 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';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Drive API.
authorize(JSON.parse(content), listFiles);
});
/**
* 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 getAccessToken(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 getAccessToken(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);
});
});
}
/**
* Lists the names and IDs of up to 10 files.
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function listFiles(auth) {
const service = google.mybusinessaccountmanagement({version: 'v1', auth});
service.accounts.get({
pageSize: 10,
fields: '*',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const accounts= res.accounts;
if (accounts.length) {
console.log('accounts:');
accounts.map((account) => {
console.log(`${account.name} (${account.id})`);
});
} else {
console.log('No files found.');
}
});
}
from comments
Lets have a look at this error message from the comments.
code: 429, errors: [ { message: "Quota exceeded for quota metric 'Requests' and limit 'Requests per minute' of service 'mybusinessaccountmanagement.googleapis.com' for consumer 'project_number:xxxxx'.", domain: 'global', reason: 'rateLimitExceeded' } ] }
When you created your project on google cloud platform you had to enable the api you were going to use.
If you go back and check the it by (tip manage button) on the left there is a note about Quota
Each api has a default quota for when you enable it. The default quota for this api is
Quota is what controls how many requests you can make to the api. Google limits us because they want us all to be able to use the api. They dont want just one app flooding the system.
As you can see the default quota for this api is 0 Which is why you are exceeding the limit already without making any requests.
At the top of this page there is a note that says
Request more quota limits or view quotas for your other services on the Quotas page, found in IAM & admin.
After a lot of digging i managed to find a link to the form for requesting additional quota Usage limits at the very bottom of the page there is a line that says
If you need higher limits, you can submit a Standard quota request.
Submit the form. I have no idea how long its going to take you to get a quota extension.

use of r1.question when using google sheets api

I am using the documentation found at https://developers.google.com/sheets/api/quickstart/nodejs in order to use the google sheets api in my node.js application. For reference, the code I'm using from the page is as below:
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly'];
// 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';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Sheets API.
authorize(JSON.parse(content), listMajors);
});
/**
* 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 while trying to retrieve 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);
});
});
}
/**
* Prints the names and majors of students in a sample spreadsheet:
* #see https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
* #param {google.auth.OAuth2} auth The authenticated Google OAuth client.
*/
function listMajors(auth) {
const sheets = google.sheets({version: 'v4', auth});
sheets.spreadsheets.values.get({
spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms',
range: 'Class Data!A2:E',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const rows = res.data.values;
if (rows.length) {
console.log('Name, Major:');
// Print columns A and E, which correspond to indices 0 and 4.
rows.map((row) => {
console.log(`${row[0]}, ${row[4]}`);
});
} else {
console.log('No data found.');
}
});
}
At the line where it says rl.question('Enter the code from that page here: ', (code) => { I get the feeling that I'm supposed to input something, but I never see any obvious prompt to enter any kind of code, and thus I do not enter anything and my code goes to return console.error('Error while trying to retrieve access token', err); and I apparently never get an access token. How exactly do I enter this code, and where do I get this code from?
EDIT: Detailed flow for replicating bug.
I have the following requires at the top of my code:
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
Below is the route which I first access in the browser in order to initiate the process.
app.get("/test", function (req,res) {
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly'];
// 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';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Sheets API.
authorize(JSON.parse(content), listMajors);
});
/**
* 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;
var client_secret = credentials.web.client_secret;
var client_id = credentials.web.client_id;
var redirect_uris = credentials.web.redirect_uris;
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 while trying to retrieve 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);
});
});
}
/**
* Prints the names and majors of students in a sample spreadsheet:
* #see https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
* #param {google.auth.OAuth2} auth The authenticated Google OAuth client.
*/
function listMajors(auth) {
const sheets = google.sheets({version: 'v4', auth});
sheets.spreadsheets.values.get({
spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms',
range: 'Class Data!A2:E',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const rows = res.data.values;
if (rows.length) {
console.log('Name, Major:');
// Print columns A and E, which correspond to indices 0 and 4.
rows.map((row) => {
console.log(`${row[0]}, ${row[4]}`);
});
} else {
console.log('No data found.');
}
});
}
})
When I access this route, the browser tries to load to something, but never gets there. In the debug console I get a prompt saying "Authorize this app by visiting this url: ..." and I go to the given url in the browser. I am already signed into google, so I am given a list of accounts to choose from where the browser says "Choose an account to continue to quickstart." I choose my google account, and then I get the "this app isn't verified screen", so I click "Advanced" towards the bottom of the screen, after which a "Go to Quickstart (unsafe)" link appears. I click this link, and then I seemingly go back to the previous oauth screen where I was given a list of accounts, except a prompt now appears asking to Grant Quickstart permission to view your google spreadsheets. I click "Allow", and then I am redirected to a page which looks similar to the previous Oath screen, except it's asking me to confirm my choices, make sure you trust quickstart etc., so I click Allow once again and I am finally redirected to the page I specified that I would go to by my redirect url. However, in my working directory I can never see token.json, and I never see the "Enter the code from that page here: " in the terminal (I am using VSCode and the terminal is set as windows powershell). When I try to enter the code I get from the url parameters (from the redirect page at the end) I get the error I mentioned before.

TypeError: Cannot read property 'redirect_uris' of undefined Youtube Data API authentication NodeJS

I have a web application through which I'd like to be able create playlists, add videos to a playlist, delete a video etc, to my youtube channel. I have created a service account and downloaded the service account credentials key file and set up my OAuth 2.0 Client IDs in the Google Developer Console.
To authenticate my app, I followed the instructions in the README.md for the google-api-nodejs-client here https://github.com/googleapis/google-api-nodejs-client - under service account credentials
Here is my controller file...
I should note that the project uses ES Modules and thus "type": "module" is set in package.json. This is why you'l notice for example that I am importing __dirname as a utility since ES Modules do not support the regular __dirname.
import googleapi from "googleapis";
const { google } = googleapi;
import Auth from "#google-cloud/local-auth";
const { authenticate } = Auth;
import path from "path";
import __dirname from "../utils/dirname.js";
async function initialize() {
try {
const auth = await authenticate({
keyfilePath: path.join(__dirname, "../service_account_credentials.json"),
scopes: ["https://www.googleapis.com/auth/youtube"],
});
console.log("Auth details");
console.log(auth);
google.options({ auth });
} catch (e) {
console.log(e);
}
}
initialize();
const oauth2Client = new google.auth.OAuth2(
"YOUR_CLIENT_ID",
"YOUR_CLIENT_SECRET",
"http://localhost:5000/oauth2callback"
);
// initialize the Youtube API library
const youtube = google.youtube({ version: "v3", auth: oauth2Client });
class YoutubeController {
static async createPlaylist(req, res) {
const { name } = req.body;
const playlist = await youtube.playlists.insert({
part: "snippet,status",
resource: {
snippet: {
title: name,
description: `${name} videos.`,
},
status: {
privacyStatus: "private",
},
},
});
res.json(playlist);
}
}
The initialize function, is the one that throws the error and I can't quite figure it out. I think because of that, when I make a POST request to the route that calls the method createPlaylist inside the class, I get back No access, refresh token or API key is set.
I've been going through the docs trying to understand how everything flows but I'm a little stuck.
A similar question was asked here - TypeError: Cannot read property 'redirect_uris' of undefined but there are no answers and the suggested workflow does not work for my case so I'd really appreciate your help on this.
service accounts
The YouTube API does not support service account authentication you need to use OAuth2.
OAuth2 Authorization
You might want to consider following the YouTube API quick start for nodejs.
The issue is that you are using service account authentication with the YouTube API which it does not support.
var fs = require('fs');
var readline = require('readline');
var {google} = require('googleapis');
var OAuth2 = google.auth.OAuth2;
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/youtube-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/youtube.readonly'];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'youtube-nodejs-quickstart.json';
// Load client secrets from a local file.
fs.readFile('client_secret.json', function processClientSecrets(err, content) {
if (err) {
console.log('Error loading client secret file: ' + err);
return;
}
// Authorize a client with the loaded credentials, then call the YouTube API.
authorize(JSON.parse(content), getChannel);
});
/**
* 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) {
var clientSecret = credentials.installed.client_secret;
var clientId = credentials.installed.client_id;
var redirectUrl = credentials.installed.redirect_uris[0];
var oauth2Client = new OAuth2(clientId, clientSecret, redirectUrl);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function(err, token) {
if (err) {
getNewToken(oauth2Client, callback);
} else {
oauth2Client.credentials = 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 to call with the authorized
* client.
*/
function getNewToken(oauth2Client, callback) {
var authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
console.log('Authorize this app by visiting this url: ', authUrl);
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Enter the code from that page here: ', function(code) {
rl.close();
oauth2Client.getToken(code, function(err, token) {
if (err) {
console.log('Error while trying to retrieve access token', err);
return;
}
oauth2Client.credentials = token;
storeToken(token);
callback(oauth2Client);
});
});
}
/**
* Store token to disk be used in later program executions.
*
* #param {Object} token The token to store to disk.
*/
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code != 'EEXIST') {
throw err;
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) throw err;
console.log('Token stored to ' + TOKEN_PATH);
});
}
/**
* Lists the names and IDs of up to 10 files.
*
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function getChannel(auth) {
var service = google.youtube('v3');
service.channels.list({
auth: auth,
part: 'snippet,contentDetails,statistics',
forUsername: 'GoogleDevelopers'
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
var channels = response.data.items;
if (channels.length == 0) {
console.log('No channel found.');
} else {
console.log('This channel\'s ID is %s. Its title is \'%s\', and ' +
'it has %s views.',
channels[0].id,
channels[0].snippet.title,
channels[0].statistics.viewCount);
}
});
}
Code shamelessly ripped from YouTube API quick start for nodejs.
backend access
Due to the fact that YouTube API does not support service accounts. Accessing the data from a backend service can be tricky but it is not imposible.
Run your application locally once.
Aurhtoirze it to access your account data.
In your code find the credentials that are stored they should contain a refresh token.
Save this refresh token as part of your application.
set up your code to read this refresh token when loading.
Unfortunately i am not a node.js developer so i cant help you with the code required to do that. The library should be storing things into a credentials object if you can find that and how thats loaded then you should be able to do what i have suggested.
I would start by digging around into what ever storeToken(token); is doing.

can't get refresh token for google api. Node js

I've got an Apps Script script that I am running remotely through Google's API. When I go to the link it gives me for the code to retrieve the access token, it says that the file requested does not exist. However, the code is in the url anyway, this gives me the access token, but not the refresh token.
I have tried adding 'access_type: offline' and 'approval_prompt: force', but those didn't change anything. Here's the code:
var { google } = require('googleapis');
var fs = require('fs');
var async = require('async');
const readline = require('readline');
// If modifying these scopes, delete token.json.
const SCOPES = [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/script.projects",
"https://www.googleapis.com/auth/spreadsheets"
];
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
fs.readFile('./credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Apps Script API.
authorize(JSON.parse(content), callScriptFunction);
});
/**
* 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("294862899955-bb0929ato2qem8cqllggrpuqpqit191v.apps.googleusercontent.com", "Ds4-q0G3QZog4UamQrc3HFrW", "https://script.google.com/oauthcallback");
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getAccessToken(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 getAccessToken(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) console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
function callScriptFunction(auth) {
var scriptId = "MwnuiFwldt-0ZLyLnoi0Q5kfoO49Cn6ao";
var script = google.script('v1');
script.scripts.run({
auth: auth,
resource: {
function: 'automateSheet',
parameters: [
process.argv[2],
process.argv[3],
process.argv[4],
process.argv[5],
process.argv[6],
process.argv[7],
process.argv[8],
]
},
scriptId: scriptId,
devMode: true
}, function (err, resp) {
if (err) {
console.log(err);
}
else {
var r = resp.data;
if ("error" in r) {
console.log("Error: %o", r.error);
} else {
console.log("Result: %o", r.response.result);
}
}
});
}
Here's the page Google gives me when I agree to allow the app access to my account:
i found the answer here:
Not receiving Google OAuth refresh token
where the guy said to go to https://myaccount.google.com/u/2/permissions?pli=1&pageId=none
then revoke your apps permission. it worked for me. because every time after the first time, the refresh token does not appear
I think there are two issues going on. One, it looks like your Authorized redirect URI isn't set correctly. It needs to be set to a page on your server that listens for the code URL parameter after the user authorizes. You can set it in the Google Cloud console under API > Credentials > 0Auth Client Settings > Authorized redirect URIs.
Second, I had an issue where sometimes I wouldn't get a refresh token. Turns out I had to add prompt: 'consent' to the generateAuthUrl request to force it to always return a refresh token, by always taking the user through the full authorization flow (instead of skipping steps if they recently authenticated). So your function would be:
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
prompt: 'consent',
});
Hope that helps. For reference: https://github.com/googleapis/google-api-nodejs-client/issues/750#issuecomment-368873635

Send email from server via a G Suite email address

I am attempting to send an email from a node.js server using nodemailer
I currently have a domain name registered with Google Domains and with this I have a G Suite instance setup to provide me with an email server.
I have an email setup: noreply#domainname.com
What im trying to do is send an email from email above from my server. I don't want to use plain user and password verification as this is very insecure for me.
Does anyone have a tutorial or document they could link me to that would help me achieve this?
Here is a Quickstart from the official Gmail API documentation.
Complete the steps described in the rest of this page, and in about
five minutes you'll have a simple Node.js command-line application
that makes requests to the Gmail API.
Snippet from the quickstart:
var fs = require('fs');
var readline = require('readline');
var google = require('googleapis');
var googleAuth = require('google-auth-library');
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/gmail-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'gmail-nodejs-quickstart.json';
// Load client secrets from a local file.
fs.readFile('client_secret.json', function processClientSecrets(err, content) {
if (err) {
console.log('Error loading client secret file: ' + err);
return;
}
// Authorize a client with the loaded credentials, then call the
// Gmail API.
authorize(JSON.parse(content), listLabels);
});
/**
* 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) {
var clientSecret = credentials.installed.client_secret;
var clientId = credentials.installed.client_id;
var redirectUrl = credentials.installed.redirect_uris[0];
var auth = new googleAuth();
var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function(err, token) {
if (err) {
getNewToken(oauth2Client, callback);
} else {
oauth2Client.credentials = 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 to call with the authorized
* client.
*/
function getNewToken(oauth2Client, callback) {
var authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
console.log('Authorize this app by visiting this url: ', authUrl);
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Enter the code from that page here: ', function(code) {
rl.close();
oauth2Client.getToken(code, function(err, token) {
if (err) {
console.log('Error while trying to retrieve access token', err);
return;
}
oauth2Client.credentials = token;
storeToken(token);
callback(oauth2Client);
});
});
}
/**
* Store token to disk be used in later program executions.
*
* #param {Object} token The token to store to disk.
*/
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code != 'EEXIST') {
throw err;
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token));
console.log('Token stored to ' + TOKEN_PATH);
}
/**
* Lists the labels in the user's account.
*
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function listLabels(auth) {
var gmail = google.gmail('v1');
gmail.users.labels.list({
auth: auth,
userId: 'me',
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
var labels = response.labels;
if (labels.length == 0) {
console.log('No labels found.');
} else {
console.log('Labels:');
for (var i = 0; i < labels.length; i++) {
var label = labels[i];
console.log('- %s', label.name);
}
}
});
}
To understand the concept and the implementation details, you can try Sending Email. To give you an overview, here is the way of sending email and high end workflow.
There are two ways to send email using the Gmail API:
You can send it directly using the messages.send method.
You can send it from a draft, using the drafts.send method.
Emails are sent as base64url encoded strings within the raw property
of a message resource. The high-level workflow to send an email
is to:
Create the email content in some convenient way and encode it as a base64url string.
Create a new message resource and set its raw property to the base64url string you just created.
Call messages.send, or, if sending a draft, drafts.send to send the message.
You can also refer to this tutorial from Nodemailer community site.
These docs apply to the unmaintained versions of Nodemailer v2 and
older. For an upgraded and up to date Nodemailer v3+ documentation,
see nodemailer.com homepage.
Instead of building whole logic for creating tokens you can simply use Passportjs google auth and then finally use the googleapis package to send emails.

Resources