Skip to content
This repository was archived by the owner on Mar 19, 2026. It is now read-only.

Commit 3427c0e

Browse files
committed
Stale RAB retry now has its own flag. RAB refresh now triggered at the requestAsync level. RAB urls now have googleapis as universe domain.
1 parent f40a3e1 commit 3427c0e

7 files changed

Lines changed: 133 additions & 153 deletions

File tree

packages/google-auth-library-nodejs/src/auth/authclient.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -444,13 +444,28 @@ export abstract class AuthClient
444444
headers.set('x-goog-user-project', this.quotaProjectId);
445445
}
446446

447+
return headers;
448+
}
449+
450+
/**
451+
* Applies regional access boundary rules to the provided headers.
452+
* This includes adding the x-allowed-locations header and triggering
453+
* a background refresh if needed.
454+
* @param headers The headers to update.
455+
* @param url Optional destination URL of the request. If missing, assumed global.
456+
*/
457+
protected applyRegionalAccessBoundary(
458+
headers: Headers,
459+
url?: string | URL,
460+
): void {
447461
const rabHeader =
448-
this.regionalAccessBoundaryManager.getRegionalAccessBoundaryHeader();
462+
this.regionalAccessBoundaryManager.getRegionalAccessBoundaryHeader(
463+
url,
464+
headers,
465+
);
449466
if (rabHeader) {
450467
headers.set('x-allowed-locations', rabHeader);
451468
}
452-
453-
return headers;
454469
}
455470

456471
/**
@@ -478,7 +493,7 @@ export abstract class AuthClient
478493
target.set('authorization', authorizationHeader);
479494
}
480495

481-
if (xGoogAllowedLocs || xGoogAllowedLocs === '') {
496+
if (xGoogAllowedLocs) {
482497
target.set('x-allowed-locations', xGoogAllowedLocs);
483498
}
484499

@@ -620,21 +635,6 @@ export abstract class AuthClient
620635
};
621636
}
622637

623-
/**
624-
* Triggers an asynchronous regional access boundary refresh if needed.
625-
* @param url The endpoint URL being accessed.
626-
* @param accessToken The access token to use for the lookup.
627-
*/
628-
protected maybeTriggerRegionalAccessBoundaryRefresh(
629-
url: string | URL | undefined,
630-
accessToken: string,
631-
) {
632-
this.regionalAccessBoundaryManager.maybeTriggerRegionalAccessBoundaryRefresh(
633-
url,
634-
accessToken,
635-
);
636-
}
637-
638638
/**
639639
* Returns whether the provided credentials are expired or will expire within
640640
* eagerRefreshThresholdMillismilliseconds.

packages/google-auth-library-nodejs/src/auth/baseexternalclient.ts

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -429,10 +429,7 @@ export abstract class BaseExternalAccountClient extends AuthClient {
429429
const headers = new Headers({
430430
authorization: `Bearer ${accessTokenResponse.token}`,
431431
});
432-
this.maybeTriggerRegionalAccessBoundaryRefresh(
433-
url,
434-
accessTokenResponse.token!,
435-
);
432+
this.applyRegionalAccessBoundary(headers, url);
436433
return this.addSharedMetadataHeaders(headers);
437434
}
438435

@@ -504,28 +501,32 @@ export abstract class BaseExternalAccountClient extends AuthClient {
504501
* returned response.
505502
* @param opts The HTTP request options.
506503
* @param reAuthRetried Whether the current attempt is a retry after a failed attempt due to an auth failure.
504+
* @param retryWithoutRAB Whether the current attempt is a retry after a failed attempt due to a stale regional access boundary.
507505
* @return A promise that resolves with the successful response.
508506
*/
509507
protected async requestAsync<T>(
510508
opts: GaxiosOptions,
511509
reAuthRetried = false,
510+
retryWithoutRAB = false,
512511
): Promise<GaxiosResponse<T>> {
513512
let response: GaxiosResponse;
514513
const requestOpts = {...opts};
515514
try {
516-
const requestHeaders = await this.getRequestHeaders();
515+
const requestHeaders = await this.getRequestHeaders(opts.url);
517516
requestOpts.headers = Gaxios.mergeHeaders(requestOpts.headers);
518517

519518
this.applyHeadersFromSource(requestOpts.headers, requestHeaders);
520519

520+
this.applyRegionalAccessBoundary(requestOpts.headers, opts.url);
521+
521522
response = await this.transporter.request<T>(requestOpts);
522523
} catch (e) {
523524
if (
524525
this.isStaleRegionalAccessBoundaryError(e as GaxiosError) &&
525-
!reAuthRetried
526+
!retryWithoutRAB
526527
) {
527528
this.clearRegionalAccessBoundaryCache();
528-
return await this.requestAsync<T>(opts, true);
529+
return await this.requestAsync<T>(opts, reAuthRetried, true);
529530
}
530531
const res = (e as GaxiosError).response;
531532
if (res) {
@@ -544,7 +545,7 @@ export abstract class BaseExternalAccountClient extends AuthClient {
544545
this.forceRefreshOnFailure
545546
) {
546547
await this.refreshAccessTokenAsync();
547-
return await this.requestAsync<T>(opts, true);
548+
return await this.requestAsync<T>(opts, true, retryWithoutRAB);
548549
}
549550
}
550551
throw e;
@@ -740,27 +741,28 @@ export abstract class BaseExternalAccountClient extends AuthClient {
740741
);
741742
}
742743
return SERVICE_ACCOUNT_LOOKUP_ENDPOINT.replace(
743-
'{universe_domain}',
744-
this.universeDomain,
745-
).replace('{service_account_email}', encodeURIComponent(email));
744+
'{service_account_email}',
745+
encodeURIComponent(email),
746+
);
746747
}
747748

