44 * SPDX-License-Identifier: Apache-2.0
55 */
66
7- import type { Credentials } from 'google-auth-library' ;
7+ import type { Credentials , AuthClient , JWTInput } from 'google-auth-library' ;
88import {
99 OAuth2Client ,
1010 Compute ,
1111 CodeChallengeMethod ,
12+ GoogleAuth ,
1213} from 'google-auth-library' ;
1314import * as http from 'node:http' ;
1415import url from 'node:url' ;
@@ -64,7 +65,7 @@ export interface OauthWebLogin {
6465 loginCompletePromise : Promise < void > ;
6566}
6667
67- const oauthClientPromises = new Map < AuthType , Promise < OAuth2Client > > ( ) ;
68+ const oauthClientPromises = new Map < AuthType , Promise < AuthClient > > ( ) ;
6869
6970function getUseEncryptedStorageFlag ( ) {
7071 return process . env [ FORCE_ENCRYPTED_FILE_ENV_VAR ] === 'true' ;
@@ -73,7 +74,28 @@ function getUseEncryptedStorageFlag() {
7374async function initOauthClient (
7475 authType : AuthType ,
7576 config : Config ,
76- ) : Promise < OAuth2Client > {
77+ ) : Promise < AuthClient > {
78+ const credentials = await fetchCachedCredentials ( ) ;
79+
80+ if (
81+ credentials &&
82+ ( credentials as { type ?: string } ) . type ===
83+ 'external_account_authorized_user'
84+ ) {
85+ const auth = new GoogleAuth ( {
86+ scopes : OAUTH_SCOPE ,
87+ } ) ;
88+ const byoidClient = await auth . fromJSON ( {
89+ ...credentials ,
90+ refresh_token : credentials . refresh_token ?? undefined ,
91+ } ) ;
92+ const token = await byoidClient . getAccessToken ( ) ;
93+ if ( token ) {
94+ debugLogger . debug ( 'Created BYOID auth client.' ) ;
95+ return byoidClient ;
96+ }
97+ }
98+
7799 const client = new OAuth2Client ( {
78100 clientId : OAUTH_CLIENT_ID ,
79101 clientSecret : OAUTH_CLIENT_SECRET ,
@@ -102,20 +124,35 @@ async function initOauthClient(
102124 }
103125 } ) ;
104126
105- // If there are cached creds on disk, they always take precedence
106- if ( await loadCachedCredentials ( client ) ) {
107- // Found valid cached credentials.
108- // Check if we need to retrieve Google Account ID or Email
109- if ( ! userAccountManager . getCachedGoogleAccount ( ) ) {
110- try {
111- await fetchAndCacheUserInfo ( client ) ;
112- } catch ( error ) {
113- // Non-fatal, continue with existing auth.
114- debugLogger . warn ( 'Failed to fetch user info:' , getErrorMessage ( error ) ) ;
127+ if ( credentials ) {
128+ client . setCredentials ( credentials as Credentials ) ;
129+ try {
130+ // This will verify locally that the credentials look good.
131+ const { token } = await client . getAccessToken ( ) ;
132+ if ( token ) {
133+ // This will check with the server to see if it hasn't been revoked.
134+ await client . getTokenInfo ( token ) ;
135+
136+ if ( ! userAccountManager . getCachedGoogleAccount ( ) ) {
137+ try {
138+ await fetchAndCacheUserInfo ( client ) ;
139+ } catch ( error ) {
140+ // Non-fatal, continue with existing auth.
141+ debugLogger . warn (
142+ 'Failed to fetch user info:' ,
143+ getErrorMessage ( error ) ,
144+ ) ;
145+ }
146+ }
147+ debugLogger . log ( 'Loaded cached credentials.' ) ;
148+ return client ;
115149 }
150+ } catch ( error ) {
151+ debugLogger . debug (
152+ `Cached credentials are not valid:` ,
153+ getErrorMessage ( error ) ,
154+ ) ;
116155 }
117- debugLogger . log ( 'Loaded cached credentials.' ) ;
118- return client ;
119156 }
120157
121158 // In Google Cloud Shell, we can use Application Default Credentials (ADC)
@@ -218,7 +255,7 @@ async function initOauthClient(
218255export async function getOauthClient (
219256 authType : AuthType ,
220257 config : Config ,
221- ) : Promise < OAuth2Client > {
258+ ) : Promise < AuthClient > {
222259 if ( ! oauthClientPromises . has ( authType ) ) {
223260 oauthClientPromises . set ( authType , initOauthClient ( authType , config ) ) ;
224261 }
@@ -432,15 +469,12 @@ export function getAvailablePort(): Promise<number> {
432469 } ) ;
433470}
434471
435- async function loadCachedCredentials ( client : OAuth2Client ) : Promise < boolean > {
472+ async function fetchCachedCredentials ( ) : Promise <
473+ Credentials | JWTInput | null
474+ > {
436475 const useEncryptedStorage = getUseEncryptedStorageFlag ( ) ;
437476 if ( useEncryptedStorage ) {
438- const credentials = await OAuthCredentialStorage . loadCredentials ( ) ;
439- if ( credentials ) {
440- client . setCredentials ( credentials ) ;
441- return true ;
442- }
443- return false ;
477+ return await OAuthCredentialStorage . loadCredentials ( ) ;
444478 }
445479
446480 const pathsToTry = [
@@ -450,19 +484,8 @@ async function loadCachedCredentials(client: OAuth2Client): Promise<boolean> {
450484
451485 for ( const keyFile of pathsToTry ) {
452486 try {
453- const creds = await fs . readFile ( keyFile , 'utf-8' ) ;
454- client . setCredentials ( JSON . parse ( creds ) ) ;
455-
456- // This will verify locally that the credentials look good.
457- const { token } = await client . getAccessToken ( ) ;
458- if ( ! token ) {
459- continue ;
460- }
461-
462- // This will check with the server to see if it hasn't been revoked.
463- await client . getTokenInfo ( token ) ;
464-
465- return true ;
487+ const keyFileString = await fs . readFile ( keyFile , 'utf-8' ) ;
488+ return JSON . parse ( keyFileString ) ;
466489 } catch ( error ) {
467490 // Log specific error for debugging, but continue trying other paths
468491 debugLogger . debug (
@@ -472,7 +495,7 @@ async function loadCachedCredentials(client: OAuth2Client): Promise<boolean> {
472495 }
473496 }
474497
475- return false ;
498+ return null ;
476499}
477500
478501async function cacheCredentials ( credentials : Credentials ) {
0 commit comments