diff --git a/README.md b/README.md index b53e301e..e42b17d5 100644 --- a/README.md +++ b/README.md @@ -46,21 +46,22 @@ Before making your API call, you must be sure the API you're calling has been en Rather than manually creating an OAuth2 client, JWT client, or Compute client, the auth library can create the correct credential type for you, depending upon the environment your code is running under. -For example, a JWT auth client will be created when your code is running on your local developer machine, and a Compute client will be created when the same code is running on Google Cloud Platform. If you need a specific set of scopes, you can pass those in the form of a string or an array into the `auth.getClient` method. +For example, a JWT auth client will be created when your code is running on your local developer machine, and a Compute client will be created when the same code is running on Google Cloud Platform. If you need a specific set of scopes, you can pass those in the form of a string or an array to the `GoogleAuth` constructor. The code below shows how to retrieve a default credential type, depending upon the runtime environment. ```js -const {auth} = require('google-auth-library'); +const {GoogleAuth} = require('google-auth-library'); /** * Instead of specifying the type of client you'd like to use (JWT, OAuth2, etc) * this library will automatically choose the right client based on the environment. */ async function main() { - const client = await auth.getClient({ + const auth = new GoogleAuth({ scopes: 'https://www.googleapis.com/auth/cloud-platform' }); + const client = await auth.getClient(); const projectId = await auth.getProjectId(); const url = `https://www.googleapis.com/dns/v1/projects/${projectId}`; const res = await client.request({ url }); @@ -168,6 +169,7 @@ main().catch(console.error); ``` #### Handling token events + This library will automatically obtain an `access_token`, and automatically refresh the `access_token` if a `refresh_token` is present. The `refresh_token` is only returned on the [first authorization](https://github.com/googleapis/google-api-nodejs-client/issues/750#issuecomment-304521450), so if you want to make sure you store it safely. An easy way to make sure you always store the most recent tokens is to use the `tokens` event: ```js diff --git a/samples/adc.js b/samples/adc.js index 5168e6a6..c74f8904 100644 --- a/samples/adc.js +++ b/samples/adc.js @@ -16,15 +16,16 @@ /** * Import the GoogleAuth library, and create a new GoogleAuth client. */ -const {auth} = require('google-auth-library'); +const {GoogleAuth} = require('google-auth-library'); /** * Acquire a client, and make a request to an API that's enabled by default. */ async function main() { - const client = await auth.getClient({ + const auth = new GoogleAuth({ scopes: 'https://www.googleapis.com/auth/cloud-platform', }); + const client = await auth.getClient(); const projectId = await auth.getProjectId(); const url = `https://www.googleapis.com/dns/v1/projects/${projectId}`; const res = await client.request({url}); diff --git a/samples/credentials.js b/samples/credentials.js index 3080b107..9cac46e7 100644 --- a/samples/credentials.js +++ b/samples/credentials.js @@ -16,7 +16,7 @@ /** * Import the GoogleAuth library, and create a new GoogleAuth client. */ -const {auth} = require('google-auth-library'); +const {GoogleAuth} = require('google-auth-library'); /** * This sample demonstrates passing a `credentials` object directly into the @@ -33,13 +33,14 @@ async function main() { this sample. `); } - const client = await auth.getClient({ + const auth = new GoogleAuth({ credentials: { client_email: clientEmail, private_key: privateKey, }, scopes: 'https://www.googleapis.com/auth/cloud-platform', }); + const client = await auth.getClient(); const projectId = await auth.getProjectId(); const url = `https://www.googleapis.com/dns/v1/projects/${projectId}`; const res = await client.request({url}); diff --git a/samples/headers.js b/samples/headers.js index 02fe30b1..384b5580 100644 --- a/samples/headers.js +++ b/samples/headers.js @@ -16,7 +16,7 @@ /** * Import the GoogleAuth library, and create a new GoogleAuth client. */ -const {auth} = require('google-auth-library'); +const {GoogleAuth} = require('google-auth-library'); const fetch = require('node-fetch'); /** @@ -25,14 +25,15 @@ const fetch = require('node-fetch'); * node-fetch, but you could use any HTTP client you like. */ async function main() { + // create auth instance with custom scopes. + const auth = new GoogleAuth({ + scopes: 'https://www.googleapis.com/auth/cloud-platform', + }); const projectId = await auth.getProjectId(); const url = `https://www.googleapis.com/dns/v1/projects/${projectId}`; // obtain an authenticated client - const client = await auth.getClient({ - scopes: 'https://www.googleapis.com/auth/cloud-platform', - }); - + const client = await auth.getClient(); // Use the client to get authenticated request headers const headers = await client.getRequestHeaders(); console.log('Headers:'); diff --git a/samples/keepalive.js b/samples/keepalive.js index da47c585..34c32348 100644 --- a/samples/keepalive.js +++ b/samples/keepalive.js @@ -23,16 +23,17 @@ /** * Import the GoogleAuth library, and create a new GoogleAuth client. */ -const {auth} = require('google-auth-library'); +const {GoogleAuth} = require('google-auth-library'); const https = require('https'); /** * Acquire a client, and make a request to an API that's enabled by default. */ async function main() { - const client = await auth.getClient({ + const auth = new GoogleAuth({ scopes: 'https://www.googleapis.com/auth/cloud-platform', }); + const client = await auth.getClient(); const projectId = await auth.getProjectId(); const url = `https://www.googleapis.com/dns/v1/projects/${projectId}`; diff --git a/samples/keyfile.js b/samples/keyfile.js index 846416ac..30d143af 100644 --- a/samples/keyfile.js +++ b/samples/keyfile.js @@ -16,7 +16,7 @@ /** * Import the GoogleAuth library, and create a new GoogleAuth client. */ -const {auth} = require('google-auth-library'); +const {GoogleAuth} = require('google-auth-library'); /** * Acquire a client, and make a request to an API that's enabled by default. @@ -25,10 +25,11 @@ async function main( // Full path to the sevice account credential keyFile = process.env.GOOGLE_APPLICATION_CREDENTIALS ) { - const client = await auth.getClient({ + const auth = new GoogleAuth({ keyFile: keyFile, scopes: 'https://www.googleapis.com/auth/cloud-platform', }); + const client = await auth.getClient(); const projectId = await auth.getProjectId(); const url = `https://www.googleapis.com/dns/v1/projects/${projectId}`; const res = await client.request({url}); diff --git a/src/auth/googleauth.ts b/src/auth/googleauth.ts index e65b440c..3ca8b9f4 100644 --- a/src/auth/googleauth.ts +++ b/src/auth/googleauth.ts @@ -46,6 +46,8 @@ export interface CredentialCallback { (err: Error | null, result?: UserRefreshClient | JWT): void; } +interface DeprecatedGetClientOptions {} + export interface ADCCallback { ( err: Error | null, @@ -441,7 +443,6 @@ export class GoogleAuth { 'Must pass in a JSON object containing the Google auth settings.' ); } - this.jsonContent = json; options = options || {}; if (json.type === 'authorized_user') { client = new UserRefreshClient(options); @@ -453,6 +454,33 @@ export class GoogleAuth { return client; } + /** + * Return a JWT or UserRefreshClient from JavaScript object, caching both the + * object used to instantiate and the client. + * @param json The input object. + * @param options The JWT or UserRefresh options for the client + * @returns JWT or UserRefresh Client with data + */ + private _cacheClientFromJSON( + json: JWTInput, + options?: RefreshOptions + ): JWT | UserRefreshClient { + let client: UserRefreshClient | JWT; + // create either a UserRefreshClient or JWT client. + options = options || {}; + if (json.type === 'authorized_user') { + client = new UserRefreshClient(options); + } else { + (options as JWTOptions).scopes = this.scopes; + client = new JWT(options); + } + client.fromJSON(json); + // cache both raw data used to instantiate client and client itself. + this.jsonContent = json; + this.cachedCredential = client; + return this.cachedCredential; + } + /** * Create a credentials instance using the given input stream. * @param inputStream The input stream. @@ -508,7 +536,7 @@ export class GoogleAuth { .on('end', () => { try { const data = JSON.parse(s); - const r = this.fromJSON(data, options); + const r = this._cacheClientFromJSON(data, options); return resolve(r); } catch (err) { return reject(err); @@ -682,27 +710,19 @@ export class GoogleAuth { * Automatically obtain a client based on the provided configuration. If no * options were passed, use Application Default Credentials. */ - async getClient(options?: GoogleAuthOptions) { + async getClient(options?: DeprecatedGetClientOptions) { if (options) { - this.keyFilename = - options.keyFilename || options.keyFile || this.keyFilename; - this.scopes = options.scopes || this.scopes; - this.jsonContent = options.credentials || this.jsonContent; - this.clientOptions = options.clientOptions; + throw new Error( + 'Passing options to getClient is forbidden in v5.0.0. Use new GoogleAuth(opts) instead.' + ); } if (!this.cachedCredential) { if (this.jsonContent) { - this.cachedCredential = await this.fromJSON( - this.jsonContent, - this.clientOptions - ); + this._cacheClientFromJSON(this.jsonContent, this.clientOptions); } else if (this.keyFilename) { const filePath = path.resolve(this.keyFilename); const stream = fs.createReadStream(filePath); - this.cachedCredential = await this.fromStreamAsync( - stream, - this.clientOptions - ); + await this.fromStreamAsync(stream, this.clientOptions); } else { await this.getApplicationDefaultAsync(this.clientOptions); } diff --git a/test/test.googleauth.ts b/test/test.googleauth.ts index 7921af53..1334be6b 100644 --- a/test/test.googleauth.ts +++ b/test/test.googleauth.ts @@ -231,6 +231,16 @@ describe('googleauth', () => { }); }); + it('fromJson should not overwrite previous client configuration', async () => { + const auth = new GoogleAuth({keyFilename: './test/fixtures/private.json'}); + auth.fromJSON({ + client_email: 'batman@example.com', + private_key: 'abc123', + }); + const client = (await auth.getClient()) as JWT; + assert.strictEqual(client.email, 'hello@youarecool.com'); + }); + it('fromAPIKey should error given an invalid api key', () => { assert.throws(() => { // Test verifies invalid parameter tests, which requires cast to any. @@ -1124,7 +1134,7 @@ describe('googleauth', () => { it('should use jsonContent if available', async () => { const json = createJwtJSON(); - auth.fromJSON(json); + const auth = new GoogleAuth({credentials: json}); // We know this returned a cached result if a nock scope isn't required const body = await auth.getCredentials(); assert.notStrictEqual(body, null); @@ -1138,10 +1148,8 @@ describe('googleauth', () => { }); it('should error when invalid keyFilename passed to getClient', async () => { - await assertRejects( - auth.getClient({keyFilename: './funky/fresh.json'}), - /ENOENT: no such file or directory/ - ); + const auth = new GoogleAuth({keyFilename: './funky/fresh.json'}); + await assertRejects(auth.getClient(), /ENOENT: no such file or directory/); }); it('should accept credentials to get a client', async () => { @@ -1165,21 +1173,24 @@ describe('googleauth', () => { it('should allow passing scopes to get a client', async () => { const scopes = ['http://examples.com/is/a/scope']; const keyFilename = './test/fixtures/private.json'; - const client = (await auth.getClient({scopes, keyFilename})) as JWT; + const auth = new GoogleAuth({scopes, keyFilename}); + const client = (await auth.getClient()) as JWT; assert.strictEqual(client.scopes, scopes); }); it('should allow passing a scope to get a client', async () => { const scopes = 'http://examples.com/is/a/scope'; const keyFilename = './test/fixtures/private.json'; - const client = (await auth.getClient({scopes, keyFilename})) as JWT; + const auth = new GoogleAuth({scopes, keyFilename}); + const client = (await auth.getClient()) as JWT; assert.strictEqual(client.scopes, scopes); }); it('should allow passing a scope to get a Compute client', async () => { const scopes = ['http://examples.com/is/a/scope']; const nockScopes = [nockIsGCE(), createGetProjectIdNock()]; - const client = (await auth.getClient({scopes})) as Compute; + const auth = new GoogleAuth({scopes}); + const client = (await auth.getClient()) as Compute; assert.strictEqual(client.scopes, scopes); nockScopes.forEach(x => x.done()); }); @@ -1348,13 +1359,6 @@ describe('googleauth', () => { assert.strictEqual(count, 0); }); - it('should pass options to the JWT constructor via getClient', async () => { - const subject = 'science!'; - const auth = new GoogleAuth({keyFilename: './test/fixtures/private.json'}); - const client = (await auth.getClient({clientOptions: {subject}})) as JWT; - assert.strictEqual(client.subject, subject); - }); - it('should pass options to the JWT constructor via constructor', async () => { const subject = 'science!'; const auth = new GoogleAuth({ @@ -1373,4 +1377,12 @@ describe('googleauth', () => { /Unable to detect a Project Id in the current environment/ ); }); + + it('should throw if options are passed to getClient()', async () => { + const auth = new GoogleAuth(); + await assertRejects( + auth.getClient({hello: 'world'}), + /Passing options to getClient is forbidden in v5.0.0/ + ); + }); });