748749
// Check if the audience corresponds to a workload identity pool.
749750
const wfPoolId = getWorkforcePoolIdFromAudience(this.audience);
750751
if (wfPoolId) {
751752
return WORKFORCE_LOOKUP_ENDPOINT.replace(
752-
'{universe_domain}',
753-
this.universeDomain,
754-
).replace('{pool_id}', encodeURIComponent(wfPoolId));
753+
'{pool_id}',
754+
encodeURIComponent(wfPoolId),
755+
);
755756
}
756757

757758
// Check if the audience corresponds to a workforce identity pool.
758759
const wlPoolId = getWorkloadPoolIdFromAudience(this.audience);
759760
const projectNumber = this.getProjectNumber(this.audience);
760761
if (wlPoolId && projectNumber) {
761-
return WORKLOAD_LOOKUP_ENDPOINT.replace('{project_id}', projectNumber)
762-
.replace('{pool_id}', wlPoolId)
763-
.replace('{universe_domain}', this.universeDomain);
762+
return WORKLOAD_LOOKUP_ENDPOINT.replace(
763+
'{project_id}',
764+
projectNumber,
765+
).replace('{pool_id}', wlPoolId);
764766
}
765767

766768
throw new RangeError(

packages/google-auth-library-nodejs/src/auth/externalAccountAuthorizedUserClient.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -221,15 +221,20 @@ export class ExternalAccountAuthorizedUserClient extends AuthClient {
221221
};
222222
}
223223

224+
/**
225+
* The main authentication interface. It takes an optional url which when
226+
* present is the endpoint being accessed, and returns a Promise which
227+
* resolves with authorization header fields.
228+
*
229+
* @param url The URI being authorized.
230+
* @returns A promise that resolves with authorization header fields.
231+
*/
224232
async getRequestHeaders(url?: string | URL): Promise<Headers> {
225233
const accessTokenResponse = await this.getAccessToken();
226234
const headers = new Headers({
227235
authorization: `Bearer ${accessTokenResponse.token}`,
228236
});
229-
this.maybeTriggerRegionalAccessBoundaryRefresh(
230-
url,
231-
accessTokenResponse.token!,
232-
);
237+
this.applyRegionalAccessBoundary(headers, url);
233238
return this.addSharedMetadataHeaders(headers);
234239
}
235240

