diff --git a/README.md b/README.md index a16e989..65b8383 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ node-gapitoken ============== -Node.js module for Google API service account authorization (Server to Server flow). +Node.js module for Google API service account authorization (Server to Server flow). [![Build Status](https://travis-ci.org/bsphere/node-gapitoken.svg)](https://travis-ci.org/bsphere/node-gapitoken) @@ -10,23 +10,23 @@ Installation ------------ npm install gapitoken - + Usage ----- var GAPI = require('gapitoken'); - + var gapi = new GAPI({ iss: 'service account email address from Google API console', scope: 'space delimited list of requested scopes', - keyFile: 'path to private_key.pem' + keyFile: 'path to private_key.p12' }, function(err) { if (err) { return console.log(err); } - + gapi.getToken(function(err, token) { if (err) { return console.log(err); } console.log(token); - }); + }); }); Another option is to pass the private key as a string @@ -48,10 +48,10 @@ Another option is to pass the private key as a string gapi.getToken(function(err, token) { if (err) { return console.log(err); } console.log(token); - }); + }); }); - + * for using node-gapitoken to access Google Cloud Storage see https://github.com/bsphere/node-gcs Creating a Private key file @@ -59,10 +59,19 @@ Creating a Private key file 1) Login to Google API Console, and under "API Access" create a "service account" for your project. -2) Download the .p12 private key file +2) Download the .p12 private key file. + +3) Reference the file using the `keyFile` property as in the example above or, if you’ve already loaded the file yourself, pass it in via the `key` property as a base64-encoded string. + +*A short note on .p12 and .pem files:* the .p12 file is an encoded format for storing your key. If you’d like to pre-decode it, you can convert it to a .pem file on the command line: + +```shell +> openssl pkcs12 -in key.p12 -out key.pem -nocerts -3) Convert the .p12 file to .pem: `openssl pkcs12 -in key.p12 -out key.pem -nocerts` +# Note: you’ll have to set a passphrase for the file. To remove it: +> openssl rsa -in key.pem -out key.pem +``` -NOTE: You must set a passphrase for the .pem file +The resulting file can then be used for the `keyFile` or `key` properties just like the .p12 file. -4) Remove the passphrase from the .pem file: `openssl rsa -in key.pem -out key.pem` +(In older versions of this library, this step was required. P12 support is now built-in.) diff --git a/gapitoken.js b/gapitoken.js index 278734a..24e7da8 100644 --- a/gapitoken.js +++ b/gapitoken.js @@ -3,6 +3,7 @@ var jws = require('jws'); var fs = require('fs'); var request = require('request'); +var p12ToPem = require('p12-to-pem'); var GAPI = function(options, callback) { this.token = null; @@ -18,12 +19,12 @@ var GAPI = function(options, callback) { process.nextTick(function() { fs.readFile(options.keyFile, function(err, res) { if (err) { return callback(err); } - self.key = res; + self.key = decodeKey(res); callback(); }); }); } else if (options.key) { - this.key = options.key; + this.key = decodeKey(options.key); process.nextTick(callback); } else { callback(new Error("Missing key, key or keyFile option must be provided!")); @@ -99,4 +100,18 @@ GAPI.prototype.getAccessToken = function(callback) { }); }; +// Takes either a raw, unprotected key or a password-protected PKCS12 file +// containing a private key and returns the key. +function decodeKey(key) { + var keyString = key.toString(); + var maybeP12 = keyString.indexOf("PRIVATE KEY-----") === -1; + if (maybeP12) { + // Google's PKCS12 files use the password "notasecret" + return p12ToPem(key, "notasecret"); + } + else { + return keyString; + } +} + module.exports = GAPI; diff --git a/package.json b/package.json index cfbdcf5..a78274c 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "readmeFilename": "README.md", "dependencies": { "jws": "~3.0.0", - "request": "^2.54.0" + "request": "^2.54.0", + "p12-to-pem": "^1.0.1" }, "devDependencies": { "mocha": "^2.2.5" diff --git a/test/auth-with-google.test.js b/test/auth-with-google.test.js index b835b28..fda331a 100644 --- a/test/auth-with-google.test.js +++ b/test/auth-with-google.test.js @@ -3,19 +3,19 @@ var path = require('path'); var assert = require('assert'); var GAPI = require('../gapitoken.js'); -var pemPath = path.join(__dirname, 'test-key.pem'); +var keyPath = path.join(__dirname, 'test-key'); describe('Authenticating with Google', function() { it('should work with a .pem file', function(done) { var gapi = new GAPI({ - iss: "985952909795-p560igbg1r2hjaagrpeust4sqca9vhi8@developer.gserviceaccount.com", + iss: '985952909795-p560igbg1r2hjaagrpeust4sqca9vhi8@developer.gserviceaccount.com', scope: 'https://www.googleapis.com/auth/bigquery', - keyFile: pemPath + keyFile: keyPath + '.pem' }, function(error) { if (error) { return done(error); } gapi.getToken(function(error, token) { - assert.ok(token, "Got a token"); + assert.ok(token, 'Got a token'); done(error); }); }); @@ -23,14 +23,44 @@ describe('Authenticating with Google', function() { it('should work with an RSA string', function(done) { var gapi = new GAPI({ - iss: "985952909795-p560igbg1r2hjaagrpeust4sqca9vhi8@developer.gserviceaccount.com", + iss: '985952909795-p560igbg1r2hjaagrpeust4sqca9vhi8@developer.gserviceaccount.com', scope: 'https://www.googleapis.com/auth/bigquery', - key: fs.readFileSync(pemPath) + key: fs.readFileSync(keyPath + '.pem') }, function(error) { if (error) { return done(error); } gapi.getToken(function(error, token) { - assert.ok(token, "Got a token"); + assert.ok(token, 'Got a token'); + done(error); + }); + }); + }); + + it('should work with a .p12 file', function(done) { + var gapi = new GAPI({ + iss: '985952909795-p560igbg1r2hjaagrpeust4sqca9vhi8@developer.gserviceaccount.com', + scope: 'https://www.googleapis.com/auth/bigquery', + keyFile: keyPath + '.p12' + }, function(error) { + if (error) { return done(error); } + + gapi.getToken(function(error, token) { + assert.ok(token, 'Got a token'); + done(error); + }); + }); + }); + + it('should work with an base64-encoded p12 string', function(done) { + var gapi = new GAPI({ + iss: '985952909795-p560igbg1r2hjaagrpeust4sqca9vhi8@developer.gserviceaccount.com', + scope: 'https://www.googleapis.com/auth/bigquery', + key: fs.readFileSync(keyPath + '.p12').toString("base64") + }, function(error) { + if (error) { return done(error); } + + gapi.getToken(function(error, token) { + assert.ok(token, 'Got a token'); done(error); }); }); diff --git a/test/test-key.p12 b/test/test-key.p12 new file mode 100644 index 0000000..ccdcf54 Binary files /dev/null and b/test/test-key.p12 differ