Basic HTTP Authentication for CloudFront with Lambda#Edge in NodeJS - node.js

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.

Related

Lambda NodeJS works intermittent with async method

I have a lambda function written in node.js. The lambda logic is to extract secrets from aws secret manager and pass the params to another module with a token to make a soap call.This lambda was working as expected without async method to get secreats when secreats hardcoded.
But after adding an async getsecrets method the lambda works intermittently while testing in aws console. The getsecreats returns params every time. But the lambda terminates without the intended output. It doesn't make any soap call.
The logic of the lambda code is
Get the secret
Pass the secret to the CallServicewithToken()
get XML data from soap and populate it into the database.
Why it works intermittently when introducing async calls? I have tried introducing async to all methods. still the same issue. Appreciate any input/help.
The code is as below
'use strict';
var soap = require('strong-soap').soap;
const aws = require("aws-sdk");
const request = require('request');
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0;
var rfcUrl = process.env.rfcser_url;
var testUrl = process.env.faccser_url;
var region = process.env.region;
var secretName = process.env.secretName;
var oauthurl = process.env.oauthurl;
var rfcRequestArgs;
var parsedResult;
var tokenrequest;
exports.handler = async function(event, context,callback) {
const secret = await getSecrets();
CallServicewithToken();
};
async function getSecrets() {
const config = { region : region, apiVersion: 'latest' };
let secretManager = new aws.SecretsManager(config);
const Result = await secretManager.getSecretValue({ SecretId: secretName }).promise();
parsedResult = JSON.parse(Result.SecretString);
tokenrequest = {
url: oauthurl,
form: {
client_id: parsedResult.client_id,
client_secret: parsedResult.client_secret,
grant_type:'client_credentials',
scope:parsedResult.scope
}
};
console.log('client_id: ' + parsedResult.client_id);
console.log('client_secret: ' + parsedResult.client_secret);
console.log('testservice_userid: ' + parsedResult.testservice_userid);
}
function CallServicewithToken() {
console.log('Calling CallServicewithToken ');
request.post(tokenrequest, (err, res, body) => {
if (err) {
console.log(' error2: ' + err);
return;
}
rfcRequestArgs = {
UserName: parsedResult.service_username
};
var tokenobj = JSON.parse(body);
var token = 'Bearer '+ tokenobj.access_token;
var credentials = {
Authorization:{
AuthToken: token
}
}
var options = {};
console.log('Calling Service.');
soap.createClient(rfcUrl, options, function(err, client) {
client.addSoapHeader(
`<aut:Authorization xmlns:aut="http://soap.xyznet.net">
<aut:AuthToken>${token}</aut:AuthToken>
</aut:Authorization>`
);
var method = client['GetSourceLocationData'];
method(RequestArgs, function(err, result, envelope, soapHeader) {
if (err) {
console.log('error3: ' + err);
return;
}
else
{
console.log('Received response from GetSourceLocationData().');
CallTESTService(JSON.stringify(result));
}
});
function CallTESTService(LocData)
{
var testRequestArgs = {
UserID: parsedResult.testservice_userid,
AuthorizationKey: parsedResult.testservice_authorizationkey,
LocationData: LocData
};
console.log('Calling test Service.');
options = {};
soap.createClient(testUrl, options, function(err, client) {
client.addSoapHeader(
`<Authorization xmlns="testWebService">
<AuthToken>${token}</AuthToken>
</Authorization>`
);
var test_method = client['UpdateLocationData'];
console.log('Called UpdateLocationData service method.');
test_method(testRequestArgs, function(err, result, envelope, soapHeader) {
if(err) {
console.log('test error: ' + err);
return;
}
else
{
console.log('Response: \n' + JSON.stringify(result));
console.log('Data updated through test service method.');
}
});
});
}
});
});
}

getting 403 from lambda calling api gateway