@@ -256,28 +261,32 @@ export class ExternalAccountAuthorizedUserClient extends AuthClient {
256261
* returned response.
257262
* @param opts The HTTP request options.
258263
* @param reAuthRetried Whether the current attempt is a retry after a failed attempt due to an auth failure.
264+
* @param retryWithoutRAB Whether the current attempt is a retry after a failed attempt due to a stale regional access boundary.
259265
* @return A promise that resolves with the successful response.
260266
*/
261267
protected async requestAsync<T>(
262268
opts: GaxiosOptions,
263269
reAuthRetried = false,
270+
retryWithoutRAB = false,
264271
): Promise<GaxiosResponse<T>> {
265272
let response: GaxiosResponse;
266273
const requestOpts = {...opts};
267274
try {
268-
const requestHeaders = await this.getRequestHeaders();
275+
const requestHeaders = await this.getRequestHeaders(opts.url);
269276
requestOpts.headers = Gaxios.mergeHeaders(requestOpts.headers);
270277

271278
this.applyHeadersFromSource(requestOpts.headers, requestHeaders);
272279

280+
this.applyRegionalAccessBoundary(requestOpts.headers, opts.url);
281+
273282
response = await this.transporter.request<T>(requestOpts);
274283
} catch (e) {
275284
if (
276285
this.isStaleRegionalAccessBoundaryError(e as GaxiosError) &&
277-
!reAuthRetried
286+
!retryWithoutRAB
278287
) {
279288
this.clearRegionalAccessBoundaryCache();
280-
return await this.requestAsync<T>(opts, true);
289+
return await this.requestAsync<T>(opts, reAuthRetried, true);
281290
}
282291
const res = (e as GaxiosError).response;
283292
if (res) {
@@ -296,7 +305,7 @@ export class ExternalAccountAuthorizedUserClient extends AuthClient {
296305
this.forceRefreshOnFailure
297306
) {
298307
await this.refreshAccessTokenAsync();
299-
return await this.requestAsync<T>(opts, true);
308+
return await this.requestAsync<T>(opts, true, retryWithoutRAB);
300309
}
301310
}
302311
throw e;
@@ -347,8 +356,8 @@ export class ExternalAccountAuthorizedUserClient extends AuthClient {
347356
);
348357
}
349358
return WORKFORCE_LOOKUP_ENDPOINT.replace(
350-
'{universe_domain}',
351-
this.universeDomain,
352-
).replace('{pool_id}', encodeURIComponent(poolId));
359+
'{pool_id}',
360+
encodeURIComponent(poolId),
361+
);
353362
}
354363
}

packages/google-auth-library-nodejs/src/auth/impersonated.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,9 @@ export class Impersonated extends OAuth2Client implements IdTokenProvider {
271271
);
272272
}
273273
const regionalAccessBoundaryUrl = SERVICE_ACCOUNT_LOOKUP_ENDPOINT.replace(
274-
'{universe_domain}',
275-
this.universeDomain,
276-
).replace('{service_account_email}', encodeURIComponent(targetPrincipal));
274+
'{service_account_email}',
275+
encodeURIComponent(targetPrincipal),
276+
);
277277
return regionalAccessBoundaryUrl;
278278
}
279279
}

packages/google-auth-library-nodejs/src/auth/jwtclient.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,6 @@ export class JWT extends OAuth2Client implements IdTokenProvider {
141141
).target_audience
142142
) {
143143
const {tokens} = await this.refreshToken();
144-
this.maybeTriggerRegionalAccessBoundaryRefresh(
145-
url ?? undefined,
146-
(tokens.access_token ?? tokens.id_token)!,
147-
);
148144
return {
149145
headers: this.addSharedMetadataHeaders(
150146
new Headers({
@@ -184,13 +180,6 @@ export class JWT extends OAuth2Client implements IdTokenProvider {
184180
useScopes ? scopes : undefined,
185181
);
186182

187-
const authHeader = headers.get('authorization');
188-
if (authHeader && authHeader.startsWith('Bearer ')) {
189-
this.maybeTriggerRegionalAccessBoundaryRefresh(
190-
url ?? undefined,
191-
authHeader.substring(7),
192-
);
193-
}
194183
return {headers: this.addSharedMetadataHeaders(headers)};
195184
}
196185
} else if (this.hasAnyScopes() || this.apiKey) {
@@ -427,9 +416,9 @@ export class JWT extends OAuth2Client implements IdTokenProvider {
427416
);
428417
}
429418
const regionalAccessBoundaryUrl = SERVICE_ACCOUNT_LOOKUP_ENDPOINT.replace(
430-
'{universe_domain}',
431-
this.universeDomain,
432-
).replace('{service_account_email}', encodeURIComponent(this.email));
419+
'{service_account_email}',
420+
encodeURIComponent(this.email),
421+
);
433422
return regionalAccessBoundaryUrl;
434423
}
435424
}

