diff --git a/.readme-partials.yaml b/.readme-partials.yaml index 480509b6..c9f06892 100644 --- a/.readme-partials.yaml +++ b/.readme-partials.yaml @@ -40,18 +40,13 @@ body: |- * 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 auth = new GoogleAuth({ - scopes: 'https://www.googleapis.com/auth/cloud-platform' - }); - const client = await auth.getClient(); - const projectId = await auth.getProjectId(); - const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`; - const res = await client.request({ url }); - console.log(res.data); - } - - main().catch(console.error); + const auth = new GoogleAuth({ + scopes: 'https://www.googleapis.com/auth/cloud-platform' + }); + const projectId = await auth.getProjectId(); + const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`; + const res = await auth.fetch(url); + console.log(res.data); ``` ## OAuth2 @@ -81,10 +76,11 @@ body: |- */ async function main() { const oAuth2Client = await getAuthenticatedClient(); - // Make a simple request to the People API using our pre-authenticated client. The `request()` method - // takes an GaxiosOptions object. Visit https://github.com/JustinBeckwith/gaxios. + // Make a simple request to the People API using our pre-authenticated client. The `fetch` and + // `request` methods accept a [`GaxiosOptions`](https://github.com/googleapis/gaxios) + // object. const url = 'https://people.googleapis.com/v1/people/me?personFields=names'; - const res = await oAuth2Client.request({url}); + const res = await oAuth2Client.fetch(url); console.log(res.data); // After acquiring an access_token, you may want to check on the audience, expiration, @@ -156,6 +152,7 @@ body: |- 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 + const auth = new GoogleAuth(); const client = await auth.getClient(); client.on('tokens', (tokens) => { @@ -166,9 +163,10 @@ body: |- console.log(tokens.access_token); }); + const projectId = await auth.getProjectId(); const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`; - const res = await client.request({ url }); // The `tokens` event would now be raised if this was the first request + const res = await client.fetch(url); ``` #### Retrieve access token @@ -241,18 +239,14 @@ body: |- const {JWT} = require('google-auth-library'); const keys = require('./jwt.keys.json'); - async function main() { - const client = new JWT({ - email: keys.client_email, - key: keys.private_key, - scopes: ['https://www.googleapis.com/auth/cloud-platform'], - }); - const url = `https://dns.googleapis.com/dns/v1/projects/${keys.project_id}`; - const res = await client.request({url}); - console.log(res.data); - } - - main().catch(console.error); + const client = new JWT({ + email: keys.client_email, + key: keys.private_key, + scopes: ['https://www.googleapis.com/auth/cloud-platform'], + }); + const url = `https://dns.googleapis.com/dns/v1/projects/${keys.project_id}`; + const res = await client.fetch(url); + console.log(res.data); ``` The parameters for the JWT auth client including how to use it with a `.pem` file are explained in [samples/jwt.js](https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/jwt.js). @@ -288,16 +282,12 @@ body: |- } const keys = JSON.parse(keysEnvVar); - async function main() { - // load the JWT or UserRefreshClient from the keys - const client = auth.fromJSON(keys); - client.scopes = ['https://www.googleapis.com/auth/cloud-platform']; - const url = `https://dns.googleapis.com/dns/v1/projects/${keys.project_id}`; - const res = await client.request({url}); - console.log(res.data); - } - - main().catch(console.error); + // load the JWT or UserRefreshClient from the keys + const client = auth.fromJSON(keys); + client.scopes = ['https://www.googleapis.com/auth/cloud-platform']; + const url = `https://dns.googleapis.com/dns/v1/projects/${keys.project_id}`; + const res = await client.fetch(url); + console.log(res.data); ``` **Important**: If you accept a credential configuration (credential JSON/File/Stream) from an external source for authentication to Google Cloud, you must validate it before providing it to any Google API or library. Providing an unvalidated credential configuration to Google APIs can compromise the security of your systems and data. For more information, refer to [Validate credential configurations from external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials). @@ -313,18 +303,14 @@ body: |- ``` js const {auth, Compute} = require('google-auth-library'); - async function main() { - const client = new Compute({ - // Specifying the service account email is optional. - serviceAccountEmail: 'my-service-account@example.com' - }); - const projectId = await auth.getProjectId(); - const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`; - const res = await client.request({url}); - console.log(res.data); - } - - main().catch(console.error); + const client = new Compute({ + // Specifying the service account email is optional. + serviceAccountEmail: 'my-service-account@example.com' + }); + const projectId = await auth.getProjectId(); + const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`; + const res = await client.fetch(url); + console.log(res.data); ``` ## Workload Identity Federation @@ -1023,17 +1009,14 @@ body: |- The library can now automatically choose the right type of client and initialize credentials from the context provided in the configuration file. ```js - async function main() { - const auth = new GoogleAuth({ - scopes: 'https://www.googleapis.com/auth/cloud-platform' - }); - const client = await auth.getClient(); - const projectId = await auth.getProjectId(); - // List all buckets in a project. - const url = `https://storage.googleapis.com/storage/v1/b?project=${projectId}`; - const res = await client.request({ url }); - console.log(res.data); - } + const auth = new GoogleAuth({ + scopes: 'https://www.googleapis.com/auth/cloud-platform' + }); + const projectId = await auth.getProjectId(); + // List all buckets in a project. + const url = `https://storage.googleapis.com/storage/v1/b?project=${projectId}`; + const res = await client.fetch(url); + console.log(res.data); ``` When using external identities with Application Default Credentials in Node.js, the `roles/browser` role needs to be granted to the service account. @@ -1056,14 +1039,12 @@ body: |- const {ExternalAccountClient} = require('google-auth-library'); const jsonConfig = require('/path/to/config.json'); - async function main() { - const client = ExternalAccountClient.fromJSON(jsonConfig); - client.scopes = ['https://www.googleapis.com/auth/cloud-platform']; - // List all buckets in a project. - const url = `https://storage.googleapis.com/storage/v1/b?project=${projectId}`; - const res = await client.request({url}); - console.log(res.data); - } + const client = ExternalAccountClient.fromJSON(jsonConfig); + client.scopes = ['https://www.googleapis.com/auth/cloud-platform']; + // List all buckets in a project. + const url = `https://storage.googleapis.com/storage/v1/b?project=${projectId}`; + const res = await client.fetch(url); + console.log(res.data); ``` #### Security Considerations @@ -1087,15 +1068,11 @@ body: |- // Make a request to a protected Cloud Run service. const {GoogleAuth} = require('google-auth-library'); - async function main() { - const url = 'https://cloud-run-1234-uc.a.run.app'; - const auth = new GoogleAuth(); - const client = await auth.getIdTokenClient(url); - const res = await client.request({url}); - console.log(res.data); - } - - main().catch(console.error); + const url = 'https://cloud-run-1234-uc.a.run.app'; + const auth = new GoogleAuth(); + const client = await auth.getIdTokenClient(url); + const res = await client.fetch(url); + console.log(res.data); ``` A complete example can be found in [`samples/idtokens-serverless.js`](https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/idtokens-serverless.js). @@ -1107,16 +1084,12 @@ body: |- // Make a request to a protected Cloud Identity-Aware Proxy (IAP) resource const {GoogleAuth} = require('google-auth-library'); - async function main() - const targetAudience = 'iap-client-id'; - const url = 'https://iap-url.com'; - const auth = new GoogleAuth(); - const client = await auth.getIdTokenClient(targetAudience); - const res = await client.request({url}); - console.log(res.data); - } - - main().catch(console.error); + const targetAudience = 'iap-client-id'; + const url = 'https://iap-url.com'; + const auth = new GoogleAuth(); + const client = await auth.getIdTokenClient(targetAudience); + const res = await client.fetch(url); + console.log(res.data); ``` A complete example can be found in [`samples/idtokens-iap.js`](https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/idtokens-iap.js). @@ -1189,7 +1162,7 @@ body: |- // Use impersonated credentials: const url = 'https://www.googleapis.com/storage/v1/b?project=anotherProjectID' - const resp = await targetClient.request({ url }); + const resp = await targetClient.fetch(url); for (const bucket of resp.data.items) { console.log(bucket.name); } diff --git a/README.md b/README.md index a4594640..75d699d8 100644 --- a/README.md +++ b/README.md @@ -84,18 +84,13 @@ 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 auth = new GoogleAuth({ - scopes: 'https://www.googleapis.com/auth/cloud-platform' - }); - const client = await auth.getClient(); - const projectId = await auth.getProjectId(); - const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`; - const res = await client.request({ url }); - console.log(res.data); -} - -main().catch(console.error); +const auth = new GoogleAuth({ + scopes: 'https://www.googleapis.com/auth/cloud-platform' +}); +const projectId = await auth.getProjectId(); +const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`; +const res = await auth.fetch(url); +console.log(res.data); ``` ## OAuth2 @@ -125,10 +120,11 @@ const keys = require('./oauth2.keys.json'); */ async function main() { const oAuth2Client = await getAuthenticatedClient(); - // Make a simple request to the People API using our pre-authenticated client. The `request()` method - // takes an GaxiosOptions object. Visit https://github.com/JustinBeckwith/gaxios. + // Make a simple request to the People API using our pre-authenticated client. The `fetch` and + // `request` methods accept a [`GaxiosOptions`](https://github.com/googleapis/gaxios) + // object. const url = 'https://people.googleapis.com/v1/people/me?personFields=names'; - const res = await oAuth2Client.request({url}); + const res = await oAuth2Client.fetch(url); console.log(res.data); // After acquiring an access_token, you may want to check on the audience, expiration, @@ -200,6 +196,7 @@ main().catch(console.error); 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 +const auth = new GoogleAuth(); const client = await auth.getClient(); client.on('tokens', (tokens) => { @@ -210,9 +207,10 @@ client.on('tokens', (tokens) => { console.log(tokens.access_token); }); +const projectId = await auth.getProjectId(); const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`; -const res = await client.request({ url }); // The `tokens` event would now be raised if this was the first request +const res = await client.fetch(url); ``` #### Retrieve access token @@ -285,18 +283,14 @@ The Google Developers Console provides a `.json` file that you can use to config const {JWT} = require('google-auth-library'); const keys = require('./jwt.keys.json'); -async function main() { - const client = new JWT({ - email: keys.client_email, - key: keys.private_key, - scopes: ['https://www.googleapis.com/auth/cloud-platform'], - }); - const url = `https://dns.googleapis.com/dns/v1/projects/${keys.project_id}`; - const res = await client.request({url}); - console.log(res.data); -} - -main().catch(console.error); +const client = new JWT({ + email: keys.client_email, + key: keys.private_key, + scopes: ['https://www.googleapis.com/auth/cloud-platform'], +}); +const url = `https://dns.googleapis.com/dns/v1/projects/${keys.project_id}`; +const res = await client.fetch(url); +console.log(res.data); ``` The parameters for the JWT auth client including how to use it with a `.pem` file are explained in [samples/jwt.js](https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/jwt.js). @@ -332,16 +326,12 @@ if (!keysEnvVar) { } const keys = JSON.parse(keysEnvVar); -async function main() { - // load the JWT or UserRefreshClient from the keys - const client = auth.fromJSON(keys); - client.scopes = ['https://www.googleapis.com/auth/cloud-platform']; - const url = `https://dns.googleapis.com/dns/v1/projects/${keys.project_id}`; - const res = await client.request({url}); - console.log(res.data); -} - -main().catch(console.error); +// load the JWT or UserRefreshClient from the keys +const client = auth.fromJSON(keys); +client.scopes = ['https://www.googleapis.com/auth/cloud-platform']; +const url = `https://dns.googleapis.com/dns/v1/projects/${keys.project_id}`; +const res = await client.fetch(url); +console.log(res.data); ``` **Important**: If you accept a credential configuration (credential JSON/File/Stream) from an external source for authentication to Google Cloud, you must validate it before providing it to any Google API or library. Providing an unvalidated credential configuration to Google APIs can compromise the security of your systems and data. For more information, refer to [Validate credential configurations from external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials). @@ -357,18 +347,14 @@ If your application is running on Google Cloud Platform, you can authenticate us ``` js const {auth, Compute} = require('google-auth-library'); -async function main() { - const client = new Compute({ - // Specifying the service account email is optional. - serviceAccountEmail: 'my-service-account@example.com' - }); - const projectId = await auth.getProjectId(); - const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`; - const res = await client.request({url}); - console.log(res.data); -} - -main().catch(console.error); +const client = new Compute({ + // Specifying the service account email is optional. + serviceAccountEmail: 'my-service-account@example.com' +}); +const projectId = await auth.getProjectId(); +const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`; +const res = await client.fetch(url); +console.log(res.data); ``` ## Workload Identity Federation @@ -1067,17 +1053,14 @@ export GOOGLE_APPLICATION_CREDENTIALS=/path/to/config.json The library can now automatically choose the right type of client and initialize credentials from the context provided in the configuration file. ```js -async function main() { - const auth = new GoogleAuth({ - scopes: 'https://www.googleapis.com/auth/cloud-platform' - }); - const client = await auth.getClient(); - const projectId = await auth.getProjectId(); - // List all buckets in a project. - const url = `https://storage.googleapis.com/storage/v1/b?project=${projectId}`; - const res = await client.request({ url }); - console.log(res.data); -} +const auth = new GoogleAuth({ + scopes: 'https://www.googleapis.com/auth/cloud-platform' +}); +const projectId = await auth.getProjectId(); +// List all buckets in a project. +const url = `https://storage.googleapis.com/storage/v1/b?project=${projectId}`; +const res = await client.fetch(url); +console.log(res.data); ``` When using external identities with Application Default Credentials in Node.js, the `roles/browser` role needs to be granted to the service account. @@ -1100,14 +1083,12 @@ You can also explicitly initialize external account clients using the generated const {ExternalAccountClient} = require('google-auth-library'); const jsonConfig = require('/path/to/config.json'); -async function main() { - const client = ExternalAccountClient.fromJSON(jsonConfig); - client.scopes = ['https://www.googleapis.com/auth/cloud-platform']; - // List all buckets in a project. - const url = `https://storage.googleapis.com/storage/v1/b?project=${projectId}`; - const res = await client.request({url}); - console.log(res.data); -} +const client = ExternalAccountClient.fromJSON(jsonConfig); +client.scopes = ['https://www.googleapis.com/auth/cloud-platform']; +// List all buckets in a project. +const url = `https://storage.googleapis.com/storage/v1/b?project=${projectId}`; +const res = await client.fetch(url); +console.log(res.data); ``` #### Security Considerations @@ -1131,15 +1112,11 @@ IAM permission. // Make a request to a protected Cloud Run service. const {GoogleAuth} = require('google-auth-library'); -async function main() { - const url = 'https://cloud-run-1234-uc.a.run.app'; - const auth = new GoogleAuth(); - const client = await auth.getIdTokenClient(url); - const res = await client.request({url}); - console.log(res.data); -} - -main().catch(console.error); +const url = 'https://cloud-run-1234-uc.a.run.app'; +const auth = new GoogleAuth(); +const client = await auth.getIdTokenClient(url); +const res = await client.fetch(url); +console.log(res.data); ``` A complete example can be found in [`samples/idtokens-serverless.js`](https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/idtokens-serverless.js). @@ -1151,16 +1128,12 @@ used when you set up your protected resource as the target audience. // Make a request to a protected Cloud Identity-Aware Proxy (IAP) resource const {GoogleAuth} = require('google-auth-library'); -async function main() - const targetAudience = 'iap-client-id'; - const url = 'https://iap-url.com'; - const auth = new GoogleAuth(); - const client = await auth.getIdTokenClient(targetAudience); - const res = await client.request({url}); - console.log(res.data); -} - -main().catch(console.error); +const targetAudience = 'iap-client-id'; +const url = 'https://iap-url.com'; +const auth = new GoogleAuth(); +const client = await auth.getIdTokenClient(targetAudience); +const res = await client.fetch(url); +console.log(res.data); ``` A complete example can be found in [`samples/idtokens-iap.js`](https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/idtokens-iap.js). @@ -1233,7 +1206,7 @@ async function main() { // Use impersonated credentials: const url = 'https://www.googleapis.com/storage/v1/b?project=anotherProjectID' - const resp = await targetClient.request({ url }); + const resp = await targetClient.fetch(url); for (const bucket of resp.data.items) { console.log(bucket.name); } diff --git a/samples/adc.js b/samples/adc.js index 2e0fc01c..60ab4c9c 100644 --- a/samples/adc.js +++ b/samples/adc.js @@ -1,4 +1,4 @@ -// Copyright 2018, Google, LLC. +// Copyright 2018 Google LLC // 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 @@ -28,7 +28,7 @@ async function main() { const client = await auth.getClient(); const projectId = await auth.getProjectId(); const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`; - const res = await client.request({url}); + const res = await client.fetch(url); console.log('DNS Info:'); console.log(res.data); } diff --git a/samples/compute.js b/samples/compute.js index b4f22596..043d6647 100644 --- a/samples/compute.js +++ b/samples/compute.js @@ -1,4 +1,4 @@ -// Copyright 2018, Google, LLC. +// Copyright 2018 Google LLC // 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 @@ -28,7 +28,7 @@ async function main() { }); const projectId = await auth.getProjectId(); const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`; - const res = await client.request({url}); + const res = await client.fetch(url); console.log(res.data); } diff --git a/samples/credentials.js b/samples/credentials.js index 5791490e..6c8ab8bb 100644 --- a/samples/credentials.js +++ b/samples/credentials.js @@ -1,4 +1,4 @@ -// Copyright 2018, Google, LLC. +// Copyright 2018 Google LLC // 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 @@ -43,7 +43,7 @@ async function main() { const client = await auth.getClient(); const projectId = await auth.getProjectId(); const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`; - const res = await client.request({url}); + const res = await client.fetch(url); console.log('DNS Info:'); console.log(res.data); } diff --git a/samples/idtokens-iap.js b/samples/idtokens-iap.js index d2142dae..dad793b9 100644 --- a/samples/idtokens-iap.js +++ b/samples/idtokens-iap.js @@ -35,7 +35,7 @@ function main( async function request() { console.info(`request IAP ${url} with target audience ${targetAudience}`); const client = await auth.getIdTokenClient(targetAudience); - const res = await client.request({url}); + const res = await client.fetch(url); console.info(res.data); } diff --git a/samples/idtokens-serverless.js b/samples/idtokens-serverless.js index 5bbba470..5b379455 100644 --- a/samples/idtokens-serverless.js +++ b/samples/idtokens-serverless.js @@ -65,7 +65,7 @@ function main( // Alternatively, one can use `client.idTokenProvider.fetchIdToken` // to return the ID Token. - const res = await client.request({url}); + const res = await client.fetch(url); console.info(res.data); } diff --git a/samples/jwt.js b/samples/jwt.js index 62b9cccd..77b89e13 100644 --- a/samples/jwt.js +++ b/samples/jwt.js @@ -38,7 +38,7 @@ async function main( scopes: ['https://www.googleapis.com/auth/cloud-platform'], }); const url = `https://dns.googleapis.com/dns/v1/projects/${keys.project_id}`; - const res = await client.request({url}); + const res = await client.fetch(url); console.log('DNS Info:'); console.log(res.data); diff --git a/samples/keyfile.js b/samples/keyfile.js index 3018d843..9398e545 100644 --- a/samples/keyfile.js +++ b/samples/keyfile.js @@ -33,7 +33,7 @@ async function main( const client = await auth.getClient(); const projectId = await auth.getProjectId(); const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`; - const res = await client.request({url}); + const res = await client.fetch(url); console.log('DNS Info:'); console.log(res.data); } diff --git a/src/auth/authclient.ts b/src/auth/authclient.ts index 09f09ba4..892accea 100644 --- a/src/auth/authclient.ts +++ b/src/auth/authclient.ts @@ -20,6 +20,18 @@ import {OriginalAndCamel, originalOrCamelOptions} from '../util'; import {PRODUCT_NAME, USER_AGENT} from '../shared.cjs'; +/** + * An interface for enforcing `fetch`-type compliance. + * + * @remarks + * + * This provides type guarantees during build-time, ensuring the `fetch` method is 1:1 + * compatible with the `Gaxios#fetch` API. + */ +interface GaxiosFetchCompliance { + fetch: typeof fetch | Gaxios['fetch']; +} + /** * Base auth configurations (e.g. from JWT or `.json` files) with conventional * camelCased options. @@ -192,7 +204,7 @@ export declare interface AuthClient { export abstract class AuthClient extends EventEmitter - implements CredentialsClient + implements CredentialsClient, GaxiosFetchCompliance { apiKey?: string; projectId?: string | null; @@ -238,9 +250,66 @@ export abstract class AuthClient this.forceRefreshOnFailure = opts.forceRefreshOnFailure ?? false; } + /** + * A {@link fetch `fetch`} compliant API for {@link AuthClient}. + * + * @see {@link AuthClient.request} for the classic method. + * + * @remarks + * + * This is useful as a drop-in replacement for `fetch` API usage. + * + * @example + * + * ```ts + * const authClient = new AuthClient(); + * const fetchWithAuthClient: typeof fetch = (...args) => authClient.fetch(...args); + * await fetchWithAuthClient('https://example.com'); + * ``` + * + * @param args `fetch` API or {@link Gaxios.fetch `Gaxios#fetch`} parameters + * @returns the {@link GaxiosResponse} with Gaxios-added properties + */ + fetch(...args: Parameters): GaxiosPromise { + // Up to 2 parameters in either overload + const input = args[0]; + const init = args[1]; + + let url: URL | undefined = undefined; + const headers = new Headers(); + + // prepare URL + if (typeof input === 'string') { + url = new URL(input); + } else if (input instanceof URL) { + url = input; + } else if (input && input.url) { + url = new URL(input.url); + } + + // prepare headers + if (input && typeof input === 'object' && 'headers' in input) { + Gaxios.mergeHeaders(headers, input.headers); + } + if (init) { + Gaxios.mergeHeaders(headers, new Headers(init.headers)); + } + + // prepare request + if (typeof input === 'object' && !(input instanceof URL)) { + // input must have been a non-URL object + return this.request({...init, ...input, headers, url}); + } else { + // input must have been a string or URL + return this.request({...init, headers, url}); + } + } + /** * The public request API in which credentials may be added to the request. * + * @see {@link AuthClient.fetch} for the modern method. + * * @param options options for `gaxios` */ abstract request(options: GaxiosOptions): GaxiosPromise; diff --git a/src/auth/googleauth.ts b/src/auth/googleauth.ts index b55b9f39..0ace9ca6 100644 --- a/src/auth/googleauth.ts +++ b/src/auth/googleauth.ts @@ -1081,9 +1081,37 @@ export class GoogleAuth { return opts; } + /** + * A {@link fetch `fetch`} compliant API for {@link GoogleAuth}. + * + * @see {@link GoogleAuth.request} for the classic method. + * + * @remarks + * + * This is useful as a drop-in replacement for `fetch` API usage. + * + * @example + * + * ```ts + * const auth = new GoogleAuth(); + * const fetchWithAuth: typeof fetch = (...args) => auth.fetch(...args); + * await fetchWithAuth('https://example.com'); + * ``` + * + * @param args `fetch` API or {@link Gaxios.fetch `Gaxios#fetch`} parameters + * @returns the {@link GaxiosResponse} with Gaxios-added properties + */ + async fetch(...args: Parameters) { + const client = await this.getClient(); + return client.fetch(...args); + } + /** * Automatically obtain application default credentials, and make an * HTTP request using the given options. + * + * @see {@link GoogleAuth.fetch} for the modern method. + * * @param opts Axios request options for the HTTP request. */ async request(opts: GaxiosOptions): Promise> { diff --git a/src/auth/passthrough.ts b/src/auth/passthrough.ts index a1515194..11949367 100644 --- a/src/auth/passthrough.ts +++ b/src/auth/passthrough.ts @@ -58,7 +58,3 @@ export class PassThroughClient extends AuthClient { return new Headers(); } } - -const a = new PassThroughClient(); - -a.getAccessToken(); diff --git a/test/test.authclient.ts b/test/test.authclient.ts index 4674cf3b..6c876d5b 100644 --- a/test/test.authclient.ts +++ b/test/test.authclient.ts @@ -14,7 +14,8 @@ import {strict as assert} from 'assert'; -import {Gaxios, GaxiosOptionsPrepared} from 'gaxios'; +import {Gaxios, GaxiosOptions, GaxiosOptionsPrepared} from 'gaxios'; +import * as nock from 'nock'; import {AuthClient, PassThroughClient} from '../src'; import {snakeToCamel} from '../src/util'; @@ -42,6 +43,58 @@ describe('AuthClient', () => { } }); + describe('fetch', () => { + const url = 'https://google.com'; + + it('should accept a `string`', async () => { + const scope = nock(url).get('/').reply(200, {}); + + const authClient = new PassThroughClient(); + const res = await authClient.fetch(url); + + scope.done(); + assert(typeof url === 'string'); + assert.deepStrictEqual(res.data, {}); + }); + + it('should accept a `URL`', async () => { + const scope = nock(url).get('/').reply(200, {}); + + const authClient = new PassThroughClient(); + const res = await authClient.fetch(new URL(url)); + + scope.done(); + assert.deepStrictEqual(res.data, {}); + }); + + it('should accept an input with initialization', async () => { + const scope = nock(url).post('/', 'abc').reply(200, {}); + + const authClient = new PassThroughClient(); + const res = await authClient.fetch(url, { + body: Buffer.from('abc'), + method: 'POST', + }); + + scope.done(); + assert.deepStrictEqual(res.data, {}); + }); + + it('should accept `GaxiosOptions`', async () => { + const scope = nock(url).post('/', 'abc').reply(200, {}); + + const authClient = new PassThroughClient(); + const options: GaxiosOptions = { + body: Buffer.from('abc'), + method: 'POST', + }; + const res = await authClient.fetch(url, options); + + scope.done(); + assert.deepStrictEqual(res.data, {}); + }); + }); + describe('shared auth interceptor', () => { it('should use the default interceptor', () => { const gaxios = new Gaxios(); diff --git a/test/test.googleauth.ts b/test/test.googleauth.ts index 73537d91..7aac1b0d 100644 --- a/test/test.googleauth.ts +++ b/test/test.googleauth.ts @@ -1399,7 +1399,18 @@ describe('googleauth', () => { assert.strictEqual(env, envDetect.GCPEnv.CLOUD_RUN); }); - it('should make the request', async () => { + it('should make the request via `#fetch`', async () => { + const url = 'http://example.com'; + const {auth, scopes} = mockGCE(); + scopes.push(createGetProjectIdNock()); + const data = {breakfast: 'coffee'}; + scopes.push(nock(url).get('/').reply(200, data)); + const res = await auth.fetch(url); + scopes.forEach(s => s.done()); + assert.deepStrictEqual(res.data, data); + }); + + it('should make the request via `#request`', async () => { const url = 'http://example.com'; const {auth, scopes} = mockGCE(); scopes.push(createGetProjectIdNock()); @@ -2564,7 +2575,29 @@ describe('googleauth', () => { scopes.forEach(s => s.done()); }); - it('request() should make the request with auth header', async () => { + it('#fetch() should make the request with auth header', async () => { + const url = 'http://example.com'; + const data = {breakfast: 'coffee'}; + const keyFilename = './test/fixtures/external-account-cred.json'; + const scopes = mockGetAccessTokenAndProjectId(false); + scopes.push( + nock(url) + .get('/', undefined, { + reqheaders: { + authorization: `Bearer ${stsSuccessfulResponse.access_token}`, + }, + }) + .reply(200, data) + ); + + const auth = new GoogleAuth({keyFilename}); + const res = await auth.fetch(url); + + assert.deepStrictEqual(res.data, data); + scopes.forEach(s => s.done()); + }); + + it('#request() should make the request with auth header', async () => { const url = 'http://example.com'; const data = {breakfast: 'coffee'}; const keyFilename = './test/fixtures/external-account-cred.json';