Hi I'm trying to update a Google Doc with NodeJS using the Google Drive API and I'm getting this error:
{
"error": {
"code": 500,
"message": null
}
}
Here's the relevant code :
var j = new google.auth.JWT(
creds.client_email,
null,
creds.private_key,
[
"https://www.googleapis.com/auth/drive"
]
);
async function refreshTokens() {
startedWaiting = true;
return j.authorize((r,t) => {
startedWaiting = false;
timeTillNeedRefresh = t["expiry_date"] - Date.now();
setTimeout(function() {
refreshTokens();
// console.log("doing Q", Q);
}, timeTillNeedRefresh);
tokens = t;
console.log("GOT A TOKEN", tokens);
Q.forEach(x=>x());
Q = [];
});
}
async function start() {
await refreshTokens();
}
start();
function myFetch(opts) {
if(!opts) opts = {};
var cb = opts.cb || empty;
var headers = {
'Accept-Encoding': 'gzip',
'User-Agent': 'google-api-nodejs-client/0.7.2 (gzip)',
Authorization:tokens.token_type +" "+ tokens.access_token,
Accept:"application/json"
};
if(opts.headers) {
for(k in opts.headers) {
headers[k] = opts.headers[k];
}
}
fetch(
opts.url
|| "",
{
method:opts.method || "GET",
headers: headers
})
.then(res => res.text())
.then(body => {
cb(body);
});
}
updateFile({
id: "1vebqOamZ9QB4HqfUaQN-U9Zt4y33eij-I21glMvaPVQ",
content: "hi there"
});
async function allFuncs(tmp) {
if(tokens === null) {
console.log("DOING it later")
Q.push(tmp);
await refreshTokens();
} else if(startedWaiting || timeTillNeedRefresh <= 0) {
console.log("NOT for a while");
Q.push(tmp);
} else {
console.log("THIS is what should happen");
start = Date.now();
tmp();
}
}
async function updateFile(opts) {
var id = opts.id,
content = opts.content || "";
if(id) {
allFuncs(() => {
myFetch({
url:`https://www.googleapis.com/upload/drive/v3/files/${id}?uploadType=media`,
method:"PATCH",
headers: {
body: opts.content,
"Content-Type": "application/vnd.google-apps.document"
},
cb(b) {
console.log(b, "DID I DO IT?!");
}
});
});
}
}
I tried looking up what this error means but I couldn't find anything related to nodejs...
Does anyone know if its a header issue or is there any way to fix it?
I don't know if its possible to do wih a service key, or if you HAVE to verify the user in order to do so??
SEEMINGLY if the service key email has edit permissions for the file, it should just be able to edit it at will.
You want to overwrite the existing Google Document by a text of hi there with Drive API using Service Account.
The Google Document is shared with the Service Account.
You can use googleapis.
I could understand like above. If my understanding is correct, how about this sample script? In this sample script, I used the files.update method of Drive API.
Sample script:
Before you run the script, please set the json file path downloaded when the Service Account is created. And please confirm the file ID of Google Document, again.
const stream = require('stream');
const {google} = require('googleapis');
const creds = require('###'); // Please set the json file path downloaded when the Service Account is created.
const jwtClient = new google.auth.JWT(
creds.client_email,
null,
creds.private_key,
['https://www.googleapis.com/auth/drive'],
null
);
const id = "1vebqOamZ9QB4HqfUaQN-U9Zt4y33eij-I21glMvaPVQ"; // Please set the file ID of Google Document
const content = "hi there"; // Please set the text.
const drive = google.drive({version: 'v3', auth: jwtClient});
const buf = Buffer.from(content, 'binary');
const buffer = Uint8Array.from(buf);
var bufferStream = new stream.PassThrough();
bufferStream.end(buffer);
const media = {
mimeType: 'application/vnd.google-apps.document',
body: bufferStream,
};
drive.files.update({
fileId: id,
media: media,
}, (err, res) => {
if (err) {
console.log(err);
return;
}
console.log(res.data);
});
Note:
When you run the script, the existing Google Document is overwritten. So please be careful this. I recommend to use a sample Document for testing.
If this script didn't work, please confirm the following points.
Drive API is enabled at API console.
The Service Account can be used.
The Google Document is shared with the Service Account.
The version of googleapis is the latest one.
Reference:
Files: update
Edit:
You don't want to use googleapis.
You want to overwrite Google Document with a text value without using googleapis.
From your comments, I understood like above. If my understanding is correct, how about this sample script? When you run this script, please confirm the following points.
About the script,
Please set privateKey and clientEmail from JSON file of Service Account.
Please set the file ID of the existing Google Document you want to overwrite.
Drive API is enabled at API console.
The Service Account can be used.
The Google Document is shared with the Service Account.
The version of googleapis is the latest one.
Sample script:
const cryptor = require('crypto');
const request = require('request');
// Get access token using Service Account
function getAccessToken(serviceAccount) {
const scopes = ["https://www.googleapis.com/auth/drive"];
const url = "https://www.googleapis.com/oauth2/v4/token";
const header = {
alg: "RS256",
typ: "JWT",
};
const now = Math.floor(Date.now() / 1000);
const claim = {
iss: serviceAccount.clientEmail,
scope: scopes.join(" "),
aud: url,
exp: (now + 3600).toString(),
iat: now.toString(),
};
const signature = Buffer.from(JSON.stringify(header)).toString('base64') + "." + Buffer.from(JSON.stringify(claim)).toString('base64');
var sign = cryptor.createSign('RSA-SHA256');
sign.update(signature);
const jwt = signature + "." + sign.sign(serviceAccount.privateKey, 'base64');
return new Promise(function(resolve, reject) {
request({
method: "post",
url: url,
body: JSON.stringify({
assertion: jwt,
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
}),
}, (err, res, body) => {
if (err) {
console.log(err);
return;
}
const obj = JSON.parse(body);
resolve(obj.access_token);
});
});
}
// Overwrite file in Google Drive
function overWriting(object) {
const metadata = {
mimeType: "application/vnd.google-apps.document",
};
const url = "https://www.googleapis.com/upload/drive/v3/files/" + object.googleDocumentFileId + "?uploadType=multipart";
const boundary = "xxxxxxxxxxx";
var data = "--" + boundary + "\r\n";
data += "Content-Disposition: form-data; name=\"metadata\"\r\n";
data += "Content-Type: application/json; charset=UTF-8\r\n\r\n";
data += JSON.stringify(metadata) + "\r\n";
data += "--" + boundary + "\r\n";
data += "Content-Disposition: form-data; name=\"file\"; filename=\"sample.txt\"\r\n";
data += "Content-Type: text/plain" + "\r\n\r\n";
const payload = Buffer.concat([
Buffer.from(data, "utf8"),
new Buffer(object.textData, 'binary'),
Buffer.from("\r\n--" + boundary + "--", "utf8"),
]);
const options = {
method: 'patch',
url: url,
headers: {
"Content-Type": "multipart/related; boundary=" + boundary,
'Authorization': 'Bearer ' + object.accessToken,
},
body: payload,
};
request(options, (error, response, body) => {
console.log(body);
});
}
async function main() {
const serviceAccount = {
privateKey: "###", // private_key of JSON file retrieved by creating Service Account
clientEmail: "###", // client_email of JSON file retrieved by creating Service Account
};
var object = {
googleDocumentFileId: "###", // Set the file ID of the existing Google Document
textData: "hi there",
};
const accessToken = await getAccessToken(serviceAccount);
if (accessToken) {
object.accessToken = accessToken;
overWriting(object);
}
}
main();
Related
I am working on protecting a static website with a username and password. I created a basic HTTP Authentication for CloudFront with Lambda#Edge in NodeJS.
I am completely new to NodeJS. Initially, I had the user and the password hardcoded, and this worked properly.
'use strict';
exports.handler = (event, context, callback) => {
// Get request and request headers
const request = event.Records[0].cf.request;
const headers = request.headers;
// Configure authentication
const authUser = 'user';
const authPass = 'pass';
// Construct the Basic Auth string
const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');
// Require Basic authentication
if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
const body = 'Unauthorized';
const response = {
status: '401',
statusDescription: 'Unauthorized',
body: body,
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
},
};
callback(null, response);
}
// Continue request processing if authentication passed
callback(null, request);
};
I stored my secrets in SSM and I want to retrieve them through the function. I tested this piece of code separately in Lambda and it returns the credentials as espected.
'use strict';
exports.handler = async (event, context, callback) => {
const ssm = new (require('aws-sdk/clients/ssm'))();
let userData = await ssm.getParameters({Names: ['website-user']}).promise();
let userPass = await ssm.getParameters({Names: ['website-pass']}).promise();
let user = userData.Parameters[0].Value;
let pass = userPass.Parameters[0].Value;
return {user, pass};
};
But when I stitch the two, I get 503 ERROR The request could not be satisfied.
Does anyone know what I might be doing wrong? Thank you for your help!
The complete code:
'use strict';
exports.handler = async (event, context, callback) => {
const ssm = new (require('aws-sdk/clients/ssm'))();
let userData = await ssm.getParameters({Names: ['website-user']}).promise();
let userPass = await ssm.getParameters({Names: ['website-pass']}).promise();
let user = userData.Parameters[0].Value;
let pass = userPass.Parameters[0].Value;
// Get request and request headers
const request = event.Records[0].cf.request;
const headers = request.headers;
// Construct the Basic Auth string
let authString = 'Basic ' + new Buffer(user + ':' + pass).toString('base64');
// Require Basic authentication
if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
const body = 'Unauthorized';
const response = {
status: '401',
statusDescription: 'Unauthorized',
body: body,
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
},
};
callback(null, response);
}
// Continue request processing if authentication passed
callback(null, request);
};
After reading about promises I was able to resolve the error. Here's the solution that worked for me:
'use strict';
var AWS = require('aws-sdk');
AWS.config.update({ region: 'us-east-1' });
var ssm = new AWS.SSM();
function getParameter(param) {
return new Promise(function (success, reject) {
ssm.getParameter(param, function (err, data) {
if (err) {
reject(err);
} else {
success(data);
}
});
});
};
exports.handler = (event, context, callback) => {
let request = event.Records[0].cf.request;
let headers = request.headers;
let user = {Name: 'user-path', WithDecryption: false};
let pass = {Name: 'password-path', WithDecryption: false};
let authUser;
let authPass;
var promises = [];
promises.push(getParameter(user), getParameter(pass));
Promise.all(promises)
.then(function (result) {
authUser = result[0].Parameter.Value;
authPass = result[1].Parameter.Value;
console.log(authUser);
console.log(authPass);
let authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');
if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
const body = 'Unauthorized';
const response = {
status: '401',
statusDescription: 'Unauthorized',
body: body,
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
},
};
callback(null, response);
}
// Continue request processing if authentication passed
callback(null, request);
})
.catch(function (err) {
console.log(err);
});
};
Thank you for sharing the solution. And let me do my humble contribution.
To successfully authenticate additionally need the choice right version of Node.js(14.x)
Grant AWS Lambda Access to SSM Parameter Store to retrieve AWS Parameter Store:
Go to Lambda function -> Configuration -> Permissions -> Role name -> Whatever-Your-Role-Name-> Add Permissions -> Create inline Policy -> JSON -> And input the policy as shown below:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:GetParametersByPath",
"ssm:PutParameter"
],
"Resource": [
"arn:aws:ssm:us-east-1:YOUR_ACCOUNT_NUMBER:PARAMETER_NAME_WITHOUT_LEADING_SLASH",
"arn:aws:ssm:us-east-1:252522211181:parameter/web-pass"
]
}
]
}
You could set "ssm:*" for the Action element in the policy to grant full parameter store access to the lambda function.
You could also set the Resource element to be *, which means the function can access all SSM parameters in the account.
I am using the Express JS server to execute AWS MWS API. As per MWS documentation, _GET_REMOTE_FULFILLMENT_ELIGIBILITY_ return excel file object.
I created API in node js but I am not able to get proper excel. I got weird characters in the downloaded excel file.
You can see the downloaded excel file in the attachment.
const getRemoveFulfillmentEligibilityDataCon = async(req,res) => {
const mwsRequestData = {
Version: '2009-01-01',
Action: 'GetReport',
'SellerId': 'MWS_SELLER_ID',
'MWSAuthToken': 'MWS_AUTH_TOKEN',
ReportId: 'XXXXXXXXXXXXXX',
};
try {
const response = await amazonMws.reports.search(mwsRequestData);
/* make the worksheet */
var ws = XLSX.utils.json_to_sheet(response.data);
var wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws);
XLSX.writeFile(wb, "sheetjs.xlsx");
return response;
} catch (error) {
console.log('error ', error);
return error;
}
}
Finally, I got a solution. I changed my method to call API as below and I got a proper excel file.
I used node-fetch and then send request to Amazon MWS by node-fetch.
node-fetch decode content encoding (gzip/deflate) properly and convert string output to UTF-8 automatically.
var param = {};
param['AWSAccessKeyId'] = 'xxxxxxxxxxxxx';
param['Action'] = 'GetReport';
param['MarketplaceId'] = 'xxxxxxxxxxxxx';
param['SellerId'] = 'xxxxxxxxxxxxx';
param['ReportId'] = 'xxxxxxxxxxxxx';
param['ItemCondition'] = 'New';
param['SignatureMethod'] = 'HmacSHA256';
param['SignatureVersion'] = '2';
param['Timestamp'] = encodeURIComponent(new Date().toISOString());
param['Version'] = '2009-01-01';
secret = 'xxxxxxxxxxxxx';
var url = [];
for(var i in param){
url.push(i+"="+param[i])
}
url.sort();
var arr = url.join("&");
var sign = 'POST\n'+'mws.amazonservices.com\n'+'/Reports/2009-01-01\n'+arr;
const crypto = require('crypto');
let s64 = crypto.createHmac("sha256", secret).update(sign).digest('base64');
let signature = encodeURIComponent(s64);
var bodyData = arr+"&Signature="+signature;
const fileName = "sheetjs.xlsx";
var apiResponse = await fetch('https://mws.amazonservices.com/Reports/2009-01-01', {
method: 'POST',
body: bodyData,
headers: {
'content-type': 'application/x-www-form-urlencoded',
'Accept': '',
},
})
.then(res => {
const dest = fs.createWriteStream('./'+fileName);
res.body.pipe(dest).on('finish', function(){
//console.log('done');
return {'Status': 200, 'Message': 'Downloaded'};
});
})
.catch(error => {
console.log('Request failed', error);
});
return apiResponse;
}
I have the following code below for clover payment api and although the code works perfectly, I do not get a response back when I call the api from postman. I know this is because of the multiple requests in the service and I tried to find a cleaner way to do it but keep failing to get it to work. I am trying to send back the final response which is the response of the request in the postPayment() function. Any help would be appreciated.
my service code is:
const db = require('../_helpers/db');
const crypto = require('crypto');
const request = require("request-promise");
module.exports = {
getAll
};
var targetEnv = 'https://sandbox.dev.clover.com/v2/merchant/';
var cardNumber = '6011361000006668';
async function getAll(data) {
var url = targetEnv + data.merchant_id + '/pay/key';
var options = {
url: url,
method: 'GET',
headers: {
Authorization: 'Bearer ' + data.api_token
}
};
request(options, (error, response, body) => {
if (!error && response.statusCode === 200) {
console.log('getAll ' +data);
processEncryption(JSON.parse(body), JSON.stringify(data));
}
});
}
// Process the encryption information received by the pay endpoint.
function processEncryption(jsonResponse, data) {
console.log('processEncryption ' +data);
var prefix = jsonResponse['prefix'];
var pem = jsonResponse['pem'];
// create a cipher from the RSA key and use it to encrypt the card number, prepended with the prefix from GET /v2/merchant/{mId}/pay/key
var encrypted = crypto.publicEncrypt(pem, Buffer(prefix + cardNumber));
// Base64 encode the resulting encrypted data into a string to Clover as the 'cardEncrypted' property.
var cardEncrypted = new Buffer(encrypted).toString('base64');
return postPayment(cardEncrypted, data);
}
// Post the payment to the pay endpoint with the encrypted card information.
async function postPayment(cardEncrypted, body) {
// POST to /v2/merchant/{mId}/pay
console.log('mid ' +JSON.parse(body));
var posturl = targetEnv + '9ZQTAJSQKZ391/pay';
var postData = {
"orderId": "4N3RBF33EBEGT",
"currency": "usd",
"amount": 2,
"tipAmount": 0,
"taxAmount": 0,
"expMonth": 12,
"cvv": 123,
"expYear": 2018,
"cardEncrypted": cardEncrypted,
"last4": 6668,
"first6": 601136,
"streetAddress": "123 Fake street",
"zip": "94080",
"merchant_id": "9ZQTAJSQKZ391",
"order_id": "4N3RBF33EBEGT",
"api_token": "4792a281-38a9-868d-b33d-e36ecbad66f5"
}
var options = {
url: posturl,
method: 'POST',
headers: {
'Authorization': 'Bearer ' + "4792a281-38a9-868d-b33d-e36ecbad66f5",
},
json: postData
};
request(options, (error, response, body) => {
if (!error && response.statusCode === 200) {
//console.log(response);
return response; <---- this response is what i need to show in postman
}
});
console.log(response);
}
my controller is:
const express = require('express');
const router = express.Router();
const tableOrderService = require('./cloverPayment.service');
// routes
router.post('/getAll', getAll);
module.exports = router;
function getAll(req, res, next) {
tableOrderService.getAll(req.body)
.then(users => res.json(users))
.catch(err => next(err));
}
Your asynchronous functions getAll() and postPayment() are not properly returning an asynchronous value (either via callback or promise).
I'd suggest converting everything to promises and returning a promise from getAll() and from postPayment(). And, since converting to promises, I'd remove the deprecated request-promise library in favor of the got() library. Then, you can call getAll(), get a promise back and use either the resolved value or the rejection to send your response from the actual request handler:
const db = require('../_helpers/db');
const crypto = require('crypto');
const got = require('got');
module.exports = {
getAll
};
var targetEnv = 'https://sandbox.dev.clover.com/v2/merchant/';
var cardNumber = '6011361000006668';
async function getAll(data) {
console.log('getAll ', data);
const url = targetEnv + data.merchant_id + '/pay/key';
const options = {
url: url,
method: 'GET',
headers: {
Authorization: 'Bearer ' + data.api_token
}
};
const response = await got(options);
return processEncryption(response, data);
}
// Process the encryption information received by the pay endpoint.
function processEncryption(jsonResponse, data) {
console.log('processEncryption ' + data);
const prefix = jsonResponse.prefix;
const pem = jsonResponse.pem;
// create a cipher from the RSA key and use it to encrypt the card number, prepended with the prefix from GET /v2/merchant/{mId}/pay/key
const encrypted = crypto.publicEncrypt(pem, Buffer(prefix + cardNumber));
// Base64 encode the resulting encrypted data into a string to Clover as the 'cardEncrypted' property.
const cardEncrypted = Buffer.from(encrypted).toString('base64');
return postPayment(cardEncrypted, data);
}
// Post the payment to the pay endpoint with the encrypted card information.
function postPayment(cardEncrypted, body) {
// POST to /v2/merchant/{mId}/pay
console.log('mid ', body);
const posturl = targetEnv + '9ZQTAJSQKZ391/pay';
const postData = {
"orderId": "4N3RBF33EBEGT",
"currency": "usd",
"amount": 2,
"tipAmount": 0,
"taxAmount": 0,
"expMonth": 12,
"cvv": 123,
"expYear": 2018,
"cardEncrypted": cardEncrypted,
"last4": 6668,
"first6": 601136,
"streetAddress": "123 Fake street",
"zip": "94080",
"merchant_id": "9ZQTAJSQKZ391",
"order_id": "4N3RBF33EBEGT",
"api_token": "4792a281-38a9-868d-b33d-e36ecbad66f5"
}
const options = {
url: posturl,
method: 'POST',
headers: {
'Authorization': 'Bearer ' + "4792a281-38a9-868d-b33d-e36ecbad66f5",
},
json: postData
};
return got(options);
}
And, then your controller:
const express = require('express');
const router = express.Router();
const tableOrderService = require('./cloverPayment.service');
// routes
router.post('/getAll', (req, res) => {
tableOrderService.getAll(req.body)
.then(users => res.json(users))
.catch(err => next(err));
});
module.exports = router;
I'm able to get access oauth2 token from google api by using below code:
<html><head></head><body>
<script>
var YOUR_CLIENT_ID =
'568020407566-s1tdctjbjeocf5nt20l64midfo1atu4h.apps.googleusercontent.com';
var YOUR_REDIRECT_URI =
'https://us-central1-hyperledger-poc.cloudfunctions.net/oauthjax';
var fragmentString = location.hash.substring(1);
// Parse query string to see if page request is coming from OAuth 2.0 server.
var params = {};
var regex = /([^&=]+)=([^&]*)/g, m;
while (m = regex.exec(fragmentString)) {
params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
}
if (Object.keys(params).length > 0) {
localStorage.setItem('oauth2-test-params', JSON.stringify(params) );
if (params['state'] && params['state'] == 'try_sample_request') {
trySampleRequest();
}
}
// If there's an access token, try an API request.
// Otherwise, start OAuth 2.0 flow.
function trySampleRequest() {
var params = JSON.parse(localStorage.getItem('oauth2-test-params'));
if (params && params['access_token']) {
var xhr = new XMLHttpRequest();
xhr.open('GET',
'https://www.googleapis.com' +
'access_token=' + params['access_token']);
xhr.onreadystatechange = function (e) {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.response);
} else if (xhr.readyState === 4 && xhr.status === 401) {
// Token invalid, so prompt for user permission.
oauth2SignIn();
}
};
xhr.send(null);
} else {
oauth2SignIn();
}
}
/*
* Create form to request access token from Google's OAuth 2.0 server.
*/
function oauth2SignIn() {
// Google's OAuth 2.0 endpoint for requesting an access token
var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
// Create element to open OAuth 2.0 endpoint in new window.
var form = document.createElement('form');
form.setAttribute('method', 'GET'); // Send as a GET request.
form.setAttribute('action', oauth2Endpoint);
// Parameters to pass to OAuth 2.0 endpoint.
var params = {'client_id': YOUR_CLIENT_ID,
'redirect_uri': YOUR_REDIRECT_URI,
'scope': 'https://www.googleapis.com/auth/userinfo.email',
'state': 'try_sample_request',
'include_granted_scopes': 'true',
'response_type': 'token'};
// Add form parameters as hidden input values.
for (var p in params) {
var input = document.createElement('input');
input.setAttribute('type', 'hidden');
input.setAttribute('name', p);
input.setAttribute('value', params[p]);
form.appendChild(input);
}
// Add form to page and submit it to open the OAuth 2.0 endpoint.
document.body.appendChild(form);
form.submit();
}
</script>
<button onclick="trySampleRequest();">Try sample request</button>
</body></html>
I'm trying to get the token information using https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123 mentioned in below google document
https://developers.google.com/identity/protocols/oauth2/openid-connect#validatinganidtoken
I'm getting below error as response:
{
"error_description": "Invalid Value"
}
But if i use the same token to get user info, it was working fine by using below code and the response as well
code which i tried:
const token = req.body.access_token;
var options = {
host: 'www.googleapis.com',
port: 443,
//path: '/oauth2/v3/userinfo',
path: '/oauth2/v3/tokeninfo?id_token='+token,
method: 'GET',
headers: {'Authorization': 'Bearer '+ token}
}
// making the https get call
let data;
var getReq = await https.request(options, function(response) {
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', function() {
// Data reception is done, do whatever with it!
console.log("line 52");
// var parsed = JSON.parse(data);
console.log("line 54"+data);
res.send(data);
// console.log('data', parsed);
// console.log('endSTATUS: ' + parsed);
});
});
// console.log("response",recievedJSON);
/*getReq.on('end',function(){
res.send(valid);
});*/
getReq.end();
getReq.on('error', function(err) {
console.log('Error: ', err);
});
Response:
{
"sub": "1070944556782450037456",
"picture": "https://lh6.googleusercontent.com/-b32hSj9vONg/AAAAAAAAAAI/AAAAAAAAAAA/tasdfsadfBfIQ/photo.jpg",
"email": "xxxxxxxxx#gmail.com",
"email_verified": true
}
I'm trying to import gmail contacts. I have successfully obtained the access_token but when trying to get the contacts browser keeps throwing the error. invalid_grant
my codes below.
for authentication and callback.
authorize: function(req, res){
var CLIENT_ID = '927112080821-vhsphqc79tb5ohfgpuvrp8uhh357mhad.apps.googleusercontent.com';
var CLIENT_SECRET = 'gdgofL0RfAXX0in5JEiQiPW8';
var SCOPE = 'https://www.google.com/m8/feeds';
oa = new oauth.OAuth2(CLIENT_ID,
CLIENT_SECRET,
"https://accounts.google.com/o",
"/oauth2/auth",
"/oauth2/token");
res.redirect(oa.getAuthorizeUrl({scope:SCOPE, response_type:'code', redirect_uri:'http://localhost:1234/callback'}));
},
callback: function(req, res){
console.log(req.query.code);
oa.getOAuthAccessToken(req.query.code, {grant_type:'authorization_code', redirect_uri:'http://localhost:1234/callback'}, function(err, access_token, refresh_token) {
if (err) {
res.end('error: ' + JSON.stringify(err));
} else {
getContactsFromGoogleApi(access_token);
//res.write('access token: ' + access_token + '\n');
//res.write('refresh token: ' + refresh_token);
//res.end();
}
});
},
for importing contacts
function getContactsFromGoogleApi (access_token, req, res, next) {
console.log('access_token ' + access_token);
request.get({
url: 'https://www.google.com/m8/feeds/contacts/default/full',
qs: {
alt: 'json',
'max-results': 1000,
'orderby': 'lastmodified'
},
headers: {
'Authorization': 'OAuth ' + access_token,
'GData-Version': '3.0'
}
}, function (err, res, body) {
if(res.statusCode === 401){
return res.redirect('index');
}
var feed = JSON.parse(body);
var users = feed.feed.entry.map(function (c) {
var r = {};
if(c['gd$name'] && ['gd$fullName']){
r.name = c['gd$name']['gd$fullName']['$t'];
}
if (c['gd$email'] && c['gd$email'].length > 0) {
r.email = c['gd$email'][0]['address'];
r.nickname = r.email;//c['gd$email'][0]['address'].split('#')[0];
}
if(c['link']){
var photoLink = c['link'].filter(function (link) {
return link.rel == 'http://schemas.google.com/contacts/2008/rel#photo' &&
'gd$etag' in link;
})[0];
if(photoLink) {
r.picture = '/users/photo?l=' + encodeURIComponent(photoLink.href);
} else if (r.email) {
r.picture = gravatar.url(r.email, {
s: 40,
d: "https://ssl.gstatic.com/s2/profiles/images/silhouette80.png"});
}
}
return r;
}).filter(function (u) {
return !!u.email && //we can only give access to persons with email at this point
!~u.email.indexOf('#reply.'); //adress with #reply. are usually temporary reply address for forum kind of websites.
});
res.json(users);
});
}
really appreciate help.
In your getContactsFromGoogleApi function change the Authorization header to the following;
headers: {
'Authorization': 'Bearer ' + access_token,
'GData-Version': '3.0'
The following is code in C# to do the same thing
webClient = new WebClient();
webClient.Headers.Add("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)");
webClient.Headers.Add(HttpRequestHeader.Authorization, String.Format("Bearer {0}", AccessToken));
do
{
contactStream = webClient.DownloadData(String.Format("{0}?start-index={1}", BaseApiEndpoints[Applications.Contacts], startIndex));
contactDocument = System.Text.Encoding.Default.GetString(contactStream);
contacts = new XmlDocument();
contacts.LoadXml(contactDocument);
// TODO: use xsl to convert results to ContactSearchResults
xslt = new XslCompiledTransform();
resultStream = new MemoryStream();
writer = new XmlTextWriter(resultStream, Encoding.ASCII);
translateContact = new XmlDocument();
xslStream = GetRequest("GoogleContacts.xslt");
xslStream.Seek(0, SeekOrigin.Begin);
templateReader = XmlReader.Create(xslStream);
xslt.Load(templateReader);
xslt.Transform(contacts,writer);
resultStream.Seek(0, SeekOrigin.Begin);
TranslatedContacts = ConvertByteArrayToString(ReadFully(resultStream));
csr = (ContactSearchResults)Deserialize(typeof(ContactSearchResults), TranslatedContacts);
foreach (Contact contact in csr.Contacts)
{
contact.RecordId = Guid.NewGuid();
// now we want to get the image
contact.Picture = GetImage(contact.PictureUrl, AccessToken);
if ((!String.IsNullOrEmpty(contact.Name)) || (!String.IsNullOrEmpty(contact.Email)))
results.Add(contact);
}
startIndex += csr.ItemsPerPage;
} while (startIndex < totalResults);
Url I used here is the same as your code; http://www.google.com/m8/feeds/
When I did this in C# I did not need to pass the version.