1818 * @module common/util
1919 */
2020
21- import { replaceProjectIdToken } from '@google-cloud/projectify' ;
21+ import {
22+ replaceProjectIdToken ,
23+ MissingProjectIdError ,
24+ } from '@google-cloud/projectify' ;
2225import * as ent from 'ent' ;
2326import * as extend from 'extend' ;
2427import { AuthClient , GoogleAuth , GoogleAuthOptions } from 'google-auth-library' ;
@@ -29,6 +32,8 @@ import {Duplex, DuplexOptions, Readable, Transform, Writable} from 'stream';
2932import { teenyRequest } from 'teeny-request' ;
3033import { Interceptor } from './service-object' ;
3134import * as uuid from 'uuid' ;
35+ import { DEFAULT_PROJECT_ID_TOKEN } from './service' ;
36+
3237const packageJson = require ( '../../../package.json' ) ;
3338
3439// eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -164,6 +169,11 @@ export interface MakeAuthenticatedRequestFactoryConfig
164169 * A new will be created if this is not set.
165170 */
166171 authClient ?: AuthClient | GoogleAuth ;
172+
173+ /**
174+ * Determines if a projectId is required for authenticated requests. Defaults to `true`.
175+ */
176+ projectIdRequired ?: boolean ;
167177}
168178
169179export interface MakeAuthenticatedRequestOptions {
@@ -592,7 +602,7 @@ export class Util {
592602 config : MakeAuthenticatedRequestFactoryConfig
593603 ) {
594604 const googleAutoAuthConfig = extend ( { } , config ) ;
595- if ( googleAutoAuthConfig . projectId === '{{projectId}}' ) {
605+ if ( googleAutoAuthConfig . projectId === DEFAULT_PROJECT_ID_TOKEN ) {
596606 delete googleAutoAuthConfig . projectId ;
597607 }
598608
@@ -650,7 +660,11 @@ export class Util {
650660 const callback =
651661 typeof optionsOrCallback === 'function' ? optionsOrCallback : undefined ;
652662
653- const onAuthenticated = (
663+ async function setProjectId ( ) {
664+ projectId = await authClient . getProjectId ( ) ;
665+ }
666+
667+ const onAuthenticated = async (
654668 err : Error | null ,
655669 authenticatedReqOpts ?: DecorateRequestOptions
656670 ) => {
@@ -667,16 +681,35 @@ export class Util {
667681
668682 if ( ! err || autoAuthFailed ) {
669683 try {
684+ // Try with existing `projectId` value
670685 authenticatedReqOpts = util . decorateRequest (
671686 authenticatedReqOpts ! ,
672687 projectId
673688 ) ;
689+
674690 err = null ;
675691 } catch ( e ) {
676- // A projectId was required, but we don't have one.
677- // Re-use the "Could not load the default credentials error" if
678- // auto auth failed.
679- err = err || ( e as Error ) ;
692+ if ( e instanceof MissingProjectIdError ) {
693+ // A `projectId` was required, but we don't have one.
694+ try {
695+ // Attempt to get the `projectId`
696+ await setProjectId ( ) ;
697+
698+ authenticatedReqOpts = util . decorateRequest (
699+ authenticatedReqOpts ! ,
700+ projectId
701+ ) ;
702+
703+ err = null ;
704+ } catch ( e ) {
705+ // Re-use the "Could not load the default credentials error" if
706+ // auto auth failed.
707+ err = err || ( e as Error ) ;
708+ }
709+ } else {
710+ // Some other error unrelated to missing `projectId`
711+ err = err || ( e as Error ) ;
712+ }
680713 }
681714 }
682715
@@ -715,23 +748,58 @@ export class Util {
715748 }
716749 } ;
717750
718- Promise . all ( [
719- config . projectId && config . projectId !== '{{projectId}}'
720- ? // The user provided a project ID. We don't need to check with the
721- // auth client, it could be incorrect.
722- new Promise ( resolve => resolve ( config . projectId ) )
723- : authClient . getProjectId ( ) ,
724- reqConfig . customEndpoint && reqConfig . useAuthWithCustomEndpoint !== true
725- ? // Using a custom API override. Do not use `google-auth-library` for
726- // authentication. (ex: connecting to a local Datastore server)
727- new Promise ( resolve => resolve ( reqOpts ) )
728- : authClient . authorizeRequest ( reqOpts ) ,
729- ] )
730- . then ( ( [ _projectId , authorizedReqOpts ] ) => {
731- projectId = _projectId as string ;
732- onAuthenticated ( null , authorizedReqOpts as DecorateRequestOptions ) ;
733- } )
734- . catch ( onAuthenticated ) ;
751+ const prepareRequest = async ( ) => {
752+ try {
753+ const getProjectId = async ( ) => {
754+ if (
755+ config . projectId &&
756+ config . projectId !== DEFAULT_PROJECT_ID_TOKEN
757+ ) {
758+ // The user provided a project ID. We don't need to check with the
759+ // auth client, it could be incorrect.
760+ return config . projectId ;
761+ }
762+
763+ if ( config . projectIdRequired === false ) {
764+ // A projectId is not required. Return the default.
765+ return DEFAULT_PROJECT_ID_TOKEN ;
766+ }
767+
768+ return setProjectId ( ) ;
769+ } ;
770+
771+ const authorizeRequest = async ( ) => {
772+ if (
773+ reqConfig . customEndpoint &&
774+ ! reqConfig . useAuthWithCustomEndpoint
775+ ) {
776+ // Using a custom API override. Do not use `google-auth-library` for
777+ // authentication. (ex: connecting to a local Datastore server)
778+ return reqOpts ;
779+ } else {
780+ return authClient . authorizeRequest ( reqOpts ) ;
781+ }
782+ } ;
783+
784+ const [ _projectId , authorizedReqOpts ] = await Promise . all ( [
785+ getProjectId ( ) ,
786+ authorizeRequest ( ) ,
787+ ] ) ;
788+
789+ if ( _projectId ) {
790+ projectId = _projectId ;
791+ }
792+
793+ return onAuthenticated (
794+ null ,
795+ authorizedReqOpts as DecorateRequestOptions
796+ ) ;
797+ } catch ( e ) {
798+ return onAuthenticated ( e as Error ) ;
799+ }
800+ } ;
801+
802+ prepareRequest ( ) ;
735803
736804 if ( stream ! ) {
737805 return stream ! ;
0 commit comments