packages/google-auth-library-nodejs/src/auth/oauth2client.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,7 @@ export class OAuth2Client extends AuthClient {
938938
*/
939939
async getRequestHeaders(url?: string | URL): Promise<Headers> {
940940
const headers = (await this.getRequestMetadataAsync(url)).headers;
941+
this.applyRegionalAccessBoundary(headers, url);
941942
return headers;
942943
}
943944

@@ -962,10 +963,6 @@ export class OAuth2Client extends AuthClient {
962963
const headers = new Headers({
963964
authorization: thisCreds.token_type + ' ' + thisCreds.access_token,
964965
});
965-
this.maybeTriggerRegionalAccessBoundaryRefresh(
966-
url ?? undefined,
967-
thisCreds.access_token,
968-
);
969966
return {headers: this.addSharedMetadataHeaders(headers)};
970967
}
971968

@@ -978,10 +975,6 @@ export class OAuth2Client extends AuthClient {
978975
const headers = new Headers({
979976
authorization: 'Bearer ' + this.credentials.access_token,
980977
});
981-
this.maybeTriggerRegionalAccessBoundaryRefresh(
982-
url ?? undefined,
983-
this.credentials.access_token!,
984-
);
985978
return {headers: this.addSharedMetadataHeaders(headers)};
986979
}
987980
}
@@ -1012,10 +1005,6 @@ export class OAuth2Client extends AuthClient {
10121005
const headers = new Headers({
10131006
authorization: credentials.token_type + ' ' + tokens.access_token,
10141007
});
1015-
this.maybeTriggerRegionalAccessBoundaryRefresh(
1016-
url ?? undefined,
1017-
tokens.access_token!,
1018-
);
10191008
return {headers: this.addSharedMetadataHeaders(headers), res: r.res};
10201009
}
10211010

@@ -1128,6 +1117,7 @@ export class OAuth2Client extends AuthClient {
11281117
protected async requestAsync<T>(
11291118
opts: GaxiosOptions,
11301119
reAuthRetried = false,
1120+
retryWithoutRAB = false,
11311121
): Promise<GaxiosResponse<T>> {
11321122
const requestOpts = {...opts};
11331123
try {
@@ -1140,15 +1130,17 @@ export class OAuth2Client extends AuthClient {
11401130
requestOpts.headers.set('X-Goog-Api-Key', this.apiKey);
11411131
}
11421132

1133+
this.applyRegionalAccessBoundary(requestOpts.headers, opts.url);
1134+
11431135
return await this.transporter.request<T>(requestOpts);
11441136
} catch (e) {
11451137
if (
11461138
this.isStaleRegionalAccessBoundaryError(e as GaxiosError) &&
1147-
!reAuthRetried
1139+
!retryWithoutRAB
11481140
) {
11491141
this.clearRegionalAccessBoundaryCache();
11501142
// Background refresh is triggered by getRequestMetadataAsync in the retry
1151-
return await this.requestAsync<T>(opts, true);
1143+
return await this.requestAsync<T>(opts, reAuthRetried, true);
11521144
}
11531145
const res = (e as GaxiosError).response;
11541146
if (res) {
@@ -1194,7 +1186,7 @@ export class OAuth2Client extends AuthClient {
11941186
mayRequireRefresh
11951187
) {
11961188
await this.refreshAccessTokenAsync();
1197-
return this.requestAsync<T>(opts, true);
1189+
return this.requestAsync<T>(opts, true, retryWithoutRAB);
11981190
} else if (
11991191
!reAuthRetried &&
12001192
isAuthErr &&
@@ -1206,7 +1198,7 @@ export class OAuth2Client extends AuthClient {
12061198
if (refreshedAccessToken?.access_token) {
12071199
this.setCredentials(refreshedAccessToken);
12081200
}
1209-
return this.requestAsync<T>(opts, true);
1201+
return this.requestAsync<T>(opts, true, retryWithoutRAB);
12101202
}
12111203
}
12121204
throw e;

0 commit comments

Comments
 (0)