I have an api post end point which would update a customer's information in dynamodb. It is set to authenticate using AWS_IAM. I am getting 403 from my lambda when calling this api. I have allowed execute-api:Invoke permission to the api for the role lambda uses. I see in this post that I need to create a canonical request. I was able to come up with the below code and I still get a 403. I can't figure out what is missing and wish if a different eye can spot the problem. Please help!
"use strict";
const https = require("https");
const crypto = require("crypto");
exports.handler = async (event, context, callback) => {
try {
var attributes = {
customerId: 1,
body: { firstName: "abc", lastName: "xyz" }
};
await updateUsingApi(attributes.customerId, attributes.body)
.then((result) => {
var jsonResult = JSON.parse(result);
if (jsonResult.statusCode === 200) {
callback(null, {
statusCode: jsonResult.statusCode,
statusMessage: "Attributes saved successfully!"
});
} else {
callback(null, jsonResult);
}
})
.catch((err) => {
console.log("error: ", err);
callback(null, err);
});
} catch (error) {
console.error("error: ", error);
callback(null, error);
}
};
function sign(key, message) {
return crypto.createHmac("sha256", key).update(message).digest();
}
function getSignatureKey(key, dateStamp, regionName, serviceName) {
var kDate = sign("AWS4" + key, dateStamp);
var kRegion = sign(kDate, regionName);
var kService = sign(kRegion, serviceName);
var kSigning = sign(kService, "aws4_request");
return kSigning;
}
function updateUsingApi(customerId, newAttributes) {
var request = {
partitionKey: `MY_CUSTOM_PREFIX_${customerId}`,
sortKey: customerId,
payLoad: newAttributes
};
var data = JSON.stringify(request);
var apiHost = new URL(process.env.REST_API_INVOKE_URL).hostname;
var apiMethod = "POST";
var path = `/stage/postEndPoint`;
var { amzdate, authorization, contentType } = getHeaders(host, method, path);
const options = {
host: host,
path: path,
method: method,
headers: {
"X-Amz-Date": amzdate,
Authorization: authorization,
"Content-Type": contentType,
"Content-Length": data.length
}
};
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
if (res && res.statusCode !== 200) {
console.log("response from api", res);
}
var response = {
statusCode: res.statusCode,
statusMessage: res.statusMessage
};
resolve(JSON.stringify(response));
});
req.on("error", (e) => {
console.log("error", e);
reject(e.message);
});
req.write(data);
req.end();
});
}
function getHeaders(host, method, path) {
var algorithm = "AWS4-HMAC-SHA256";
var region = "us-east-1";
var serviceName = "execute-api";
var secretKey = process.env.AWS_SECRET_ACCESS_KEY;
var accessKey = process.env.AWS_ACCESS_KEY_ID;
var contentType = "application/x-amz-json-1.0";
var now = new Date();
var amzdate = now
.toJSON()
.replace(/[-:]/g, "")
.replace(/\.[0-9]*/, "");
var datestamp = now.toJSON().replace(/-/g, "").replace(/T.*/, "");
var canonicalHeaders = `content-type:${contentType}\nhost:${host}\nx-amz-date:${amzdate}\n`;
var signedHeaders = "content-type;host;x-amz-date";
var payloadHash = crypto.createHash("sha256").update("").digest("hex");
var canonicalRequest = [
method,
path,
canonicalHeaders,
signedHeaders,
payloadHash
].join("/n");
var credentialScope = [datestamp, region, serviceName, "aws4_request"].join(
"/"
);
const sha56 = crypto
.createHash("sha256")
.update(canonicalRequest)
.digest("hex");
var stringToSign = [algorithm, amzdate, credentialScope, sha56].join("\n");
var signingKey = getSignatureKey(secretKey, datestamp, region, serviceName);
var signature = crypto
.createHmac("sha256", signingKey)
.update(stringToSign)
.digest("hex");
var authorization = `${algorithm} Credential=${accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
return { amzdate, authorization, contentType };
}

NodeJS joining promise with request functions

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;

Storing username with post confirmation trigger (AWS, Lambda, DynamoDB)

I try to store some parameters in a AWS DynamoDB with Cognito post confirmation trigger.
The lambda is written in node.js but I am not able to store the username of the signed up cognito user.
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient({region: 'eu-central-1'});
exports.handler = async (event, context, callback) => {
console.log(event);
const username = event.userName;
await createMessage(username).then(() => {
callback(null, {
statusCode: 201,
body: '',
headers: {
'Access Control Allow Origin' : '*'
}
});
}).catch((err) => {
console.error(err);
});
};
function createMessage(username){
const familyid = (new Date()).getTime().toString(36) + Math.random().toString(36).slice(2);
const params = {
TableName: 'eldertech',
Item: {
'UserId' : username,
'message' : familyid
}
};
return dynamodb.put(params).promise();
}
My test:
{
"username":"admin",
"email":"admin#admin.com",
"userId": "AD87S"
}
The error:
ERROR ValidationException: One or more parameter values were invalid: Missing the key UserId in the item
Can somebody please help a newbie?
Okay, I just had a typo....
const username = event.username;
No capital n in username...

NodeJS Google Drive API how to update file

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();

Resources