Skip to content

Commit

Permalink
Use node-fetch for promises in HTTP requests.
Browse files Browse the repository at this point in the history
  • Loading branch information
tswast committed Aug 29, 2018
1 parent 4e59f87 commit 677cf79
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 108 deletions.
186 changes: 79 additions & 107 deletions functions/composer-storage-trigger/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
* limitations under the License.
*/

// [START composer_trigger]
'use strict';

// [START composer_trigger]
const fetch = require('node-fetch');
const FormData = require('form-data');

/**
* Triggered from a message on a Cloud Storage bucket.
*
Expand All @@ -42,105 +45,84 @@ exports.triggerDag = function triggerDag (event, callback) {
const DAG_NAME = 'composer_sample_trigger_response_dag';

// Other constants
const WEBSERVER_URL = 'https://' + WEBSERVER_ID +
'.appspot.com/api/experimental/dags/' + DAG_NAME + '/dag_runs';
const WEBSERVER_URL = `https://${WEBSERVER_ID}.appspot.com/api/experimental/dags/${DAG_NAME}/dag_runs`;
const USER_AGENT = 'gcf-event-trigger';
const BODY = {'conf': JSON.stringify(event.data)};

// Make the request
authorizeIap(
CLIENT_ID, PROJECT_ID, USER_AGENT,
function iapAuthorizationCallback (err, jwt, idToken) {
if (err) {
return callback(err);
}
makeIapPostRequest(
WEBSERVER_URL, BODY, idToken, USER_AGENT, jwt, callback);
});
authorizeIap(CLIENT_ID, PROJECT_ID, USER_AGENT)
.then(function iapAuthorizationCallback (iap) {
makeIapPostRequest(WEBSERVER_URL, BODY, iap.idToken, USER_AGENT, iap.jwt);
})
.then(_ => callback(null))
.catch(callback);
};

/**
* @param {string} clientId The client id associated with the Composer webserver application.
* @param {string} projectId The id for the project containing the Cloud Function.
* @param {string} userAgent The user agent string which will be provided with the webserver request.
* @param {!Function} callback A callback accepting error, jwt, and idToken arguments.
*/
function authorizeIap (clientId, projectId, userAgent, callback) {
const request = require('request');
function authorizeIap (clientId, projectId, userAgent) {
const SERVICE_ACCOUNT = `${projectId}@appspot.gserviceaccount.com`;
const JWT_HEADER = Buffer.from(JSON.stringify({alg: 'RS256', typ: 'JWT'}))
.toString('base64');

const SERVICE_ACCOUNT = [projectId, '@appspot.gserviceaccount.com'].join('');
var jwt = '';
var jwtClaimset = '';

var options = {
url: [
'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/',
SERVICE_ACCOUNT, '/token'
].join(''),
headers: {'User-Agent': userAgent, 'Metadata-Flavor': 'Google'}
};
// Obtain an Oauth2 access token for the appspot service account
request(options, function obtainAccessTokenCallback (err, res, body) {
if (err) {
return callback(err);
}
if (body.error) {
return callback(body);
}
var tokenResponse = JSON.parse(body);
var accessToken = tokenResponse.access_token;
var jwtHeader = Buffer.from(JSON.stringify({alg: 'RS256', typ: 'JWT'}))
.toString('base64');
var iat = Math.floor(new Date().getTime() / 1000);
var claims = {
iss: SERVICE_ACCOUNT,
aud: 'https://www.googleapis.com/oauth2/v4/token',
iat: iat,
exp: iat + 60,
target_audience: clientId
};
var jwtClaimset = Buffer.from(JSON.stringify(claims)).toString('base64');
var toSign = [jwtHeader, jwtClaimset].join('.');
var options = {
url: [
'https://iam.googleapis.com/v1/projects/', projectId,
'/serviceAccounts/', SERVICE_ACCOUNT, ':signBlob'
].join(''),
method: 'POST',
json: {'bytesToSign': Buffer.from(toSign).toString('base64')},
headers: {
'User-Agent': userAgent,
'Authorization': ['Bearer', accessToken].join(' ')
}
};
// Obtain an Oauth2 access token for the appspot service account
return fetch(
`http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/${SERVICE_ACCOUNT}/token`,
{
headers: {'User-Agent': userAgent, 'Metadata-Flavor': 'Google'}
})
.then(res => res.json())
.then(function obtainAccessTokenCallback (tokenResponse) {
var accessToken = tokenResponse.access_token;
var iat = Math.floor(new Date().getTime() / 1000);
var claims = {
iss: SERVICE_ACCOUNT,
aud: 'https://www.googleapis.com/oauth2/v4/token',
iat: iat,
exp: iat + 60,
target_audience: clientId
};
jwtClaimset = Buffer.from(JSON.stringify(claims)).toString('base64');
var toSign = [JWT_HEADER, jwtClaimset].join('.');

return fetch(
`https://iam.googleapis.com/v1/projects/${projectId}/serviceAccounts/${SERVICE_ACCOUNT}:signBlob`,
{
method: 'POST',
body: JSON.stringify({'bytesToSign': Buffer.from(toSign).toString('base64')}),
headers: {
'User-Agent': userAgent,
'Authorization': `Bearer ${accessToken}`
}
});
})
.then(res => res.json())
.then(function signJsonClaimCallback (body) {
// Request service account signature on header and claimset
request(options, function signJsonClaimCallback (err, res, body) {
if (err) {
return callback(err);
}
if (body.error) {
return callback(body);
}
var jwtSignature = body.signature;
var jwt = [jwtHeader, jwtClaimset, jwtSignature].join('.');
var options = {
url: 'https://www.googleapis.com/oauth2/v4/token',
form: {
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: jwt
}
jwt = [JWT_HEADER, jwtClaimset, jwtSignature].join('.');
var form = new FormData();
form.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
form.append('assertion', jwt);
return fetch(
'https://www.googleapis.com/oauth2/v4/token', {
method: 'POST',
body: form
});
})
.then(res => res.json())
.then(function returnJwt (body) {
return {
jwt: jwt,
idToken: body.id_token
};
// Request oauth id token with jwt header, claims, and signature
request.post(options, function getIdTokenCallback (err, res, body) {
if (err) {
return callback(err);
}
if (body.error) {
return callback(body);
}
var idToken = JSON.parse(body).id_token;
callback(null, jwt, idToken);
});
});
});
}

/**
Expand All @@ -149,35 +131,25 @@ function authorizeIap (clientId, projectId, userAgent, callback) {
* @param {string} idToken Bearer token used to authorize the iap request.
* @param {string} userAgent The user agent to identify the requester.
* @param {string} jwt A Json web token used to authenticate the request.
* @param {!Function} callback The Cloud Functions callback.
*/
function makeIapPostRequest (url, body, idToken, userAgent, jwt, callback) {
const request = require('request');

request.post(
{
url: url,
form: {
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: jwt
}
},
function makeIapPostRequestCallback (err, res) {
if (err) {
return callback(err);
}
function makeIapPostRequest (url, body, idToken, userAgent, jwt) {
var form = new FormData();
form.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
form.append('assertion', jwt);

var options = {
url: url,
return fetch(
url, {
method: 'POST',
body: form
})
.then(function makeIapPostRequestCallback () {
return fetch(url, {
method: 'POST',
headers: {
'User-Agent': userAgent,
'Authorization': ['Bearer', idToken].join(' ')
'Authorization': `Bearer ${idToken}`
},
method: 'POST',
json: body
};
request(options, function (err, res, body) {
callback(err);
body: JSON.stringify(body)
});
});
}
Expand Down
3 changes: 2 additions & 1 deletion functions/composer-storage-trigger/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"name": "nodejs-docs-samples-functions-composer-storage-trigger",
"version": "0.0.1",
"dependencies": {
"request": ">=2.83"
"form-data": "^2.3.2",
"node-fetch": "^2.2.0"
},
"engines": {
"node": ">=4.3.2"
Expand Down

0 comments on commit 677cf79

Please sign in to comment.