diff --git a/lib/auth/jwtclient.js b/lib/auth/jwtclient.js new file mode 100644 index 00000000000..9e7e2415613 --- /dev/null +++ b/lib/auth/jwtclient.js @@ -0,0 +1,90 @@ +/** + * Copyright 2013 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var Auth2Client = require('./oauth2client.js'); +var util = require('util'); +var GAPI = require('gapitoken'); + +/** + * @constructor + * JWT service account credentials. + * + * Retrieve access token using gapitoken. + * + * @param {string=} email service account email address. + * @param {string=} keyFile path to private key file. + * @param {array=} scopes list of requested scopes. + * + */ +function JWT(email, keyFile, scopes) { + JWT.super_.call(this); + this.email = email; + this.keyFile = keyFile; + this.scopes = scopes; + this.GAPI = GAPI; +} + +/** + * Inherit from Auth2Client. + */ +util.inherits(JWT, Auth2Client); + +/** + * Get the initial access token using gapitoken. + * @param {function=} opt_callback Optional callback. + */ +JWT.prototype.authorize = function(opt_callback) { + var that = this; + that.gapi = new that.GAPI({ + iss: that.email, + scope: that.scopes.join(' '), + keyFile: that.keyFile + }, function(err) { + if (err) { + opt_callback && opt_callback(err, null); + } else { + that.refreshToken_(null, function(err, result) { + if (!err) { + that.credentials = result; + that.credentials.refresh_token = 'jwt-placeholder'; + } + opt_callback && opt_callback(err, result); + }); + } + }); +}; + +/** + * @private + * Refreshes the access token. + * @param {object=} ignored_ + * @param {function=} opt_callback Optional callback. + */ +JWT.prototype.refreshToken_ = function(ignored_, opt_callback) { + var that = this; + that.gapi.getToken(function(err, token) { + opt_callback && opt_callback(err, { + access_token: token, + token_type: 'Bearer', + expires_in: that.gapi.token_expires + }); + }); +}; + +/** + * Export Compute. + */ +module.exports = JWT; diff --git a/lib/googleapis.js b/lib/googleapis.js index 3e4964b84ac..b35fc2b4993 100644 --- a/lib/googleapis.js +++ b/lib/googleapis.js @@ -254,6 +254,7 @@ googleapis.OAuth2Client = require('./auth/oauth2client.js'); */ googleapis.auth = { Compute: require('./auth/computeclient.js'), + JWT: require('./auth/jwtclient.js'), OAuth2Client: googleapis.OAuth2Client }; diff --git a/package.json b/package.json index b035ba9b956..340a53bf2e1 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ ], "dependencies" : { "request": "~2.25.0", - "async": "0.2.6" + "async": "0.2.6", + "gapitoken": "0.0.3" }, "devDependencies" : { "mocha": "1.8.1", diff --git a/tests/test.jwt.js b/tests/test.jwt.js new file mode 100644 index 00000000000..49699c13b3b --- /dev/null +++ b/tests/test.jwt.js @@ -0,0 +1,71 @@ +/** + * Copyright 2013 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var assert = require('assert'); + +var googleapis = require('../lib/googleapis.js'); + +describe('JWT auth client', function() { + it('should get an initial access token', function(done) { + var jwt = new googleapis.auth.JWT( + 'foo@serviceaccount.com', + '/path/to/key.pem', + ['http://bar', 'http://foo']); + jwt.GAPI = function(opts, callback) { + assert.equal('foo@serviceaccount.com', opts.iss); + assert.equal('/path/to/key.pem', opts.keyFile); + assert.equal('http://bar http://foo', opts.scope); + setTimeout(function() { + callback(null); + }, 0); + return { + getToken: function(opt_callback) { + opt_callback(null, 'initial-access-token'); + } + } + }; + jwt.authorize(function() { + assert.equal('initial-access-token', jwt.credentials.access_token); + assert.equal('jwt-placeholder', jwt.credentials.refresh_token); + done(); + }); + }); + it('should refresh token when request fails', function(done) { + var jwt = new googleapis.auth.JWT( + 'foo@serviceaccount.com', + '/path/to/key.pem', + ['http://bar', 'http://foo']); + jwt.credentials = { + access_token: 'initial-access-token', + refresh_token: 'jwt-placeholder' + }; + jwt.transporter = { + request: function(opts, opt_callback) { + opt_callback(null, null, {statusCode: 401}); + } + }; + jwt.refreshToken_ = function(token, callback) { + callback(null, { + 'access_token': 'another-access-token', + 'token_type': 'Bearer' + }); + }; + jwt.request({}, function() { + assert.equal('another-access-token', jwt.credentials.access_token); + done(); + }); + }); +});