Skip to content

Commit

Permalink
feat!: Request Revamp (#1938)
Browse files Browse the repository at this point in the history
* chore: update `gaxios`

* refactor!: Request Revamp

* style: lint

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

* fix: Node 18-related fixes

`Headers` in Node v18 are not case-insensitive

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

---------

Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
  • Loading branch information
d-goog and gcf-owl-bot[bot] authored Feb 15, 2025
1 parent dbcc44b commit f23e807
Show file tree
Hide file tree
Showing 42 changed files with 795 additions and 762 deletions.
4 changes: 2 additions & 2 deletions .readme-partials.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ body: |-
## OAuth2
This library comes with an [OAuth2](https://developers.google.com/identity/protocols/OAuth2) client that allows you to retrieve an access token and refreshes the token and retry the request seamlessly if you also provide an `expiry_date` and the token is expired. The basics of Google's OAuth2 implementation is explained on [Google Authorization and Authentication documentation](https://developers.google.com/accounts/docs/OAuth2Login).
This library comes with an [OAuth2](https://developers.google.com/identity/protocols/OAuth2) client that allows you to retrieve an access token and refreshes the token and retry the request seamlessly if you also provide an `expiry_date` and the token is expired. The basics of Google's OAuth2 implementation is explained on [Google authorization and Authentication documentation](https://developers.google.com/accounts/docs/OAuth2Login).
In the following examples, you may need a `CLIENT_ID`, `CLIENT_SECRET` and `REDIRECT_URL`. You can find these pieces of information by going to the [Developer Console](https://console.cloud.google.com/), clicking your project > APIs & auth > credentials.
Expand Down Expand Up @@ -1185,7 +1185,7 @@ body: |-
// Get impersonated credentials:
const authHeaders = await targetClient.getRequestHeaders();
// Do something with `authHeaders.Authorization`.
// Do something with `authHeaders.get('authorization')`.
// Use impersonated credentials:
const url = 'https://www.googleapis.com/storage/v1/b?project=anotherProjectID'
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ main().catch(console.error);

## OAuth2

This library comes with an [OAuth2](https://developers.google.com/identity/protocols/OAuth2) client that allows you to retrieve an access token and refreshes the token and retry the request seamlessly if you also provide an `expiry_date` and the token is expired. The basics of Google's OAuth2 implementation is explained on [Google Authorization and Authentication documentation](https://developers.google.com/accounts/docs/OAuth2Login).
This library comes with an [OAuth2](https://developers.google.com/identity/protocols/OAuth2) client that allows you to retrieve an access token and refreshes the token and retry the request seamlessly if you also provide an `expiry_date` and the token is expired. The basics of Google's OAuth2 implementation is explained on [Google authorization and Authentication documentation](https://developers.google.com/accounts/docs/OAuth2Login).

In the following examples, you may need a `CLIENT_ID`, `CLIENT_SECRET` and `REDIRECT_URL`. You can find these pieces of information by going to the [Developer Console](https://console.cloud.google.com/), clicking your project > APIs & auth > credentials.

Expand Down Expand Up @@ -1229,7 +1229,7 @@ async function main() {

// Get impersonated credentials:
const authHeaders = await targetClient.getRequestHeaders();
// Do something with `authHeaders.Authorization`.
// Do something with `authHeaders.get('authorization')`.

// Use impersonated credentials:
const url = 'https://www.googleapis.com/storage/v1/b?project=anotherProjectID'
Expand Down
4 changes: 2 additions & 2 deletions browser-test/test.oauth2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ const FEDERATED_SIGNON_JWK_CERTS = [
},
];
const FEDERATED_SIGNON_JWK_CERTS_AXIOS_RESPONSE = {
headers: {
headers: new Headers({
'cache-control':
'cache-control: public, max-age=24000, must-revalidate, no-transform',
},
}),
data: {keys: FEDERATED_SIGNON_JWK_CERTS},
};

Expand Down
11 changes: 5 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
"dependencies": {
"base64-js": "^1.3.0",
"ecdsa-sig-formatter": "^1.0.11",
"gaxios": "^6.1.1",
"gcp-metadata": "^6.1.0",
"gtoken": "^7.0.0",
"gaxios": "^7.0.0-rc.4",
"gcp-metadata": "^7.0.0-rc.1",
"gtoken": "^8.0.0-rc.1",
"jws": "^4.0.0"
},
"devDependencies": {
Expand Down Expand Up @@ -54,7 +54,7 @@
"mocha": "^9.2.2",
"mv": "^2.1.1",
"ncp": "^2.0.0",
"nock": "^13.0.0",
"nock": "^14.0.1",
"null-loader": "^4.0.0",
"puppeteer": "^24.0.0",
"sinon": "^18.0.0",
Expand Down Expand Up @@ -84,8 +84,7 @@
"browser-test": "karma start",
"docs-test": "linkinator docs",
"predocs-test": "npm run docs",
"prelint": "cd samples; npm link ../; npm install",
"precompile": "gts clean"
"prelint": "cd samples; npm link ../; npm install"
},
"license": "Apache-2.0"
}
2 changes: 1 addition & 1 deletion samples/idTokenFromMetadataServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

/**
* Uses the Google Cloud metadata server environment to create an identity token
* and add it to the HTTP request as part of an Authorization header.
* and add it to the HTTP request as part of an authorization header.
*
* @param {string} targetAudience - The url or target audience to obtain the ID token for.
*/
Expand Down
4 changes: 2 additions & 2 deletions samples/test/externalclient.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,15 +356,15 @@ describe('samples for external-account', () => {
if (req.url === '/token' && req.method === 'GET') {
// Confirm expected header is passed along the request.
if (req.headers['my-header'] === 'some-value') {
res.setHeader('Content-Type', 'application/json');
res.setHeader('content-type', 'application/json');
res.writeHead(200);
res.end(
JSON.stringify({
access_token: oidcToken,
})
);
} else {
res.setHeader('Content-Type', 'application/json');
res.setHeader('content-type', 'application/json');
res.writeHead(400);
res.end(
JSON.stringify({
Expand Down
63 changes: 44 additions & 19 deletions src/auth/authclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,10 @@ export interface CredentialsClient {
* resolves with authorization header fields.
*
* The result has the form:
* { Authorization: 'Bearer <access_token_value>' }
* { authorization: 'Bearer <access_token_value>' }
* @param url The URI being authorized.
*/
getRequestHeaders(url?: string): Promise<Headers>;
getRequestHeaders(url?: string | URL): Promise<Headers>;

/**
* Provides an alternative Gaxios request implementation with auth credentials
Expand Down Expand Up @@ -251,10 +251,13 @@ export abstract class AuthClient
* resolves with authorization header fields.
*
* The result has the form:
* { Authorization: 'Bearer <access_token_value>' }
* ```ts
* new Headers({'authorization': 'Bearer <access_token_value>'});
* ```
*
* @param url The URI being authorized.
*/
abstract getRequestHeaders(url?: string): Promise<Headers>;
abstract getRequestHeaders(url?: string | URL): Promise<Headers>;

/**
* @return A promise that resolves with the current GCP access token
Expand Down Expand Up @@ -285,35 +288,58 @@ export abstract class AuthClient
// the x-goog-user-project header, to indicate an alternate account for
// billing and quota:
if (
!headers['x-goog-user-project'] && // don't override a value the user sets.
!headers.has('x-goog-user-project') && // don't override a value the user sets.
this.quotaProjectId
) {
headers['x-goog-user-project'] = this.quotaProjectId;
headers.set('x-goog-user-project', this.quotaProjectId);
}
return headers;
}

/**
* Adds the `x-goog-user-project` and `authorization` headers to the target Headers
* object, if they exist on the source.
*
* @param target the headers to target
* @param source the headers to source from
* @returns the target headers
*/
protected addUserProjectAndAuthHeaders<T extends Headers>(
target: T,
source: Headers
): T {
const xGoogUserProject = source.get('x-goog-user-project');
const authorizationHeader = source.get('authorization');

if (xGoogUserProject) {
target.set('x-goog-user-project', xGoogUserProject);
}

if (authorizationHeader) {
target.set('authorization', authorizationHeader);
}

return target;
}

static readonly DEFAULT_REQUEST_INTERCEPTOR: Parameters<
Gaxios['interceptors']['request']['add']
>[0] = {
resolved: async config => {
const headers = config.headers || {};

// Set `x-goog-api-client`, if not already set
if (!headers['x-goog-api-client']) {
if (!config.headers.has('x-goog-api-client')) {
const nodeVersion = process.version.replace(/^v/, '');
headers['x-goog-api-client'] = `gl-node/${nodeVersion}`;
config.headers.set('x-goog-api-client', `gl-node/${nodeVersion}`);
}

// Set `User-Agent`
if (!headers['User-Agent']) {
headers['User-Agent'] = USER_AGENT;
} else if (!headers['User-Agent'].includes(`${PRODUCT_NAME}/`)) {
headers['User-Agent'] = `${headers['User-Agent']} ${USER_AGENT}`;
const userAgent = config.headers.get('User-Agent');
if (!userAgent) {
config.headers.set('User-Agent', USER_AGENT);
} else if (!userAgent.includes(`${PRODUCT_NAME}/`)) {
config.headers.set('User-Agent', `${userAgent} ${USER_AGENT}`);
}

config.headers = headers;

return config;
},
};
Expand All @@ -337,9 +363,8 @@ export abstract class AuthClient
}
}

export interface Headers {
[index: string]: string;
}
// TypeScript does not have `HeadersInit` in the standard types yet
export type HeadersInit = ConstructorParameters<typeof Headers>[0];

export interface GetAccessTokenResponse {
token?: string | null;
Expand Down
16 changes: 8 additions & 8 deletions src/auth/awsclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {

import {DefaultAwsSecurityCredentialsSupplier} from './defaultawssecuritycredentialssupplier';
import {originalOrCamelOptions, SnakeToCamelObject} from '../util';
import {Gaxios} from 'gaxios';

/**
* AWS credentials JSON interface. This is used for AWS workloads.
Expand Down Expand Up @@ -230,7 +231,7 @@ export class AwsClient extends BaseExternalAccountClient {
// The GCP STS endpoint expects the headers to be formatted as:
// [
// {key: 'x-amz-date', value: '...'},
// {key: 'Authorization', value: '...'},
// {key: 'authorization', value: '...'},
// ...
// ]
// And then serialized as:
Expand All @@ -240,7 +241,7 @@ export class AwsClient extends BaseExternalAccountClient {
// headers: [{key: 'x-amz-date', value: '...'}, ...]
// }))
const reformattedHeader: {key: string; value: string}[] = [];
const extendedHeaders = Object.assign(
const extendedHeaders = Gaxios.mergeHeaders(
{
// The full, canonical resource name of the workload identity pool
// provider, with or without the HTTPS prefix.
Expand All @@ -250,13 +251,12 @@ export class AwsClient extends BaseExternalAccountClient {
},
options.headers
);

// Reformat header to GCP STS expected format.
for (const key in extendedHeaders) {
reformattedHeader.push({
key,
value: extendedHeaders[key],
});
}
extendedHeaders.forEach((value, key) =>
reformattedHeader.push({key, value})
);

// Serialize the reformatted signed request.
return encodeURIComponent(
JSON.stringify({
Expand Down
Loading

0 comments on commit f23e807

Please sign in to comment.