diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 31a3ddda..3ca7b8f5 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -55,6 +55,7 @@ "posttest": "npm run check" }, "dependencies": { + "google-auth-library": "^6.0.0", "semver": "^6.2.0" }, "files": [ diff --git a/packages/grpc-js/src/call-credentials.ts b/packages/grpc-js/src/call-credentials.ts index 40a26fec..bbc88a89 100644 --- a/packages/grpc-js/src/call-credentials.ts +++ b/packages/grpc-js/src/call-credentials.ts @@ -26,6 +26,35 @@ export type CallMetadataGenerator = ( cb: (err: Error | null, metadata?: Metadata) => void ) => void; +// google-auth-library pre-v2.0.0 does not have getRequestHeaders +// but has getRequestMetadata, which is deprecated in v2.0.0 +export interface OldOAuth2Client { + getRequestMetadata: ( + url: string, + callback: ( + err: Error | null, + headers?: { + [index: string]: string; + } + ) => void + ) => void; +} + +export interface CurrentOAuth2Client { + getRequestHeaders: (url?: string) => Promise<{ [index: string]: string }>; +} + +export type OAuth2Client = OldOAuth2Client | CurrentOAuth2Client; + +function isCurrentOauth2Client( + client: OAuth2Client +): client is CurrentOAuth2Client { + return ( + 'getRequestHeaders' in client && + typeof client.getRequestHeaders === 'function' + ); +} + /** * A class that represents a generic method of adding authentication-related * metadata on a per-request basis. @@ -65,6 +94,47 @@ export abstract class CallCredentials { return new SingleCallCredentials(metadataGenerator); } + /** + * Create a gRPC credential from a Google credential object. + * @param googleCredentials The authentication client to use. + * @return The resulting CallCredentials object. + */ + static createFromGoogleCredential( + googleCredentials: OAuth2Client + ): CallCredentials { + return CallCredentials.createFromMetadataGenerator((options, callback) => { + let getHeaders: Promise<{ [index: string]: string }>; + if (isCurrentOauth2Client(googleCredentials)) { + getHeaders = googleCredentials.getRequestHeaders(options.service_url); + } else { + getHeaders = new Promise((resolve, reject) => { + googleCredentials.getRequestMetadata( + options.service_url, + (err, headers) => { + if (err) { + reject(err); + return; + } + resolve(headers); + } + ); + }); + } + getHeaders.then( + (headers) => { + const metadata = new Metadata(); + for (const key of Object.keys(headers)) { + metadata.add(key, headers[key]); + } + callback(null, metadata); + }, + (err) => { + callback(err); + } + ); + }); + } + static createEmpty(): CallCredentials { return new EmptyCallCredentials(); } diff --git a/packages/grpc-js/src/channel-credentials.ts b/packages/grpc-js/src/channel-credentials.ts index 675e9162..aadf638b 100644 --- a/packages/grpc-js/src/channel-credentials.ts +++ b/packages/grpc-js/src/channel-credentials.ts @@ -19,6 +19,7 @@ import { ConnectionOptions, createSecureContext, PeerCertificate } from 'tls'; import { CallCredentials } from './call-credentials'; import { CIPHER_SUITES, getDefaultRootsData } from './tls-helpers'; +import { GoogleAuth as GoogleAuthType } from 'google-auth-library'; // eslint-disable-next-line @typescript-eslint/no-explicit-any function verifyIsBufferOrNull(obj: any, friendlyName: string): void { @@ -278,3 +279,13 @@ class ComposedChannelCredentialsImpl extends ChannelCredentials { } } } + +export function createGoogleDefaultCredentials(): ChannelCredentials { + const GoogleAuth = require('google-auth-library') + .GoogleAuth as typeof GoogleAuthType; + const sslCreds = ChannelCredentials.createSsl(); + const googleAuthCreds = CallCredentials.createFromGoogleCredential( + new GoogleAuth() + ); + return sslCreds.compose(googleAuthCreds); +} diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index 2e1d521a..54a407cd 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -24,7 +24,7 @@ import { ClientWritableStream, ServiceError, } from './call'; -import { CallCredentials } from './call-credentials'; +import { CallCredentials, OAuth2Client } from './call-credentials'; import { Deadline, StatusObject } from './call-stream'; import { Channel, ConnectivityState, ChannelImplementation } from './channel'; import { ChannelCredentials } from './channel-credentials'; @@ -33,7 +33,7 @@ import { Client, CallInvocationTransformer, CallProperties, - UnaryCallback + UnaryCallback, } from './client'; import { LogVerbosity, Status } from './constants'; import * as logging from './logging'; @@ -87,71 +87,13 @@ function mixin(...sources: IndexedObject[]) { return result; } -export interface OAuth2Client { - getRequestMetadata: ( - url: string, - callback: ( - err: Error | null, - headers?: { - [index: string]: string; - } - ) => void - ) => void; - getRequestHeaders: (url?: string) => Promise<{ [index: string]: string }>; -} +export { OAuth2Client }; /**** Client Credentials ****/ // Using assign only copies enumerable properties, which is what we want export const credentials = mixin( { - /** - * Create a gRPC credential from a Google credential object. - * @param googleCredentials The authentication client to use. - * @return The resulting CallCredentials object. - */ - createFromGoogleCredential: ( - googleCredentials: OAuth2Client - ): CallCredentials => { - return CallCredentials.createFromMetadataGenerator( - (options, callback) => { - // google-auth-library pre-v2.0.0 does not have getRequestHeaders - // but has getRequestMetadata, which is deprecated in v2.0.0 - let getHeaders: Promise<{ [index: string]: string }>; - if (typeof googleCredentials.getRequestHeaders === 'function') { - getHeaders = googleCredentials.getRequestHeaders( - options.service_url - ); - } else { - getHeaders = new Promise((resolve, reject) => { - googleCredentials.getRequestMetadata( - options.service_url, - (err, headers) => { - if (err) { - reject(err); - return; - } - resolve(headers); - } - ); - }); - } - getHeaders.then( - (headers) => { - const metadata = new Metadata(); - for (const key of Object.keys(headers)) { - metadata.add(key, headers[key]); - } - callback(null, metadata); - }, - (err) => { - callback(err); - } - ); - } - ); - }, - /** * Combine a ChannelCredentials with any number of CallCredentials into a * single ChannelCredentials object. @@ -211,7 +153,7 @@ export { CallInvocationTransformer, ChannelImplementation as Channel, Channel as ChannelInterface, - UnaryCallback as requestCallback + UnaryCallback as requestCallback, }; /**